On Static Media and Django
We all know not to serve static media (images, CSS, Javascript, etc.) in production directly from Django. Thankfully, Django gives us some nice settings like MEDIA_URL and MEDIA_ROOT to make serving them a lot less painless. Lately, however, I’ve come to realize that these settings shouldn’t really apply to all static media.
Not All Static Media Is Created Equal
Static media really comes in two flavors. Media that is part of your site like your stylesheets and user generated media or files that are uploaded to the site once it is live. We don’t want to serve all this media from the same place for a couple of reasons:
- Our source checkouts shouldn’t be crufted up by stuff our users are uploading
- User generated content and source code should live in two different places on the filesystem
We could fix the first problem with some .gitignore, but that doesn’t solve the filesystem issue.
Solution: Segregate Your Static Files
The answer is to segregate your static files. Django already does this with ADMIN_MEDIA_PREFIX. We use two additional settings in our Django projects, STATIC_URL and STATIC_ROOT. This lets us put user generated content in MEDIA_ROOT outside our project while still serving STATIC_ROOT from inside our source repo.
Implementation
We use Nginx as a front-end proxy to most of our sites and add this stanza to serve all our static media:
location /s/ {
alias /var/www/domainname.com/;
}
Then we add three directories to /var/www/domainname.com/:
- admin, a symlink to django/contrib/admin/media
- static, a symlink pointing inside our project repo respectively
- media, a new folder that stores all the user generated content.
Our project settings file looks like this now:
MEDIA_ROOT = '/var/www/domainname.com/media/' MEDIA_URL = '/s/media/' STATIC_ROOT = '/path/to/project_src/static/' STATIC_URL = '/s/static/' ADMIN_MEDIA_PREFIX = '/s/admin/'
and we slap the following code into our urls.py to serve media during development:
from django.conf import settings if getattr(settings, 'LOCAL_DEV', False): urlpatterns += patterns('django.views.static', (r'^%s(?P<path>.*)' % settings.STATIC_URL, 'serve', {'document_root': settings.STATIC_ROOT}), (r'^%s(?P</path><path>.*)' % settings.MEDIA_URL, 'serve', {'document_root': settings.MEDIA_ROOT}), ) </path>
This has worked very well for us, how do you tackle the issue (or is it not an issue to you)?
Comments
Got something to say?
This was written on November, 13 2008 and is filed in django.
Our Products
Categories
- SEO
- accessiblity
- code
- company news
- django
- gondola
- portfolio
- presentation
- screencast
- software
- subversion
- trailmapping
- wordpress
Archives
- December, 2008
- November, 2008
- September, 2008
- August, 2008
- July, 2008
- June, 2008
- May, 2008
- April, 2008
Elsewhere
What we’ve been up to online
-
Aaron Mentele, Charisma 18 » Turns
Electric Pulp's experience of the last economic downturn
Pete, 0 minutes ago -
pushed to master at lincolnloop/django-beancounter
Pete, 1 hour, 41 minutes ago -
committed to lincolnloop/django-beancounter
Pete, 1 hour, 41 minutes ago -
committed to lincolnloop/django-beancounter
Pete, 2 hours, 20 minutes ago -
pushed to master at lincolnloop/django-beancounter
Pete, 2 hours, 20 minutes ago -
committed to lincolnloop/django-beancounter
Pete, 2 hours, 20 minutes ago -
pushed to master at lincolnloop/django-beancounter
Pete, 2 hours, 28 minutes ago -
committed to lincolnloop/django-beancounter
Pete, 2 hours, 28 minutes ago -
committed to lincolnloop/django-beancounter
Pete, 1 day, 1 hour ago -
committed to lincolnloop/django-beancounter
Pete, 1 day, 1 hour ago -
committed to lincolnloop/django-beancounter
Pete, 1 day, 1 hour ago -
committed to lincolnloop/django-beancounter
Pete, 1 day, 1 hour ago -
committed to lincolnloop/django-beancounter
Pete, 1 day, 1 hour ago -
pushed to master at lincolnloop/django-beancounter
Pete, 1 day, 1 hour ago -
applied fork commits to lincoln-loop-deploy/master
Pete, 1 week, 5 days ago


Looks like some of my code got BeautifulSoup’ed. Both the regexes in the urls.py are
r’^%s(?P<path>.*)’First of all i use a separate domain for static/media, preferable 2 because it speeds up your site (default settings in all browsers is to have max 2 concurrent connections to the same domain) something like static1.foo.com static2.foo.com which resolves to the same ip. You dont want more than 2 domains for statics because domain resolving on many domains slows down your site (you can read about this in steves sounder (creator of yslow)s book High Performence Web Sites.
My preferred setup then looks like this. one or multiple apache instances with mod_wsgi for the django app, and then one or multiple nginx instances for the statics depening on how large the site is, and one nginx acting as a proxy infront of the whole setup. If its a small site i serve the statics from the same nginx which acts as proxy.
And for separating user generated statics from the sites i do something like this:
location / { root /home/foo/site/media; }location /static {
root /home/foo/upload;
}
Great post! I love your method here, and I’m going to start using it going forward. Thanks so much for sharing such a useful best practice!
I know this is a bit old, but I noticed a problem with the code above, since I tried to use it in my own app.
I had to set up my patterns like this:
urlpatterns += patterns(‘django.views.static’,.*)’ % settings.STATIC_URL[1:], ‘serve’,.*)’ % settings.MEDIA_URL[1:], ‘serve’,
(r’^%s(?P
{‘document_root’: settings.STATIC_ROOT}),
(r’^%s(?P
{‘document_root’: settings.MEDIA_ROOT}),
)
With the [1:] slice on STATIC_URL and MEDIA_URL, because otherwise the initial slash in those URLs will cause the URLs not to work.
I’ve included it in:
http://github.com/mikl/djime/commit/874829443423aae83a2f0af1d4b7c39cfde8f37d – thanks :)
@Andreas – I am a bit skeptical of the max 2 concurrent connections thing. I have observed safari 3 and firefox load multiple files simultaneously, and generally from a browser maker’s perspective this seems like it would make the browser feel slower and less snappy (which is not what they are motivated to do).
I looked at the oreilly website for the book you mention, and i don’t see that seperation in the “core 14 priniciples”, but I do see “Reduce DNS Lookups”, which would make me lean towards centralize everything on one domain.
However, I am always very interested in making our sites load faster, so if you have any references about this specifically, I am intersted in it, so in the off chance you hit this thread again, please post some!
The Google Browser Security Handbook has some numbers on concurrent connections in various browsers:
http://code.google.com/p/browsersec/wiki/Part2#Simultaneous_connection_limits