thnee.se

Django settings package

August 19, 2017

When starting a new Django project, you get a settings.py file
with some initial basic settings in it.
This is rarely complex enough for real world projects.
Lets take a look at how to improve the settings management in Django.

There is no claim that the examples here will fit perfectly
for your specific project.
However, this provides a framework for dealing with Django settings,
and it is based on several years of real world
experience in large Django projects.

With this method, there is no need to ever set DJANGO_SETTINGS_MODULE,
it stays exactly the same as when the project was created
with django-admin startproject.
Yet, it allows for dynamic control over any Django setting
via other environment variables.

Convert settings into a package

An import statement in Python can point to
either a module or a package interchangeably.
This means that the settings module can be converted into a package,
without having to change anything else in the framework.
As long as there is an __init__.py file in the package
that does whatever the module would have done,
the code that is doing the import (Django) will not care.

A list called module_paths is created,
and populated with multiple strings
that contain paths to python modules that are going to be imported.
One of the module path strings is constructed dynamically,
based on the value of the environment variable DJANGO_SETTINGS_ENV
stored in the env variable.

The actual imports happen on the last two lines.
The calls to importlib.update_module() and globals().update() produces
a result that is nearly identical to a from somewhere import * statement.
(Assuming that module_path contains the string 'somewhere').

This makes all module level variables in the various imported modules
(such as INSTALLED_APPS in base.py) appear at
the module level of the __init__ module.
And any variable in the __init__ module will automatically
be available at the settings package level,
which satisfies Django when it imports rainbows.settings.

I say nearly identical, because when doing import * with
the regular import statement, it actually ignores names that start
with an underscore. (See the section on public names).
But since Django settings files should never contain any variables
that start with an underscore, this will not be an issue.

settings/base.py

This file contains regular Django settings such as INSTALLED_APPS,
that should apply regardless of the current value of DJANGO_SETTINGS_ENV.

settings/envs/__init__.py

This file is empty.

settings/envs/dev.py

This file contains regular Django settings, such as DEBUG,
that should apply only for the dev environment.

Final words

To avoid confusion,
make sure that a setting only occurs at one level in the hierarchy,
and that it occurs in all modules at that level.
For example; the DEBUG setting in the above code is being set
at the environment level.
Lets make sure that it does not also occur in base.py
And lets make sure that it does occur in
all three env modules: dev.py, stage.py, and prod.py.

Use only environment variables as input for the settings package.
It is a standard way of interfacing between an OS and a program.
That means it will be portable across any platform you decide to deploy on.

With this method, it is simple to add more dimensions.
In this example there is only one input, DJANGO_SETTINGS_ENV.
But you could add more environment variables and sub packages
for anything you come up with.

Perhaps you want to spin up multiple instances of the same project,
under different domains with different url patterns for each domain.
You could add an environment variable DJANGO_SETTINGS_SITE,
and a settings/sites package,
and have different values for ROOT_URLCONF in the various site modules,
and adapt allowed_hosts.py to accomodate for the different sites.

Or perhaps you want to deploy multiple instances
of the project in different zones,
and want to point each instance to a different API gateway.
You could add an environment variable DJANGO_SETTINGS_ZONE,
and a settings/zones package,
and have a API_GATEWAY setting that varies for each zone module.