I’m a hacker, and I love to build stuff for the Web.


Django Settings Flavours

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.

Getting Started

I begin with the default project layout. Running django-admin startproject myproject will give this:

|-- __init__.py
|-- manage.py
|-- settings.py
`-- urls.py

I then break settings.py into a directory, like so:

|-- __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.

The _merge() function

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

Applying the Solution

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.