This is an in-progress draft, enjoy.

App settings

Every Django project is configurable by a settings.py file (or othwerise named and specified settings file). This file provides for the configuration needs of a site that may differ from deployment to deployment - like desktop development and production - and from day to day.

Individual apps often have their own configuration needs. This can involve toggling certain features, API credentials, cache key and timeout information - there's a wide range of knobs which may warrant tuning depending on what the end user or deployed site needs.

Sourcing app settings

There are a few obvious candidates for how to source your app's specific settings. We'll review in order of least attractive to most attractive.

The environment

If you're following the 12 factor application pattern you likely have settings for your site that are provided via environment variables. This is a fine way of providing project level configuration, but a poor one for providing direct configuration to sub-apps.

Let's say you have a Django app that includes a third-party API client. In your own project you provide the API credentials through the process environment, so you simply pull these from the environ in your API client module like so:

# myapp/client.py
import os

API_TOKEN = os.environ.get("API_TOKEN")

class ApiClient:
    """All client code follows from here"""
    ...

This certainly works but suffers from some serious problems.

First and sufficiently it necessitates that every project using your app provides settings via the process environment. This is hardly the only way to specify unique settings and it's an unnecessary imposition for your package to dictate how the projects using it provide their settings.

For people who are providing individual setting values via environment variables it dictates what those variables must be named which in most cases is not something your package should have any reason to dictate. Whether or not the names you have chosen are descriptive and unique, other people may have very good reasons to use variables with other names.

And for non-secret settings, it may make more sense for these to be hard coded on a per-deployment basis (e.g. for a production settings module vs a development settings module). In this case your environment variable-based settings add non-trvial overhead.

Avoid this pattern in your Django app!

Directly from django.conf.settings

Far better is to delegate how these settings are provided to the project's own settings module.

# myapp/client.py
from django.conf import settings

API_TOKEN = settings.API_TOKEN

class ApiClient:
    """All client code follows from here"""
    ...

This solves the problem of how the individual settings values are added to the project. However as is it doesn't do anything to provide default values or any kind of setting validation.

App configuration modules

We can actually do one better than our previous example of importing directly from the project settings by instead using an intermediary app-specific settings module.

To motivate this, let's look at an example where we want a default value. An API credential is a bad example, but something like a "retry" count could have a default value.

# myapp/client.py
from django.conf import settings

try:
    API_RETRIES = settings.API_RETRIES
except AttributeError:
    API_RETRIES = 0

class ApiClient:
    """All client code follows from here"""
    ...

This code ensures there is a value available whether one is specified or not. We probably want to go further than this though and provide at least some minimal validation. The problem with settings values is that when they're wrong because the value is wrong (e.g. wrong type) it's not often immediately clear why, and instead the user is presented with a stacktrace that might extend to the other libraries. So if there are hard limits to a setting try to enforce them upfront.

# myapp/client.py
from django.conf import settings
from django.core.excpetions import ImproperlyConfigured

try:
    API_RETRIES = int(settings.API_RETRIES)
except AttributeError:
    API_RETRIES = 0
except(TypeError, ValueError):
    raise ImproperlyConfigured("API_RETRIES must be an integer value")

if not 0 <= API_RETRIES <= 10:
    raise ImproperlyConfigured("API_RETRIES must be between 0 and 10")

class ApiClient:
    """All client code follows from here"""
    ...

Now, not every setting demands this kind of validation! But you can see how this can quickly create clutter, not to mention duplication in the event a value is needed in more than one place in your app.

Instead, use a separate module in your app from which you can cleanly import the necessary settings, validated and with sane defaults.

# myapp/conf.py
from django.conf import settings
from django.core.excpetions import ImproperlyConfigured

try:
    API_RETRIES = int(settings.API_RETRIES)
except AttributeError:
    API_RETRIES = 0
except(TypeError, ValueError):
    raise ImproperlyConfigured("API_RETRIES must be an integer value")

if not 0 <= API_RETRIES <= 10:
    raise ImproperlyConfigured("API_RETRIES must be between 0 and 10")

# myapp/client.py
from myapp.conf import API_RETRIES

class ApiClient:
    """All client code follows from here"""
    ...

Settings formats

There are two basic ways of collecting application settings from a project level settings.py file:

  1. Individually named settings
  2. Monolithic dictionaries

In either case, the settings values your app looks for in django.conf.settings should be consistently namespaced to prevent collisions with other app settings and to make the settings easily identifiable.

SOMETHING_COOL = True

That's not helpful.

MYAPP_SOMETHING_COOL = True

Now this immediately describes what this setting is associated with.

Individual named settings

This should be your default choice, individual values in the settings files, namespaced by your choice of app settings namespace.

MYAPP_SOMETHING_COOL = True
MYAPP_SAY_HELLO = 'whatever.models.Okay'
MYAPP_IS_IT_ON = DEBUG

First, most apps have only a few settings. This pattern follows conventions and is easy to work with. It can get a bit ungainly if there are many settings values though, or any kind of nested settings (Django's TEMPLATES setting should come to mind).

Monolithic dictionary

This doesn't have to be a dictionary per se, e.g. a tuple would work as well, but a dictionary makes the most sense here.

CUMULUS = {
    'USERNAME': 'YourUsername',
    'API_KEY': 'YourAPIKey',
    'CONTAINER': 'ContainerName',
    'PYRAX_IDENTITY_TYPE': 'rackspace',
}

This style of setting makes sense when you have required configuration, like to a backing service. If the alternative is something like this in every single settings.py file:

MYAPP_USERNAME = 'YourUsername',
MYAPP_API_KEY = 'YourAPIKey',
MYAPP_CONTAINER = 'ContainerName',
MYAPP_PYRAX_IDENTITY_TYPE = 'rackspace',

Then a consolidated dictionary will be easier to read and understand, for both you as the third-part developer and whoever is using your app. The specifics of how these values are populated are up to the end user, and in any case do not differ substantially.

The primary drawback to using a nested structure like a dictionary is that it is not easily overrided in part. If a number of non-secret settings are used across a Django site with different deployment configurations, then either the entire setting dictionary must be defined again or the original must be mutated in place.

Quick example:

# myapp/settings/staging.py
from myapp.settings import base

base.MYAPP_API['RETRIES'] = 4

The lesson here is to at least have a good reason for using a dictionry or other container for app settings and to ensure they are settings that you would expect to be updated in tandem.

Mixing settings styles

The two styles of settings are not mutually exclusive.

MYAPP_API = {
    'USERNAME': 'YourUsername',
    'API_KEY': 'YourAPIKey',
    'CONTAINER': 'ContainerName',
    'PYRAX_IDENTITY_TYPE': 'rackspace',
}
MYAPP_USE_CACHING = False