The awesomest way possible to serve your static stuff in Django with Nginx
http://github.com/peterbe/django-static24th of March 2010
I'm the proud creator of django-static which is a Django app that takes care of how you serve your static media the best way possible. Although some of these things are subjective generally this is the ideal checklist of servicing your static media:
- Cache headers must be set to infinity
- URLs must be unique so that browsers never have to depend on refreshing
- The developer (who decided which media to include) should not have to worry himself with deployment
- The developer/artist (who makes the media) should not have to worry himself with deployment
- All Javascript and CSS must be whitespace optimized in a safe way and served with Gzip
- All images referenced inside CSS should be taken care of too
- It must be possible to combine multiple resources of Javascript or CSS into one
- It must be possible to easily test production deployment in development environment without too much effort
- A sysadmin shouldn't have to understand a developers Django application
- A development environment must be unhindered by this optimization
- Processing overhead of must be kept to a minimum
- Must be possible to easily say which resources can be whitespace optimized and which can not
So let's get started setting all of this up in your Django + Nginx environment. Let's start with the Django side of things.
Download and install django-static by first running something like easy_install django-static then add django_static to INSTALLED_APPS in your settings.py. and add this to enable 'django-static':
DJANGO_STATIC = True
Then edit your base.html template from this:
<html>
<link rel="stylesheet" href="/css/screen.css">
<link rel="stylesheet" href="/css/typo.css">
<link rel="stylesheet" href="/css/print.css" media="print">
<body>
<img src="/img/logo.png" alt="Logo">
{% block body %}{% endblock %}
<script type="text/javascript" src="/js/site.js"></script>
<script type="text/javascript">
window.onload = function() {
dostuff();
};
</script>
</body>
To this new optimized version:
{% load django_static %}
<html>
{% slimall %}
<link rel="stylesheet" href="/css/screen.css">
<link rel="stylesheet" href="/css/typo.css">
<link rel="stylesheet" href="/css/print.css" media="print">
{% endslimall %}
<body>
<img src="{% staticfile "/img/logo.png" %}" alt="Logo">
{% block body %}{% endblock %}
<script type="text/javascript" src="{% slimfile "/js/site.js" %}"></script>
<script type="text/javascript">
{% slimcontent %}
window.onload = function() {
dostuff();
};
{% endslimcontent %}
</script>
</body>
</html>
django_static when loaded offers you the following tags:
-
staticfile<filename> -
slimfile<filename> -
slimcontent ... endslimcontent -
staticall ... endstaticall -
slimall ... endslimall
All the tags with the word slim are copies of the equivalent without; but on its way to publication it attempts to whitespace optimize the content. Now, rendering this, what do you get? It will look something like this if you view the rendered source:
<html>
<link rel="stylesheet" href="/css/screen_typo.1269174558.css">
<link rel="stylesheet" href="/css/print.1269178381.css" media="print">
<body>
<img src="/img/logo.1269170122.png" alt="Logo">
[[[ MAIN CONTENT SNIPPED ]]]
<script type="text/javascript" src="/js/site.1269198161.js"></script>
<script type="text/javascript">
indow.onload=function(){dostuff()};
</script>
</body>
As you can see timestamps are put into the URLs. These timestamps are the modification time of the files which means that you never run the risk of serving an old file by an already used name.
The next step is to wire this up in your Nginx. Here is the relevant rewrite rule:
location ^~ /css/ {
root /var/mydjangosite/media;
expires max;
access_log off;
}
location ^~ /js/ {
root /var/mydjangosite/media;
expires max;
access_log off;
}
location ^~ /img/ {
root /var/mydjangosite/media;
expires max;
access_log off;
}
That wasn't particularly pretty. Besides as we haven't done any configuration yet this means that files like print.1269178381.css has been created inside your media files directory. Since these files are sooner or later going to be obsolete and they're never going to get included in your source control we probably want to put them somewhere else. Add this setting to your 'settings.py':
DJANGO_STATIC_SAVE_PREFIX = '/tmp/cache-forever'
That means that all the whitespace optimized files are put in this place instead. And the files that aren't whitespace optimized have symlinks into this directory.
The next problem with the Nginx config lines is that we're repeating ourselves for each prefix. Let's instead set a general prefix with this config:
DJANGO_STATIC_NAME_PREFIX = '/cache-forever'
And with that in place you can change your Nginx config to this:
location ^~ /cache-forever/ {
root /tmp;
expires max;
access_log off;
}
django-static is wired up to depend on slimmer if available but you can use different ones, namely Yahoo! YUI Compressor and Google Closure Tools. So, let's use YUI Compressor for whitespace optimizing the CSS and Google Closure for the whitespace optimize the Javascript. Add this to your 'settings.py':
DJANGO_STATIC_CLOSURE_COMPILER = '/var/lib/stuff/compiler.jar' DJANGO_STATIC_YUI_COMPRESSOR = '/var/lib/stuff/yuicompressor-2.4.2.jar'
Now we get the best possible whitespace optimization and a really neat Nginx configuration. Lastly (and this is optional) we might want to serve the static media from a different domain name as the browser won't download more than two resources at a time from the same domain. Or even better you might have a domain name dedicated that never accepts or sends any cookie headers (for example, Yahoo! uses yimg.com). This is accomplished by setting this setting:
DJANGO_STATIC_MEDIA_URL = 'http://static.peterbe.com' # no trailing slash
Now you're ready to go! Every single item on the big list above is checked. With a few easy steps and some modifications to your templates you can get the simplest yet best performing setup for your static media. As an example, study the static media URLs and headers of crosstips.org.
Some people might prefer to use a remote CDN to host the static media. This is something django-static is currently not able to do but I'm more than happy to accept patches and ideas from people who want to use it in production and who are eager to help. Everything else still applies. We would just need a callback function that can handle the network copy.
UPDATE
At the time of writing, version 1.3.7 has 93% test coverage. The number of lines of tests is double to the actual code itself. Code: 661 lines. Tests: 1358
Comment
Show all 19 commentsCommenting is currently disabled in Mobile version