""" Django settings for publish 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 json import os from django.utils.translation import gettext_lazy as _ import sentry_sdk from configurations import Configuration, values from sentry_sdk.integrations.django import DjangoIntegration # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) DATA_DIR = os.path.join("/", "data") def get_release(): """ Get the current release of the application By release, we mean the release from the version.json file à la Mozilla [1] (if any). If this file has not been found, it defaults to "NA". [1] https://github.com/mozilla-services/Dockerflow/blob/master/docs/version_object.md """ # Try to get the current release from the version.json file generated by the # CI during the Docker image build try: with open(os.path.join(BASE_DIR, "version.json"), encoding="utf8") as version: return json.load(version)["version"] except FileNotFoundError: return "NA" # Default: not available class Base(Configuration): """ This is the base configuration every configuration (aka environnement) 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: * DJANGO_SENTRY_DSN * DB_NAME * DB_HOST * DB_PASSWORD * DB_USER """ DEBUG = False USE_SWAGGER = False API_VERSION = "v1.0" # Security ALLOWED_HOSTS = values.ListValue([]) SECRET_KEY = values.Value(None) # Application definition ROOT_URLCONF = "publish.urls" WSGI_APPLICATION = "publish.wsgi.application" # Database DATABASES = { "default": { "ENGINE": values.Value( "django.db.backends.postgresql_psycopg2", environ_name="DB_ENGINE", environ_prefix=None, ), "NAME": values.Value("publish", environ_name="DB_NAME", environ_prefix=None), "USER": values.Value("dinum", environ_name="DB_USER", environ_prefix=None), "PASSWORD": values.Value( "pass", 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" # 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") SITE_ID = 1 STORAGES = { "default": { "BACKEND": "django.core.files.storage.FileSystemStorage", }, "staticfiles": { "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", }, } # Internationalization # https://docs.djangoproject.com/en/3.1/topics/i18n/ # Languages LANGUAGE_CODE = values.Value("en-us") 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")), ) ) 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", ] # Django applications from the highest priority to the lowest INSTALLED_APPS = [ # publish "core", "demo", "drf_spectacular", # Third party apps "corsheaders", "dockerflow.django", "rest_framework", "parler", "easy_thumbnails", # 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", ] # Cache CACHES = { "default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}, } REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ( "core.authentication.DelegatedJWTAuthentication", ), "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", } SPECTACULAR_SETTINGS = { "TITLE": "publish API", "DESCRIPTION": "This is the publish 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", } SIMPLE_JWT = { "ALGORITHM": values.Value("HS256", environ_name="JWT_ALGORITHM"), "SIGNING_KEY": values.SecretValue( environ_name="JWT_PRIVATE_SIGNING_KEY", ), "AUTH_HEADER_TYPES": ("Bearer",), "AUTH_HEADER_NAME": "HTTP_AUTHORIZATION", "USER_ID_FIELD": "sub", "USER_ID_CLAIM": "sub", "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",), } JWT_USER_GETTER = values.Value( "core.models.oidc_user_getter", environ_name="publish_JWT_USER_GETTER", environ_prefix=None, ) # Mail EMAIL_BACKEND = values.Value("django.core.mail.backends.smtp.EmailBackend") EMAIL_HOST = values.Value(None) EMAIL_HOST_USER = values.Value(None) EMAIL_HOST_PASSWORD = values.Value(None) EMAIL_PORT = values.PositiveIntegerValue(None) EMAIL_USE_TLS = values.BooleanValue(False) EMAIL_FROM = values.Value("from@example.com") 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") # Easy thumbnails THUMBNAIL_EXTENSION = "webp" THUMBNAIL_TRANSPARENCY_EXTENSION = "webp" THUMBNAIL_ALIASES = {} # Celery CELERY_BROKER_URL = values.Value("redis://redis:6379/0") CELERY_BROKER_TRANSPORT_OPTIONS = values.DictValue({}) # 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()], ) with sentry_sdk.configure_scope() as scope: scope.set_extra("application", "backend") 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:8072"] DEBUG = True SESSION_COOKIE_NAME = "publish_sessionid" USE_SWAGGER = True def __init__(self): # pylint: disable=invalid-name self.INSTALLED_APPS += ["django_extensions", "drf_spectacular_sidecar"] class Test(Base): """Test environment settings""" LOGGING = values.DictValue( { "version": 1, "disable_existing_loggers": False, "handlers": { "console": { "class": "logging.StreamHandler", }, }, "loggers": { "publish": { "handlers": ["console"], "level": "DEBUG", }, }, } ) PASSWORD_HASHERS = [ "django.contrib.auth.hashers.MD5PasswordHasher", ] USE_SWAGGER = True STORAGES = { "default": { "BACKEND": "django.core.files.storage.FileSystemStorage", }, "staticfiles": { "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", }, } CELERY_TASK_ALWAYS_EAGER = values.BooleanValue(True) def __init__(self): # pylint: disable=invalid-name 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 ALLOWED_HOSTS = values.ListValue(None) 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") # Modern browsers require to have the `secure` attribute on cookies with `Samesite=none` CSRF_COOKIE_SECURE = True SESSION_COOKIE_SECURE = True # For static files in production, we want to use a backend that includes a hash in # the filename, that is calculated from the file content, so that browsers always # get the updated version of each file. STORAGES = { "default": { "BACKEND": "storages.backends.s3.S3Storage", }, "staticfiles": { # For static files in production, we want to use a backend that includes a hash in # the filename, that is calculated from the file content, so that browsers always # get the updated version of each file. "BACKEND": values.Value( "whitenoise.storage.CompressedManifestStaticFilesStorage", environ_name="STORAGES_STATICFILES_BACKEND", ) }, } # Privacy SECURE_REFERRER_POLICY = "same-origin" # Media AWS_S3_ENDPOINT_URL = values.Value() AWS_S3_ACCESS_KEY_ID = values.Value() AWS_S3_SECRET_ACCESS_KEY = values.Value() AWS_STORAGE_BUCKET_NAME = values.Value("tf-default-publish-media-storage") AWS_S3_REGION_NAME = values.Value() 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. """