How to configure your Django project for multiple environments?
Written by
Krystian Hanek
Published on
February 9, 2023
TL;DR
Learn how to configure Django project for multiple environments to write manageable code with fewer duplications and more accurate settings. If you'd like to just take a quick glance at the code, take a look at this SlideShare presentation. Below, I'll explain how to configure Django project step by step.
Oops! Something went wrong while submitting the form.
Share
If you're about to configure Django settings for multiple environments, you need to think ahead. Your simple project may grow significantly and you'll have to introduce changes in order to run your app on different environments.
In this article, I will show you how to configure Django settings for multiple environments, based on The Twelve-Factor App methodology for building software-as-a-service apps. I have also used Cookiecutter Django framework by Pydanny.
This project uses several third-party tools including PostgreSQL, Sentry, AWS, WhiteNoise, Gunicorn, Redis, Anymail.
But before we set up the new project, let’s tackle this question:
Django settings for multiple environments
The no 1 reason to configure Django settings for multiple environments is that when you first start a new project, it lacks such arrangement. Not having the bundles split makes it difficult to configure the project later without having to alter the code.
Also, without setting the Django project for multiple environments, there are no dedicated solutions for production like dedicated path for admin panel, logging errors (e.g., Sentry), cache configuration (memcache/redis), saving the data uploaded to cloud by the user (S3), HSTS or secure cookies.
Testing environment, on the other hand, lacks dedicated solutions either, including turning off debugging templates, in-memory caching, sending mails to console, password hasher, storing the templates.
As a result, setting Django project for multiple environments gives you:
more manageable code with fewer duplications
more accurate settings depending on environment type
First, you need to set up our new Django project. To do it, install virtualenv/virtualenvwrapper and Django: pip install Django==1.11.5 or whatever Django version you want to use for your project. Then, create a new project: django-admin: django-admin startproject djangohotspot. At this point, your project should look like this:
[code language="python"]django==1.11.5# Configurationdjango-environ==0.4.4whitenoise==3.3.0# Modelsdjango-model-utils==3.0.0# ImagesPillow==4.2.1# Password storageargon2-cffi==16.3.0# Python-PostgreSQL Database Adapterpsycopg2==2.7.3.1# Unicode slugificationawesome-slugify==1.6.5# Time zones supportpytz==2017.2# Redis supportdjango-redis==4.8.0redis>=2.10.5[/code]
Production.txt
[code language="python"]-r base.txt# WSGI Handlergevent==1.2.2gunicorn==19.7.1# Static and Media Storageboto3==1.4.7django-storages==1.6.5# Email backends for Mailgun, Postmark,# SendGrid and moredjango-anymail==0.11.1# Raven is the Sentry clientraven==6.1.0[/code]
[code language="python"]-r test.txt-r production.txtdjango-extensions==1.9.0ipdb==0.10.3[/code]It’s time to configure the settings of each environment.
Splitting settings
First, you need to remove settings.py from the main folder of your Django app djangohotspot/djangohotspot/settings.py and create new Python module named config, where you create another module, settings, where all the settings files will be stored. The structure of your project has changed and should look like this now:
To configure settings in base.py in this example, I have used the django-environ library.
[code language="python"]ROOT_DIR = environ.Path(__file__) - 3 # djangohotspot/APPS_DIR = ROOT_DIR.path('djangohotspot') # path for django appsINSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPSAUTOSLUG_SLUGIFY_FUNCTION = 'slugify.slugify' # allows you to define a function for unicode-supported SlugDATABASES = { 'default': env.db('DATABASE_URL', default='postgres:///djangohotspot'), }DATABASES['default']['ATOMIC_REQUESTS'] = True # allows you to open and commit transaction when there are no exceptions.
This could affect the performance negatively for traffic-heavy apps.EMAIL_BACKEND = env('DJANGO_EMAIL_BACKEND', default='django.core.mail.backends.smtp.EmailBackend')ADMIN_URL = env('DJANGO_ADMIN_URL', default=r'^admin/')PASSWORD_HASHERS = ['django.contrib.auth.hashers.Argon2PasswordHasher', (...)] # add this object at the beginning of the list[/code]
config.settings.local
Configuring local settings, you need to import base settings:
At this point it’s worth to add DJANGO_ADMIN_URL to the production settings. Change it from default to avoid attack attempts on the default URL admin panel.Next, you need to add your domain or domains:
Store sent mails in memory. They are available in django.core.mail.outbox:
[code language="python"]EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'[/code]Set the cache:[code language="python"]CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 'LOCATION': '' }}[/code]Set the password hasher to speed up the tests:[code language="python"]PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher', ][/code]
If you use Django templates, you can set them to be stored in memory:[code language="python"]TEMPLATES[0]['OPTIONS']['loaders'] = [ ['django.template.loaders.cached.Loader', [ 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', ] , ], ][/code]
uwsgi.py and urls.py files
Because we’ve split the main settings file into dedicated files with configuration for each environment, we need to point a file which will be used by default when it’s not clearly indicated. In urls.py file we define the 4xx and 5xx pages. We also add debug toolbar here. Move uwsgi.py and urls.py files from djangohotspot/djangohotspot catalogue to config module and add following changes to config.settings:
[code language="python"]WSGI_APPLICATION = 'config.wsgi.application'ROOT_URLCONF = 'config.urls'[/code]At the end of the config.urls file add the following code to debug 4xx and 5xx pages:[code language="python"]if settings.DEBUG: urlpatterns += [ url(r'^400/$', default_views.bad_request, kwargs={'exception': Exception('Bad Request!')}), url(r'^403/$', default_views.permission_denied, kwargs={'exception': Exception('Permission Denied')}), url(r'^404/$', default_views.page_not_found, kwargs={'exception': Exception('Page not Found')}), url(r'^500/$', default_views.server_error), ]if 'debug_toolbar' in settings.INSTALLED_APPS: import debug_toolbar urlpatterns = [ url(r'^__debug__/', include(debug_toolbar.urls)),] + urlpatterns[/code]In our example, config.uwsgi file will look like this:[code language="python"]import osimport sys from django.core.wsgi import get_wsgi_applicationapp_path = os.path.dirname(os.path.abspath(__file__)).replace('/config', '')sys.path.append(os.path.join(app_path, 'djangohotspot'))if os.environ.get('DJANGO_SETTINGS_MODULE') == 'config.settings.production': from raven.contrib.django.raven_compat.middleware.wsgi import Sentryos.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.production")application = get_wsgi_application()if os.environ.get('DJANGO_SETTINGS_MODULE') == 'config.settings.production': application = Sentry(application)[/code]
Summary
As you’ve seen in this article, setting a Django project for multiple environments is a toilsome task. But trust me, it pays off quickly once you start working on the project. From this point on, you can think of some containerization with Docker, which will give you portability and easiness of setup for your project regardless the environment it will be run on.