Arthur

Pemberton

Full-stack web applications developer


Varying Django Settings By Environment

October 21, 2013Arthur Pemberton0 Comments

If you’ve progressed beyond the exploration phase in your Django journey, you’ve probably come to the point where, at the least, you would like to use one database during your development, and another, once published to your production system. You may even want to

The beauty of Django, and it’s settings, is that it is itself merely a python object, specifically a python module. The settings module gets executed once upon initialization of the web application. One of my first attempts at segmenting the settings was by checking the hostname or host IP address of the machine running the web application server — this is independent of the method of running Django (development server, mod_wsgi, etc).

This implementation looked something like this:

import socket

MACHINE = socket.gethostname().lower()
DEBUG_MACHINES = ['test-machine']
DEBUG = MACHINE in DEBUG_MACHINES

if DEBUG:
	DATABASE_HOST = 'localhost'
else:
	DATABASE_HOST = 'live-mysql-server'

DATABASES = {
	'default': {
		'ENGINE': 'django.db.backends.mysql',
		'NAME': 'database',
		'USER': 'username',
		'PASSWORD': 'password',
		'HOST': DATABASE_HOST,
		'PORT': '',
	}
}

I get the hostname of the machine, check if it is in a known list of debug (development) machines, setting the DEBUG setting to True. The database hostname is then chosen based on being in debug mode or not.

This approach works fine, but requires keeping track of IP addresses, and essentially locks one machine into a single role. I eventually wanted to use one machine for both development and testing purposes, and a second machine as the actual live system. For this, I took more flexible approach — using environment variables to determine the mode that I wanted. I was happy with three modes: DEBUG, TESTING, and LIVE. I decide to use an environment variable, APP_MODE. My settings.py then looked like:

import os

APP_MODE = os.environ.get('APP_MODE', 'DEBUG') ; APP_MODE = APP_MODE.upper()
if not APP_MODE in ('DEBUG','TESTING','LIVE'): APP_MODE = 'DEBUG'

DEBUG = APP_MODE not in ('LIVE')

if MG_MODE == 'LIVE':
	DATABASE_NAME = 'database'
	DATABASE_HOST = 'live-mysql-server'
	pass
elif MG_MODE == 'TESTING':
	DATABASE_NAME = 'database-testing'
	DATABASE_HOST = 'localhost'
else:
	DATABASE_NAME = 'database-debug'
	DATABASE_HOST = 'localhost'

DATABASES = {
	'default': {
		'ENGINE': 'django.db.backends.mysql',
		'NAME': DATABASE_NAME,
		'USER': 'username',
		'PASSWORD': 'password',
		'HOST': DATABASE_HOST,
		'PORT': '',
	}
}

Now, whenever the Django instance is ran with no explicit environment variables, it is in DEBUG mode. If we want to explicitly run the development in, say, TESTING mode. We run the server as:

$ APP_MODE=TESTING python manage.py runserver

The troublesome part was getting the production Apache+mod_wsgi instance to run in LIVE mode. Setting the environment variable from the <VirtualHost> section was easy:

SetEnv APP_MODE LIVE

The issue, however is that the default WSGI Django setup does not (seem) to automatically pass external environmental variables to the Django application object instance. I found it necessary to subclass and enhance the application created to pass in just the environment variables that I was interested in. This gave me a wsgi.py file somewhat like this:

import os

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myapp.settings")

# create basic handler
from django.core.wsgi import get_wsgi_application
_application = get_wsgi_application()

# wrap handler with our own callable
def application(environ, start_response):
	# copy APP_ env variables into process
	for k in environ.keys():
		if not k.startswith('APP_'): continue
		os.environ[k] = environ[k]
	# return base handler
	return _application(environ, start_response)

This checks for all environment variables with a prefix of “APP_” and adds them to the environment of the application.

With that I can now setup one or more <VirtualHost> sections with alternate APP_MODEs, or run python manage.py syncdb to create the production database. The settings varied are entirely up to the developer, as is how complex the logic is. It is important to remember that settings.py is not specific to individual HTTP requests.


Leave a Reply