Set up static web page resources -- Images, CSS, JavaScript

The set up process for static resources in Django projects varies considerably if a project runs with DEBUG=True or DEBUG=False. This means static resource deployment depends on whether you're doing work on a development environment -- where you generally use DEBUG=True -- or on a production environment -- where you generally use DEBUG=False.

Considering you'll always start a Django project in a development environment and later migrate to a production environment, I'll describe the development set up process first and later describe the production set up process.

Set up static resources in a development environment (DEBUG=False)

By default when DEBUG=False, Django automatically sets up static resources from two major locations. The first location are static folders in all Django apps and the second location are folders declared in the STATICFILES_DIR variable in settings.py.

Although you'll need to manually create the static folder inside Django apps, it's this easy to set up static resources in a project. Because Django sets up all the static folders for every project app, it's a recommended practice to further add a sub-directory to the static folder (e.g.<app_folder>/static/<app_name>/<static_files_here>) to qualify static resources and avoid potential naming conflicts. Listing 5-10 illustrates a sample directory structure for static resources.

Listing 5-10. Django app structure with static directories

+-<BASE_DIR_project_name>
|
+-manage.py 
|
+-bootstrap-3.1.1-dist+
|                     +-bootstrap.min.js
|
+-jquery-1-11-1-dist+ 
|                   +jquery.min.js
|
+-jquery-ui-1.10.4+
|                 +jquery-ui.min.js
|
+-website-static-default+
|                       +-favicon.ico
|                       +-robots.txt
|
|
+---+-<PROJECT_DIR_project_name>
    |
    +-__init__.py
    +-settings.py
    +-urls.py
    +-wsgi.py
    |
    +-about(app)-+
    |            +-__init__.py
    |            +-models.py
    |            +-tests.py
    |            +-views.py
    |            +-static-+
    |                     |
    |                     +-about-+
    |                             +-img-+
    |                             |     +-logo.png
    |                             |
    |                             +-css-+
    |                                   +-custom.css
    +-stores(app)-+
                 +-__init__.py
                 +-models.py
                 +-tests.py
                 +-views.py
                 +-static-+
                          |
                          +-stores-+
                                   +-img-+
                                   |     +-coffee.gif
                                   |
                                   +-css-+
                                         +-custom.css

As illustrated in listing 5-10, all Django app directories have a static sub-directory that contain static resources. Anything under these static sub-directories is set up for access.

Also notice in listing 5-10 the importance of the app name sub-directory within the static sub-directories that acts as a namespace. If static resources were placed directly below the static folder in all apps, in this scenario it would lead to two identical file paths named /static/css/custom.css. In which case a call to load this static resource would lead to a conflict. Technically, Django always uses the first file it finds, but will the first one be the right one ? By using an app name sub-directory inside static it avoids any potential conflict, with one static resource set up at /static/about/css/custom.css and the other at /static/stores/css/custom.css.

Because there can be static resources that don't necessarily belong to a specific project app, Django also supports the ability to set up static resources stored on any sub-directory.

If you look again at listing 5-10 in between the BASE_DIR and PROJECT_DIR, you'll see there are various sub-folders that contain popular static resource libraries -- jquery, jquery-ui and bootstrap -- as well as a sub-folder website-static-default with a web site's standard static resources -- robots.txt & favicon.ico.

In order to set up these additional static resources, you define the location of these directories in the STATICFILES_DIR variable in settings.py. Listing 5-11 illustrates an example of a STATICFILES_DIR definition.

Listing 5-11 Django STATICFILES_DIR definition with namespaces in settings.py

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

STATICFILES_DIRS = ('%s/website-static-default/'% (BASE_DIR),
                    ('bootstrap','%s/bootstrap-3.1.1-dist/'% (BASE_DIR)),
                    ('jquery','%s/jquery-1-11-1-dist/'% (BASE_DIR)),
                    ('jquery-ui','%s/jquery-ui-1.10.4/'% (BASE_DIR)),)

