Django 1.4 first thoughts: CachedStaticFilesStorage

The first alpha release of Django 1.4 is out. I've been toying with trunk on and off for a while now, but hadn’t taken more than a passing glance at all the new features. At first glance: I’m thrilled.

I plan on writing a few posts about some new bits that stick out to me. Here’s the first.

CachedStaticFilesStorage and {% static %}

The new CachedStaticFileStorage stores your files but also creates a copy where the MD5 hash of the file's contents are added to the file's name. (i.e.: something like foo/style.css would be saved as foo/style.55e7cbb9ba48.css)

Taking a look at the implementation, it looks like a mixin (CachedFilesMixin) provides the functionality — and appears to be compatible as a plugin to any existing storage backend. The implications of this are pretty useful.

Say, for example, you’re currently using S3/CloudFront to serve your static files (by using the S3BotoStorage in django-storages: you’d simply do the following…

1
2
3
4
5
6
7
8
9
10
from storages.backends.s3boto import S3BotoStorage
from django.contrib.staticfiles.storage import CachedFilesMixin

class S3HashedFilesStorage(CachedFilesMixin, S3BotoStorage):
    """
Extends S3BotoStorage to also save hashed copies (i.e.
with filenames containing the file's MD5 hash) of the
files it saves.
"""
    pass

… and then set your STATICFILES_STORAGE to reference the above class. (Caveat: I haven’t tested the above yet.)

To get your templates to pick up the hashed versions of filenames, you'd use the new {% static %} template tag: just convert this…

1
<link href="{{ STATIC_URL }}style/base.css" rel="stylesheet"/>

…to this…

1
2
{% load static from staticfiles %}
<link href="{% static "style/base.css" %}" rel="stylesheet"/>

…and you’re now ready to rock with static files that you can cache forever without worry of them going stale.

Disclosure: I’m both excited and pissed because I actually spent a bit of time on a staticfiles hashing utility that wasn’t nearly as elegant as this mixin. Kudos to anyone who worked on these staticfiles enhancements.


Update (12:15PM PST): It isn’t mentioned in the docs for CachedStaticFilesStorage, but the filenames are cached in Django’s cache system (so the static files themselves do not need to be read constantly to generate the MD5 hashes). If you’re using memcached as your cache backend, this means that for deep directory structures and long filenames, you may hit the 250 character limit imposed on memcached keys. (Note that encountering this is rare outside of collecting static assets since models.FileField defaults to max_length=100 anyway.)

Solution: set up a function to shorten/hash your cache keys and point settings.KEY_FUNCTION to that function.


Another addendum: If you’re using multiple memcached servers and don’t want to incur network overhead just for those staticfile MD5 hashes, you can set a custom cache backend config — CachedStaticFilesStorage looks for a 'staticfiles' cache before falling back to the default, so you can force CachedStaticFilesStorage to cache these values in local memory instead of memcached (hat tip to Ted):

1
2
3
4
5
6
7
8
9
10
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
        'LOCATION': '127.0.0.1:11211',
    },
    'staticfiles': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'staticfiles-filehashes'
    }
}

Comments