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

Problem

You want to use static web page resources like Images, Cascading Style Sheets(CSS) and JavaScript in Django or Jinja templates in Django projects

Solution

Place project wide static resources in their own special directories at a project's BASE_DIR level. Place app static resources inside folders named static in Django apps. Set STATICFILES_DIR in settings.py and assign it a list of special directories that contain project wide static resources. For Django templates use the {% load static %} tag to load Django's staticfiles app into a template and then use the {% static "images/background.jpg" %} tag syntax to declare a static resource. For Jinja templates set up a global variable named static to leverage the same Django staticfiles app and create the same behavior as Django's staticfiles {% static %} tag.

Additionally for production environments, set STATIC_ROOT in settings.py to define a consolidation directory for all static resources. Run python manage.py collectstatic to copy all project static resources into the STATIC_ROOT folder. Deploy the STATIC_ROOT folder on its own web server or upload the STATIC_ROOT folder to a file service like Amazon S3.

How it works

The set up process for static resources in Django projects varies considerably if a project is running with DEBUG=True or DEBUG=False in settings.py.

Essentially, this means the set up process for static resources varies depending 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 any static resources present in any Django app's static folder. Although you'll need to manually create this folder inside an app's structure, it's this easy to set up static resources.

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 1 illustrates a sample directory structure for static resources.

Listing 1 - 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
|                       +-sitemap.xml
|
+---+-<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 1, 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 1 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 will always use the first file it finds, but will the first one be the right one of the two ? 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 1 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 and sitemap.xml.

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

Listing 2 - 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 2, STATICFILES_DIRS can accept 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 is that you can optionally declare a namespace, similar to the approach used in an app's static sub-directories.

The first directory definition in listing 2 is a simple string (i.e. it has no namepsace), which means the static resources in website-static-default will be 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 have a prefix namespace in their access pattern (e.g. to access static resources on bootstrap-3.1.1-dist the access pattern will need to 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 will look like. Listing 3 shows a visualization of the static resources presented in the previous listings.

Listing 3 - Django visualization of static resources in apps and STATICFILES_DIRS

+-favicon.ico
+-robots.txt
+-sitemap.xml
|
+-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,sitemap.xml in listing 3 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 patterns 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 3. By default, STATIC_URL is assigned the /static/ value. This means that if STATIC_URL='/static/', the static resource robots.txt would become accessible at the URL /static/robots.txt, just like stores/img/coffee.gif would become accessible at the URL /static/stores/img/coffee.gif.

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

Note Automatic access to static resources only work 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 3.

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 in the final section of this recipe.

Access static resources in Django templates

The recommended approach to reference static resources in Django templates is through the staticfiles tag in conjuction with the {% static %} tag. Listing 4 illustrates various examples of the staticfiles app syntax.

Listing 4 - Django {% staticfiles %} and {% static %} tags 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 4 is available through the staticfiles app which is installed by default on all Django projects in the INSTALLED_APPS variable. So 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 this will work.

As you can see in listing 4, at the top of the template you will always need to declare the {% load static %} statement. Once this is done a template can then use the {% static %} tag to generate dynamic paths to 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 4 how the {% static %} tag is always followed by a file path identical to the Django visualization of static resources in listing 3. Due to the STATIC_URL variable having a value of /static/, it means the {% static %} statements in listing 4 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 backend to serve static resources -- this last scenario is briefly discussed in the side below and explained at length in the recipes related to Django deployment techniques.

Access static resources in Jinja templates

Jinja templates offer an alternative to Django's own templates, as described in the recipe Use and customize Jinja templates in Django. 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. The recipe 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.

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

Django templates up until a few versions ago used the STATIC_URL variable directly in templates (e.g. <img src="{{STATIC_URL}}about/img/logo.gif">). A trace of this fact remains in 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.

To re-write a static resource's path a tag like {% static %} becomes ideal, because it 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.

Granted not all projects require the use of advanced static serving technologies. By using the {% static %} tag of the staticresources 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.

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 will 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.

Note You can access static resources as if DEBUG=False with Django's built-in web server

There's actually a workaround to make Django's built-in web server serve static resources as if DEBUG=False when it's actually set to DEBUG=True. You need to run the web server with the --insecure flag, as follows: python manage.py runserver --insecure.

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 -- the last of which is a typical setting for development. 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 last of which is a typical setting for production.

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 3. It's precisely this single tree that 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 3. Note that this directory should be empty, as it will be 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 6 illustrates the sample output of the syncing process.

Listing 6 - 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 3.

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.

The following recipes describe how to deploy Django static resources with different techniques: