Skip to content
Using plists for site-specific Django settings

Using plists for site-specific Django settings

May 20, 2009

I have a Django project that I am going to deploy at several sites, but I need to tweak the project settings slightly for each site. Specifically I need different a EMAIL_HOST address and related settings for sending mail at each site.

The simplest route is to customize the project settings.py as part of the site deployment, but that will drive you insane when you deploy the wrong custom-settings to a site.

Another approach is similar to that used by many for switching between settings when moving between testing / staging / live environments: your settings.py has a few lines something like

try:
    from sitesettings import *
except ImportError:
    pass

so you can over-ride any setting by putting them in a sitesettings.py file, and then make sure your deployment never overwrites that site-specific file.

In my case I want to make it easy for the site administrator to customize the settings, but I am worried that it is too easy for someone who does not know Python syntax to inadvertently break things by writing a sitesettings.py that throws a SyntaxError exception. Given the significance of white-space in Python I feel this would be easy to get wrong.

So I’ve gone for storing the custom settings in Mac OS X’s property list format. Bless Python for it has the plistlib module that reads and writes the simple XML format of property lists.

Here’s my module that imports all properties from a plist straight into the module’s namespace. This then makes it easy to over-ride Django’s settings by doing

from plistsettings import *

A couple bits made my lips move during the writing. The contents of __all__ are updated dynamically because I wanted to use this with from plistingsettings import * without worrying that my module’s imports would get clobbered by imports used in the plistsettings module. And working out how to bind keys and values to the module itself is not obvious to me - it feels like one ought to be able to use self within the scope of the module to refer to the module itself. Except you can’t. No biggie.

# plistsettings.py
import os.path
import plistlib
import sys
from xml.parsers.expat import ExpatError


__all__ = []


PLIST_PATH = '/Library/Preferences/com.example.plist'


def read_prefs(plist_path):
    """Import settings from preference file into this module's global namespace.

    Returns a dictionary as returned by plistlib.readPlist().
    """
    try:
        if os.path.exists(plist_path):
            prefs = plistlib.readPlist(plist_path)
        else:
            return
    except ExpatError:
        return

    mod = sys.modules[__name__]
    global __all__

    for key, value in prefs.items():
        setattr(mod, key, value)
        __all__.append(key)
    return prefs

read_prefs(PLIST_PATH)

Now if you are the kind of Mac guy who enjoys using defaults you can write out your site-specific settings from the command-line.

defaults write /Library/Preferences/com.example EMAIL_HOST smtp.example.com
plutil -convert xml1 /Library/Preferences/com.example.plist

N.B. Mac OS X 10.5 defaults uses the binary format by default, so you need plutil to convert it back to XML because plistlib does not handle the binary format.

Last updated on