2General

Want to know more about us? Visit 2general.com »

Splitting up settings in Django

By default all Django settings are in one monolithic settings.py file. A single big file is hard to read and hard to maintain.

Django users have found many ways to split up the settings into multiple files, all of them with their pros and cons. In our latest projects, we have developed yet another way, which uses file inclusion instead of importing Python scripts.

The main features of django-split-settings are:

  1. Settings can be split into files and directories.
  2. Files later in the list can modify configurations, for example add or remove apps from INSTALLED_APPS.
  3. The main settings file lists the files that make up the project’s settings.
  4. Files can be marked optional. Optional files can be used to override settings per instance.
  5. Wildcards can be used in file paths.
  6. Maintains support for Django’s runserver auto-reloading.

Installation

Install django-split-settings with pip:

$ pip install django-split-settings

Before you start: Use a settings package instead of settings.py

It’s a good practice to use a settings package instead of a single settings.py file right when you start your project. If you ever need multiple configurations for your Django project, you don’t want to clutter the root directory with prod_settings.py, test_settings.py and others. Also, a package provides you a convenient place for all the settings-related files and directories.

Using django-split-settings

If you make settings/__init__.py load your development settings, you’ll be able to use manage.py runserver without any extra parameters or changes to manage.py. We also made settings/__init__.py notice when test settings are needed.

Here’s a minimal example of what settings/__init__.py might contain:

from split_settings.tools import optional, include

include(
    'components/base.py',
    'components/database.py',
    optional('local_settings.py'),

    scope=locals()
)

Test settings and different server settings

With this kind of settings setup, you can easily create different settings for different situations. For example, you can have settings/test.py that has an alternative list of components.

settings/test.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from my_project.settings.tools import optional, include

include(
    "components/base.py",
    "components/locale.py",
    "components/apps_middleware.py",
    "env/testing/base.py",
    "env/testing/database.py",

    scope=locals()
)

You can then use this set of settings by calling

You can also use something like this to include optional files that contain host-specific settings:

1
2
3
4
5
6
7
8
9
from my_project.settings.tools import optional, include
import socket

include(
    # ...
    optional('env/%s.py' % socket.gethostname().split('.', 1)[0]),

    scope=locals()
)

Using wildcards in paths

You can use wildcards in file paths to include multiple files or files whose names are not known:

1
2
3
4
5
6
7
8
from my_project.settings.tools import optional, include

include(
    "components/*.py",
    optional("local_*.py"),

    scope=locals()
)

Files are then included in the order in which glob gives them, most probably in the same order as ls -U would list them.

Django runserver auto-reload

When partial settings files are included, django-split-settings modifies the sys.modules list. This allows Django runserver to notice if any of the files has been changed and automatically restarts the server when needed.

Example: How to override settings

Files on the inclusion list can override and modify the settings configured in the previous files. For example:

settings/__init__.py:

1
2
3
4
5
6
7
8
9
from split_settings.tools import optional, include

include(
    'components/base.py',
    'components/database.py',
    optional('local_settings.py'),

    scope=locals()
)

settings/components/base.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
DEBUG = False
TEMPLATE_DEBUG = DEBUG

MIDDLEWARE_CLASSES = (
    # Your project's default middleware classes
)

INSTALLED_APPS = (
    # Your project's default apps
)

settings/components/database.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'example',
        'USER': 'db_user',
        'PASSWORD': 'abc123',
        'HOST': '',
        'PORT': '',
    }
}

settings/local_settings.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Use debug mode locally
DEBUG = True
TEMPLATE_DEBUG = DEBUG

# Add django-debug-toolbar
MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
INSTALLED_APPS += ('debug_toolbar',)

# Use a different database password in development
DATABASES['default']['PASSWORD'] = 'password1'

See the example project in our GitHub repository for further details.