A guide to organizing settings in Django
apibakery.com
apibakery.com
1. Make settings.py a package
2. Please do not put anything more than imports in __init__.py since you are adding logic downstream.
3. Use django environ https://github.com/joke2k/django-environ which gives you a defined schema of your environment needs with casting and default values when needed.
Now you keep your .env files, keep your setting overrides (settings/base.py, settings/local.py, etc) when needed AND you have defined a schema with non-string types for your environment variables.
From the readme you can see how you make a schema with a casted type and default value:
env = environ.Env(
DEBUG=(bool, False)
)The biggest knock against django-environ is that it does not treat the `.env` syntax the same as Docker or bash -- meaning that the same environment file can't be reliably used to provide variables for both the container and Django.
django-classy-settings has been a joy to use, and its code is really simple and readable (~150 lines).
MY_VAR="foo"
Docker does not do any quote parsing. For this same env file, it will set the value of the variable to `"foo"` (retaining the doublequotes in the value).Bash, of course, requires quotes if the variable contains any special bash characters (for example, literal JSON with curly brackets), but its quote handling is much more complex. django-environ doesn't interpret bash code; it just does simple quote chomping.
There's no reliable .env syntax you can use that works in all 3 of django-environ, Docker, and bash; and any variable that should start and end with quotes that are not stripped off can't be expressed in a way that both Docker and django-environ will read in the same way.
This may seem like a nit-picking edge case, but it's indicative of the design philosophy in django-environ of trying to be "helpful", but in ways which lead to subtle confusion. The way it guesses the path to your `.env` file is another example.
[1] https://github.com/joke2k/django-environ/blob/main/environ/e...
I personally don't prefer django-environ because it's a bit too verbose/magical for my tastes, so use python-dotenv. I would recommend against using `local.py`. It's easy to overcomplicate things here and one of the things I worry about is how easy it is to have a new person set the project up and start working on it.
So, in general, absolutely agree with you. Details (which package to use and how exactly to set up the layout) are always going to be preference-based.
Or at least until the nice folks from Black alleviate us from that pain as well :)
You are entitled to your opinion but I don't think trying to defend your stance with homemade env parsing functions while writing an authoritative sounding blog post helps the community as a whole when there are sound libraries and patterns that solve this exact problem.
https://whalesalad.com/blog/doing-python-configuration-right
You can do it right without packages or external dependencies.
I like that it avoids counterproductive dependencies for loading a dotfile within the app. This is an operation that should be done at the same level as launches the Python interpreter, and Python should then complain if required envs are not set.
# settings.py
from environ import Env
ENV = Env()
DEBUG = ENV.bool("DEBUG", True)
SECRET_KEY = ENV.str("SECRET_KEY", "debug-secret-key")
ALLOWED_HOSTS = ENV.list("ALLOWED_HOSTS", default=["*"])
...
# Running with local .env
$ foreman run -- python3 manage.py runserver
# Running with prod.env
$ foreman run -e prod.env -- python3 manage.py runserver
It's easy and scales well with a lot of settings/environments/developers.It doesn't have any security hardening, is single threaded and blocking, will reload code as it changes, and is generally very slow.
It's generally best to use something like gunicorn, uwsgi, uvicorn, etc, and also to then host behind something like nginx which is designed to be exposed to potentially malicious users.
So for local development we follow the principle of least surprise, and we have a fairly rigorous policy/procedure for configuring and auditing production environment settings.
Django debug toolbar for example for a development environment? It needs to be listed as an installed app.
Or something specific to tests when the CI/CD pipeline is running the test suite.
Other configurations work the same way.
Keep everything in settings.py and use environment variables (django-environ) to configure it.
In a project with tens and tens of apps and so many modules requiring configurations and easily reaching more than thousand settings key, keeping 1 settings.py has not failed me.
With the approach mentioned in the article, you'll get to a point where you'll be having so different setting module for each env.
01. local
02. testing
03. CI/CI
04. E2E
05. Docker
06. Kubernetes
07. staging
08. release
09. production
10. k8s_win.py
11. k8s_linux.py
12. k8s_mac.py
13. heroku.py
14. pie.py
15. home.py
16. vacation.py
17. ...
Soon you'll get to a place where you need to kind of merge 2 of them (settings/docker.py and settings/kubernetes.py) or worse, several of them and then hell is unleashed. You get confused, your team gets confused, the new team member is confused, no one has any idea where to look at.
Just keep it in 1 place, let your brain stay healthier and prevent early cancer.
> We can now go back to only having settings.py that imports dotenv, fetches the configuration, and initializes Django settings as needed.
What is better depends on your tooling.
We've got a Django site that we run in production for different partners. We've got 560 settings, and then dev/test/prod configs for each partner, plus we vary the list of INSTALLED_APPS per partner. django-configurations is pretty nice for making this all manageable, and mypy helps to make sure everything is consistent.
I have also never agreed with the common suggestion of putting secrets into env vars- I believe secrets belong in something like Hashicorp's Vault and stored on the running system using proper OS level access control.
My key observation is this: If you're actually deploying and maintaining a django app you can't escape having quite a few environment variables that control different pieces of config. Once I accept that I can't shake these off, I'd rather minimize all other complexity, including having to reason about multiple settings modules that import/override each other in creative ways.
What I've settled on is:
1. Every thing controlled by env vars, not choice of settings module
2. the one and only settings.py toggles config based on env vars, say something like this:
STORAGE_MODE = os.environ['STORAGE_MODE']
STATICFILES_STORAGE = {
'local': '...StaticFileStorage',
's3': '...S3Boto3Storage'
}[STORAGE_MODE]
EDIT: wording and typosJust wanted to say though I don't think the critique of the "settings/__init__.py" approach is accurate.
You can easily access environment variables inside these files using os.environ, and it doesn't require a file locally that isn't present in the repo. As you say you just specify which settings file to use with the DJANGO_SETTINGS_MODULE env var. Am I missing something?
Once you switch to using environment variables, having additional specialized settings files is a complexity you don't need, in my experience. So I prefer to simplify without losing much in terms of convenience.
You can use whatever you want in settings.py. It's "why" that matters.
Yeah, you can boil it down to "use env", but do people know why it's usually the best approach? In my experience, many don't, hence the blog post.
I mean, if it were obvious and accepted truth, Django would be doing this by default already.