Tuesday, August 26, 2008

Django|Static Files

Django and Static Files

One of the most common questions that comes up in #django is about static files. Why are my images not loading? How do I make Django serve my CSS files? The answer depends on the environment. You might want to hook up django.views.static.serve in development and Alias in production. I want to help clarify all static file questions.

How do I make Django serve static files?

It is possible to setup Django to serve your static files. This functionality is off by default. Django is a web application framework. It is not intended to serve any static files. It might serve static content which is cached in memcached, the database or on the filesystem. The method to setup static file serving in Django is disclaimed in the documentation:

Using this method is inefficient and insecure. Do not use this in a production setting. Use this only for development.

Check out the static documentation for Django to get more information about how to set it up with your urls.py. Lets cover what settings that Django has that I will use through out the rest of this article:

MEDIA_ROOT
The default value is "" (an empty string). Django uses MEDIA_ROOT as the root path for the upload_to parameter for FileField and ImageField. It is typically the place where you will want to make publically available through your web server.
MEDIA_URL
The default value is "" (an empty string). There is nothing magically about MEDIA_URL. It is more of an aide to you to properly map static files to the correct location in your templates. You can get MEDIA_URL defined in all of your templates by using django.core.context_processors.media in your TEMPLATE_CONTEXT_PROCESSORS. This is turned on by default if you are using SVN revision 5379 or newer. However, you need to ensure you are using the RequestContext as your context in reach view that renders a template.

A quick tip serving static files in development

I can't say that I invented this idea, because I originally found it in Satchmo. Create a new settings variable called LOCAL_DEVELOPMENT. Set the value to either True or False depending on whether the project is local or on a production server. Then make your urls.py look similar to this:

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

urlpatterns = patterns("",
# normal url patterns here.
)

if settings.LOCAL_DEVELOPMENT:
urlpatterns += patterns("django.views",
url(r"%s(?P<path>.*)/$" % settings.MEDIA_URL[1:], "static.serve", {
"document_root": settings.MEDIA_ROOT,
})
)

Keep in mind that if you have a url pattern that consumes your MEDIA_URL before getting to the new one I show you above then you will need to restructure things. Also keep in mind that if your MEDIA_URL is an absolute URL then this will clearly not work. If you have separated your settings.py file to allow for different settings in different locations then just override your main MEDIA_URL to be something that can be used in the urlpatterns.

UPDATE: It has come to my attension that the above code was wrong and has now been corrected. It used to allow settings.MEDIA_URL pass through to the regex, but that was incorrect when settings.MEDIA_URL has a leading slash as it should to be properly referenced in the templates. I am now using settings.MEDIA_URL[1:] to eat the leading slash.

Serving static files in production

I will describe how to accomplish static file serving using Apache. This is the easiest method to configure considering you are probably deploying to Apache. Here is a quick example of how you might setup Django within Apache and mod_python:

<Location "/">
SetHandler python-program
PythonHandler django.core.handlers.modpython
SetEnv DJANGO_SETTINGS_MODULE mysite.settings
PythonDebug On
</Location>

This works well, but as you can see if a request is made to /site_media/css/layout.css then it is passed to Django. Then Django would need to resolve it and return something back to client. If you accidently left LOCAL_DEVELOPMENT turned on then this will magically work and you may ignore that and assume everything is okay. However, this is very bad because now Django is being forced to serve static files when the web server should be responsible for that. Apache can serve the static files much more efficiently than Django can. So, let's add on to our Apache configuration:

<Location "/site_media">
SetHandler None
</Location>

Okay, now we have told Apache that when a request is made with /site_media as the beginning portion of the path to do nothing. We are not done yet, because we need to tell Apache to map requests from /site_media to the files located on the file system. This can be done using Apache's Alias directive:

Alias /site_media/ /path/to/media
<Location "/site_media">
SetHandler None
</Location>

Replace /path/to/media to your MEDIA_ROOT path. Setting up the admin media files is done the same way. Just map ADMIN_MEDIA_PREFIX to the location of the admin media files. The location will vary from system to system, but the path inside the Django source tree is django/contrib/admin/media. Be sure to find out where your Django source tree resides and map it accordingly. Also, keep in mind you may need to look at the permissions to ensure the webserver user that Apache runs as has the permission to read those files. This applies to all static files being served by Apache.

This wraps up how to serve static files in a Django environment. I am looking forward to expand on this article and cover Cherokee and the environment that is required for that setup.

No comments: