Using plists for site-specific Django settings
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.