Colin Shevlin
Jul 18, 2018 · 3 min read

Best practices for managing settings in Django have evolved over time, and have become more complex when implementing a Django project using Google App Engine. I’d like to share an approach that we use at Holberg Financial that solves many of the problems that we ran into when managing environments on Google App Engine.

First let’s consider the method of keeping many local_settings.py files. In this scenario, you keep many differing copies of local_settings.py on each machine.

This approach is ill-advised for a few different reasons in absence of Google App Engine. But when running a Django project on App Engine this approach becomes even more cost prohibitive: in addition to managing local_settings.py on each engineer’s machine, you now have to keep a local_settings.py that you deploy to App Engine on every release. That’s more overhead than I’m willing to impose on myself and my team.

Instead, let’s take the approach to settings suggested in Two Scoops of Django by Daniel and Audrey Roy Greenfield, which itself was based on a talk by Jacob Kaplan-Moss at OSCON in 2011.

This approach is simple: break up settings into local, staging, and prod components that inherit from a common settings base object. The settings directory after this change will look like this¹:

django/
foo/
|--...
|--settings/
|--__init__.py
|--base.py
|--local.py
|--staging.py
|--production.py

Inside base.py, we set up everything that is common to all environments, as well as logical fallbacks if some settings are not overridden:

# foo/settings/base.pyimport os
import sys
PROJECT_DIR = os.path.abspath(os.path.dirname(__file__))DEBUG = FalseINSTALLED_APPS = [
‘django.contrib.auth’,
‘django.contrib.contenttypes’,
‘django.contrib.sessions’,
‘django.contrib.staticfiles’,
‘django.contrib.humanize’,
‘foo’
]
# ...more settings...DATABASES = {
‘default’: {
‘ENGINE’: ‘django.db.backends.mysql’,
‘NAME’: ‘foo’,
‘USER’: ‘some user’,
‘PASSWORD’: ‘some password’,
‘HOST’: ‘127.0.0.1’,
‘PORT’: ‘3306’
}
}

Then in each of our environment settings, we override only the settings that we’re interested in.

# import the base settings
from .base import *
# set DEBUG to True for the staging environment
DEBUG = True
# if we’re running on Google App Engine, connect to the Cloud SQL instance.
if os.getenv(‘SERVER_SOFTWARE’, ‘’).startswith(‘Google App Engine’):
DATABASES = {
‘default’: {
‘ENGINE’: ‘django.db.backends.mysql’,
‘HOST’: ‘/cloudsql/foo-connection-name’,
‘NAME’: ‘foo’,
‘USER’: ‘some staging user’,
‘PASSWORD’: ‘some staging password’
}
}
else:
DATABASES = {
‘default’: {
‘ENGINE’: ‘django.db.backends.mysql’,
‘HOST’: ‘127.0.0.1’,
‘PORT’: ‘3306’,
‘NAME’: ‘foo’,
‘USER’: ‘some staging user’,
‘PASSWORD’: ‘some staging password’
}
}

If we were developing this locally without App Engine, we could select the appropriate settings from the command line:

python manage.py runserver --settings=foo.settings.local

But we’re using Google App Engine! So we’re going to be running our app with dev_appserver.py. We’ll pass our configuration file like so:

dev_appserver.py app.yaml

Currently, app.yaml doesn’t specify any environment. We’re going to fix this and create multiple configuration files that make use of our newly-created settings directory. Since we have local, staging, and production environments, we’ll create a configuration file for each of these (emphasis mine):

django/
foo/
.gitignore
README.rst
manage.py
requirements.txt
app_production.yaml
app_staging.yaml
app_local.yaml

Within each configuration file, most configuration will stay the same. However, the important change is that we set the DJANGO_SETTINGS_MODULE environment variable to use the corresponding settings file (emphasis mine):

runtime: python27
api_version: 1
threadsafe: true
handlers:
- url: /stylesheets
static_dir: stylesheets
- url: /.*
script: foo.wsgi.app
libraries:
- name: MySQLdb
version: ‘latest’
- name: ssl
version: ‘2.7.11’
- name: django
version: ‘1.11’
# reads from our staging settings
env_variables:
DJANGO_SETTINGS_MODULE: ‘foo.settings.staging’

With our new configuration files, we can use dev_appserver.py to start a local development server in any of our environments:

# local development server with local settings
dev_appserver.py app_local.yaml
# local development server with staging settings
dev_appserver.py app_staging.yaml
# flirting with danger: local development server with production settings
dev_appserver.py app_production.yaml

And easily deploy!

# deploy to staging
gcloud app deploy --project=foo-staging app_staging.yaml
# deploy to production
dev_appserver.py --project=foo app_production.yaml

Holberg Financial is hiring! We care about financial justice, inclusion, and working in a joyful environment. If you’re also into that, send us your resume!

¹ Roy Greenfield, Daniel, and Audrey Roy Greenfield. Two Scoops of Django 1.11: Best Practices for the Django Web Framework. 4th ed. Daniel Roy Greenfeld, Audrey Roy Greenfeld, and Two Scoops Press, 159

holbergfinancial

We're a VC-backed financial health startup that is challenging the financial status quo. We are on a path to help millions of people build their financial health and reach their financial goals and dreams.

Colin Shevlin

Written by

Lead engineer at @holbergfinance.

holbergfinancial

We're a VC-backed financial health startup that is challenging the financial status quo. We are on a path to help millions of people build their financial health and reach their financial goals and dreams.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade