DEBUG = True
, more verbose logging, additional apps, some mocked data, etc). You need an approach that allows you to keep all these Django setting configurations.SECRET_KEY
in each Django project. On top of this there can be DB passwords and tokens for third-party APIs like Amazon or Twitter. This data cannot be stored in VCS.settings_local.py
The basic idea of this method is to extend all environment-specific settings in the settings_local.py
file, which is ignored by VCS. Here’s an example of the settings.py
file:
ALLOWED_HOSTS = ['example.com']
DEBUG = False
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'production_db',
'USER': 'user',
'PASSWORD': 'password',
'HOST': 'db.example.com',
'PORT': '5432',
'OPTIONS': {
'sslmode': 'require'
}
}
}
...
from .settings_local import *
And the settings_local.py
file:
ALLOWED_HOSTS = ['localhost']
DEBUG = True
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'local_db',
'HOST': '127.0.0.1',
'PORT': '5432',
}
}
settings_local.py
is not in VCS, so you can lose some of your Django environment settings.settings_local.py
can have some non-obvious logic.settings_local.example
(in VCS) to share the default Django configurations for developers.In this case, there are multiple files from which projects on Django get settings, and you make a settings
package with the following file structure:
settings/
├── __init__.py
├── base.py
├── ci.py
├── local.py
├── staging.py
├── production.py
└── qa.py
settings/local.py
:
from .base import *
ALLOWED_HOSTS = ['localhost']
DEBUG = True
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'local_db',
'HOST': '127.0.0.1',
'PORT': '5432',
}
}
python manage.py runserver --settings=settings.local
.To solve the issue with sensitive data, you can use environment variables in Django:
import os
SECRET_KEY = os.environ['SECRET_KEY']
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ['DATABASE_NAME'],
'HOST': os.environ['DATABASE_HOST'],
'PORT': int(os.environ['DATABASE_PORT']),
}
}
os.environ
and it has several issues:
KeyError
exceptions.DATABASE_PORT
usage).To fix KeyError
, you can write your own custom wrapper. For example:
import os
from django.core.exceptions import ImproperlyConfigured
def get_env_value(env_variable):
try:
return os.environ[env_variable]
except KeyError:
error_msg = 'Set the {} environment variable'.format(var_name)
raise ImproperlyConfigured(error_msg)
SECRET_KEY = get_env_value('SECRET_KEY')
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': get_env_value('DATABASE_NAME'),
'HOST': get_env_value('DATABASE_HOST'),
'PORT': int(get_env_value('DATABASE_PORT')),
}
}
os.environ
could be tricky sometimes and require additional effort to handle errors. It’s better to use django-environ
instead. Technically it’s a merge of:
envparse
honcho
dj-database-url
dj-search-url
dj-config-url
django-cache-url
Django settings.py
file before:
import os
SITE_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
DEBUG = True
TEMPLATE_DEBUG = DEBUG
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'production_db',
'USER': 'user',
'PASSWORD': 'password',
'HOST': 'db.example.com',
'PORT': '5432',
'OPTIONS': {
'sslmode': 'require'
}
}
}
MEDIA_ROOT = os.path.join(SITE_ROOT, 'assets')
MEDIA_URL = 'media/'
STATIC_ROOT = os.path.join(SITE_ROOT, 'static')
STATIC_URL = 'static/'
SECRET_KEY = 'Some-Autogenerated-Secret-Key'
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': '127.0.0.1:6379/1',
}
}
Django settings.py file
after:
import environ
root = environ.Path(__file__) - 3 # get root of the project
env = environ.Env()
environ.Env.read_env() # reading .env file
SITE_ROOT = root()
DEBUG = env.bool('DEBUG', default=False)
TEMPLATE_DEBUG = DEBUG
DATABASES = {'default': env.db('DATABASE_URL')}
public_root = root.path('public/')
MEDIA_ROOT = public_root('media')
MEDIA_URL = env.str('MEDIA_URL', default='media/')
STATIC_ROOT = public_root('static')
STATIC_URL = env.str('STATIC_URL', default='static/')
SECRET_KEY = env.str('SECRET_KEY')
CACHES = {'default': env.cache('REDIS_CACHE_URL')}
.env
file:
DEBUG=True
DATABASE_URL=postgres://user:password@db.example.com:5432/production_db?sslmode=require
REDIS_CACHE_URL=redis://user:password@cache.example.com:6379/1
SECRET_KEY=Some-Autogenerated-Secret-Key
File structure:
project/
├── apps/
├── settings/
│ ├── __init__.py
│ ├── djano.py
│ ├── project.py
│ └── third_party.py
└── manage.py
__init__.py file:
__init__.py
file:
from .django import * # All Django related settings
from .third_party import * # Celery, Django REST Framework & other 3rd parties
from .project import * # You custom settings
Each module could be done as a package, and you can split it more granularly:
project/
├── apps/
├── settings/
│ ├── project
│ │ ├── __init__.py
│ │ ├── custom_module_foo.py
│ │ ├── custom_module_bar.py
│ │ └── custom_module_xyz.py
│ ├── third_party
│ │ ├── __init__.py
│ │ ├── celery.py
│ │ ├── email.py
│ │ └── rest_framework.py
│ ├── __init__.py
│ └── djano.py
└── manage.py
API_SYNC_CRONTAB = env.str('API_SYNC_CRONTAB')
Good example:
# Run job for getting new tweets.
# Accept string in crontab format. By default: every 30 minutes.
MYAWESOMEPROJECT_TWEETS_API_SYNC_CRONTAB = env.str(
'MYAWESOMEPROJECT_TWEETS_API_SYNC_CRONTAB', default='30 * * * *'
)
Source: https://djangostars.com/blog/configuring-django-settings-best-practices/