I’m a hacker, and I love to build stuff for the Web.
Thursday 10th September, 2009
Anyone who’s ever deployed a Django site will have encountered a common problem. You’ll usually have both a development and a production environment in which you’re running your Django project; many large-scale deployments will also have a staging environment. How do you maintain separate configurations for each of these environments, using each configuration in its appropriate environment?
In my Django projects, I have a setup, refined over a period of several months,
which I feel provides a very neat, elegant and flexible solution to this
problem. It leverages the fact that the default Django project layout, as
django-admin startproject, assumes very little about the
individual development process or deployment architecture which the project will
have as it matures. While this can be intimidating for newcomers, it is a design
decision which allows seasoned Djangonauts to stretch the boundaries of the
framework, taking advantage of the power and flexibility provided.
I begin with the default project layout. Running
django-admin startproject myproject will give this:
myproject/ |-- __init__.py |-- manage.py |-- settings.py `-- urls.py
I then break
settings.py into a directory, like so:
myproject/ |-- __init__.py |-- manage.py |-- settings | |-- __init__.py | |-- common.py | |-- development.py | `-- production.py `-- urls.py
There are a few things to note about the
settings directory and its contents:
It contains an
__init__.py file, which makes it a Python package.
common.py contains the settings which are common to all environments. This
includes stuff like
processors, middleware and so on. It also includes a function called
_merge(), which I’ll get to later.
There are modules for each deployment environment. These contain database
CACHE_BACKEND, et cetera.
In order to use a particular settings ‘flavour’, simply set the
DJANGO_SETTINGS_MODULE environment variable in the context where you’ll be
running your application. For example, it might be
myproject.settings.development for your development environment, and
myproject.settings.production in production.
The only issue with this solution, as it stands, is how to get the settings from
common.py into the environment-specific modules. You could try
from myproject.settings.common import *, but that ties you into an absolute
import. Instead, add the following function to the bottom of
def _merge(local_vars): local_vars.update((k, v) for k, v in globals().items() if k != '_')
And at the top of
production.py, et cetera):
from . import common; common._merge(vars())
This is essentially a workaround for the fact that relative imports cannot use
import *. When
globals(), it gets the dictionary of the
common.py namespace, with each variable in the namespace being represented by
key => value pair in the dictionary. At the module level in
vars() returns this same dictionary, but for that module
instead. Where it really helps is that both of these dictionaries support
assignment; that is,
vars()['foo'] = "bar" is perfectly valid and will assign
"bar" to the variable
_merge() now has dictionaries for both
development.py namespaces, and it simply copies over all
the public variables from
common.py (i.e. those not prefixed with an
It’s a concept best demonstrated by example. Let’s say
common.py contained the
A = 1 B = 2 def _merge(local_vars): local_vars.update((k, v) for k, v in globals().items() if k != '_')
development.py contained this:
from . import common; common._merge(vars()) C = 3 print (A, B, C)
python -m settings.development from the project root will print
(1, 2, 3). The variables
B have been copied over into the
development.py namespace for you to use as you wish.
All it takes is writing up your
common.py followed by your
environment-specific modules, and you’re done. You can set the
DJANGO_SETTINGS_MODULE environment variable from an
application.wsgi file, a
activate script, a web server configuration, or on the command line
(if you’re using
For your information, I used the UNIX
tree command to generate the file
listings for the example project.