Django and css/javascript files

Tags: django

How to bundle css/javascript files with your django applications? They’re an integral part of an application and should be versioned and bundled together with the app. Solution: django-staticfiles.

The old situation, at least in the projects where I started working on earlier this year, was to handle the static files at the site level instead of at the application level. I’d get a document with instructions to set up a static directory somewhere that would be hosted by apache. And how to copy-paste several directories in there. Testing on your development machine automatically meant you had to run apache, too. Yuck. Too manual and not really versioned and reliable.

I looked around and found out about django-staticfiles, which fits the bill nicely. With some settings.py boilerplate and some conventions, we now have the following setup:

  • Every application has a media/ directory next to the templates/ directory.

  • Just like in templates/, you normally make a subdirectory in media/ named after your application.

  • Inside that subdirectory, you place your css, javascript, images, etcetera.

  • Just as with templates, applications that are higher up in your INSTALLED_APPS can override media files in “lower” applications. So customizing a logo.png couldn’t be easier.

  • In development, django itself returns the static files. No need for a webserver in front.

  • In production, a one-time call to manage.py build_static (or bin/django build_static if you’re using the django buildout recipe) gathers up all your static files from all applications and builds a directory that you can serve with apache.

There’s some boilerplate and some settings that you need. Some relevant bits from the kind of setup I have in my settings.py are below. I set up my projects with buildout and the relevant parts of my directory structure are:

my-site/
my-site/setup.py
my-site/buildout.cfg

my-site/my_site/  # The django site itself.
my-site/my_site/models.py
my-site/my_site/settings.py
my-site/my_site/templates/
my-site/my_site/templates/my_site/
my-site/my_site/media/
my-site/my_site/media/my_site/
my-site/my_site/media/another_app/
my-site/my_site/media/another_app/logo.png

my-site/etc/  # Apache configs and so.
my-site/etc/my-site.apache.conf.in
my-site/var/
my-site/var/log/
my-site/var/media/   # User-uploaded media
my-site/var/static/  # Django-staticfiles' files.

setup.py extract:

...
install_requires = [
  ...
  'django-staticfiles',
  ...

buildout.cfg extract:

...
[mkdir]
# Handy recipe to create directories if they don't exist yet.
recipe = z3c.recipe.mkdir
paths =
    ${buildout:directory}/var/static
    ${buildout:directory}/var/media
    ${buildout:directory}/var/log
...

[apacheconf]
# Generate a file from a template (with variable substitution).
recipe = collective.recipe.template
input = ${buildout:directory}/etc/my-site.apache.conf.in
output = ${buildout:directory}/etc/my-site.apache.conf
...

settings.py extract:

import os
...

# SETTINGS_DIR allows media paths and so to be relative to
# this settings file instead of hardcoded to
# c:\only\on\my\computer.
SETTINGS_DIR = os.path.dirname(os.path.realpath(__file__))

# BUILDOUT_DIR is for access to the "surrounding"
# buildout, for instance for BUILDOUT_DIR/var/media files
# to give django-staticfiles a proper place to place all
# collected static files.
BUILDOUT_DIR = os.path.abspath(os.path.join(SETTINGS_DIR, '..'))

# Absolute path to the directory that holds user-uploaded
# media.
MEDIA_ROOT = os.path.join(BUILDOUT_DIR, 'var', 'media')

# Absolute path to the directory where django-staticfiles'
# "bin/django build_static" places all collected static
# files from all applications' /media directory.
STATIC_ROOT = os.path.join(BUILDOUT_DIR, 'var', 'static')

# URL that handles the media served from MEDIA_ROOT. Make
# sure to use a trailing slash if there is a path
# component (optional in other cases).
MEDIA_URL = '/media/'

# URL for the per-application /media static files
# collected by django-staticfiles.  Use it in templates
# like "{{ MEDIA_URL }}mypackage/my.css".
STATIC_URL = '/static_media/'

# URL prefix for admin media -- CSS, JavaScript and
# images. Make sure to use a trailing slash.  Uses
# STATIC_URL as django-staticfiles nicely collects admin's
# static media into STATIC_ROOT/admin.
ADMIN_MEDIA_PREFIX = STATIC_URL + 'admin/'

# django-staticfiles needs an extra context processor to
# allow you to use {{ STATIC_URL }}myapp/my.css in your
# templates.
TEMPLATE_CONTEXT_PROCESSORS = (
    # Default items that we have to copy/paste in here.
    'django.core.context_processors.auth',
    'django.core.context_processors.debug',
    'django.core.context_processors.i18n',
    'django.core.context_processors.media',
    # Extra for django-staticfiles.
    'staticfiles.context_processors.static_url',
    )

And a small bit of code in urls.py:

from django.conf.urls.defaults import *
from django.conf import settings

urlpatterns = ...

if settings.DEBUG:
    # Allow django.staticfiles to do its job itself
    # in debug mode.
    urlpatterns += patterns(
        '',
        (r'', include('staticfiles.urls')),
    )

For apache’s static file hosting in etc/my-site.apache.conf.in (those $() items are replaced by buildout when generating the real my-site.apache.conf, handy for getting the absolute directories always right wherever you install it):

<VirtualHost *:80>
  ServerName ...
  ...
  <Location /static_media/>
    ExpiresActive On
    ExpiresDefault "access plus 1 hour"
    # ^^^ Yeah, got to set up better caching...
  </Location>
  ...
  # Static files are hosted by apache itself.
  # User-uploaded media: MEDIA_URL = '/media/'
  Alias /media/ ${buildout:directory}/var/media/
  # django-staticfiles: STATIC_URL = '/static_media/'
  Alias /static_media/ ${buildout:directory}/var/static/
  # Django is run via WSGI.
  WSGIScriptAlias / ${buildout:directory}/bin/django.wsgi
  ...

In the templates, you can reference the static files like {{ STATIC_URL }}myapp/my.css. Provided you’re using the RequestContext to make your context processors available in your template.

I’m real happy with how it all works. A drawback is the boilerplate you need to add here and there. After that, working with css, images and javascript is pretty much painless.

And for boilerplate, you’ve got initial project generation tools, which I’ll write about later in my last post about software releases.

 
vanrees.org logo

About me

My name is Reinout van Rees and I work a lot with Python (programming language) and Django (website framework). I live in The Netherlands and I'm happily married to Annie van Rees-Kooiman.

Weblog feeds

Most of my website content is in my weblog. You can keep up to date by subscribing to the automatic feeds (for instance with Google reader):