As you can see in listing 5-11, STATICFILES_DIRS accepts a list of directories. In this case, all directories are under a Django project's BASE_DIR, so it's using the BASE_DIR variable which dynamically determines the parent directory. Another aspect of the directory list in listing 5-11 is you can optionally declare a namespace, similar to the approach used in an app's static sub-directories.

The first directory definition in listing 5-11 is a simple string (i.e. it has no namepsace), which means the static resources in website-static-default is set up with a direct access pattern. The remaining directory definitions are tuples and not strings. By using a tuple, it defines the first part of the tuple as the namespace and the second part as the directory with the static resources. Definitions with a namespace mean that all static resources under a given directory will use a prefix namespace in their access pattern (e.g. to access static resources on bootstrap-3.1.1-dist the access pattern should be prefixed with bootstrap).

Now that you know where and how to set up all static resources, lets take a quick a look at how Django visualizes these static resources to understand what the final access patterns for static resources looks like. Listing 5-12 shows a visualization of the static resources presented in the previous listings.

Listing 5-12 Django visualization of static resources in apps and STATICFILES_DIRS

+-favicon.ico
+-robots.txt
|
+-jquery+ 
|       +jquery.min.js
|
+-jquery-ui+
|          +jquery-ui.min.js
|
+-bootstrap+
|          +-bootstrap.css
|
+-about-+
|       +-img-+
|       |     +-logo.png
|       |
|       +-css-+
|             +-custom.css
|
+-stores-+
         +-img-+
         |     +-coffee.gif
         |
         +-css-+
               +-custom.css

The files favicon.ico & robots.txt in listing 5-12 are in the top level of the visualization because their source directory -- website-static-default -- was defined without a namespace in STATICFILES_DIRS.

The remainder of the static resources are all grouped in sub-folders because we either defined a namespace for them in STATICFILES_DIRS or defined a sub-folder as a namespace within an app's static sub-directory.

Now that you understand how Django visualizes static resources as a group and how this determines the final access pattern for static resources, let's turn our attention to the STATIC_URL variable in settings.py.

The STATIC_URL is used to define a URL entry point into Django's visualization of static resources presented in listing 5-12. By default, STATIC_URL is assigned the /static/ value. This means that if STATIC_URL='/static/', the static resource robots.txt becomes accessible at the URL /static/robots.txt, just like stores/img/coffee.gif becomes accessible at the URL /static/stores/img/coffee.gif.

This means you access static resources on the /static/ URL, or on a different URL if you change the STATIC_URL value. However, don't go and hard-code these static resources paths on templates! (e.g.<img src="/static/stores/img/coffee.gif"/>). You should use a variable so the final path is determined dynamically in case STATIC_URL changes. The next section describes how to do this in Django templates.

Caution Automatic access to static resources only works with Django's built-in web server and when DEBUG=True

The previous set up process for static resources has a little 'behind the scenes' help from Django. It only works with Django's built-in web server (i.e. python manage.py runserver) and only if DEBUG=True. As soon as you change to a different web server or switch DEBUG=False even using Django's built-in web server, no static resource will be available as visualized in listing 5-12.

