As a sort-of-followup to my brief remarks of last week, today I’m going to say a few words about how I lay out my Django projects, and how I configure the Apache server to present them. There’s nothing too surprising here, but since some of this is a little fiddly, I thought it worth going over.
I like to lay out an individual project (basically corresponding to a single “web site”) like this:
/some/filesystem/path/to/project/ static/ [directory for static media] adminmedia/ [symbolic link to django/contrib/admin/media/] imgs/ [just by way of example ...] foo.png ... css/ bar.css ... apache/ django.wsgi [core mod_wsgi/Django bridge] sharedapps/ __init__.py [make sharedapps a python module] app1/ [shared app 1] urls.py models.py ... app2/ [shared app 1] urls.py models.py ... sitespecific/ templates/ [shared templates] 404.html 500.html ... admin/ [shared overridden admin templates] ... manage.py settings.py urls.py ... app3/ [non-shared app 3] urls.py models.py ... app4/ [non-shared app 4] urls.py models.py ...
sitespecific directory is the one you’d normally get when you run
startproject, and the
app4 directories are the ones that would normally be created by
python manage.py startapp. Some of the other directories might take a word of explanation:
staticdirectory holds everything that I want to serve up w/o going through Django; Apache is configured to serve these files directly.
- I set up the
static/adminmediasymbolic link s.t. Apache can be configured to serve the admin media without referencing the Python installation directly.
apache/django.wsgiscript is what actually connects Apache (when set up for mod_wsgi scripting) to Django. I talk a bit more about it below.
sharedappsdirectory holds those Django apps that have been decoupled from any particular Django project. It isn’t strictly necessary, but I prefer to keep the project-specific and decoupled apps separate. Note that the
__init__.pyfile, while empty, is necessary to make
importstatements work properly.
Here are some of the relevant parts of an exemplary Apache configuration script intended to be used with the project layout above:
# Single-thread, multi-process; Permissions may need to be set on the python-eggs directory WSGIDaemonProcess django processes=10 threads=1 maximum-requests=10000 python-eggs="/Library/Webserver/.django-python-eggs" WSGIProcessGroup django # Alias the static media URL Alias /some/url/path/static /some/filesystem/path/to/project/static # Alias the application root URL to its script Alias /some/url/path /some/filesystem/path/to/project/apache/django.wsgi # Enable serving of the static (including symlinked admin) media <Directory "/some/filesystem/path/to/project/static/"> Options FollowSymLinks # To access admin media Order deny,allow Deny from all Allow from localhost # ... or what permissions you prefer </Directory> # Enable WSGI scripting in the project's WSGI directory <Directory "/some/filesystem/path/to/project/apache/"> Options ExecCGI # Set up the WSGI hander SetHandler wsgi-script WSGIPassAuthorization On # As discussed last week Order deny,allow Deny from all Allow from localhost # ... or what permissions you prefer </Directory>
Note that I use Apache to serve static content; it’s actually recommended that you use another web server to do this when running Django. Such an arrangement is left as an exercise for the reader.
Here is the complete
import os import sys sys.path.append('/some/filesystem/path/to/project/') os.environ['DJANGO_SETTINGS_MODULE'] = 'sitespecific.settings' import django.core.handlers.wsgi application = django.core.handlers.wsgi.WSGIHandler()
It doesn’t do anything too clever, aside from putting the parent of the
sitespecific directory on the Python path. Note that all project-specific dotted-paths are relative to this directory; the
sitespecific.settings syntax seen here is representative of what you’ll see in
import statements throughout the code.
Because I’ve written my Django project under the assumption that the parent of the
sitespecific directory is on the Python path, I have to tweak the
manage.py script to support this assumption. Here’s the revised script:
#!/usr/bin/env python from django.core.management import execute_manager try: import settings # Assumed to be in the same directory. except ImportError: import sys sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) sys.exit(1) if __name__ == "__main__": # Hack s.t. the development server environment mirrors production import os import sys sys.path.append(os.path.split(os.getcwd())) # Generated code execute_manager(settings)
This arrangement will allow you to run the development server (
python manage.py runserver) s.t. it will work in most cases. Be aware that most
static media won’t be served up without some additional steps, not detailed here. (Admin media will work because of special hacks in the development server.) Also note that your application will always be presented at the root of the development server, instead of at the path you set in Apache configuration.