Merge pull request #88 from HackSoftware/settings-structure

Section: Settings
This commit is contained in:
Radoslav Georgiev 2021-11-22 14:44:27 +02:00 committed by GitHub
commit ac2d344f8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

134
README.md
View File

@ -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 - <https://github.com/HackSoftware/Styleguide-Example/blob/master/styleguide_example/api/exception_handlers.py>