The primary reason behind this behavior is because reserving and dispatching static resources from an application's main web server/URL structure (e.g./static/) is very inefficient. So this just works as a convenience in development using Django's built-in web server and when DEBUG=True. Of course, you can assign a full URL domain to STATIC_URL (e.g. http://static.coffeehouse.com/) but this assumes you've already set up the project's static resources on a production-like environment, something that I'll discuss shortly once I describe how to access static resources in Django and Jinja templates.

Access static resources in Django templates

The recommended approach to reference static resources in Django templates is through staticfiles app via the {% static %} tag. Listing 5-13 illustrates various examples of the staticfiles app syntax.

Listing 5-13 Django {% static %} tag to reference static resources

{% load static %}

# For static resource at about/img/logo.png
<img src="{% static 'about/img/logo.gif' %}">

# For static resource at bootstrap/bootstrap.css
<link href="{% static 'bootstrap/bootstrap.css' %}" rel="stylesheet">

# For static resource at jquery/jquery.min.js
<script src="{% static 'jquery/jquery.min.js' %}"></script>

First it's important to note the {% load static %} tag in listing 5-13 is available through the staticfiles app which is installed by default on all Django projects in the INSTALLED_APPS variable. If for some reason you modified the default values in INSTALLED_APPS, make sure you have the django.contrib.staticfiles value in the INSTALLED_APPS variable or none of what follows will work.

As you can see in listing 5-13, at the top of the template you always declare the {% load static %} statement. Once this is done, a template can use the {% static %} tag to generate dynamic paths for static resources. In most circumstances, the {% static %} tag relies on the STATIC_URL variable in settings.py to generate an appropriate path to the static resources.

For more advanced cases, the {% static %} tag uses a combination of the same STATIC_URL variable and the backing storage technology (e.g. CDN-'Content Delivery Network') configuration to generate an appropriate path to the static resources.

For example, notice in listing 5-13 how the {% static %} tag is always followed by a file path identical to the Django visualization of static resources in listing 5-12. Due to the STATIC_URL variable having a value of /static/, it means the {% static %} statements in listing 5-13 get substituted with this value (e.g. {% static 'bootstrap/bootstrap.css' %} becomes /static/bootstrap/bootstrap.css).

The cases where the {% static %} tag gets substituted for something different than the STATIC_URL variable are when a Django project uses a non-standard back-end to serve static resources -- this last scenario is briefly discussed in the side-bar below.

Why use the staticfiles {% static %} tag vs. using the STATIC_URL variable directly in templates ?

In the early versions of Django, Django templates used the STATIC_URL variable directly in templates (e.g. <img src="{{STATIC_URL}}about/img/logo.gif">). A trace of this remains in the fact that you can still gain access to the STATIC_URL variable on all Django templates via the django.template.context_processors.static context processor.

However, with the underlying technology to serve static resources becoming more sophisticated, the STATIC_URL variable by itself proves insufficient. For example, static serving technologies like CDNs or Amazon S3 often use special tokens to enforce authentication or caching strategies. This means a statement like <img src="{{STATIC_URL}}about/img/logo.gif"> needs to be converted into something like <img src="http://cdnprovider.com/about/img/logo.gif?token=e354534566"> or <img src="http://staticresources.com/about/img/logo32AzTB9r5.gif">. And while it's possible to change the STATIC_URL variable to a full domain, what becomes difficult is to modify the static resource's path itself.

Re-writing a static resource's path with a tag like {% static %} is easy. Because {% static %} can take a static resource's base string (e.g. about/img/logo.gif) and dynamically produce a full path with the STATIC_URL variable and any special tokens required by the underlying static serving technology. This process is achieved by using a custom storage class -- designed for the static serving technology.

Granted not all projects require the use of advanced static serving technologies. But by using the {% static %} tag of the staticfiles app to declare static resources in Django templates, you ensure a Django project is capable of using any static serving technology, from the most basic to the most advanced.

Access static resources in Jinja templates

Jinja templates offer an alternative to Django's own templates, as described in the previous chapter on Jinja templates. But unlike Django templates, you'll have to follow a different set up to use something like Django's {% static %} tag from the staticfiles app in Jinja templates.

To be able to use the same staticfiles app / {% static %} tag behavior in Jinja templates, you'll need to set up a global variable named static that hooks into this functionality. In the previous chapter on Jinja template, the section Jinja globals: Set up data for access on all Jinja templates in Django, like Django context processors describes how to create a global variable with this functionality.

Set up static resources in a production environment (DEBUG=True)

When you switch your Django project's DEBUG variable to True or change to a different web server (e.g. Apache, Nginx), you'll be surprised that none of the static resources in your project appear anymore. Don't be alarmed, this is by design. It isn't too difficult to set up Django to serve static resources when DEBUG=True with Django's built-in web server or if you switch to a third party web server.

Tip You can access static resources to make Django's built-in web server serve static resources as if DEBUG=False when it's actually set to DEBUG=True. Run the web server with the --insecure flag: python manage.py runserver --insecure.
Caution Although the previous workaround is available, I recommend you don't use it, in case the flag name itself --insecure wasn't enough to keep you from using it.

Django's built-in web server (i.e. python manage.py runserver) is really a convenience tool to get up and running quickly, which as part of this convenience also serves static resources when DEBUG=False.

However, it really is wasteful to allow the same web server process to handle both dynamic content (Django web pages) and static resources (Images, CSS, JavaScript). The recommended approach is to use a separate web server entirely to serve static resources, which is why Django goes to the extent of breaking this convenience in its built-in web server when switching the DEBUG=True.

The first thing you need to do when DEBUG=True is create a directory to hold a copy of all the static resources Django visualizes as static resources. Previously you learned that when DEBUG=False, Django visualizes static resources from several locations and sub-directories in a single tree -- illustrated in listing 5-12. It's precisely this single tree Django visualizes that you need to create a copy of to run on a production environment.

You'll need to define the STATIC_ROOT variable in settings.py. The value you assign to STATIC_ROOT should be a directory and it will be where Django copies all of your project's static resources -- identical to how Django visualizes them when DEBUG=True as illustrated in listing 5-12. Note this directory should be empty, as it's overwritten constantly each time you perform a syncing process. The location of this directory could be anywhere on you system depending on your needs. For simplicity, I'll keep the STATIC_ROOT directory under the Django project's BASE_DIR as STATIC_ROOT = '%s/coffeestatic/'% (BASE_DIR).

To trigger the syncing process (i.e. copy all static resources to STATIC_ROOT) you'll need to use the collectstatic command available in the manage.py script. Listing 5-14 illustrates the sample output of the syncing process.

Listing 5-14 Django collectstatic command to copy all static resources

[user@coffeehouse ~]$ python manage.py collectstatic
You have requested to collect static files at the destination
location as specified in your settings:
    /www/STORE/coffeestatic
This will overwrite existing files!
Are you sure you want to do this?
Type 'yes' to continue, or 'no' to cancel: yes
yes
Copying '/www/STORE/website-static-default/sitemap.xml'
Copying '/www/STORE/website-static-default/robots.txt'
Copying '/www/STORE/website-static-default/favicon.ico'
....
....
....
Copying '/www/STORE/coffeehouse/about/static/css/custom.css'
732 static files copied to '/www/STORE/coffeestatic'.

Once you collect all your project's static resources in a single folder -- in this case /www/STORE/coffeestatic -- they're ready to be set up on a production server (e.g. Apache, Nginx or AWS S3). Keep in mind the directory/file structure generated by collectstatic is identical to the one visualized by Django in the previous section illustrated in listing 5-12.

The final step you need to do is update the STATIC_URL value in settings.py to reflect the new location of the static resources. For example, if you mount the /www/STORE/coffeestatic/ directory on Apache or Nginx under the http://static.coffeehouse.com/ domain, you would set STATIC_URL='http://static.coffeehouse.com'. Similarly, if you copy the static resources in /www/STORE/coffeestatic/ to an Amazon AWS S3 bucket named http://coffeehouse.s3.amazonaws.com, you would set STATIC_URL='http://coffeehouse.s3.amazonaws.com'

Once you make this last change, all the statements in your Django templates that use the {% static %} tag get updated with this new full-domain URL. In which case a resource like /www/STORE/coffeestatic/bootstrap/bootstrap.css becomes available at http://static.coffeehouse.com/bootstrap/bootstrap.css or http://coffeehouse.s3.amazonaws.com/bootstrap/bootstrap.css.