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:
-
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. - 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!
- 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.
- 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.