Files
lasuite-messages/src/backend/messages/settings.py
T
Stanislas BruhièreandGitHub 38439b95ce Improve development docker compose (#324)
* 🐛(docker) stop ALL services when running make stop

* ✨(docker) make sure backend is healthy before exiting docker compose up

* 🧑‍💻 (docker) use default user and not root for healthchecks

* ➖(backend) Remove dockerflow

* ✨(backend) add healthcheck route, and suppress healthcheck access logs

* 🚨(backend) ignore monkey patching

* 🚨(backend) coderabbitai + linter fixes

* 🚚(backend) remove version in healthz route

* 🔨(docker) use python http lib instead of curl and remove curl from dockerfile
2025-09-02 12:10:52 +02:00

876 lines
28 KiB
Python
Executable File

"""
Django settings for messages project.
Generated by 'django-admin startproject' using Django 3.1.5.
For more information on this file, see
https://docs.djangoproject.com/en/3.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.1/ref/settings/
"""
import os
import tomllib
from socket import gethostbyname, gethostname
from django.utils.translation import gettext_lazy as _
import dj_database_url
import sentry_sdk
from configurations import Configuration, values
from sentry_sdk.integrations.django import DjangoIntegration
from core.utils import JSONValue
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def get_release():
"""
Get the current release of the application
"""
try:
with open(os.path.join(BASE_DIR, "pyproject.toml"), "rb") as f:
pyproject_data = tomllib.load(f)
return pyproject_data["project"]["version"]
except (FileNotFoundError, KeyError):
return "NA" # Default: not available
class Base(Configuration):
"""
This is the base configuration every configuration (aka environment) should inherit from. It
is recommended to configure third-party applications by creating a configuration mixins in
./configurations and compose the Base configuration with those mixins.
It depends on an environment variable that SHOULD be defined:
* DJANGO_SECRET_KEY
You may also want to override default configuration by setting the following environment
variables:
* SENTRY_DSN
* DB_NAME
* DB_HOST
* DB_PASSWORD
* DB_USER
"""
DEBUG = False
USE_SWAGGER = False
API_VERSION = "v1.0"
# Admin URL configuration
ADMIN_URL = values.Value("admin/")
# OpenSearch configuration
OPENSEARCH_HOSTS = values.ListValue(
["http://opensearch:9200"],
environ_name="OPENSEARCH_URL",
environ_prefix=None,
)
OPENSEARCH_TIMEOUT = values.PositiveIntegerValue(
20, environ_name="OPENSEARCH_TIMEOUT", environ_prefix=None
)
OPENSEARCH_INDEX_THREADS = values.BooleanValue(
True, environ_name="OPENSEARCH_INDEX_THREADS", environ_prefix=None
)
OPENSEARCH_CA_CERTS = values.Value(
None, environ_name="OPENSEARCH_CA_CERTS", environ_prefix=None
)
# Security
ALLOWED_HOSTS = values.ListValue([])
SECRET_KEY = values.Value(None)
SERVER_TO_SERVER_API_TOKENS = values.ListValue([])
# Application definition
ROOT_URLCONF = "messages.urls"
WSGI_APPLICATION = "messages.wsgi.application"
# Database
DATABASES = {
"default": dj_database_url.config()
if os.environ.get("DATABASE_URL")
else {
"ENGINE": values.Value(
"django.db.backends.postgresql_psycopg2",
environ_name="DB_ENGINE",
environ_prefix=None,
),
"NAME": values.Value(
"messages", environ_name="DB_NAME", environ_prefix=None
),
"USER": values.Value("dbuser", environ_name="DB_USER", environ_prefix=None),
"PASSWORD": values.Value(
"dbpass", environ_name="DB_PASSWORD", environ_prefix=None
),
"HOST": values.Value(
"localhost", environ_name="DB_HOST", environ_prefix=None
),
"PORT": values.Value(5432, environ_name="DB_PORT", environ_prefix=None),
}
}
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
# Model custom attributes schema
SCHEMA_CUSTOM_ATTRIBUTES_USER = JSONValue(
{}, environ_name="SCHEMA_CUSTOM_ATTRIBUTES_USER", environ_prefix=None
)
SCHEMA_CUSTOM_ATTRIBUTES_MAILDOMAIN = JSONValue(
{}, environ_name="SCHEMA_CUSTOM_ATTRIBUTES_MAILDOMAIN", environ_prefix=None
)
DATA_DIR = values.Value(
"/data", environ_name="DJANGO_DATA_DIR", environ_prefix=None
)
# Static files (CSS, JavaScript, Images)
STATIC_URL = "/static/"
STATIC_ROOT = os.path.join(DATA_DIR, "static")
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(DATA_DIR, "media")
MEDIA_BASE_URL = values.Value(
None, environ_name="MEDIA_BASE_URL", environ_prefix=None
)
SITE_ID = 1
STORAGES = {
"default": {
"BACKEND": "storages.backends.s3.S3Storage",
},
"staticfiles": {
"BACKEND": values.Value(
"whitenoise.storage.CompressedManifestStaticFilesStorage",
environ_name="STORAGES_STATICFILES_BACKEND",
),
},
}
# MDA/MTA settings
MDA_API_SECRET = values.Value(
"default-mda-api-secret", environ_name="MDA_API_SECRET", environ_prefix=None
)
MTA_OUT_HOST = values.Value(None, environ_name="MTA_OUT_HOST", environ_prefix=None)
MTA_OUT_SMTP_USERNAME = values.Value(
None, environ_name="MTA_OUT_SMTP_USERNAME", environ_prefix=None
)
MTA_OUT_SMTP_PASSWORD = values.Value(
None, environ_name="MTA_OUT_SMTP_PASSWORD", environ_prefix=None
)
MTA_OUT_SMTP_USE_TLS = values.BooleanValue(
default=True, environ_name="MTA_OUT_SMTP_USE_TLS", environ_prefix=None
)
# Test domain settings
MESSAGES_TESTDOMAIN = values.Value(
None, environ_name="MESSAGES_TESTDOMAIN", environ_prefix=None
)
MESSAGES_TESTDOMAIN_MAPPING_BASEDOMAIN = values.Value(
None,
environ_name="MESSAGES_TESTDOMAIN_MAPPING_BASEDOMAIN",
environ_prefix=None,
)
MESSAGES_ACCEPT_ALL_EMAILS = values.BooleanValue(
default=False,
environ_name="MESSAGES_ACCEPT_ALL_EMAILS",
environ_prefix=None,
)
# Blob compression settings
MESSAGES_BLOB_ZSTD_LEVEL = values.PositiveIntegerValue(
default=3, environ_name="MESSAGES_BLOB_ZSTD_LEVEL", environ_prefix=None
)
# Django fernet encrypted fields settings
# Can be a list for key rotation: ['new_key', 'old_key']
SALT_KEY = values.ListValue([], environ_name="SALT_KEY", environ_prefix=None)
# DKIM settings
MESSAGES_DKIM_DEFAULT_SELECTOR = values.Value(
"stmessages", environ_name="MESSAGES_DKIM_DEFAULT_SELECTOR", environ_prefix=None
)
# Technical domain for DNS records (MX, SPF, DKIM hosting)
MESSAGES_TECHNICAL_DOMAIN = values.Value(
"localhost", environ_name="MESSAGES_TECHNICAL_DOMAIN", environ_prefix=None
)
# DNS Provider settings
DNS_SCALEWAY_API_TOKEN = values.Value(
None, environ_name="DNS_SCALEWAY_API_TOKEN", environ_prefix=None
)
DNS_SCALEWAY_PROJECT_ID = values.Value(
None, environ_name="DNS_SCALEWAY_PROJECT_ID", environ_prefix=None
)
DNS_SCALEWAY_TTL = values.PositiveIntegerValue(
3600, environ_name="DNS_SCALEWAY_TTL", environ_prefix=None
)
DNS_DEFAULT_PROVIDER = values.Value(
None, environ_name="DNS_DEFAULT_PROVIDER", environ_prefix=None
)
# Media
AWS_S3_ENDPOINT_URL = values.Value(
environ_name="AWS_S3_ENDPOINT_URL", environ_prefix=None
)
AWS_S3_ACCESS_KEY_ID = values.Value(
environ_name="AWS_S3_ACCESS_KEY_ID", environ_prefix=None
)
AWS_S3_SECRET_ACCESS_KEY = values.Value(
environ_name="AWS_S3_SECRET_ACCESS_KEY", environ_prefix=None
)
AWS_S3_REGION_NAME = values.Value(
environ_name="AWS_S3_REGION_NAME", environ_prefix=None
)
AWS_STORAGE_BUCKET_NAME = values.Value(
"st-messages-media-storage",
environ_name="AWS_STORAGE_BUCKET_NAME",
environ_prefix=None,
)
AWS_S3_UPLOAD_POLICY_EXPIRATION = values.Value(
24 * 60 * 60, # 24h
environ_name="AWS_S3_UPLOAD_POLICY_EXPIRATION",
environ_prefix=None,
)
# item images
ITEM_FILE_MAX_SIZE = values.PositiveIntegerValue(
5 * (2**30), # 5GB
environ_name="ITEM_FILE_MAX_SIZE",
environ_prefix=None,
)
# Internationalization
# https://docs.djangoproject.com/en/3.1/topics/i18n/
# Languages
LANGUAGE_CODE = values.Value("en-us")
LANGUAGE_COOKIE_NAME = "messages_language" # cookie & language is set from frontend
DRF_NESTED_MULTIPART_PARSER = {
# output of parser is converted to querydict
# if is set to False, dict python is returned
"querydict": False,
}
# Careful! Languages should be ordered by priority, as this tuple is used to get
# fallback/default languages throughout the app.
LANGUAGES = values.SingleNestedTupleValue(
(
("en-us", _("English")),
("fr-fr", _("French")),
("de-de", _("German")),
)
)
LOCALE_PATHS = (os.path.join(BASE_DIR, "locale"),)
TIME_ZONE = "UTC"
USE_I18N = True
USE_TZ = True
# Templates
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [os.path.join(BASE_DIR, "templates")],
"OPTIONS": {
"context_processors": [
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
"django.template.context_processors.csrf",
"django.template.context_processors.debug",
"django.template.context_processors.i18n",
"django.template.context_processors.media",
"django.template.context_processors.request",
"django.template.context_processors.tz",
],
"loaders": [
"django.template.loaders.filesystem.Loader",
"django.template.loaders.app_directories.Loader",
],
},
},
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"corsheaders.middleware.CorsMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
]
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"core.authentication.backends.OIDCAuthenticationBackend",
]
# Django applications from the highest priority to the lowest
INSTALLED_APPS = [
"core",
"drf_spectacular",
# Third party apps
"corsheaders",
"django_celery_beat",
"django_celery_results",
"django_filters",
"rest_framework",
"parler",
# Django
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.postgres",
"django.contrib.sessions",
"django.contrib.sites",
"django.contrib.messages",
"django.contrib.staticfiles",
# OIDC third party
"mozilla_django_oidc",
]
# Cache
CACHES = {
"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"},
}
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"mozilla_django_oidc.contrib.drf.OIDCAuthentication",
"rest_framework.authentication.SessionAuthentication",
),
"DEFAULT_PARSER_CLASSES": [
"rest_framework.parsers.JSONParser",
"nested_multipart_parser.drf.DrfNestedParser",
],
"EXCEPTION_HANDLER": "core.api.exception_handler",
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 20,
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
"DEFAULT_THROTTLE_RATES": {
"user_list_sustained": values.Value(
default="180/hour",
environ_name="API_USERS_LIST_THROTTLE_RATE_SUSTAINED",
environ_prefix=None,
),
"user_list_burst": values.Value(
default="30/minute",
environ_name="API_USERS_LIST_THROTTLE_RATE_BURST",
environ_prefix=None,
),
},
}
SPECTACULAR_SETTINGS = {
"TITLE": "messages API",
"DESCRIPTION": "This is the messages API schema.",
"VERSION": "1.0.0",
"SERVE_INCLUDE_SCHEMA": False,
"ENABLE_DJANGO_DEPLOY_CHECK": values.BooleanValue(
default=False,
environ_name="SPECTACULAR_SETTINGS_ENABLE_DJANGO_DEPLOY_CHECK",
),
"COMPONENT_SPLIT_REQUEST": True,
# OTHER SETTINGS
"SWAGGER_UI_DIST": "SIDECAR", # shorthand to use the sidecar instead
"SWAGGER_UI_FAVICON_HREF": "SIDECAR",
"REDOC_DIST": "SIDECAR",
}
TRASHBIN_CUTOFF_DAYS = values.Value(
30, environ_name="TRASHBIN_CUTOFF_DAYS", environ_prefix=None
)
AUTH_USER_MODEL = "core.User"
# CORS
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_ALL_ORIGINS = values.BooleanValue(False)
CORS_ALLOWED_ORIGINS = values.ListValue([])
CORS_ALLOWED_ORIGIN_REGEXES = values.ListValue([])
# Sentry
SENTRY_DSN = values.Value(None, environ_name="SENTRY_DSN", environ_prefix=None)
# Frontend
FRONTEND_THEME = values.Value(
None, environ_name="FRONTEND_THEME", environ_prefix=None
)
# Posthog
POSTHOG_KEY = values.Value(None, environ_name="POSTHOG_KEY", environ_prefix=None)
POSTHOG_HOST = values.Value(
"https://eu.i.posthog.com", environ_name="POSTHOG_HOST", environ_prefix=None
)
POSTHOG_SURVEY_ID = values.Value(
None, environ_name="POSTHOG_SURVEY_ID", environ_prefix=None
)
# Celery
CELERY_BROKER_URL = values.Value(
"redis://redis:6379", environ_name="CELERY_BROKER_URL", environ_prefix=None
)
CELERY_RESULT_BACKEND = "django-db"
CELERY_CACHE_BACKEND = "django-cache"
CELERY_BROKER_TRANSPORT_OPTIONS = values.DictValue({})
CELERY_RESULT_EXTENDED = True
CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24 * 30 # 30 days
CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"
# Session
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"
SESSION_COOKIE_AGE = 60 * 60 * 12
# OIDC - Authorization Code Flow
OIDC_CREATE_USER = values.BooleanValue(
default=False, environ_name="OIDC_CREATE_USER", environ_prefix=None
)
OIDC_RP_SIGN_ALGO = values.Value(
"RS256", environ_name="OIDC_RP_SIGN_ALGO", environ_prefix=None
)
OIDC_RP_CLIENT_ID = values.Value(
"st_messages", environ_name="OIDC_RP_CLIENT_ID", environ_prefix=None
)
OIDC_RP_CLIENT_SECRET = values.Value(
None,
environ_name="OIDC_RP_CLIENT_SECRET",
environ_prefix=None,
)
OIDC_OP_JWKS_ENDPOINT = values.Value(
environ_name="OIDC_OP_JWKS_ENDPOINT", environ_prefix=None
)
OIDC_OP_AUTHORIZATION_ENDPOINT = values.Value(
environ_name="OIDC_OP_AUTHORIZATION_ENDPOINT", environ_prefix=None
)
OIDC_OP_TOKEN_ENDPOINT = values.Value(
None, environ_name="OIDC_OP_TOKEN_ENDPOINT", environ_prefix=None
)
OIDC_OP_USER_ENDPOINT = values.Value(
None, environ_name="OIDC_OP_USER_ENDPOINT", environ_prefix=None
)
OIDC_OP_LOGOUT_ENDPOINT = values.Value(
None, environ_name="OIDC_OP_LOGOUT_ENDPOINT", environ_prefix=None
)
OIDC_AUTH_REQUEST_EXTRA_PARAMS = values.DictValue(
{}, environ_name="OIDC_AUTH_REQUEST_EXTRA_PARAMS", environ_prefix=None
)
OIDC_RP_SCOPES = values.Value(
"openid email", environ_name="OIDC_RP_SCOPES", environ_prefix=None
)
LOGIN_REDIRECT_URL = values.Value(
None, environ_name="LOGIN_REDIRECT_URL", environ_prefix=None
)
LOGIN_REDIRECT_URL_FAILURE = values.Value(
None, environ_name="LOGIN_REDIRECT_URL_FAILURE", environ_prefix=None
)
LOGOUT_REDIRECT_URL = values.Value(
None, environ_name="LOGOUT_REDIRECT_URL", environ_prefix=None
)
OIDC_USE_NONCE = values.BooleanValue(
default=True, environ_name="OIDC_USE_NONCE", environ_prefix=None
)
OIDC_REDIRECT_REQUIRE_HTTPS = values.BooleanValue(
default=False, environ_name="OIDC_REDIRECT_REQUIRE_HTTPS", environ_prefix=None
)
OIDC_REDIRECT_ALLOWED_HOSTS = values.ListValue(
default=[], environ_name="OIDC_REDIRECT_ALLOWED_HOSTS", environ_prefix=None
)
OIDC_STORE_ID_TOKEN = values.BooleanValue(
default=True, environ_name="OIDC_STORE_ID_TOKEN", environ_prefix=None
)
OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION = values.BooleanValue(
default=True,
environ_name="OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION",
environ_prefix=None,
)
# WARNING: Enabling this setting allows multiple user accounts to share the same email
# address. This may cause security issues and is not recommended for production use when
# email is activated as fallback for identification (see previous setting).
OIDC_ALLOW_DUPLICATE_EMAILS = values.BooleanValue(
default=False,
environ_name="OIDC_ALLOW_DUPLICATE_EMAILS",
environ_prefix=None,
)
USER_OIDC_ESSENTIAL_CLAIMS = values.ListValue(
default=[], environ_name="USER_OIDC_ESSENTIAL_CLAIMS", environ_prefix=None
)
USER_OIDC_FIELDS_TO_FULLNAME = values.ListValue(
default=["first_name", "last_name"],
environ_name="USER_OIDC_FIELDS_TO_FULLNAME",
environ_prefix=None,
)
USER_OIDC_FIELD_TO_SHORTNAME = values.Value(
default="first_name",
environ_name="USER_OIDC_FIELD_TO_SHORTNAME",
environ_prefix=None,
)
ALLOW_LOGOUT_GET_METHOD = values.BooleanValue(
default=True, environ_name="ALLOW_LOGOUT_GET_METHOD", environ_prefix=None
)
IDENTITY_PROVIDER = values.Value(
None, environ_name="IDENTITY_PROVIDER", environ_prefix=None
)
KEYCLOAK_REALM = values.Value(
None, environ_name="KEYCLOAK_REALM", environ_prefix=None
)
KEYCLOAK_URL = values.Value(None, environ_name="KEYCLOAK_URL", environ_prefix=None)
KEYCLOAK_CLIENT_ID = values.Value(
None, environ_name="KEYCLOAK_CLIENT_ID", environ_prefix=None
)
KEYCLOAK_CLIENT_SECRET = values.Value(
None,
environ_name="KEYCLOAK_CLIENT_SECRET",
environ_prefix=None,
)
KEYCLOAK_GROUP_PATH_PREFIX = values.Value(
None,
environ_name="KEYCLOAK_GROUP_PATH_PREFIX",
environ_prefix=None,
)
# AI
AI_API_KEY = values.Value(None, environ_name="AI_API_KEY", environ_prefix=None)
AI_BASE_URL = values.Value(None, environ_name="AI_BASE_URL", environ_prefix=None)
AI_MODEL = values.Value(None, environ_name="AI_MODEL", environ_prefix=None)
AI_FEATURE_SUMMARY_ENABLED = values.BooleanValue(
default=False, environ_name="AI_FEATURE_SUMMARY_ENABLED", environ_prefix=None
)
AI_FEATURE_AUTOLABELS_ENABLED = values.BooleanValue(
default=False, environ_name="AI_FEATURE_AUTOLABELS_ENABLED", environ_prefix=None
)
# Logging
# We want to make it easy to log to console but by default we log production
# to Sentry and don't want to log to console.
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"simple": {
"format": "{asctime} {name} {levelname} {message}",
"style": "{",
},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "simple",
},
},
# Override root logger to send it to console
"root": {
"handlers": ["console"],
"level": values.Value(
"INFO", environ_name="LOGGING_LEVEL_LOGGERS_ROOT", environ_prefix=None
),
},
"loggers": {
"core": {
"handlers": ["console"],
"level": values.Value(
"INFO",
environ_name="LOGGING_LEVEL_LOGGERS_APP",
environ_prefix=None,
),
"propagate": False,
},
},
}
API_USERS_LIST_LIMIT = values.PositiveIntegerValue(
default=5,
environ_name="API_USERS_LIST_LIMIT",
environ_prefix=None,
)
# External services
# Settings related to the interoperability with external services
# that messages is able to use
# 1. Drive - https://github.com/suitenumerique/drive
DRIVE_CONFIG = {
"base_url": values.Value(
default=None, environ_name="DRIVE_BASE_URL", environ_prefix=None
),
"sdk_url": "/sdk",
"api_url": "/api/v1.0",
}
# pylint: disable=invalid-name
@property
def ENVIRONMENT(self):
"""Environment in which the application is launched."""
return self.__class__.__name__.lower()
# pylint: disable=invalid-name
@property
def RELEASE(self):
"""
Return the release information.
Delegate to the module function to enable easier testing.
"""
return get_release()
# pylint: disable=invalid-name
@property
def PARLER_LANGUAGES(self):
"""
Return languages for Parler computed from the LANGUAGES and LANGUAGE_CODE settings.
"""
return {
self.SITE_ID: tuple({"code": code} for code, _name in self.LANGUAGES),
"default": {
"fallbacks": [self.LANGUAGE_CODE],
"hide_untranslated": False,
},
}
@classmethod
def post_setup(cls):
"""Post setup configuration.
This is the place where you can configure settings that require other
settings to be loaded.
"""
super().post_setup()
# The SENTRY_DSN setting should be available to activate sentry for an environment
if cls.SENTRY_DSN is not None:
sentry_sdk.init(
dsn=cls.SENTRY_DSN,
environment=cls.__name__.lower(),
release=get_release(),
integrations=[DjangoIntegration()],
)
sentry_sdk.set_tag("application", "backend")
if (
cls.OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION
and cls.OIDC_ALLOW_DUPLICATE_EMAILS
):
raise ValueError(
"Both OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION and "
"OIDC_ALLOW_DUPLICATE_EMAILS cannot be set to True simultaneously. "
)
class Build(Base):
"""Settings used when the application is built.
This environment should not be used to run the application. Just to build it with non-blocking
settings.
"""
SECRET_KEY = values.Value("DummyKey")
STORAGES = {
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
},
"staticfiles": {
"BACKEND": values.Value(
"whitenoise.storage.CompressedManifestStaticFilesStorage",
environ_name="STORAGES_STATICFILES_BACKEND",
),
},
}
class Development(Base):
"""
Development environment settings
We set DEBUG to True and configure the server to respond from all hosts.
"""
ALLOWED_HOSTS = ["*"]
CORS_ALLOW_ALL_ORIGINS = True
CSRF_TRUSTED_ORIGINS = ["http://localhost:8900", "http://localhost:8901"]
DEBUG = True
SESSION_COOKIE_NAME = "st_messages_sessionid"
USE_SWAGGER = True
SESSION_CACHE_ALIAS = "session"
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.dummy.DummyCache",
},
"session": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": values.Value(
"redis://redis:6379",
environ_name="REDIS_URL",
environ_prefix=None,
),
"TIMEOUT": values.IntegerValue(
30, # timeout in seconds
environ_name="CACHES_DEFAULT_TIMEOUT",
environ_prefix=None,
),
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
},
}
# pylint: disable=invalid-name
def __init__(self):
super().__init__()
self.INSTALLED_APPS += ["django_extensions", "drf_spectacular_sidecar"]
class DevelopmentMinimal(Development):
"""
Development environment settings with minimal dependencies
"""
CELERY_TASK_ALWAYS_EAGER = True
OPENSEARCH_INDEX_THREADS = False
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.dummy.DummyCache",
},
"session": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"},
}
class Test(Base):
"""Test environment settings"""
PASSWORD_HASHERS = [
"django.contrib.auth.hashers.MD5PasswordHasher",
]
USE_SWAGGER = True
IDENTITY_PROVIDER = None
CELERY_TASK_ALWAYS_EAGER = values.BooleanValue(True)
# Add a test encryption key for django-fernet-encrypted-fields
SALT_KEY = ["test-salt-for-development-only"]
SCHEMA_CUSTOM_ATTRIBUTES_USER = {}
SCHEMA_CUSTOM_ATTRIBUTES_MAILDOMAIN = {}
# pylint: disable=invalid-name
def __init__(self):
super().__init__()
self.INSTALLED_APPS += ["drf_spectacular_sidecar"]
class ContinuousIntegration(Test):
"""
Continuous Integration environment settings
nota bene: it should inherit from the Test environment.
"""
class Production(Base):
"""
Production environment settings
You must define the ALLOWED_HOSTS environment variable in Production
configuration (and derived configurations):
ALLOWED_HOSTS=["foo.com", "foo.fr"]
"""
# Security
# Add allowed host from environment variables.
# The machine hostname is added by default,
# it makes the application pingable by a load balancer on the same machine by example
ALLOWED_HOSTS = [
*values.ListValue([], environ_name="ALLOWED_HOSTS"),
gethostbyname(gethostname()),
]
CSRF_TRUSTED_ORIGINS = values.ListValue([])
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
# SECURE_PROXY_SSL_HEADER allows to fix the scheme in Django's HttpRequest
# object when your application is behind a reverse proxy.
#
# Keep this SECURE_PROXY_SSL_HEADER configuration only if :
# - your Django app is behind a proxy.
# - your proxy strips the X-Forwarded-Proto header from all incoming requests
# - Your proxy sets the X-Forwarded-Proto header and sends it to Django
#
# In other cases, you should comment the following line to avoid security issues.
# SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_HSTS_SECONDS = 60
SECURE_HSTS_PRELOAD = True
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_SSL_REDIRECT = True
SECURE_REDIRECT_EXEMPT = ["^__lbheartbeat__", "^__heartbeat__", "^api/v1\\.0/mta/"]
# Modern browsers require to have the `secure` attribute on cookies with `Samesite=none`
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
# Privacy
SECURE_REFERRER_POLICY = "same-origin"
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": values.Value(
"redis://redis:6379",
environ_name="REDIS_URL",
environ_prefix=None,
),
"TIMEOUT": values.IntegerValue(
30, # timeout in seconds
environ_name="CACHES_DEFAULT_TIMEOUT",
environ_prefix=None,
),
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
},
}
class Feature(Production):
"""
Feature environment settings
nota bene: it should inherit from the Production environment.
"""
class Staging(Production):
"""
Staging environment settings
nota bene: it should inherit from the Production environment.
"""
class PreProduction(Production):
"""
Pre-production environment settings
nota bene: it should inherit from the Production environment.
"""