Mastering Django Static Files

Informal Namespacing

“If I called my app polls, then why do I put my CSS at polls/static/polls/app.css? Why do I need that second polls?”

This question, or variants thereof, comes up regularly.

So what is the point of having a second polls dir inside polls/static?

Here’s the thing - the polls/static directory is one of potentially many sources for static files. If you create polls/static/app.css, then in a template, you’d refer to it with {% static 'app.css' %}. That lookup happens across all static files of your project - just because it’s a template inside your polls app doesn’t mean it’ll prefer static files from your polls app.

Thus, if:

  • You’ve got polls/static/app.css
  • I’ve created a reusable app with myawesomeapp/static/app.css
  • You’re using my app alongside your polls app (which, of course you are, because my app’s awesome),

then one of our apps is going to end up with the other’s CSS, which is probably not what you want!

To work around this, convention is to use that extra directory:

  • You put your CSS at polls/static/polls/app.css
  • You refer to it with {% static 'polls/app.css' %} inside your templates
  • I’ll put mine at myawesomeapp/static/myawesomeapp/app.css

and then we won’t collide at all.

You’ll see the same convention happen with templates.

I call this “informal namespacing”, because there’s no actual enforcement of this - there’s nothing to stop you creating polls/static/myawesomeapp/app.css. This is, in fact, a common technique for overriding static files or templates in third party apps, for example django.contrib.admin (see the official docs on overriding admin templates).

There you go - strictly speaking you don’t need the “extra” directory, but you almost certainly want it!

Failproof Favicons

In pretty much every Django project I’ve written, I have a URL pattern for ^favicon.ico$' in my urls.py.

I’ve seen a lot of different ways people try to handle favicons; lots of them rely on unstated assumptions, and I really don’t like those. So here’s what I do:

from django.contrib.staticfiles.storage import staticfiles_storage
from django.views.generic.base import RedirectView

urlpatterns = patterns(‘’, url( r'^favicon.ico$‘, RedirectView.as_view( url=staticfiles_storage.url('favicon.ico’), permanent=False), name=“favicon” ), # …

What does that do?

Requests for /favicon.ico get redirected to the URL of 'favicon.ico'according to my staticfiles storage. The storage is what actually serves the favicon, but it’s Django that handles the (first) request.

But why?

I’ve read much more than I wanted to about different browser handling of favicons. I still don’t completely understand it. There are a lot of gotchas. The main thing I’ve taken away is:

Almost all browsers try /favicon.ico by default, so just make sure that points to your favicon and it Should Just Work

How does that redirect really work?

Pretty much in the same way the static template tag does (and if you’re not using it in your templates, you should be).

django.contrib.staticfiles.storage provides staticfiles_storage, which is an instance of your project’s configured static file storage. Storages have a url method that “[r]eturns an absolute URL where the file’s contents can be accessed directly by a Web browser.”, which is exactly what the {% static %} tag uses.

So {% static 'favicon.ico' %} in a template is essentially equivalent to staticfiles_storage.url('favicon.ico') in Python.

Alternatives?

<link> tag

As I mentioned above, lots of people do this:

<link rel=“icon” href=“{% static ‘favicon.ico’ %}”>

Fine enough, if you get the rel attribute right, but (with most staticfiles setups) you’re 404ing when the browser tries /favicon.ico.

I do include this in my markup, but I want to make sure I’m handling /favicon.ico too.

Redirect in your fronting httpd

You might instead handle favicons in front of Django, e.g. with something like the following snippet of nginx config:

location /favicon.ico {
    alias /path/to/your/static/files/favicon.ico;
    # or
    redirect 301 http://your.static.files.host.example.com/favicon.ico;
}

However with that, you’ve added another deployment requirement to your Django project, and another place where you need to duplicate elements of your static files location. Those aren’t huge problems, but since I can easily avoid them, I’ll gladly do so.

But what about performance?

Sure, you’re having Django handle these requests, even if it is only to return a redirect; from a performance perspective it’d almost certainly be better to deal with them before they reach Django (i.e. in some sort of fronting http proxy like Apache, nginx or HAProxy).

This is a general recommendation, to minimise headaches, and it’ll get you far. If you’re in an environment where this would have significant performance impact, I’d love to hear from you and see your measurements. I doubt many people are.

Stop using STATIC_URL in templates

Stop using STATIC_URL and start using the static template tag to ensure your URLs work across storages.

What does that mean? It means when you have static files in templates, do this:

