How to Use Multiple Settings Files in Django Project

Learn how to split your settings module in Django into multiple settings files

Picture of Nsikak Imoh, author of Macsika Blog
Abstract image with the text How to Use Multiple Settings Files in Django Project
Abstract image with the text How to Use Multiple Settings Files in Django Project

This post is part of the tutorial series titled Learn to Use Django with FastAPI Frameworks

Table of Content

When you create a new project in Django, you are provided with a default settings file with some parameters and constants already set.

However, if you have any intention of taking your project to a production or staging environment, it is a good idea to break down your setting into multiple configuration files.

This is because as a Django project develops in apps and functionality, the settings.py module can get fairly complex over time.

Although some developers tend to use conditional if statements to separate configuration parameters and variables.

In those cases, you also want to avoid using if statements like if not DEBUG: # do something....

For clarity, it is important to make a strict distinction between what is development configuration and what is production configuration.

This is done effectively by breaking down the settings.py module into multiple files.

Let's take a brand new Django project structure, which looks like the tree structure below. Here's how you can split the settings file into multiple configuration files:

src/
     |-- core/
     |    |-- __init__.py
     |    |-- asgi.py
     |    |-- settings.py
     |    |-- urls.py
     |    +-- wsgi.py
     +-- manage.py
    
Highlighted code sample.

1. Create a directory named “settings” within the core directory

The first thing we want to do is to create a folder named settings.

2. Add a __init__.py file to the settings directory

The __init__.py will make the settings directory a python module.

3. Rename the previous settings.py file to base.py and move it into the settings directory

The base.py will be responsible for providing the settings configuration that is common among all environments such as development, production, staging, etc.

Here is how the current project structure should look after the three steps above.

src/
     |-- core/
     |    |-- __init__.py
     |    |-- asgi.py
     |    |-- settings/         <--
     |    |    |-- __init__.py  <--
     |    |    +-- base.py      <--
     |    |-- urls.py
     |    +-- wsgi.py
     +-- manage.py
    
Highlighted code sample.

4. Create a settings.py module for each environment.

You should create settings.py for all common environments using the name of the environment as the file name.

Some common use cases are:

  • ci.py
    This file will contain configurations of continuous integration (CI/CD) or tests. You can use tests.py in place of ci.py.
  • development.py
    This file will contain configurations of the Development version alone. It is sometimes named local.py or dev.py.
  • production.py
    This file will contain configurations of the production environment. It is sometimes named prod.py.
  • staging.py
    This file will contain configurations of the staging environment.

It is important to know that the file names are merely conventional and not enforced. You can call it whatever you like, as long it adheres to python's file naming rules.

Here's how the file structure would look like after creating all the files representing the various environments above:

src/
     |-- core/
     |    |-- __init__.py
     |    |-- asgi.py
     |    |-- settings/
     |    |    |-- __init__.py
     |    |    |-- base.py
     |    |    |-- ci.py
     |    |    |-- development.py
     |    |    |-- production.py
     |    |    +-- staging.py
     |    |-- urls.py
     |    +-- wsgi.py
     +-- manage.py
    
Highlighted code sample.

How to Configure Settings for Different File Environments

Here's how you would go about setting the configurations for the development, production, and base settings.

To do this, we will use the decouple package.

Decouple helps you to organize your settings so that you can change parameters without having to redeploy your app.

Decouple adheres to the following principles, which we will follow:

  1. Store parameters in ini or .env files.
  2. Define comprehensive default values.
  3. Convert values to the correct data type.
  4. Have only one configuration module to rule all your instances.

Install the decouple package using the code below

pip install python-decouple
    
Highlighted code sample.

First, let's start with the base.py module in settings/base.py.

We will only define a handful of settings, so the example does not get too big.

from decouple import config
    
    SECRET_KEY = config('SECRET_KEY')
    
    INSTALLED_APPS = [
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
    
        'src.estore',
        'src.blog',
    ]
    
    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
    ]
    
    ROOT_URLCONF = 'core.urls'
    
    WSGI_APPLICATION = 'core.wsgi.application'
    
Highlighted code sample.

Next, we will create a development.py configuration by first extending our base.py settings module.

In settings/development.py,

from .base import *
    
    DEBUG = True
    
    INSTALLED_APPS += [
        'debug_toolbar',
    ]
    
    MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware', ]
    
    EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
    
    DEBUG_TOOLBAR_CONFIG = {
        'JQUERY_URL': '',
    }
    
Highlighted code sample.

Next, we will define a production.py configuration, which also extends the base.

In settings/production.py,

from .base import *
    
    DEBUG = False
    
    ALLOWED_HOSTS = ['ourgamechangerapplication.com', ]
    
    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
            'LOCATION': '127.0.0.1:11211',
        }
    }
    
    EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
    EMAIL_HOST = 'smtp.mailgun.org'
    EMAIL_PORT = 587
    EMAIL_HOST_USER = config('EMAIL_HOST_USER')
    EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD')
    EMAIL_USE_TLS = True
    
Highlighted code sample.

When working with Python, it is generally advised to avoid using star import, which indicates, importing everything.

This is because star imports may put lots of unnecessary stuff in the namespace, which in some cases can cause issues.

However, importing all from Django base settings is one of the few cases where it is accepted.

When importing everything from the base, it is important to know that

Also bear in mind that even though we are using different files for development and production, you still have to protect sensitive data.

Keep passwords and secret keys in environment variables or use a library like Python-Decouple which is used in this example.

How to Set Up Django Server to Use Different Settings in Different Environments

Since we no longer have a single settings.py file in the project root, running the command: python manage.py runserver will no longer work unless we make some changes.

There are three ways you can do this:

Method One: Run the server with the settings you want to use

Here, you have to pass the settings.py module you want to use in the command line:

python manage.py runserver --settings=mysite.settings.development
    
Highlighted code sample.

Or

python manage.py migrate --settings=mysite.settings.production
    
Highlighted code sample.

Method Two: Configure it in manage.py

You can edit the manage.py to set the default settings module to your development.py module.

To do that, simply edit the manage.py file, like this:

In manage.py,

#!/usr/bin/env python
    
    """Django's command-line utility for administrative tasks."""
    
    import  os
    import  sys
    
    def  main():
    
    """Run administrative tasks."""
    
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings.development")
    
    try:
        from django.core.management import execute_from_command_line
    
    except  ImportError  as  exc:
        raise  ImportError(
    
    "Couldn't import Django. Are you sure it's installed and "
    
    "available on your PYTHONPATH environment variable? Did you "
    
    "forget to activate a virtual environment?"
    
    ) from  exc
    
    execute_from_command_line(sys.argv)
    
    if  __name__ == "__main__":
    
    main()
    
Highlighted code sample.

So, basically, we changed the line from:

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings")
    
Highlighted code sample.

To:

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings.development")
    
Highlighted code sample.

Now you can run the manage.py commands again without using the --settings argument.

Remember, with this approach, you have to refer to the correct settings module in production!

Method Three: Configure it in settings\__init__.py

try:
        from .development  import *
    except:
        from .production  import *
    
Highlighted code sample.

This solution works with the idea that the development file will not be in the production environment.

Therefore, you should remember to add settings\development.py to .gitignore, when deploying using a version control system.

Additional things to do

Since we have different settings modules, you can move the AUTH_PASSWORD_VALIDATORS from the settings/base.py and only add it to the settings/production.py module.

This will allow you to use simple passwords like “123” during development, but in the production environment, it will be protected by the validators.

Also, in your settings/tests.py or settings/ci.py, you can override the following configuration, so your tests run faster:

DATABASES['default'] = {
        'ENGINE': 'django.db.backends.sqlite3'
    }
    
    PASSWORD_HASHERS = (
        'django.contrib.auth.hashers.MD5PasswordHasher',
    )
    
Highlighted code sample.

Wrap Off

I hope you found this post useful somehow!

If you have any questions or need clarification, feel free to reach out to me.

If you learned from this tutorial, or it helped you in any way, please consider sharing and subscribing to our newsletter.

Get the Complete Code of Django and FastAPI Combo Tutorials on Github.

Connect with me.

Need an engineer on your team to grease an idea, build a great product, grow a business or just sip tea and share a laugh?