Environment Variables¶
As we’re following the 12 factors, everything can be configured using environment variables.
Environment variables¶
Required basic values — Those things cannot be invented nor have decent default value. You need to specify them.
DATABASE_URL— The URL to the database (must be a PostgreSQL DB)ENVIRONMENT— Name of the environment (current branch name, develop, etc). That’s only required in production, if the current user seems to have a home in/homewe’ll invent an environment name based on the user name and the DB name.SECRET_KEY— Django’s secret key value
Cache/Queue
REDIS_URL— Base URL of the Redis. It will be used by several services (cache, queue, etc), each with a different (auto-generated) prefix.
Helper features
DEBUG(YAML) — Enables debug modePOOL_DB_CONNECTIONS(YAML) — Enable this in production to enable DB connection poolingSENTRY_DSN— Set here the Sentry DSN to enable reporting exceptions to SentryTIME_ZONE— Default time zone to be used by Django
External communications — See below
External communications¶
The preset comes with Wailer and thus the ability to send emails or SMS.
Both systems work with the same idea:
Can be entirely configured with environment variables
Supports different backends
Safe by default: print to console unless asked otherwise and pure in-memory implementation during unit testing, so that communications don’t leave the developer’s machine
Email¶
The emails can be configured with the EMAIL_MODE environment variables, that
has 3 possible values, with associated environment variables:
console— Default print-to-console backendmailjet— Using Mailjet to send emailsMAILJET_API_KEY_PUBLIC— Public API keyMAILJET_API_KEY_PRIVATE— Private API key
mandrill— Using Mandrill to send emailsMANDRILL_API_KEY— Mandrill’s API key
SMS¶
It’s the same concept, driven by SMS_MODE:
console— Default print-to-console backendmailjet— Using Mailjet to send SMSesMAILJET_API_TOKEN— API token for SMS sending
Preset implementation¶
Here’s the documentation from the preset code to understand how those are used:
- class model_w.preset.django.ModelWDjango(*, sentry_sample_rate: float = 1.0, base_dir: str | Path | None = None, enable_postgis: bool = False, default_time_zone: str = 'Europe/Madrid', url_prefix: str = '/back', conn_max_age_when_pooled: int | float = 60, enable_cache: bool = True, enable_celery: bool | None = None, celery_task_track_started: bool = True, celery_task_time_limit: float | int = 3600, enable_channels: bool | None = None, enable_wagtail: bool | None = None, enable_storages: bool | None = None, enable_health_check: bool | None = None)¶
ModelW preset for Django running on PaaS platforms like DigitalOcean. Feel free to override any method if it doesn’t suit your needs. All values that are set in
pre_methods can easily be overridden by just setting it from inside the settings.py.- __init__(*, sentry_sample_rate: float = 1.0, base_dir: str | Path | None = None, enable_postgis: bool = False, default_time_zone: str = 'Europe/Madrid', url_prefix: str = '/back', conn_max_age_when_pooled: int | float = 60, enable_cache: bool = True, enable_celery: bool | None = None, celery_task_track_started: bool = True, celery_task_time_limit: float | int = 3600, enable_channels: bool | None = None, enable_wagtail: bool | None = None, enable_storages: bool | None = None, enable_health_check: bool | None = None)¶
You can set here different adjustments within the supported options
- Parameters:
sentry_sample_rate – Trace rate for Sentry performance tracing. Defaults to 1 for new projects but at scale you must reduce this value.
base_dir – Base dir of the Django project. In most setups that exist, this is the directory that contains the manage.py file. If this parameter is missing, that’s how we’ll determine the base directory, based on the location of manage.py.
enable_postgis – If your project does some geographical stuff, you need to enable PostGIS using this flag.
default_time_zone – The time zone to set by default in Django. Not very neutral default value for sure but we’re a Madrid-based company so…
url_prefix – In PaaS platforms, it is convenient to have all the calls to the backend prefixed with a folder name. Here it’s /back by default, meaning that you should configure your URLs to be /back/admin, /back/api and so forth. The usefulness of this value is for the settings like the static URL prefix.
conn_max_age_when_pooled – There is a POOL_DB_CONNECTIONS environment variable that enables connection pooling (by default connections will open/close at each request, which is slower but more cost-effective in some cases). This value indicates the conn_max_age (in seconds) when pooling is enabled.
enable_cache – Enables the cache engine (in Redis).
enable_celery – Enables a default configuration for Celery, that can then be overridden. By default it will enable itself if Celery can be imported. Celery will be installed if you install the celery extra dependency (pip install modelw-preset-django[celery]).
celery_task_track_started – Enable or not the tracking of tasks start
celery_task_time_limit – Maximum duration before a worker gets killed on a task (hard limit). Choose something wide but choose something. The default of 1h seems reasonable. This avoids stalled processes (requests without timeout…) clogging the queue.
enable_channels – Enables Django Channels (which will configure it to use Redis). By default it will detect if channels is present and enable it automatically if so. You can just request the “channels” extra to this package.
enable_health_check – Enables the health check system. It will be automatically enabled if django-health-check is installed. It will check if celery is also installed and add the appropriate apps related to it.
- pre_base_dir()¶
Saving the base dir into settings
- pre_sentry(env: EnvManager)¶
Configures Sentry if the DSN is found
- pre_basic_stuff(env: EnvManager)¶
A bunch of quite basic things that are common to all projects
- pre_logging(env)¶
Configuring nice console logging, especially when in debug mode. We’re setting exceptions for a lot of commonly used root modules that would otherwise spam the console.
- pre_database(env: EnvManager)¶
Database has to be PostgreSQL. We’re using dj_database_url to parse the settings, but with a bunch of overridden things in the middle (like the connection pooling configuration or using the psqlextra backend).
- pre_password()¶
Our default password hasher is Argon2. It’s much more secure than the rest for a much lower cost. The only reason why it’s not the default in Django is that it requires an external dependency (but we don’t mind depending on it so all is fine).
- pre_languages(env: EnvManager)¶
Enabling localization features
- post_languages(context)¶
We’re checking that some LANGUAGES are set and we can use the first language as default language for the app (this way you don’t need to set LANGUAGES and LANGUAGE_CODE separately).
- pre_static_files()¶
Configuring here static files. They’ll be served by WhiteNoise (see below).
- pre_middleware()¶
We’re putting here the middlewares that come with a default Django project (as opposed to the default in Django which is just empty). Overall, good luck running your website without those.
It is recommended that from your settings.py file you don’t replace this list but rather you add your own middlewares to it by doing something like:
>>> MIDDLEWARE = [] >>> with EnvManager() as env: >>> MIDDLEWARE.append("my.middleware.Class")
Let’s note in the example above the MIDDLEWARE = [] line. It’s just so that the IDE is not confused when EnvManager() sneakily adds MIDDLEWARE to local variables. It will still contain the values defined below, not an empty array.
The reason to do like this is that if one day Django or Model W come with more interesting middlewares it’d be a shame to miss on them because you overrided the whole thing.
- post_whitenoise(context)¶
We use WhiteNoise as static files storage engine and we make sure that it is present within middlewares.
- pre_templates()¶
Basic template config from default Django settings
- pre_cache(env)¶
Let’s configure the cache engine to use Redis
- pre_wailer()¶
We’re just defining those so that Wailer doesn’t crash, however we’ll let the user define their own emails/sms when then want.
- post_wailer(context)¶
Making sure that Wailer is installed in the apps
- post_email(env: EnvManager)¶
Configuring emails sending. Unless an environment variable explicitly enables emails, they will be printed on the console for obvious safety reasons.
- post_sms(env: EnvManager)¶
Configuring SMS sending. Unless an environment variable explicitly enables SMSes, they will be printed on the console for obvious safety reasons.
- pre_drf()¶
Some basic DRF configuration, feel free to make it your own
- post_drf(context)¶
We’re installing DRF
- post_helper(context)¶
This preset comes with a Django app that brings a settings helper. It has no models on its own so it’s really just the command. We discretely insert the app into the INSTALLED_APPS, shouldn’t be too intrusive.
- pre_celery(env: EnvManager)¶
When Celery is enabled, we inject some decent default using Redis as a broker and Django-backed backends.
- post_celery(env: EnvManager, context)¶
These settings are put in post because we want to give a chance to the user to override the broker URL. If they didn’t do it, we proceed to hijacking the broker options to enforce a prefix in Redis keys.
- pre_channels(env: EnvManager)¶
If Channels is enabled we need to configure the broker to be Redis and to be prefixing properly its keys (otherwise we’re at risk of conflict with the other parts of the app that also use Redis).
- post_channels(context)¶
Trying to be nice and adding channels to the installed apps.
- pre_storages(env)¶
If storages is enabled, we’ll look to enable S3.
There can be two modes to work with S3:
s3, when it’s S3 storage on AWS cloud
do, when it’s S3 storage on DigitalOcean
Depending on this, different environment variables will be required. It will also detect if we’re running from inside an AWS container or something of the sort in order to avoid manually setting the access key when Boto3 will determine it on its own.
In do mode, the lib is configured to use the DigitalOcean endpoint instead of the default AWS one.
- pre_wagtail()¶
Reasonable default settings for Wagtail
- post_wagtail(context)¶
Making sure Wagtail components and middlewares are loaded
- pre_base_url(env)¶
Defining a BASE_URL and WAGTAIL_BASE_URL environment variable which is used both by Wagtail and by Wailer and potentially other systems in order to know which is the BASE_URL we should use for this website. It’s helpful because things don’t always come from an HTTP query (like crons) and thus you cannot always know the Host header.
- pre_health_check(env: EnvManager)¶
If health check is enabled, we’ll look to enable the health check system.
- post_health_check(context)¶
Making sure that Health Check is installed in the apps