{% load static from staticfiles %}
<img src={% static "images/hi.jpg” %} alt="Hi!” />

Not this:

<img src={{ STATIC_URL }}images/hi.jpg” alt=“Hi!” />

Why? Like many of the worst mistakes to make, this will work absolutely fine until it doesn’t.

So there you are, writing your templates. You think “what’s the point in loading an extra template tag when I already have STATIC_URL in the context?”. You collectstatic your files to somewhere your nginx can serve them, and everything works fine. Later you try moving to Amazon S3, or CloudFront, or some other CDN. At which point all your static links break, and there goes your afternoon.

Why?

What the static tag does is call the url method of your configured STATICFILES_STORAGE (remember those from http://blog.doismellburning.co.uk/2012/06/25/django-and-static-files/ ?) with the path provided, to get the correct URL for the file path.

For the vanilla StaticFilesStorage, this is straightforward, so StaticFileStorage.url effectively just prepends STATIC_URL to the path given, which is almost the same result as blind in-template concatenation.

For some of the more complex storages, like S3BotoStorage from django-storages, determining a file’s URL can potentially involve generating an access token, expiry time, and other such fun. At this point, simple concatenation won’t quite cut it.

Anyone who’s been using Django for some time may be particularly in need of this advice, as, prior to Django 1.5, the documentation advocated just using STATIC_URL, only mentioning the template tag as a sidenote, and it generally works often enough that you could never realise the shortcomings until they come to bite you.

Don’t get caught out.

UPDATE:

Another great example of when you absolutely must use the static tag: if you’re using Django’s CachedStaticFilesStorage as described in my post about Setting up static file caching, because it uses a hash of the file’s contents as part of the file name (so css/styles.css might be saved as css/styles.55e7cbb9ba48.css) and no amount of carefully constructing STATIC_URL will help you here!

Setting up static file caching

Even a fairly light-weight page will probably load some CSS and JS, and a few images - it’s pretty easy to find each page load triggering at least a dozen subsequent fetches. Now this doesn’t mean the files will be re-fetched - a sensible webserver setup will respond with 304 (Not Modified) and no content, so the browser will use its cache, but those are requests that don’t need to be made, and only so many that your server will handle at once.

Where static files are concerned, caching is simple: all you need are three easy things:

  1. Set expiry headers
  2. Use CachedStaticFilesStorage
  3. Set Cache-Control: public

Set expiry headers

There are two important HTTP headers here: Expires and Cache-Control: max-age. They have slightly different semantics, but the distinction is minor - Apache and nginx both provide a single configuration directive to set both. They tell caches how long they can keep files for - you’ll want to set this to a year from access. Why a year? Historically this used to be the maximum allowed value - that restriction has been lifted, but in many cases, there’s little point in setting a larger value, and I don’t consider it worth it.

For Apache, this means first ensuring that mod_expires is enabled:

$ sudo a2enmod expires

then, in the configuration block serving your static files:

ExpiresActive on
ExpiresDefault “access plus 1 year”

For nginx it’s much simpler (nearly all distros package nginx with the ngx_http_headers_module included):

expires 1y;

That’s it; now caches should keep your files for a year. Now you’re probably thinking that that’s fine for things that don’t change often, if at all, like images, but what about your CSS and JavaScript that tend to change between releases? This is where the next task comes in:

Use CachedStaticFilesStorage

STATICFILES_STORAGE = ‘django.contrib.staticfiles.storage.CachedStaticFilesStorage’

This is a slightly misleadingly-named class: what it does is rename your static files to include a hash of their content. If the content changes, the filename changes, so clients will get changes immediately rather than waiting for cached content to expire.

The “Cached” part of the name is because it uses the Django cache framework to store the hashes - Django 1.7 brings ManifestStaticFilesStorage which instead writes them to a JSON file it loads at startup - a much-awaited improvement!

Set Cache-Control: public

There are two types of caches: private (e.g. a user’s browser) and public (e.g. an institutional shared proxy, or a local CDN).

Sending Cache-Control: public with your static files tells shared caches that it’s ok to retain them.

For Apache, this means ensuring that mod_headers is enabled:

$ sudo a2enmod headers

then adding the following to the configuration block for your static files:

Header append Cache-Control public

(append rather than set so as not to overwrite the Cache-Control: max-age setting described above; add would be equally valid)

For nginx:

add_header Cache-Control public

Conclusion

Caching configuration is really useful, and simple to set up. If you’ve not done it yet for your apps, follow this guide and speed things up for your users.

Understanding Django's staticfiles app

Django’s handling of static files is great, but sometimes causes confusion. If you’re wondering how it all fits together, what some of the settings mean, or just want some example uses, then keep reading.

If you want to understand the fundamentals of how the excellent staticfiles app works in Django, what the configuration options mean, and how to get them right, then look no further than this article I wrote for my personal blog: Django and Static Files.