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.
Project Structure
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
...
The sitespecific
directory is the one you’d normally get when you run startproject
, and the app3
and 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:
- The
static
directory 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/adminmedia
symbolic link s.t. Apache can be configured to serve the admin media without referencing the Python installation directly. - The
apache/django.wsgi
script is what actually connects Apache (when set up for mod_wsgi scripting) to Django. I talk a bit more about it below. - The
sharedapps
directory 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__.py
file, while empty, is necessary to makeimport
statements work properly.
Apache
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.
django.wsgi
Here is the complete django.wsgi
script:
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.
manage.py
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())[0])
# 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.
Pingback: Turbo OAuth for Django | Things that were not immediately obvious to me