Automatic asset versioning in Django

Following on from Ed's "Automatic versioning of CSS, JavaScript and Images" here is a method to version filenames based on modification times to be used in Django as a template tag.

This is really handy technique for when you set expires headers to a long way into the future. With headers set in this way files need to have their filenames versioned to force the client to download the latest version of a file. The purpose being that with far futures expires the browser will agressively cache the asset thus minimising the amount of requests for assets.

This template tag for Django uses the same method as Ed's to provide a version string that's appended to the filename. The awesome part of this is once configured you can just happily change the file as you need to and the caching is taken care of.

{% load utils %}
<link rel="stylesheet" type="text/css" href="{% version '/static/css/style.css' %}">

The code for this is placed in a utils.py in "project/app/templatetags"

from django import template  
register = template.Library()
import os, re        

STATIC_PATH="/path/to/templates/"                              
version_cache = {}

rx = re.compile(r"^(.*)\.(.*?)$")
def version(path_string):                                                                 
    try:
        if path_string in version_cache:
            mtime = version_cache[path_string]
        else:
            mtime = os.path.getmtime('%s%s' % (STATIC_PATH, path_string,))
            version_cache[path_string] = mtime

        return rx.sub(r"\1.%d.\2" % mtime, path_string)
    except:
        return path_string 

register.simple_tag(version) 

As Brad has pointed out in the comments below the original query string method used will not be cached by UAs following the http spec. The output will now look like the following:

/static/css/style.css?v=1207433992
/static/css/style.1207433992.css

In addition a mod_rewrite rule is required to map the versioned file to the original.

RewriteRule ^/static/(.*?)\.[0-9]+\.(css|js|jpe?g|gif|png) /static/$1\.$2 [L]
comments powered by Disqus