I have a [Django][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][emailhost] 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][syntaxerror]. 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][manplist]. Bless Python for it has [the plistlib module][plistlib] 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.
[django]: http://www.djangoproject.com/
[emailhost]: http://docs.djangoproject.com/en/dev/ref/settings/#email-host
[syntaxerror]: http://docs.python.org/library/exceptions.html#exceptions.SyntaxError
[plistlib]: http://docs.python.org/library/plistlib.html
[manplist]: http://developer.apple.com/documentation/Darwin/Reference/ManPages/man5/plist.5.html