How to split Django settings for different environments?
Tags: django, devops, python
Published: 2021-05-27 21:17:17

So it's time to put your code into production. That means you have to connect some third party stuff, probably some external database or SMTP server. At the same time it would be nice to keep an option to modify and test your codebase with your local database and double-check on some other staging or QA server. But how to achieve this with default `settings.py` file?

Searching for the solution I found several approaches of splitting the Django settings, but one that worked for me appeared to be a mixture. Here's how I've got it to work with AWS EC2, but it should work with any python environment where you can set your own env variables.

### Short recipe

1. Django settings as a module.
2. Key environmental variables for production and/or staging servers.
3. `__init__.py`.

### Directory view

```
your_main_django_app
-settings
    --__init__.py
    --base.py
    --local.py
    --production.py
    --staging.py

-__init__.py
-asgi.py
-urls.py
-views.py
-wsgi.py
```

## Step-by-step guide

**1. Set environmental variable with unique key or use one.** 

I didn't set any env vars during development. But once I deployed my app to the staging server, it got a new env variable called `'RDS_DB_NAME'`. I used it to differentiate between my local server and staging/production, but you can use your own unique key to perform the main check in the `__init__.py` file (i.e.`'ENV_NAME' : 'Staging'`).

**2. Prepare base.py**

```
from pathlib import Path
import os

# The most important thing is to be build relative path
BASE_DIR = Path(__file__).resolve().parent.parent.parent

INSTALLED_APPS = []

#...other environmentally independent settings 
# (TEMPLATES, WSGI_APPLICATION, TIME_ZONE, STATIC_ROOT etc.)
```

**3. Add environment-specific settings**

Since we have base.py, we can put the rest of the settings into their respective files: `local.py`, `staging.py`, `production.py` (you've got the idea).

By the way, don't forget to place your sensitive data (secrets, api keys etc.) into env variables instead of hard-coding them into your `production.py`. Here's the example of retrieving them in your settings:

`'SECRET': os.environ.get('SECRET_KEY')`

**4. Tweak the import in __init__.py with *if* statement**

Having all the settings in a separate module allows us not to specify local config directly each time we run `manage.py runserver`, because Django loads settings.py by default. And our settings module/folder is simply an equivalent to it.

Here's the **__init__.py** from the settings folder.

```
from .base import *
import os


if os.environ.get("ENV_NAME") == 'Production':
	from .production import *
elif os.environ.get("ENV_NAME") == 'Staging':
	from .staging import *
else:
	from .local import *
```

Now, whenever your Django app is initialized on the server, your environment variable called `"ENV_NAME"` will determine the proper settings file. And if it's just your local dev server, no env variables will be accessible and your app will fall back to `local.py` settings.

*If you happened to use AWS Beanstalk for deploying python app, you might want to simplify your `__init__.py` to something shorter:

```
from .base import *
import os


if os.environ.get("RDS_DB_NAME") is None:
	from .local import *
```

In this case, we're only telling the server to check one env variable and fallback to local.py. What about the production configuration then? Well, Beanstalk allows you to specify preferred settings in an environmental variable called `DJANGO_SETTINGS_MODULE`. Thus, you can just tune your Beanstalk app to load respective configuration like this:

`DJANGO_SETTINGS_MODULE: "your_main_django_app.settings.production"`

And that's all.

Everyone gets its own settings and your `manage.py runserver` works as before. Happy production.

*This article was originally [published in my blog post](https://valentine.click/blog/how-to-split-django-settings-for-different-environments).*