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:

  1. Our source checkouts shouldn’t be crufted up by stuff our users are uploading
  2. 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)?