Kristian Glass - Do I Smell Burning?

Mostly technical things

Django Settings Environments

So you’ve started your Django project. You’ve done some work on it, and all’s going well. You’re about to make your first deployment somewhere. You realise “Hey, I’m going to need different settings for each environment, what’s the best way to do that?”. It turns out people ask this a fair bit. I’ve read through a bunch of these, and none quite do it for me. So here’s what I use – if it works for you, I’d love to hear it; if you think you can make it better, I’d love to know.

UPDATE

I now use the method described by The Twelve-Factor App and use django12factor instead. Stop reading this article and go read more about django12factor.

Splitting up the settings

First, some context. I want the side-effects of my changes to be minimal. I want to be able to easily tell which environment I’m in. I want switching environments to be painless. I want my changes to be no friction to someone else coming along to my code.

To that end, lets replace the settings module with a package:

1
2
3
4
5
6
$ ls -1 myproject/settings/
__init__.py
base.py
live.py
staging.py
local.py

We’ll migrate the contents of our original settings.py into settings/base.py, then have any configuration overrides for the live environment in settings/live.py (and the equivalent for the staging environment) and any specific local overrides in settings/local.py (we’ll also want to add this file to .gitignore or similar).

Now, the bit where the magic happens – settings/__init__.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import os
from base import *

ENVIRONMENT = os.getenv("DJANGO_ENVIRONMENT")

if ENVIRONMENT == "live":
    from live import *
elif ENVIRONMENT == "staging":
    from staging import *

try:
    from local import *
except ImportError:
    pass</pre>

Hopefully this should be fairly self-illustrative – in summary:

  1. Load in our core settings from base.py
  2. Grab the environment variable DJANGO_ENVIRONMENT and store it in settings.ENVIRONMENT
  3. Load any environment-specific settings overrides
  4. If we have local overrides, pull those in

With just that, you’re ready to go. No further modifications necessary.

Why do this?

So why is this better than, say, DJANGO_SETTINGS_MODULE or some random imports?

  • Environment settings are clean. Want to add another environment? Just create a file of that name, and start putting settings in
  • Easy to change environments for local developmen
  • I use Heroku, (their free tier is awesome for small initial deployments) - unless I want to start hacking buildpacks, this seems the cleanest way

The final issue is how environment-specific settings should be populated. If you haven’t read The Twelve-Factor App, go and do so. That said, I disagree slightly with some of their points about configuration – specifically, unsurprisingly, the dislike for environments. That said, I feel that the environments should be kept as minimal as possible, so as an example, here’s my settings/live.py:

(UPDATE: Nope, they had a point about scaling, so now I use django12factor)

1
2
3
4
5
AWS_STORAGE_BUCKET_NAME = PROJECT_NAME
STATICFILES_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
S3_URL = 'http://%s.s3.amazonaws.com/' % AWS_STORAGE_BUCKET_NAME
STATIC_URL = S3_URL
ADMIN_MEDIA_PREFIX = S3_URL + 'admin/'

This is nothing more than staticfiles configuration for storages using Amazon S3 and boto. You’ll note that I set nothing else in here – no DATABASES, or DEBUG, or AWS_ACCESS_KEY_ID et cetera – all of these things come from environment variables. Static files is the main thing for me that is a function of environment.

Warning to Heroku users

Any Heroku users reading the above and planning on implementing it, just be warned – the current Heroku Python buildpack has the neat feature of autogenerating a Procfile for you if it detects that you’re pushing a Django app. However, this detection is done by looking for a settings.py file, so if you implement what I describe above, you’ll need to create your own Procfile – this is no arduous task, you shouldn’t be using the autogenerated one anyway, but this threw me a bit when I encountered it!