So, I’m one of those people where I don’t like running things “too far” from what a production setup might look like (I code on OS/X, deploy to Linux). This is why I jump(ed) through various hoops on my OS X system to get Apache/Django/mod_wsgi/etc all up and running and happy (not for serving the site; just developing).
Since I like simple/succinct guides, I thought I’d post what I did so others can follow in my stead.
Note: These instructions work with the python 2.5 version which ships with Leopard, or a self-compiled version of 2.6 (which is what I prefer) – see the InstallationOnMacOSX mod_wsgi page. Additionally, see the “Missing Code For Architecture” section for possible work-arounds if you find yourself needing 32 bit execution of Apache; I think the “Forcing 32 Bit Execution” are preferred over the “thinning” of the Apache binary.
First, download and install mod_wsgi on leopard, this is as easy as (on Leopard):
curl -o mod_wsgi.tgz http://modwsgi.googlecode.com/files/mod_wsgi-2.5.tar.gz tar -xzf mod_wsgi.tgz cd mod_wsgi-2.5 ./configure make sudo make install
Now, edit (via sudo) /etc/apache2/httpd.conf and add the line:
LoadModule wsgi_module libexec/apache2/mod_wsgi.so
After the rest of the LoadModule lines. Cool.
Invariably all of my directions play with virtualenv/virtualenvwrapper and pip:
mkvirtualenv django cdvirtualenv easy_install pip pip install http://media.djangoproject.com/releases/1.1/Django-1.1-rc-1.tar.gz django-admin.py startproject mysite django-admin.py startapp myapp cd mysite mkdir apache mkdir media
Now, that just sets up the skeleton – the meat of the wsgi configuration goes in apache/ in the mysite/apache directory. The first file is named mysite.wsgi:
1 2 3 4 5 6 7 8 9 10 11 | import os, sys #Calculate the path based on the location of the WSGI script. apache_configuration = os.path.dirname(__file__) project = os.path.dirname(apache_configuration) workspace = os.path.dirname(project) sys.path.append(workspace) os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings' import django.core.handlers.wsgi application = django.core.handlers.wsgi.WSGIHandler() |
This does the needed wsgi project magic for the Django application – don’t worry about the interpreter path; we’ll do that next.
Next up is a file named apache_django_wsgi.conf, this looks like this:
# mod_wsgi configuration directives - I like having stdout access, the other two # options run mod_wsgi in daemon mode - more on this in a minute. WSGIPythonHome /<path to virtualenv> WSGIRestrictStdout Off WSGIDaemonProcess django WSGIProcessGroup django # # This should be the path of the /mysite/media directory # for example "/Users/jesse/mysite/media/" # Alias /site_media/ "<PATH TO>/mysite/media/" <Directory "<PATH TO>/mysite/media"> Order allow,deny Options Indexes Allow from all IndexOptions FancyIndexing </Directory> # # Directory path to the admin media, for example: # Alias /media/ "<PATH TO>/virtualenv/site-packages/django/contrib/admin/media/" <Directory "<PATH TO>/virtualenv/site-packages/django/contrib/admin/media"> Order allow,deny Options Indexes Allow from all IndexOptions FancyIndexing </Directory> # # Path to the mysite.wsgi file, for example: # "/Users/jesse/mysite/apache/mysite.wsgi" # WSGIScriptAlias / "<PATH TO>/mysite/apache/mysite.wsgi" <Directory "<PATH TO>/mysite/apache"> Allow from all </Directory>
The apache_django_wsgi.conf file is the meat-and-potatoes here. This sets up all the paths/permissions, and is in Apache httpd.conf format. You can pretty much logjam any apache configuration directive here that you like.
Your final step is to once again edit (via sudo) /etc/apache2/httpd.conf and add a line like this at the verrrrry bottom:
Include "/path to/mysite/apache/apache_django_wsgi.conf"
And then run “sudo apachectl restart”
You should now be able to hit http://127.0.0.1/ and see the friendly and inviting django welcome page. Note, that if you are using sqlite as your database, you should chmod a+rw the file, so that processes which are not you can mess with it.
There’s a final piece to this though. Normally, if you run mod_wsgi in embedded mode, you’re going to need to restart apache every single time you make a change to your django app.
Ah! But we’re running in daemon mode. This means all you need to do when you change a file is:
touch mysite/apache/mysite.wsgi
This will trigger a reload and magic happens. Me being as lazy as I am (ask my wife) ended up snagging Bruno Bord’s tdaemon script, and hacking it up a bit. The tdaemon script will watch a directory and run tests. Well, I wanted it to watch a directory (and let me filter sub directories) and then run that touch command. So I reused my watcher.py (here) – I used this to monitor my sphinx tree and run builds as well (and other stuff). Here’s how I’d use this:
workon django cdvirtualenv cd mysite python ~/.slash/bin/watcher.py --command "touch apache/mysite.wsgi" -f media
This will auto-fire the touch command whenever it detects a file change (including svn updates).
You can also do this another way
In my rush to reuse a tool I use a bit (watcher) I skipped past the mod_wsgi document section on code reloading that shows how to setup a monitor which will watch .py file changes and kill the wsgi daemon, here. If you scroll down a bit, you’ll see the “Monitoring For Code Changes” section. All you need to do here is copy the code from the wiki into a module on your PYTHONPATH – in my case, I wrote it to mysite/apache/wsgi_monitor.py (just for this example! you should put it someplace else!) and then changed the mysite.wsgi file to import it, and set it up:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import os, sys #Calculate the path based on the location of the WSGI script. apache_configuration = os.path.dirname(__file__) project = os.path.dirname(apache_configuration) workspace = os.path.dirname(project) sys.path.append(workspace) sys.path.append(apache_configuration) # you probably shouldn't do this. import wsgi_monitor wsgi_monitor.start(interval=1.0) os.environ['DJANGO_SETTINGS_MODULE'] = 'ui.settings' import django.core.handlers.wsgi application = django.core.handlers.wsgi.WSGIHandler() |
This method – once you reload apache – will watch the project for changes and then kill the wsgi daemon (forces a reload). So there you go – two ways of doing it.
The nice thing about this setup is that I can make production version of the wsgi scripts and check them in, but keep local “my copies” (ala local_settings.py) additionally, I don’t have to jump through hoops to get static media and content served up via the django development server.
Additional reading: