Settings for Multiple Environments in Django

Multiple Settings File

So far, a single settings.py file has served us well. Now we are moving to the production environment, as a result, some of the settings in settings.py file needs to be changed. Most notably, we will change DEBUG to False and ALLOWED_HOSTS to the production server IP or domain.

To efficiently run our Django project in different environments, we will split our single settings.py into multiple files, each one representing settings for a particular environment.

Let’s start by renaming settings.py to old.settings.py and creating a directory named settings in the Django configurations directory (djangobin/django_project/django_project). Inside the settings directory create following four files:

  1. __init__.py
  2. base.py
  3. dev.py
  4. prod.py.

The __init__.py file tells Python that the settings directory is a package. The base.py contains the settings common to development and production environment. The dev.py and prod.py contains the settings specific to the development and production respectively.

At this point, Django configurations directory should look like this:

The complete code of base.py is as follows (changes are highlighted):

djangobin/django_project/django_project/settings/base.py

Here are few things to notice:

* For security reasons, you must never hardcode sensitive configurations like SECRET_KEY, database credentials, or API keys in your code. Furthermore, these configurations are also subject to change across deploys. If you put these configurations in the code then you would have to constantly update the code everytime you move to a new environment.

In our case, we have stored all our sensitive configurations in a JSON file named djangobin-secrets.json, which resides in project root directory (djangobin/django_project). The contents of djangobin-secrets.json looks like this:

djangobin/django_project/djangobin-secrets.json

This file contains the SECRET_KEY, database credentials and email credentials. If you are using a version control system (which you should) add this file to .gitignore.

To generate the secret key we the get_random_string() function of django.utils.crypto module:

In production, we will use PostgreSQL as our database and SendGrid to send emails.

If you already know how to install and configure PostgreSQL, go ahead and populate the database credentials, otherwise, wait until the next lesson where we will look at how to install and configure PostgreSQL.

To obtain the email credentials sign up for a free account on SendGrid. As of this writing, SendGrid free plan allows you to send 100 email daily.

Back to base.py file.

In lines 16-17, we read the contents of djangobin-secrets.json file and store it in the secrets variable as a dictionary.

If you try to access a configuration which doesn’t exist in the secrets directory, you will get a KeyError exception. Sadly, this is not very helpful. To make debugging easier we have defined get_secret_setting() function. This method returns the value of the setting it is called with or an ImproperlyConfigured exception if the setting is not found in the secrets directory.

* Our settings file is now one level deep inside the Django configurations directory. In other words, the BASE_DIR setting no longer points to project root directory (djangobin/django_project/) instead it points to the Django configurations directory (djangobin/django_project/django_project). This effectively breaks the path to templates, static files and media files. To account for this, in line 27, we have we added an additional call to os.path.dirname(). This will make sure that the BASE_DIR setting points to the correct base directory.

* In line 23, we have added 'django.middleware.common.BrokenLinkEmailsMiddleware' to the MIDDLEWARE list. This will email the MANAGERS whenever an HTTP 404 error occurs.

The code for dev.py is as follows:

djangobin/django_project/django_project/settings/dev.py

Nothing fancy here, the dev.py just contains development specific settings. To import the common settings from base.py we use from .base import * statement (line 1). Note that we have deliberately hardcoded some of the sensitive configurations because it makes the development process easy. However, this is not the case with prod.py file.

djangobin/django_project/django_project/settings/prod.py

The prod.py is almost similar to dev.py but it defines settings specific to the production environment.

Here are few things to notice:

1. Unlike dev.py, in prod.py we are loading our sensitive configurations via get_secret_setting() function defined in the base.py file.

2. In line 10, we set ALLOWED_HOSTS to [*]. ALLOWED_HOSTS setting is a security feature which is used to validate whether the request is coming from allowed domains or not. It specifies a list of a string representing host/domain names this Django project can serve. By default, it is set to an empty list. Once you move to production you required to set it otherwise you will get 500 Internal Server Error.

If you own example.com and want to allow requests from example.com or www.example.com then you would need to set ALLOWED_HOSTS to:

If you want to allow requests from example.com and all its subdomains then use the period as a subdomain wildcard. For example:

To allow Django to accept requests from any domain set ALLOWED_HOSTS to “*”.

However, in a real deploy, you should limit this setting only to the host/domain you want to allow.

3. In lines 15-19, we define configurations to connect to PostgreSQL database.

4. In lines 25-28, we define settings required to send emails via SendGrid.

Running Project

We have refactored our code quite a lot. Now let’s take a look at how we can interact with our Django project with this new setup.

In the terminal execute ./manage.py file and you will get the error as follows:

You will get the same error (just different wording) if you try to run the runserver or shell command.

The problem is that the Django doesn’t know where our settings file is located. We can specify the location of settings file from the command line using the --setting option.

Note that you will need to specify --settings option everytime you execute the manage.py script. For example:

Specifying the --settings option everytime you execute ./manage.py file can quickly become tedious. Alternatively, you can set the DJANGO_SETTINGS_MODULE environment variable to the desired settings file as follows:

You can now execute the ./manage.py as usual without specifying the path to the settings file.

The DJANGO_SETTINGS_MODULE variable will remain in existence until the shell session is active. Unfortunately, If you start a new shell you will have to set DJANGO_SETTINGS_MODULE again.

A much better approach would be to modify the virtualenv’s activate script and set DJANGO_SETTINGS_MODULE environment variable when activating the virtualenv and unset it when deactivating the virtualenv.

Open activate script and modify it as follows:

djangobin/env/bin/activate

Start a new shell, activate the virtual environment and check the existence of DJANGO_SETTINGS_MODULE environment variable using the echo command:

As expected, DJANGO_SETTINGS_MODULE environment variable is available.

Now you can run ./manage.py file without setting any environment variable or specifying the --settings option

Start the Django development server to make sure everything is working as expected.

The output should look like this:

The DJANGO_SETTINGS_MODULE variable will be automatically removed upon deactivating the virtual environment.

Creating Requirements file

A requirements file is a simple text file containing the project dependencies. We use the requirements file to install the project dependencies.

To create the requirements file execute the following command:

Our DjangoBin project is now complete. In the next lesson, we will learn how to deploy it to the DigitalOcean server.

Leave a Comment