During the time I've spent with Django, I've picked up a couple tricks for making life a little bit less terrible.
First, split the project project into three (or more) parts, each with its own settings.py: the main project, the development environment and the production environment. For example, my current project, code named eos, has a directory structure something like this:
eos/ .hg/ .hgignore manage.py run eos/ __init__.py settings.py templates/ urls.py ... hacking/ __init__.py settings.py db.sqlite3 ... production/ __init__.py settings.py run.wsgi ...
The eos/ directory is more or less a standard Django project (ie, created by django-admin.py startproject), except that eos/settings.py does not have any environment-specific information in it (for example, it doesn't have any database settings or URLs).
The hacking/ and production/ directories also contain settings.py files, except they define only environment specific settings. For example, hacking/settings.py looks a bit like this:
from eos.settings import * path = lambda *parts: os.path.join(os.path.dirname(__file__), *parts) DATABASE_ENGINE = "sqlite3" DATABASE_NAME = path("db.sqlite3") DEBUG = True
While production/settings.py contains:
from eos.settings import * DATABASE_ENGINE = "psycopg2" DATABASE_NAME = "eos" DATABASE_USER = "eos" DATABASE_PASSWORD = "secret" DEBUG = False
Then, instead of configuring Django (ie, calling setup_environment) on eos.settings, it is called on either hacking.settings or production.settings. For example, manage.py contains:
... import hacking.settings execute_manager(hacking.settings)
And production/run.wsgi contains:
... os.environ["DJANGO_SETTINGS_MODULE"] = "production.settings" ...
Second, every settings.py file should contain the path lambda:
path = lambda *parts: os.path.join(os.path.dirname(__file__), *parts)
It will make specifying paths relative to the settings.py file very easy, and completely do away with relative-path-related issues. For example:
MEDIA_ROOT = path("media/") DATA_ROOT = path("data/") DATABASE_NAME = path("db.sqlite3")
Third, there should be scripts for running, saving and re-building the environment. I use two scripts for this: run and dump_dev_data. By default the run script calls ./manage.py runserver 8631 (specifying a port is useful so that web browsers can distinguish between different applications - keeping passwords, history, etc. separate). Run can also be passed a reset argument, which will delete the development database and rebuild it from the dev fixtures. These fixtures are created by the dump_dev_data script, which calls ./manage.py dumpdata for each application, saving the data to fixtures named dev (these fixtures are committed along side the code, so all developers can work off the same data).
So, for example, when I'm developing a new model, my workflow will look something like this:
... add new model to models.py ... $ ./run reset # Reset the database adding the new model ... use the website to create data for the new model ... $ ./dump_dev_data # Dump the newly created data $ hg commit -m "Adding new model + test data"