My Django and WSGI Setup

A description of the setup I use for Django and WSGI.

The info at both modwsgi's "Integration With Django" and Django's "How to use django with mod_wsgi" are useful, but I'm hoping to cut to the chase here with a short and sweet example of how things work for me. I'll just go ahead and leave in the name of the project (lisinfo) the setup is for, but you should get the idea about where to change things. Okay, I'll start cutting and chasing now, then.

First, my Apache configuration, which, this being a Debian box, lives at /etc/apache2/sites-available/lisinfo.breaksalot.org:

<VirtualHost *>
    ServerName lisinfo.breaksalot.org
    ServerAdmin "the comptroller, and Miss Piggy"

    WSGIDaemonProcess lisinfo
    WSGIProcessGroup lisinfo

    WSGIScriptAlias / /var/www/lisinfo/django.wsgi
    <Directory /var/www/lisinfo/>
        Order deny,allow
        Allow from all
    </Directory>
    Alias /media /var/www/lisinfo/media/
    <Directory /var/www/lisinfo/media>
        Order deny,allow
        Allow from all
    </Directory>
    Alias /admin/media /usr/local/django/django/contrib/admin/media/
    <Directory /usr/local/django/django/contrib/admin/media>
        Order deny,allow
        Allow from all
    </Directory>
</VirtualHost>

The WSGIDaemonProcess and WSGIProcessGroup bits allow me to touch django.wsgi (the insides of which we'll get to in a sec) to kill and restart any processes associated with lisinfo, so code changes can be made visible without restarting Apache. This feature of the mod_wsgi setup is probably the one that makes the most positive difference for me. It makes developing almost as fast as with the built-in server. There's documentation on this stuff at modwsgi Configuration Directives if you're interested.

The django.wsgi script lives at /var/www/lisinfo/django.wsgi, the root of the lisinfo project, along with settings.py, urls.py, etc. It's short and sweet:

import os, sys

# path is the parent directory of this script ('/var/www' in this case)
path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# we check for path because we're told to at the tail end of
# http://code.google.com/p/modwsgi/wiki/ConfigurationDirectives#WSGIReloadMechanism 
if path not in sys.path:
    sys.path.append(path)

os.environ['DJANGO_SETTINGS_MODULE'] = 'lisinfo.settings'
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()

Update 2008-08-01: The steps below are no longer necessary as of r8015 and r8032. Also, those changes have obviated the need to put your non-root script alias in the urls.py. Many thanks to Malcolm Tredinnick for the fix.

Okay, so far, so good, huh? That's all you need to do if your WSGIScriptAlias is at the document root, i.e. just '/'. If it has a non-root path then you need to apply the patch I submitted to ticket #3414, which looks like this:

Index: django/core/handlers/wsgi.py
===================================================================
--- django/core/handlers/wsgi.py        (revision 7534)
+++ django/core/handlers/wsgi.py        (working copy)
@@ -75,7 +75,7 @@
 class WSGIRequest(http.HttpRequest):
     def __init__(self, environ):
         self.environ = environ
-        self.path = force_unicode(environ['PATH_INFO'])
+        self.path = force_unicode(environ['SCRIPT_NAME'] + environ['PATH_INFO'])
         self.META = environ
         self.method = environ['REQUEST_METHOD'].upper()

That seems to make it work for me. I hope I haven't missed anything. Happy hacking!

Update 2008-08-26: Some looking around at things today taught me some new stuff. Graham Dumpleton actually recommends MPM Worker over Prefork, as noted in this informative thread. Also, I wanted to make a note here that on one of our servers the setting of maximum-requests=10000 on the WSGIDaemonProcess, as suggested in the Defining Process Groups section in the Configuration Guidelines, has helped some memory issues.

keywords: Django, WSGI, mod_wsgi, modwsgi, Apache, Python, code, tech created 2008-06-19 last modified 2010-09-13