My Django Project Conventions, pt. I

These few posts (there are more to come) are about some of my favourite Django project conventions. Python has this style of “there’s only one (obvious) way to do it”, and whilst this is nice, it is at best a bit idealistic and at worst a bit constraining. Of course, there will sometimes be ‘best practices’. That’s not the point of this blog post (although it will probably be the focus of the comments).

I have my own style which I like to adopt when working on Django projects. You might not want to use them, but maybe there will be some nuggets of gold in here for you to take.

The Layout

This is what the layout of a project of mine might look like. I’ll go into more depth later. I’ve laid out the files and folders in what, I hope, is an intuitive manner.

DIRECTORY   myproject/
FILE            __init__.py
FILE            manage.py
FILE            urls.py
DIRECTORY       myproject/settings/
FILE                __init__.py
FILE                common.py
FILE                debug.py
FILE                staging.py
FILE                production.py
FILE                modes.txt
DIRECTORY       myproject/apps/
FILE                __init__.py
FILE                external.py
DIRECTORY           myproject/apps/local/
FILE                    __init__.py
FILES                   ...
DIRECTORY       myproject/templates/
FILES               ...
DIRECTORY       myproject/media/
DIRECTORY       myproject/media/css/
DIRECTORY       myproject/media/js/
DIRECTORY       myproject/media/img/

You might have a few questions about this already. I’m going to try and answer a few here:

  1. Why no uploads directory? I like to use the /var/ directory for user-generated content. I believe that allowing users to upload their own data into the Python source tree is a little bit dangerous.
  2. Why has settings.py been replaced by a directory? There are a couple of reasons for this, which I shall explain shortly. Hang on tight!
  3. Aren’t templates supposed to be at the app level? Well, I like to stick some of the templates at the project level. This is especially important if you’re going to be making re-usable apps. A project is specific to a site, an app may be used on multiple sites. If you want to keep your apps re-usable, you probably need project-level templates.
  4. You keep the media stuff here? Yeah. I like to put the media in my project so that, when using a (D)VCS, the media gets included in the version control history. Images, JavaScript and CSS play an important part in the development process, too.

Now, on to the first major part of the project layout: the settings.

The Settings

As you can probably see, I have a settings directory instead of a simple Python module. When Django imports your settings, it does something like this:

from myproject import settings

Normally, the settings object which is created by this statement contains everything defined in the myproject/settings.py file. In the case of a directory, it takes all the values defined in myproject/settings/__init__.py. This file, therefore, is where the magic must happen. Look at the following piece of code:

UPDATE: Fixed some of this code so that it actually works.

import os

from myproject.settings.common import *

SETTING_MODES = []
SETTING_MODE = None

modes_fp = open(os.path.join(os.path.dirname(__file__), 'modes.txt'))
try:
    for line in map(str.strip, modes_fp):
        if line.endswith('*'):
            SETTING_MODE = line.strip('*').strip()
        SETTING_MODES.append(line)
finally:
    modes_fp.close()

SETTING_MODULE = __import__(__name__ + '.' + SETTING_MODE.lower(),
    fromlist=__name__.split('.'))
for attr in dir(SETTING_MODULE):
    if not (attr.startswith('__') and attr.endswith('__')):
        globals()[attr] = getattr(SETTING_MODULE, attr)

I don’t know about you, but It makes me want to hurl. Thing is, you see, it’s actually quite a useful piece of sludge. In fact, it’s possibly the most useful piece of sludge you’ll ever come across in your life, unless you are a member of a local SUG (Sludge User Group). What this does is the following: let’s say you have a project with some specified common settings, such as timezone, default language, installed apps, middleware and other nick nacks. However, you’re working on a super-critical application which involves debugging, staging and production servers, so you can develop your code, test it out and then release it to the public. If you’re running off a central (D)VCS branch, you would normally (with a simple settings.py file) have to change certain settings for each server, like database connections and cacheing. Every time you made a change and wanted to update the checked-out code on each server, you’d have to patch these settings in.

Well, no more!

Now, you get to have your common settings in one file, the settings for the debug server in another, the settings for the staging server in yet another, and the production settings in another file. This is all managed by the modes.txt file you see in the settings directory. This file looks something like this:

DEBUG *
STAGING
PRODUCTION

This is a list of all the modes you’re currently running. The one with the asterisk character (*) at the end of it is the currently selected mode. The code I showed you before does all the heavy lifting for you; change the line with the asterisk and it will switch from debugging mode to staging mode. In practice, what I normally do is have a .gitignore file in this directory with the single line ‘modes.txt’; this means each server’s settings mode persists through multiple commits and checkouts from and on different machines.

posted 10 months ago | Permatime

blog comments powered by Disqus