Book Image

Django 3 Web Development Cookbook - Fourth Edition

By : Aidas Bendoraitis, Jake Kronika
Book Image

Django 3 Web Development Cookbook - Fourth Edition

By: Aidas Bendoraitis, Jake Kronika

Overview of this book

Django is a web framework for perfectionists with deadlines, designed to help you build manageable medium and large web projects in a short time span. This fourth edition of the Django Web Development Cookbook is updated with Django 3's latest features to guide you effectively through the development process. This Django book starts by helping you create a virtual environment and project structure for building Python web apps. You'll learn how to build models, views, forms, and templates for your web apps and then integrate JavaScript in your Django apps to add more features. As you advance, you'll create responsive multilingual websites, ready to be shared on social networks. The book will take you through uploading and processing images, rendering data in HTML5, PDF, and Excel, using and creating APIs, and navigating different data types in Django. You'll become well-versed in security best practices and caching techniques to enhance your website's security and speed. This edition not only helps you work with the PostgreSQL database but also the MySQL database. You'll also discover advanced recipes for using Django with Docker and Ansible in development, staging, and production environments. By the end of this book, you will have become proficient in using Django's powerful features and will be equipped to create robust websites.
Table of Contents (15 chapters)

Setting up STATIC_URL dynamically

If you set STATIC_URL to a static value, then each time you update a CSS file, a JavaScript file, or an image, you and your website visitors will need to clear the browser cache in order to see the changes. There is a trick to work around clearing the browser's cache. It is to have the timestamp of the latest changes shown in STATIC_URL. Whenever the code is updated, the visitor's browser will force the loading of all new static files.

In this recipe, we will see how to put a timestamp in STATIC_URL for Git users.

Getting ready

Make sure that your project is under Git version control and that you have BASE_DIR defined in your settings, as shown in the Defining relative paths in the settings recipe.

How to do it...

The procedure to put the Git timestamp in the STATIC_URL setting consists of the following two steps:

  1. If you haven't done so yet, create the myproject.apps.core app in your Django project. You should also create a versioning.py file there:
# versioning.py
import
subprocess
from datetime import datetime


def get_git_changeset_timestamp(absolute_path):
repo_dir = absolute_path
git_log = subprocess.Popen(
"git log --pretty=format:%ct --quiet -1 HEAD",
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True,
cwd=repo_dir,
universal_newlines=True,
)

timestamp = git_log.communicate()[0]
try:
timestamp = datetime.utcfromtimestamp(int(timestamp))
except ValueError:
# Fallback to current timestamp
return datetime.now().strftime('%Y%m%d%H%M%S')
changeset_timestamp = timestamp.strftime('%Y%m%d%H%M%S')
return changeset_timestamp
  1. Import the newly created get_git_changeset_timestamp() function in the settings and use it for the STATIC_URL path, as follows:
# settings/_base.py
from myproject.apps.core.versioning import get_git_changeset_timestamp
# ...
timestamp = get_git_changeset_timestamp(BASE_DIR)
STATIC_URL = f'/static/{timestamp}/'

How it works...

The get_git_changeset_timestamp() function takes the absolute_path directory as a parameter and calls the git log shell command with the parameters to show the Unix timestamp of the HEAD revision in the directory. We pass BASE_DIR to the function, as we are sure that it is under version control. The timestamp is parsed, converted to a string consisting of the year, month, day, hour, minutes, and seconds returned, and is then included in the definition of the STATIC_URL.

There's more...

This method works only if each of your environments contains the full Git repository of the project—in some cases, for example, when you use Heroku or Docker for deployments—you don't have access to a Git repository and the git log command in the remote servers. In order to have the STATIC_URL with a dynamic fragment, you have to read the timestamp from a text file—for example, myproject/settings/last-modified.txt—that should be updated with each commit.

In this case, your settings would contain the following lines:

# settings/_base.py
with
open(os.path.join(BASE_DIR, 'myproject', 'settings', 'last-update.txt'), 'r') as f:
timestamp = f.readline().strip()

STATIC_URL = f'/static/{timestamp}/'

You can make your Git repository update last-modified.txt with a pre-commit hook. This is an executable bash script that should be called pre-commit and placed under django-myproject/.git/hooks/:

# django-myproject/.git/hooks/pre-commit
#!/usr/bin/env python
from subprocess import check_output, CalledProcessError
import os
from datetime import datetime

def root():
''' returns the absolute path of the repository root '''
try:
base = check_output(['git', 'rev-parse', '--show-toplevel'])
except CalledProcessError:
raise IOError('Current working directory is not a git repository')
return base.decode('utf-8').strip()

def abspath(relpath):
''' returns the absolute path for a path given relative to the root of
the git repository
'''
return os.path.join(root(), relpath)

def add_to_git(file_path):
''' adds a file to git '''
try:
base = check_output(['git', 'add', file_path])
except CalledProcessError:
raise IOError('Current working directory is not a git repository')
return base.decode('utf-8').strip()


def main():
file_path = abspath("myproject/settings/last-update.txt")

with open(file_path, 'w') as f:
f.write(datetime.now().strftime("%Y%m%d%H%M%S"))

add_to_git(file_path)

if __name__ == '__main__':
main()

This script will update last-modified.txt whenever you commit to the Git repository and will add that file to the Git index.

See also

  • The Creating the Git ignore file recipe