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
generated by 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 ROOT_URLCONF, INSTALLED_APPS, USE_I18N, context
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
settings, DEBUG and TEMPLATE_DEBUG, 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.
_merge() functionThe 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 common.py:
def _merge(local_vars):
local_vars.update((k, v) for k, v in globals().items() if k[0] != '_')
And at the top of development.py (or 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 _merge() calls globals(), it gets the dictionary of the
common.py namespace, with each variable in the namespace being represented by
a key => value pair in the dictionary. At the module level in
development.py, 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 foo. Hence, _merge() now has dictionaries for both
the common.py and development.py namespaces, and it simply copies over all
the public variables from common.py (i.e. those not prefixed with an
underscore) to development.py.
It’s a concept best demonstrated by example. Let’s say common.py contained the
following:
A = 1
B = 2
def _merge(local_vars):
local_vars.update((k, v) for k, v in globals().items() if k[0] != '_')
And development.py contained this:
from . import common; common._merge(vars())
C = 3
print (A, B, C)
Running python -m settings.development from the project root will print
(1, 2, 3). The variables A and 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
virtualenv activate script, a web server configuration, or on the command line
(if you’re using ./manage.py).
For your information, I used the UNIX
tree command to generate the file
listings for the example project.