Django Project Layout

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 make import 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.

Share and Enjoy:
  • Twitter
  • Facebook
  • Digg
  • Reddit
  • HackerNews
  • del.icio.us
  • Google Bookmarks
  • Slashdot
This entry was posted in Python, Web stuff. Bookmark the permalink.

One Response to Django Project Layout

  1. Pingback: Turbo OAuth for Django | Things that were not immediately obvious to me