From 9462bdd7c9674e3f0871e01c1a7edb1b6e7a1ce7 Mon Sep 17 00:00:00 2001 From: Tudor Amariei Date: Thu, 28 Mar 2024 10:30:45 +0200 Subject: [PATCH] Update back-end settings --- .github/workflows/api_pipeline.yml | 2 +- Makefile | 3 - backend/manage.py | 2 +- backend/pytest.ini | 2 +- backend/requirements-dev.in | 4 +- backend/requirements-dev.txt | 32 +- backend/requirements-test.in | 1 - backend/requirements-test.txt | 204 ----------- backend/requirements.in | 3 + backend/requirements.txt | 24 +- .../{settings/base.py => settings.py} | 332 +++++++++++++----- backend/seismic_site/settings/__init__.py | 0 backend/seismic_site/settings/development.py | 25 -- backend/seismic_site/settings/production.py | 22 -- .../{settings/test.py => test_settings.py} | 2 +- backend/seismic_site/urls.py | 7 - backend/seismic_site/wsgi.py | 2 +- 17 files changed, 296 insertions(+), 371 deletions(-) delete mode 100644 backend/requirements-test.in delete mode 100644 backend/requirements-test.txt rename backend/seismic_site/{settings/base.py => settings.py} (56%) delete mode 100644 backend/seismic_site/settings/__init__.py delete mode 100644 backend/seismic_site/settings/development.py delete mode 100644 backend/seismic_site/settings/production.py rename backend/seismic_site/{settings/test.py => test_settings.py} (80%) diff --git a/.github/workflows/api_pipeline.yml b/.github/workflows/api_pipeline.yml index dba4df09..d3f02e90 100644 --- a/.github/workflows/api_pipeline.yml +++ b/.github/workflows/api_pipeline.yml @@ -111,4 +111,4 @@ jobs: - name: Run tests run: | - ENVIRONMENT=test DJANGO_SETTINGS_MODULE=seismic_site.settings.test ./backend/manage.py test + ENVIRONMENT=test DJANGO_SETTINGS_MODULE=seismic_site.test_settings ./backend/manage.py test diff --git a/Makefile b/Makefile index c35bdc5d..3d4a1afb 100644 --- a/Makefile +++ b/Makefile @@ -154,17 +154,14 @@ bash: ## start a bash shell requirements-build: ## run pip compile and add requirements from the *.in files docker exec seismic_backend_dev sh -c " \ pip-compile --strip-extras --resolver=backtracking -o requirements.txt requirements.in && \ - pip-compile --strip-extras --resolver=backtracking -o requirements-test.txt requirements-test.in && \ pip-compile --strip-extras --resolver=backtracking -o requirements-dev.txt requirements-dev.in \ " requirements-update: ## run pip compile and rebuild the requirements files docker exec seismic_backend_dev sh -c " \ pip-compile --strip-extras --resolver=backtracking -r -U -o requirements.txt requirements.in && \ - pip-compile --strip-extras --resolver=backtracking -r -U -o requirements-test.txt requirements-test.in && \ pip-compile --strip-extras --resolver=backtracking -r -U -o requirements-dev.txt requirements-dev.in && \ chmod a+r requirements.txt && \ - chmod a+r requirements-test.txt && \ chmod a+r requirements-dev.txt \ " diff --git a/backend/manage.py b/backend/manage.py index 1a10c9f3..0ea26fef 100755 --- a/backend/manage.py +++ b/backend/manage.py @@ -5,7 +5,7 @@ def main(): - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "seismic_site.settings.development") + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "seismic_site.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: diff --git a/backend/pytest.ini b/backend/pytest.ini index b72dcc4c..d256d78e 100644 --- a/backend/pytest.ini +++ b/backend/pytest.ini @@ -1,3 +1,3 @@ [pytest] -DJANGO_SETTINGS_MODULE = seismic_site.settings.test +DJANGO_SETTINGS_MODULE = seismic_site.test_settings ENVIRONMENT = test diff --git a/backend/requirements-dev.in b/backend/requirements-dev.in index 29491d47..dc998ebb 100644 --- a/backend/requirements-dev.in +++ b/backend/requirements-dev.in @@ -6,9 +6,9 @@ werkzeug~=3.0.1 # tests pytest~=8.1.1 pytest-flake8~=1.1.1 -pytest-cov~=4.1.0 +pytest-cov~=5.0.0 pytest-django~=4.8.0 -faker~=24.2.0 +faker~=24.4.0 tqdm~=4.66.2 # utils diff --git a/backend/requirements-dev.txt b/backend/requirements-dev.txt index a23e9987..9546fc80 100644 --- a/backend/requirements-dev.txt +++ b/backend/requirements-dev.txt @@ -4,7 +4,7 @@ # # pip-compile --output-file=requirements-dev.txt --strip-extras requirements-dev.in # -asgiref==3.7.2 +asgiref==3.8.1 # via # -r requirements.txt # django @@ -20,9 +20,11 @@ black==24.3.0 # via -r requirements-dev.in blessed==1.20.0 # via -r requirements.txt -boto3==1.34.62 - # via -r requirements.txt -botocore==1.34.62 +boto3==1.34.72 + # via + # -r requirements.txt + # django-ses +botocore==1.34.72 # via # -r requirements.txt # boto3 @@ -33,9 +35,9 @@ click==8.1.7 # via # black # pip-tools -coverage==7.4.3 +coverage==7.4.4 # via pytest-cov -croniter==2.0.2 +croniter==2.0.3 # via -r requirements.txt decorator==5.1.1 # via ipython @@ -61,6 +63,7 @@ django==4.2.11 # django-jquery # django-picklefield # django-q2 + # django-ses # django-storages # djangorestframework # drf-spectacular @@ -74,7 +77,7 @@ django-extensions==3.2.3 # via -r requirements-dev.in django-import-export==3.3.7 # via -r requirements.txt -django-jazzmin==2.6.0 +django-jazzmin==2.6.1 # via -r requirements.txt django-jquery==3.1.0 # via -r requirements.txt @@ -84,6 +87,8 @@ django-picklefield==3.1 # django-q2 django-q2==1.6.2 # via -r requirements.txt +django-ses==3.5.2 + # via -r requirements.txt django-storages==1.14.2 # via -r requirements.txt djangorestframework==3.14.0 @@ -98,7 +103,7 @@ et-xmlfile==1.1.0 # openpyxl executing==2.0.1 # via stack-data -faker==24.2.0 +faker==24.2.1 # via -r requirements-dev.in flake8==7.0.0 # via pytest-flake8 @@ -200,7 +205,7 @@ pytest==8.1.1 # pytest-cov # pytest-django # pytest-flake8 -pytest-cov==4.1.0 +pytest-cov==5.0.0 # via -r requirements-dev.in pytest-django==4.8.0 # via -r requirements-dev.in @@ -216,13 +221,14 @@ pytz==2024.1 # via # -r requirements.txt # croniter + # django-ses # djangorestframework pyyaml==6.0.1 # via # -r requirements.txt # drf-spectacular # tablib -referencing==0.33.0 +referencing==0.34.0 # via # -r requirements.txt # jsonschema @@ -232,9 +238,9 @@ rpds-py==0.18.0 # -r requirements.txt # jsonschema # referencing -ruff==0.3.2 +ruff==0.3.4 # via -r requirements-dev.in -s3transfer==0.10.0 +s3transfer==0.10.1 # via # -r requirements.txt # boto3 @@ -269,7 +275,7 @@ uritemplate==4.1.1 # via # -r requirements.txt # drf-spectacular -urllib3==2.0.7 +urllib3==2.2.1 # via # -r requirements.txt # botocore diff --git a/backend/requirements-test.in b/backend/requirements-test.in deleted file mode 100644 index bc04b496..00000000 --- a/backend/requirements-test.in +++ /dev/null @@ -1 +0,0 @@ --r requirements.txt diff --git a/backend/requirements-test.txt b/backend/requirements-test.txt deleted file mode 100644 index a53109f6..00000000 --- a/backend/requirements-test.txt +++ /dev/null @@ -1,204 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --output-file=requirements-test.txt --strip-extras requirements-test.in -# -asgiref==3.7.2 - # via - # -r requirements.txt - # django - # django-cors-headers -attrs==23.2.0 - # via - # -r requirements.txt - # jsonschema - # referencing -blessed==1.20.0 - # via -r requirements.txt -boto3==1.34.62 - # via -r requirements.txt -botocore==1.34.62 - # via - # -r requirements.txt - # boto3 - # s3transfer -croniter==2.0.2 - # via -r requirements.txt -defusedxml==0.7.1 - # via - # -r requirements.txt - # odfpy -diff-match-patch==20230430 - # via - # -r requirements.txt - # django-import-export -dj-database-url==2.1.0 - # via -r requirements.txt -django==4.2.11 - # via - # -r requirements.txt - # dj-database-url - # django-cors-headers - # django-import-export - # django-jazzmin - # django-jquery - # django-picklefield - # django-q2 - # django-storages - # djangorestframework - # drf-spectacular -django-cors-headers==4.3.1 - # via -r requirements.txt -django-environ==0.11.2 - # via -r requirements.txt -django-import-export==3.3.7 - # via -r requirements.txt -django-jazzmin==2.6.0 - # via -r requirements.txt -django-jquery==3.1.0 - # via -r requirements.txt -django-picklefield==3.1 - # via - # -r requirements.txt - # django-q2 -django-q2==1.6.2 - # via -r requirements.txt -django-storages==1.14.2 - # via -r requirements.txt -djangorestframework==3.14.0 - # via - # -r requirements.txt - # drf-spectacular -drf-spectacular==0.27.1 - # via -r requirements.txt -et-xmlfile==1.1.0 - # via - # -r requirements.txt - # openpyxl -gevent==24.2.1 - # via -r requirements.txt -greenlet==3.0.3 - # via - # -r requirements.txt - # gevent -gunicorn==21.2.0 - # via -r requirements.txt -inflection==0.5.1 - # via - # -r requirements.txt - # drf-spectacular -jmespath==1.0.1 - # via - # -r requirements.txt - # boto3 - # botocore -jsonschema==4.21.1 - # via - # -r requirements.txt - # drf-spectacular -jsonschema-specifications==2023.12.1 - # via - # -r requirements.txt - # jsonschema -markuppy==1.14 - # via - # -r requirements.txt - # tablib -odfpy==1.4.1 - # via - # -r requirements.txt - # tablib -openpyxl==3.1.2 - # via - # -r requirements.txt - # tablib -packaging==24.0 - # via - # -r requirements.txt - # gunicorn -pillow==10.2.0 - # via -r requirements.txt -psutil==5.9.8 - # via -r requirements.txt -psycopg2-binary==2.9.9 - # via -r requirements.txt -python-dateutil==2.9.0.post0 - # via - # -r requirements.txt - # botocore - # croniter -pytz==2024.1 - # via - # -r requirements.txt - # croniter - # djangorestframework -pyyaml==6.0.1 - # via - # -r requirements.txt - # drf-spectacular - # tablib -referencing==0.33.0 - # via - # -r requirements.txt - # jsonschema - # jsonschema-specifications -rpds-py==0.18.0 - # via - # -r requirements.txt - # jsonschema - # referencing -s3transfer==0.10.0 - # via - # -r requirements.txt - # boto3 -six==1.16.0 - # via - # -r requirements.txt - # blessed - # python-dateutil -sqlparse==0.4.4 - # via - # -r requirements.txt - # django -tablib==3.5.0 - # via - # -r requirements.txt - # django-import-export -typing-extensions==4.10.0 - # via - # -r requirements.txt - # dj-database-url -uritemplate==4.1.1 - # via - # -r requirements.txt - # drf-spectacular -urllib3==2.0.7 - # via - # -r requirements.txt - # botocore -wcwidth==0.2.13 - # via - # -r requirements.txt - # blessed -whitenoise==6.6.0 - # via -r requirements.txt -xlrd==2.0.1 - # via - # -r requirements.txt - # tablib -xlwt==1.3.0 - # via - # -r requirements.txt - # tablib -zope-event==5.0 - # via - # -r requirements.txt - # gevent -zope-interface==6.2 - # via - # -r requirements.txt - # gevent - -# The following packages are considered to be unsafe in a requirements file: -# setuptools diff --git a/backend/requirements.in b/backend/requirements.in index a7104d93..fbc35acb 100644 --- a/backend/requirements.in +++ b/backend/requirements.in @@ -28,6 +28,9 @@ boto3~=1.34.62 pillow~=10.2.0 whitenoise~=6.6.0 +# emails +django-ses~=3.5.2 + # misc libraries tablib~=3.5.0 diff --git a/backend/requirements.txt b/backend/requirements.txt index ea67849f..42f97991 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -4,7 +4,7 @@ # # pip-compile --output-file=requirements.txt --strip-extras requirements.in # -asgiref==3.7.2 +asgiref==3.8.1 # via # django # django-cors-headers @@ -14,13 +14,15 @@ attrs==23.2.0 # referencing blessed==1.20.0 # via -r requirements.in -boto3==1.34.62 - # via -r requirements.in -botocore==1.34.62 +boto3==1.34.72 + # via + # -r requirements.in + # django-ses +botocore==1.34.72 # via # boto3 # s3transfer -croniter==2.0.2 +croniter==2.0.3 # via -r requirements.in defusedxml==0.7.1 # via odfpy @@ -38,6 +40,7 @@ django==4.2.11 # django-jquery # django-picklefield # django-q2 + # django-ses # django-storages # djangorestframework # drf-spectacular @@ -47,7 +50,7 @@ django-environ==0.11.2 # via -r requirements.in django-import-export==3.3.7 # via -r requirements.in -django-jazzmin==2.6.0 +django-jazzmin==2.6.1 # via -r requirements.in django-jquery==3.1.0 # via -r requirements.in @@ -55,6 +58,8 @@ django-picklefield==3.1 # via django-q2 django-q2==1.6.2 # via -r requirements.in +django-ses==3.5.2 + # via -r requirements.in django-storages==1.14.2 # via -r requirements.in djangorestframework==3.14.0 @@ -102,12 +107,13 @@ python-dateutil==2.9.0.post0 pytz==2024.1 # via # croniter + # django-ses # djangorestframework pyyaml==6.0.1 # via # drf-spectacular # tablib -referencing==0.33.0 +referencing==0.34.0 # via # jsonschema # jsonschema-specifications @@ -115,7 +121,7 @@ rpds-py==0.18.0 # via # jsonschema # referencing -s3transfer==0.10.0 +s3transfer==0.10.1 # via boto3 six==1.16.0 # via @@ -131,7 +137,7 @@ typing-extensions==4.10.0 # via dj-database-url uritemplate==4.1.1 # via drf-spectacular -urllib3==2.0.7 +urllib3==2.2.1 # via botocore wcwidth==0.2.13 # via blessed diff --git a/backend/seismic_site/settings/base.py b/backend/seismic_site/settings.py similarity index 56% rename from backend/seismic_site/settings/base.py rename to backend/seismic_site/settings.py index ee4c1fe5..86cb41cb 100644 --- a/backend/seismic_site/settings/base.py +++ b/backend/seismic_site/settings.py @@ -2,55 +2,130 @@ Django settings for the project. For more information on this file, see -https://docs.djangoproject.com/en/3.2/topics/settings/ +https://docs.djangoproject.com/en/4.2/topics/settings/ For the full list of settings and their values, see -https://docs.djangoproject.com/en/3.2/ref/settings/ +https://docs.djangoproject.com/en/4.2/ref/settings/ """ import os +from copy import deepcopy +from pathlib import Path from typing import Any, Dict, List import environ from django.utils.translation import gettext_lazy as _ +# Build paths inside the project like this: BASE_DIR / 'subdir'. +ROOT = Path(__file__).resolve().parent.parent.parent +BASE_DIR = os.path.abspath(os.path.join(ROOT, "backend")) + +ENV_FILE_NAME = os.environ.get("ENV_FILE_NAME", ".env.local") +ENV_FILE_PATH = os.path.join(BASE_DIR, os.pardir, ENV_FILE_NAME) + env = environ.Env( # set casting, default value DEBUG=(bool, False), ENVIRONMENT=(str, "production"), - ENABLE_DEBUG_TOOLBAR=(bool, False), + SECRET_KEY=(str, "secret-key"), + LOG_LEVEL=(str, "INFO"), LANGUAGE_CODE=(str, "en"), - NO_REPLY_EMAIL=(str, "noreply@code4.ro"), - DEFAULT_FROM_EMAIL=(str, "noreply@code4.ro"), HERE_MAPS_API_KEY=(str, ""), DATA_UPLOAD_MAX_NUMBER_FIELDS=(int, 1000), - # hosts and origins - ALLOWED_HOSTS=(list, []), + BACKGROUND_WORKERS_COUNT=(int, 1), + # email settings + EMAIL_SEND_METHOD=(str, "async"), + EMAIL_BACKEND=(str, "django.core.mail.backends.console.EmailBackend"), + EMAIL_HOST=(str, ""), + EMAIL_PORT=(str, ""), + EMAIL_HOST_USER=(str, ""), + EMAIL_HOST_PASSWORD=(str, ""), + EMAIL_USE_TLS=(str, ""), + EMAIL_FAIL_SILENTLY=(bool, False), + DEFAULT_FROM_EMAIL=(str, "no-reply@code4.ro"), + NO_REPLY_EMAIL=(str, "no-reply@code4.ro"), + # security settings + ALLOWED_HOSTS=(list, ["*"]), CSRF_TRUSTED_ORIGINS=(list, []), + CORS_ALLOW_ALL_ORIGINS=(bool, False), CORS_ALLOWED_ORIGINS=(list, []), CORS_ALLOWED_ORIGIN_REGEXES=(list, []), # aws settings + AWS_REGION_NAME=(str, ""), + # S3 USE_S3=(bool, False), - AWS_ACCESS_KEY_ID=(str, ""), - AWS_SECRET_ACCESS_KEY=(str, ""), - AWS_STORAGE_BUCKET_NAME=(str, ""), - AWS_SUBDOMAIN=(str, "s3.amazonaws.com"), AWS_S3_REGION_NAME=(str, ""), - BACKGROUND_WORKERS_COUNT=(int, 1), + AWS_S3_SIGNATURE_VERSION=(str, "s3v4"), + AWS_S3_ADDRESSING_STYLE=(str, "virtual"), + AWS_S3_STORAGE_DEFAULT_BUCKET_NAME=(str, ""), + AWS_S3_STORAGE_PUBLIC_BUCKET_NAME=(str, ""), + AWS_S3_STORAGE_STATIC_BUCKET_NAME=(str, ""), + AWS_S3_DEFAULT_ACL=(str, "private"), + AWS_S3_PUBLIC_ACL=(str, ""), + AWS_S3_STATIC_ACL=(str, ""), + AWS_S3_DEFAULT_PREFIX=(str, ""), + AWS_S3_PUBLIC_PREFIX=(str, ""), + AWS_S3_STATIC_PREFIX=(str, ""), + AWS_S3_DEFAULT_CUSTOM_DOMAIN=(str, ""), + AWS_S3_PUBLIC_CUSTOM_DOMAIN=(str, ""), + AWS_S3_STATIC_CUSTOM_DOMAIN=(str, ""), + # SES + AWS_SES_REGION_NAME=(str, ""), + AWS_SES_USE_V2=(bool, True), + AWS_SES_CONFIGURATION_SET_NAME=(str, None), + AWS_SES_AUTO_THROTTLE=(float, 0.5), + AWS_SES_REGION_ENDPOINT=(str, ""), ) +environ.Env.read_env(ENV_FILE_PATH) + +# SECURITY WARNING: don't run with debug turned on in production +DEBUG = TEMPLATE_DEBUG = env("DEBUG") +ENVIRONMENT = env.str("ENVIRONMENT") + +# SECURITY WARNING: keep the secret key used in production secret +SECRET_KEY = env.str("SECRET_KEY") +if SECRET_KEY == "secret-key" and DEBUG is False: + raise ValueError("SECRET_KEY must be set in production environment") + DATA_UPLOAD_MAX_NUMBER_FIELDS = env.int("DATA_UPLOAD_MAX_NUMBER_FIELDS") -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) -BASE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../..") +# Proxy HOST & Scheme headers +USE_X_FORWARDED_HOST = env.bool("USE_PROXY_FORWARDED_HOST", False) +if proxy_ssl_header_name := env.str("PROXY_SSL_HEADER", ""): + SECURE_PROXY_SSL_HEADER = (proxy_ssl_header_name, "https") -DEBUG = TEMPLATE_DEBUG = env("DEBUG") -ALLOWED_HOSTS: List[str] = env.list("ALLOWED_HOSTS") -CORS_ORIGIN_ALLOW_ALL = False +ALLOWED_HOSTS = env.list("ALLOWED_HOSTS") +CSRF_HEADER_NAME = "HTTP_X_XSRF_TOKEN" +CSRF_COOKIE_NAME = "XSRF-TOKEN" CSRF_TRUSTED_ORIGINS: List[str] = env.list("CSRF_TRUSTED_ORIGINS") +CORS_ALLOW_ALL_ORIGINS: bool = env.bool("CORS_ALLOW_ALL_ORIGINS") +CORS_ALLOWED_ORIGINS: List[str] = env.list("CORS_ALLOWED_ORIGINS") +CORS_ALLOWED_ORIGIN_REGEXES: List[str] = env.list("CORS_ALLOWED_ORIGIN_REGEXES") + + +# Logging +DJANGO_LOG_LEVEL = env.str("LOG_LEVEL").upper() + +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "handlers": { + "console": { + "class": "logging.StreamHandler", + }, + }, + "root": { + "handlers": ["console"], + "level": DJANGO_LOG_LEVEL, + }, +} + + +# Application definition INSTALLED_APPS = [ "jazzmin", # django apps @@ -69,6 +144,7 @@ "storages", "corsheaders", "django_q", + "whitenoise.runserver_nostatic", # project apps "utils", "buildings", @@ -92,8 +168,6 @@ SITE_ID = 1 -ENABLE_DEBUG_TOOLBAR = env.bool("ENABLE_DEBUG_TOOLBAR") - ROOT_URLCONF = "seismic_site.urls" TEMPLATES = [ @@ -115,39 +189,21 @@ WSGI_APPLICATION = "seismic_site.wsgi.application" # Database -# https://docs.djangoproject.com/en/3.2/ref/settings/#databases - -if env("ENVIRONMENT") == "test": - DATABASES = { - "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": "/tmp/test.db", # noqa - } - } -else: - DATABASES = { - "default": { - "ENGINE": "django.db.backends.postgresql", - "USER": env("DATABASE_USER"), - "PASSWORD": env("DATABASE_PASSWORD"), - "NAME": env("DATABASE_NAME"), - "HOST": env("DATABASE_HOST"), - "PORT": env("DATABASE_PORT"), - } - } +# https://docs.djangoproject.com/en/4.2/ref/settings/#databases -DEFAULT_AUTO_FIELD = "django.db.models.AutoField" +DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": env("DATABASE_NAME"), + "USER": env("DATABASE_USER"), + "PASSWORD": env("DATABASE_PASSWORD"), + "HOST": env("DATABASE_HOST"), + "PORT": env("DATABASE_PORT"), + } +} -# Password validation -# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators - -AUTH_PASSWORD_VALIDATORS = [ - {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"}, # noqa - {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, # noqa - {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, # noqa - {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, # noqa -] +# Cache CACHES = { "default": { "BACKEND": "django.core.cache.backends.db.DatabaseCache", @@ -161,8 +217,63 @@ }, } + +# Default primary key field type +# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + + +# Password validation +# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"}, # noqa + {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, # noqa + {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, # noqa + {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, # noqa +] + + +# Email settings +EMAIL_BACKEND = env.str("EMAIL_BACKEND") +EMAIL_SEND_METHOD = env.str("EMAIL_SEND_METHOD") + +DEFAULT_FROM_EMAIL = env.str("DEFAULT_FROM_EMAIL") +NO_REPLY_EMAIL = env.str("NO_REPLY_EMAIL") + +EMAIL_HOST = env.str("EMAIL_HOST") +EMAIL_PORT = env.str("EMAIL_PORT") +EMAIL_HOST_USER = env.str("EMAIL_HOST_USER") +EMAIL_HOST_PASSWORD = env.str("EMAIL_HOST_PASSWORD") +EMAIL_USE_TLS = env.bool("EMAIL_USE_TLS") + +EMAIL_FAIL_SILENTLY = env.bool("EMAIL_FAIL_SILENTLY") + +if EMAIL_BACKEND == "django_ses.SESBackend": + AWS_SES_CONFIGURATION_SET_NAME = env.str("AWS_SES_CONFIGURATION_SET_NAME") + + AWS_SES_AUTO_THROTTLE = env.float("AWS_SES_AUTO_THROTTLE", default=0.5) + AWS_SES_REGION_NAME = env.str("AWS_SES_REGION_NAME") if env("AWS_SES_REGION_NAME") else env("AWS_REGION_NAME") + AWS_SES_REGION_ENDPOINT = env.str("AWS_SES_REGION_ENDPOINT", default=f"email.{AWS_SES_REGION_NAME}.amazonaws.com") + + AWS_SES_FROM_EMAIL = DEFAULT_FROM_EMAIL + + USE_SES_V2 = env.bool("AWS_SES_USE_V2", default=True) + + if aws_access_key := env("AWS_ACCESS_KEY_ID", default=None): + AWS_ACCESS_KEY_ID = aws_access_key + AWS_SECRET_ACCESS_KEY = env("AWS_SECRET_ACCESS_KEY") +else: + EMAIL_HOST = env.str("EMAIL_HOST") + EMAIL_PORT = env.str("EMAIL_PORT") + EMAIL_HOST_USER = env.str("EMAIL_HOST_USER") + EMAIL_HOST_PASSWORD = env.str("EMAIL_HOST_PASSWORD") + EMAIL_USE_TLS = env.bool("EMAIL_USE_TLS") + + # Internationalization -# https://docs.djangoproject.com/en/3.2/topics/i18n/ +# https://docs.djangoproject.com/en/4.2/topics/i18n/ LANGUAGE_CODE = env("LANGUAGE_CODE") TIME_ZONE = "Europe/Bucharest" @@ -175,46 +286,107 @@ ("en", _("English")), ] -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/3.2/howto/static-files/ +LOCALE_PATHS = (os.path.join(BASE_DIR, "locale"),) -USE_S3 = ( - env.bool("USE_S3") and env("AWS_ACCESS_KEY_ID") and env("AWS_SECRET_ACCESS_KEY") and env("AWS_STORAGE_BUCKET_NAME") -) -if USE_S3: - # aws settings - AWS_ACCESS_KEY_ID = env("AWS_ACCESS_KEY_ID") - AWS_SECRET_ACCESS_KEY = env("AWS_SECRET_ACCESS_KEY") - AWS_STORAGE_BUCKET_NAME = env("AWS_STORAGE_BUCKET_NAME") +# Media & Static files storage +# https://docs.djangoproject.com/en/4.2/howto/static-files/ - AWS_SUBDOMAIN = env("AWS_SUBDOMAIN") +static_static_location = "static" +public_media_location = "media" +private_media_location = "media" - AWS_DEFAULT_ACL = None - AWS_S3_REGION_NAME = env("AWS_S3_REGION_NAME") - AWS_S3_CUSTOM_DOMAIN = f"{AWS_STORAGE_BUCKET_NAME}.{AWS_SUBDOMAIN}" - AWS_S3_OBJECT_PARAMETERS = {"CacheControl": "max-age=86400"} - AWS_S3_FILE_OVERWRITE = True +static_storage = "whitenoise.storage.CompressedStaticFilesStorage" +media_storage = "django.core.files.storage.FileSystemStorage" - DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage" +STATIC_URL = f"{static_static_location}/" +MEDIA_URL = f"{public_media_location}/" - # s3 public media settings - PUBLIC_MEDIA_LOCATION = "media" - MEDIA_URL = f"https://{AWS_S3_CUSTOM_DOMAIN}/{PUBLIC_MEDIA_LOCATION}/" +STATIC_ROOT = os.path.abspath(os.path.join(BASE_DIR, "static")) +MEDIA_ROOT = os.path.abspath(os.path.join(BASE_DIR, "media")) - # s3 private media settings - PRIVATE_MEDIA_LOCATION = "private" -else: - PRIVATE_FILE_STORAGE = "django.core.files.storage.FileSystemStorage" - MEDIA_URL = "/media/" - MEDIA_ROOT = os.path.join(BASE_DIR, "./public/media") +DEV_DEPENDECIES_LOCATION = "bower_components" -STATIC_URL = "/static/" -STATIC_ROOT = os.path.join(BASE_DIR, "static") -STATICFILES_STORAGE = "whitenoise.storage.CompressedStaticFilesStorage" +STATICFILES_DIRS = [ + os.path.abspath(os.path.join(DEV_DEPENDECIES_LOCATION)), + os.path.abspath(os.path.join("static_extras")), +] + +default_storage_options = {} + +public_storage_options = {} +static_storage_options = {} + +if env.bool("USE_S3"): + media_storage = "storages.backends.s3boto3.S3Boto3Storage" + static_storage = "storages.backends.s3boto3.S3StaticStorage" + + # https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html + default_storage_options = { + "bucket_name": env.str("AWS_S3_STORAGE_DEFAULT_BUCKET_NAME"), + "default_acl": env.str("AWS_S3_DEFAULT_ACL"), + "region_name": env.str("AWS_S3_REGION_NAME") or env.str("AWS_REGION_NAME"), + "object_parameters": {"CacheControl": "max-age=86400"}, + "file_overwrite": False, + "signature_version": env.str("AWS_S3_SIGNATURE_VERSION"), + "addressing_style": env.str("AWS_S3_ADDRESSING_STYLE"), + } + + # Authentication, if not using IAM roles + if aws_session_profile := env.str("AWS_S3_SESSION_PROFILE", default=None): + default_storage_options["session_profile"] = aws_session_profile + elif aws_access_key := env.str("AWS_ACCESS_KEY_ID", default=None): + default_storage_options["access_key"] = aws_access_key + default_storage_options["secret_key"] = env.str("AWS_SECRET_ACCESS_KEY") + + # Additional default configurations + if default_prefix := env.str("AWS_S3_DEFAULT_PREFIX", default=None): + default_storage_options["location"] = default_prefix + if custom_domain := env.str("AWS_S3_DEFAULT_CUSTOM_DOMAIN", default=None): + public_storage_options["custom_domain"] = custom_domain + + # Public storage options + public_storage_options = deepcopy(default_storage_options) + if public_acl := env.str("AWS_S3_PUBLIC_ACL"): + public_storage_options["default_acl"] = public_acl + if public_bucket_name := env.str("AWS_S3_STORAGE_PUBLIC_BUCKET_NAME"): + public_storage_options["bucket_name"] = public_bucket_name + if public_prefix := env.str("AWS_S3_PUBLIC_PREFIX", default=None): + public_storage_options["location"] = public_prefix + if custom_domain := env.str("AWS_S3_PUBLIC_CUSTOM_DOMAIN", default=None): + public_storage_options["custom_domain"] = custom_domain + + static_storage_options = deepcopy(public_storage_options) + if static_acl := env.str("AWS_S3_STATIC_ACL"): + static_storage_options["default_acl"] = static_acl + if static_bucket_name := env.str("AWS_S3_STORAGE_STATIC_BUCKET_NAME"): + static_storage_options["bucket_name"] = static_bucket_name + if static_prefix := env.str("AWS_S3_STATIC_PREFIX", default=None): + static_storage_options["location"] = static_prefix + if custom_domain := env.str("AWS_S3_STATIC_CUSTOM_DOMAIN", default=None): + static_storage_options["custom_domain"] = custom_domain + + +STORAGES = { + "default": { + "BACKEND": media_storage, + "LOCATION": private_media_location, + "OPTIONS": default_storage_options, + }, + "public": { + "BACKEND": media_storage, + "LOCATION": public_media_location, + "OPTIONS": public_storage_options, + }, + "staticfiles": { + "BACKEND": static_storage, + "LOCATION": static_static_location, + "OPTIONS": static_storage_options, + }, +} -LOCALE_PATHS = (os.path.join(BASE_DIR, "locale"),) +# API settings REST_FRAMEWORK = { # Use Django's standard `django.contrib.auth` permissions # or allow read-only access for unauthenticated users. diff --git a/backend/seismic_site/settings/__init__.py b/backend/seismic_site/settings/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/seismic_site/settings/development.py b/backend/seismic_site/settings/development.py deleted file mode 100644 index 044c0845..00000000 --- a/backend/seismic_site/settings/development.py +++ /dev/null @@ -1,25 +0,0 @@ -from seismic_site.settings.base import * - -DEBUG = True -ALLOWED_HOSTS = ["*"] -CORS_ORIGIN_ALLOW_ALL = True -SECRET_KEY = "secret" - -EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" - -INSTALLED_APPS = ["whitenoise.runserver_nostatic"] + INSTALLED_APPS - -# if DEBUG and env("ENABLE_DEBUG_TOOLBAR"): -# INSTALLED_APPS += ["debug_toolbar", "django_extensions"] -# MIDDLEWARE.insert(1, "debug_toolbar.middleware.DebugToolbarMiddleware") - -# def show_toolbar(_): -# return True - -# DEBUG_TOOLBAR_CONFIG = { -# "SHOW_TOOLBAR_CALLBACK": show_toolbar, -# } - -if not DEBUG: - STATIC_ROOT = os.path.join(BASE_DIR, "static") - STATICFILES_DIRS = [] diff --git a/backend/seismic_site/settings/production.py b/backend/seismic_site/settings/production.py deleted file mode 100644 index ab75b002..00000000 --- a/backend/seismic_site/settings/production.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import List - -from seismic_site.settings.base import * - -SECRET_KEY = env.str("SECRET_KEY") # noqa - -EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" -EMAIL_USE_TLS = True -EMAIL_CONFIG = env.email_url("EMAIL_URL", default="smtp://user:password@localhost:25") -vars().update(EMAIL_CONFIG) - -NO_REPLY_EMAIL = env("NO_REPLY_EMAIL") -DEFAULT_FROM_EMAIL = env("DEFAULT_FROM_EMAIL") - -STATIC_ROOT = os.path.join(BASE_DIR, "static") -STATICFILES_DIRS = [] - -CSRF_TRUSTED_ORIGINS: List[str] = env.list("CSRF_TRUSTED_ORIGINS") -CORS_ALLOWED_ORIGINS: List[str] = env.list("CORS_ALLOWED_ORIGINS") -CORS_ALLOWED_ORIGIN_REGEXES: List[str] = env.list("CORS_ALLOWED_ORIGIN_REGEXES") - -CORS_ORIGIN_ALLOW_ALL = False diff --git a/backend/seismic_site/settings/test.py b/backend/seismic_site/test_settings.py similarity index 80% rename from backend/seismic_site/settings/test.py rename to backend/seismic_site/test_settings.py index 38a42a57..a22fedaa 100644 --- a/backend/seismic_site/settings/test.py +++ b/backend/seismic_site/test_settings.py @@ -1,4 +1,4 @@ -from seismic_site.settings.base import * +from seismic_site.settings import * # noqa DEBUG = True SECRET_KEY = "test_secret" diff --git a/backend/seismic_site/urls.py b/backend/seismic_site/urls.py index e9d619c1..069f9840 100644 --- a/backend/seismic_site/urls.py +++ b/backend/seismic_site/urls.py @@ -62,10 +62,3 @@ ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) ) - -# if settings.DEBUG and settings.ENABLE_DEBUG_TOOLBAR: -# import debug_toolbar - -# urlpatterns = [path("__debug__/", include(debug_toolbar.urls))] + urlpatterns - -# urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/backend/seismic_site/wsgi.py b/backend/seismic_site/wsgi.py index dc407c7e..c57cabb2 100644 --- a/backend/seismic_site/wsgi.py +++ b/backend/seismic_site/wsgi.py @@ -2,6 +2,6 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "seismic_site.settings.development") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "seismic_site.settings") application = get_wsgi_application()