Factor Your Django Settings Into uwsgi Ini Files
·If you have one codebase and multiple sites, keep your settings in ini files.
Although this is certainly not the usual use case for a Django site, I am
deploying a new site that will be used by several programs at the school where
I work. Each program will have its own specific Django settings file with its
own values (for example, the database connection info) but for the most part
the sites share a base settings_production.py
file. Here is a sample of this
file:
from mysite.settings import *
DEBUG = False
TEMPLATE_DEBUG = DEBUG
ALLOWED_HOSTS.append('.university.edu')
INSTALLED_APPS.append(
'websso'
)
MIDDLEWARE_CLASSES.append(
'django.contrib.auth.middleware.RemoteUserMiddleware'
)
AUTHENTICATION_BACKENDS.append(
'websso.backends.RegistryRemoteUserBackend'
)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': os.environ.get('MYSITE_DB_NAME'),
'USER': os.environ.get('MYSITE_DB_USER'),
'PASSWORD': os.environ.get('MYSITE_DB_PASSWORD'),
'HOST': os.environ.get('MYSITE_DB_HOST'),
'PORT': '5432',
}
}
As you can see, we get the database connection values from the environment.
I am using Apache (with mod_proxy_uwsgi) and uwsgi in emperor mode to run the
site, and each site has a uwsgi ini file that actually sets all the relevant
per-site settings via the
env
configuration option. This was quite easy to set up, and this is an example
uwsgi ini
file:
[uwsgi]
chdir=/srv/myvirtualenv/mysite
module=mysite.wsgi:application
max-requests=5000
socket=127.0.0.1:3032
logto=/tmp/uwsgi-example.log
env = DJANGO_SETTINGS_MODULE=mysite.settings_production
env = MYSITE_DB_NAME=mysite_dbname
env = MYSITE_DB_USER=mysite_username
env = MYSITE_DB_PASSWORD=abc123
env = MYSITE_DB_HOST=mysite-dbname.hostname.us-east-1.rds.amazonaws.com
So far so good – we can run uwsgi in emperor mode, point it at our conf/
directory full of ini files, and everything works fine.
Now, what if we want to use manage.py shell
? We have fully factored these
settings out of the settings_production.py
file and we can’t rely on envdir
because the uwsgi emperor won’t know how to use it.
The answer (for me anyway) is fabric and
python’s ConfigParser
module. With a little work we can actually write helper functions in our
fabfile that use ConfigParser
objects to read the uwsgi ini files and set the
appropriate environment variables before calling manage.py
.
The important wrinkle here is that the env
setting is used several times in
the ini file, while the default behavior of ConfigParser
is to use a simple
dict to store each option found in the ini file, and if an option is specified
multiple times, only the last one is saved. We can override this however by
specifying our own dict type to hold the option values:
from fabric.api import task, shell_env, local
from collections import OrderedDict
import ConfigParser
@task
def shell(site_name):
filename = 'conf/uwsgi-%s.ini' % (site_name,)
env_settings = parse_uwsgi_ini_env_settings(filename)
with shell_env(**env_settings):
local('python manage.py shell --settings=mysite.settings_production')
class MultiOrderedDict(OrderedDict):
def __setitem__(self, key, value):
if isinstance(value, list) and key in self:
self[key].extend(value)
else:
super(OrderedDict, self).__setitem__(key, value)
def parse_uwsgi_ini_env_settings(filename):
env_settings = {}
config = ConfigParser.RawConfigParser(dict_type=MultiOrderedDict)
config.read(filename)
for env_setting in config.get('uwsgi', 'env'):
key, val = env_setting.split('=', 1)
env_settings[key] = val
return env_settings
Now rather than run python manage.py shell
, run fab shell:foo
your
environment variables will be read from conf/uwsgi-foo.ini
and set properly.