Muffinresearch Labs by Stuart Colville

Automatic asset versioning in Django | Comments (9)

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

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

Comments: Add yours

1. On April 8th, 2008 at 7:43 am Brad Wright said:

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.

2. On April 8th, 2008 at 8:35 am Brad Wright said:

Oops, my mod_rewrite had fail:

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

Is better.

3. On April 8th, 2008 at 10:34 am Stuart Colville said:

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

4. On April 10th, 2008 at 7:32 am Ross Bruniges said:

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

5. On April 28th, 2008 at 5:50 am Rich said:

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

6. On June 10th, 2008 at 8:44 pm Lukasz Korzybski said:

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

7. On December 8th, 2008 at 1:28 pm Ɓukasz Korzybski said:

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.

8. On October 22nd, 2009 at 11:51 pm Ben said:

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!

9. On October 27th, 2009 at 9:37 pm Stuart Colville said:

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







XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>



Using Loggerhead with mod_wsgi|(0)

Here’s a post I wrote over on the Project Fondue Blog about our use of Loggerhead with mod_wsgi under Apache. Loggerhead is the rather nice branch viewer for bazaar branches as used on Launchpad.net.

If you’re not already subscribed to the Project Fondue blog feed then I can recommend it, as there should be some interesting posts coming out of there in the coming months (yes I’m unashamedly biased!).

Ubuntu: Turn off changing workspace with mouse wheel|(1)

I found the changing with the workspace with the mouse wheel really annoying. To disable it go to System => Preferences => CompizConfig (available if the compizconfig-settings-manager package is installed) and uncheck “Viewport Switcher” which is under the “Desktop” heading.

Photos on Flickr

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