Settings management in Django

November 16, 2016

Django has a pretty good system in place for handling configuration: the settings file. It’s the one place you go to set configuration variables for your app. There are a couple of common scenarios however that the settings file doesn’t handle super well. Namely sensitive settings and different environments (dev, prod, staging).

We don’t want sensitive settings like the secret key or database passwords to be hard-coded in our settings file. There are obvious security implications, like your production secret key being the same as your secret key in development, and anybody who has access to the repo is also inadvertently given direct access to your database.

There’s also the matter of different settings for different environments. You’re pretty much always going to use a different database during local development and production. You’d probably also want DEBUG to be enabled during local development, but disabled in production.

There have been some approaches that handle these scenarios in the past. For example, to handle sensitive settings, we used environment variables, but it wasn’t always easy to set these for local development. And to handle different settings for different environments, we used multiple settings files, which can get pretty redundant. These approaches work well enough, but in my opinion are kind of clunky.

I eventually came across a library called django-environ, and it made configuration management much simpler. What it does is simple, it makes it easy to load environment variables from the system while also allowing us to load configuration variables from a file. Let’s see how it works.

We’ll set up django-environ in our settings.py file.

# ./myapp/settings.py

import os

import environ

PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))
BASE_DIR = os.path.dirname(PROJECT_DIR)

env = environ.Env(
    SECRET_KEY=str,
    DEBUG=(bool, False),
    ALLOWED_HOSTS=(list, ['127.0.0.1:8000']),
    DATABASE_URL=str,
)

What we’re doing here is defining which variables django-environ should look for, set their type, and optionally set a default value.

You might be wondering what DATABASE_URL is. It’s basically a string that holds all the information required to connect to a database. Its type (Postgres, MySQL, sqlite), username, password, hostname, port, and database name. Here’s an example:

postgres://user:password@host:5432/mydb

You don’t have to use it, but it’s becoming a common convention these days. It’s really convenient that the database parameters don’t need to be declared separately.

django-environ primarily gets the configuration from the current environment, but we can also make it load variables from a file (usually named .env). We’ll make sure to add .env to our .gitignore as to not commit it to our repo. This is important since .env will usually hold sensitive settings, as well as environment specific settings.

Let’s add a .env file to our project root.

# ./.env

SECRET_KEY=not-so-secret
DEBUG=True
DATABASE_URL=postgres://postgres:password@localhost:5432/myapp

We can then load this file in settings.py like so:

environ.Env.read_env(os.path.join(BASE_DIR, '.env'))

The variables found in .env will override the system environment variables.

Now, to actually use these variables, we’ll just use the env variable that we declared earlier.

SECRET_KEY = env('SECRET_KEY')
DEBUG = env('DEBUG')

DATABASES = {
  'default': env.db(),
}

env() will return the value for the given key, or if its not set, the default we specified earlier on. If no value is found and there’s no default value, django-environ will raise an exception.

If DATBASE_URL is set, django-environ can automatically parse and convert it into a database configuration dictionary ready to be used by Django.

Simple stuff, but this actually solves both of our issues. Sensitive settings are still set in environment variables, but we can conveniently override them in the .env file during local development. And to make our app run in different environments, we only need to set the appropriate settings in the .env file.

Real cool.

Comments

comments powered by Disqus