diff --git a/README.md b/README.md index 87fe713..340b62f 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,10 @@ Django styleguide that we use in [HackSoft](https://hacksoft.io). * [Nested serializers](#nested-serializers) * [Advanced serialization](#advanced-serialization) - [Urls](#urls) +- [Settings](#settings) + * [Prefixing environment variables with `DJANGO_`](#prefixing-environment-variables-with-django_) + * [Integrations](#integrations) + * [Reading from `.env`](#reading-from-env) - [Errors & Exception Handling](#errors--exception-handling) * [How exception handling works (in the context of DRF)](#how-exception-handling-works-in-the-context-of-drf) + [DRF's `ValidationError`](#drfs-validationerror) @@ -1019,6 +1023,136 @@ urlpatterns = [ **Splitting urls like that can give you the extra flexibility to move separate domain patterns to separate modules**, especially for really big projects, where you'll often have merge conflicts in `urls.py`. +## Settings + +When it comes to Django settings, we tend to follow the folder structure from [`cookiecutter-django`](https://github.com/cookiecutter/cookiecutter-django), with few adjustments: + +- We separate Django specific settings from other settings. +- Everything should be included in `base.py`. + - There should be nothing that's only included in `production.py`. + - Things that need to only work in production are controlled via environment variables. + +Here's the folder structure of our [`Styleguide-Example`](https://github.com/HackSoftware/Styleguide-Example) project: + +``` +config +├── __init__.py +├── django +│   ├── __init__.py +│   ├── base.py +│   ├── local.py +│   ├── production.py +│   └── test.py +├── settings +│   ├── __init__.py +│   ├── celery.py +│   ├── cors.py +│   ├── sentry.py +│   └── sessions.py +├── urls.py +├── env.py +└── wsgi.py +├── asgi.py +``` + +In `config/django`, we put everything that's Django related: + +- `base.py` contains most of the settings & imports everything else from `config/settings` +- `production.py` imports from `base.py` and then overwrites some specific settings for production. +- `test.py` imports from `base.py` and then overwrites some specific settings for running tests. + - This should be used as the settings module in `pytest.ini`. +- `local.py` imports from `base.py` and can overwrite some specific settings for local development. + - If you want to use that, point to `local` in `manage.py`. Otherwise stick with `base.py` + +In `config/settings`, we put everything else: + +- Celery configuration. +- 3rd party configurations. +- etc. + +This gives you a nice separation of modules. + +Additionally, we usually have `config/env.py` with the following code: + +```python +import environ + +env = environ.Env() +``` + +And then, whenever we need to read something from the environment, we import like that: + +```python +from config.env import env +``` + +Usually, at the end of the `base.py` module, we import everything from `config/settings`: + +```python +from config.settings.cors import * # noqa +from config.settings.sessions import * # noqa +from config.settings.celery import * # noqa +from config.settings.sentry import * # noqa +``` + +### Prefixing environment variables with `DJANGO_` + +In a lot of examples, you'll see that environment variables are usually prefixed with `DJANGO_`. This is very helpful when there are other applications running alongside your Django app & reading from the same environment. + +We tend to prefix with `DJANGO_` only `DJANGO_SETTINGS_MODULE` and `DJANGO_DEBUG` & not prefix anything else. + +This is mostly up to personal preference. **Just make sure you are consistent with that.** + +### Integrations + +Since everything should be imported in `base.py`, but sometimes we don't want to configure a certain integration for local development, we derived the following approach: + +- Integration-specific settings are placed in `config/settings/some_integration.py` +- There's always a boolean setting called `USE_SOME_INTEGRATION`, which reads from the environment & defaults to `False`. +- If the value is `True`, then proceed reading other settings & failing if things are not present in the environment. + +For example, lets take a look at `config/settings/sentry.py`: + +```python +from config.env import env + +SENTRY_DSN = env('SENTRY_DSN', default='') + +if SENTRY_DSN: + import sentry_sdk + from sentry_sdk.integrations.django import DjangoIntegration + from sentry_sdk.integrations.celery import CeleryIntegration + + # ... we proceed with sentry settings here ... + # View the full file here - https://github.com/HackSoftware/Styleguide-Example/blob/master/config/settings/sentry.py +``` + +### Reading from `.env` + +Having a local `.env` is a nice way of providing values for your settings. + +And the good thing is, [`django-environ`](https://django-environ.readthedocs.io/en/latest/) provides you with a way to do that: + +```python +# That's in the beginning of base.py + +import os + +from config.env import env, environ + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = environ.Path(__file__) - 3 + +env.read_env(os.path.join(BASE_DIR, ".env")) +``` + +Now you can have a `.env` (but it's not required) file in your project root & place values for your settings there. + +There are 2 things worth mentioning here: + +1. Don't put `.env` in your source control, since this will leak credentials. +2. Rather put an `.env.example` with empty values for everything, so new developers can figure out what's being used. + ## Errors & Exception Handling > 👀 If you want the code, hop to the `Styleguide-Example` project -