Muffinresearch Labs by Stuart Colville

Automatic asset versioning in Django | 9 Comments

Posted in Code, performance on 8th April 2008, 1:45 am by

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]

Post Tools

  • http://intranation.com/ Brad Wright

    Hey Stu,

    It seems Opera and Safari don’t cache file paths with query strings, as per the HTTP spec, and given that it’s part of the spec we have no guarantee that other browsers won’t behave the same in the future. Probably best to do something like:

    return path_string.replace('.css', '.%d.css' % mtime)

    And have something like this in mod_rewrite:

    RewriteRule ^/static/([a-z-]+)\.[0-9]+\.(css|js) /static/$1\.$s [L]

    This keeps the filenames looking like: /static/css/style.1207433992.css, but rewrites them to the latest versions in your static server.

  • http://intranation.com/ Brad Wright

    Oops, my mod_rewrite had fail:

    RewriteRule ^/static/([a-z-]+)\.[0-9]+\.(css|js) /static/$1\.$2 [L]

    Is better.

  • http://muffinresearch.co.uk Stuart Colville

    @Brad: Excellent points well made. I’ve updated the script accordingly.

  • http://www.thecssdiv.co.uk Ross Bruniges

    I have a feeling that this will provide very very useful to a number of people :>

  • http://jetfar.com/ Rich

    I was just looking around for some background to write something just like this for Django!

    Thanks very much for saving me several hours!

    - rich

  • Lukasz Korzybski

    Great stuff, thanks a lot :)

    One note thought:

    Atually as Steve pointed out in his comment to High Performance Web Sites: Rule 3 – Add an Expires Header the HTTP 1.1 specs says that URLs with query strings should not be cached only when there is no explicit expiration information:

    From HTTP 1.1 spec, section 13.9:

    e note one exception to this rule: since some applications have
    traditionally used GETs and HEADs with query URLs (those containing a
    “?” in the rel_path part) to perform operations with significant side
    effects, caches MUST NOT treat responses to such URIs as fresh unless
    the server provides an explicit expiration time.

    Also, RoR have some kind of template tag for auto versioning and it uses QS as far as I know.

    – Lukasz

  • http://www.domirynek.pl Łukasz Korzybski

    Recently I found this very interesting post, by Steve, about proxy and query strings issues. Steve proved that Squid’s default configuration is not to cache URLs with query strings.

    http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/

    So at the end it seems that using conservative file name versioning is better and more safe than QS method.

  • Ben

    For the regexp substitution, I think you mean:
    rx.sub(r"\1.%d.\2" % mtime, path_string)

    Without the raw string you’ll get binary in your return value the dots will be wildcards.

    This was a helpful article, though. Thanks!

  • http://muffinresearch.co.uk Stuart Colville

    @Ben: Thanks – I’ve updated the code example

Insert a tab character in vim when expand tabs is on|(0)

I have vim set-up to use spaces in place of tabs. Sometimes you need to use an actual tab e.g. editing a Makefile. Now whilst it’s possible to change settings so that tabs are used for specific files, a quick tip to remember is to simply type in insert mode:

Ctrl+v tab

That is Ctrl and “V” and hit the tab key, et voila you’ve entered an actual tab.

GNU screen: open tab in current working directory|(1)

A nice trick for having screen open a new tab in the same directory as the one you’re currently in. To use it add it to your .screenrc

# Open new window in current dir.
bind c stuff "screen -X chdir \$PWD;screen^M"
bind ^c stuff "screen -X chdir \$PWD;screen^M"

Hat tip: mteckert on SuperUser.com

Photos on Flickr

© Copyright 2004-13 Stuart Colville, all rights reserved. May contain traces of Muffin. Powered by WordPress. Hosting by Slicehost.com This page was baked in 0.468s.