diff --git a/.gitignore b/.gitignore index 60f057f1..4d687a37 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ **/.venv *console.log .coverage -.env +.env* .idea/ .mypy_cache/**/* .pytest_cache/**/* diff --git a/config/settings/_base.py b/config/settings/_base.py index 18d2d0c4..42f5a8c0 100644 --- a/config/settings/_base.py +++ b/config/settings/_base.py @@ -1,21 +1,29 @@ import contextlib import socket +from functools import partial +from pathlib import Path -import environs +from django.utils.crypto import get_random_string -env = environs.Env() +from django_envtools import Env """ Django settings for config project. For more information on this file, see -https://docs.djangoproject.com/en/4.2/topics/settings/ +https://docs.djangoproject.com/en/dev/topics/settings/ For the full list of settings and their values, see -https://docs.djangoproject.com/en/4.2/ref/settings/ +https://docs.djangoproject.com/en/dev/ref/settings/ """ -BASE_DIR = environs.Path(__file__).parent.parent.parent # type: ignore + +get_secret_key = partial(get_random_string, length=50, allowed_chars="abcdefghijklmnopqrstuvwxyz0123456789!@%^&*-_=") + + +BASE_DIR = Path(__file__).parent.parent.parent # type: ignore + +env = Env() READ_DOT_ENV_FILE = env.bool("READ_DOT_ENV_FILE", default=True) @@ -23,19 +31,41 @@ env.read_env(str(BASE_DIR.joinpath(".env"))) # Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ +# See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = env("SECRET_KEY") +SECRET_KEY = env.str( + "SECRET_KEY", + help_text="Django's secret key, see" + "https://docs.djangoproject.com/en/dev/ref/settings/#secret-key for more information", + initial_func=get_secret_key, +) # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = env.bool("DEBUG", default=False) +DEBUG = env.bool("DEBUG", default=False, initial="on", help_text="Set to `on` to enable debugging") + +ALLOWED_HOSTS = env.list( + "ALLOWED_HOSTS", + default=[], + help_text="List of allowed hosts (e.g., `127.0.0.1,example.com`), see " + "https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts for more information", +) -ALLOWED_HOSTS: list[str] = env.list("ALLOWED_HOSTS", default=[]) -INTERNAL_IPS = env.list("INTERNAL_IPS", default=["127.0.0.1"]) +INTERNAL_IPS = env.list( + "INTERNAL_IPS", + default=["127.0.0.1"], + initial="127.0.0.1,0.0.0.0", + help_text="IPs that are allowed to use debug() (e.g., `127.0.0.1,example.com`), see " + "https://docs.djangoproject.com/en/dev/ref/settings/#internal-ips for more information", +) # Get the IP to use for Django Debug Toolbar when developing with docker -if env.bool("USE_DOCKER", default=False) is True: +if ( + env.bool( + "USE_DOCKER", default=False, help_text="Boolean used to add docker's internal ip to the `INTERNAL_IPS` setting" + ) + is True +): ip = socket.gethostbyname(socket.gethostname()) INTERNAL_IPS += [ip[:-1] + "1"] @@ -56,6 +86,7 @@ "allauth.account", "crispy_forms", "crispy_bootstrap5", + "django_envtools", "storages", ] @@ -92,23 +123,35 @@ }, ] -WSGI_APPLICATION = env("WSGI_APPLICATION", default="config.wsgi.application") -DB_SSL_REQUIRED = env.bool("DB_SSL_REQUIRED", default=not DEBUG) +WSGI_APPLICATION = env.str( + "WSGI_APPLICATION", + default="config.wsgi.application", + help_text="WSGI application callable, see https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application for " + "more information", +) +DB_SSL_REQUIRED = env.bool( + "DB_SSL_REQUIRED", + default=not DEBUG, + help_text="Set to `on` to require SSL for database connections, default is `off` when DEBUG is `on`", +) -# Database -# See https://github.com/jacobian/dj-database-url for more examples DATABASES = { "default": env.dj_db_url( - "DATABASE_URL", default="postgres://postgres@postgres/postgres", ssl_require=DB_SSL_REQUIRED + "DATABASE_URL", + default="postgres://postgres@postgres/postgres", + ssl_require=DB_SSL_REQUIRED, + initial="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}", + help_text="Database URL for the default database, see https://github.com/jacobian/dj-database-url for " + "more examples", ) } # Custom User Model -# https://docs.djangoproject.com/en/4.2/topics/auth/customizing/#substituting-a-custom-user-model +# https://docs.djangoproject.com/en/dev/topics/auth/customizing/#substituting-a-custom-user-model AUTH_USER_MODEL = "accounts.User" # Password validation -# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators +# https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { @@ -127,7 +170,7 @@ # Internationalization -# https://docs.djangoproject.com/en/4.2/topics/i18n/ +# https://docs.djangoproject.com/en/dev/topics/i18n/ LANGUAGE_CODE = "en-us" @@ -140,30 +183,42 @@ # Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/4.2/howto/static-files/ +# https://docs.djangoproject.com/en/dev/howto/static-files/ STATICFILES_FINDERS = ( "django.contrib.staticfiles.finders.FileSystemFinder", "django.contrib.staticfiles.finders.AppDirectoriesFinder", ) +DEFAULT_FILE_STORAGE = env.str( + "DEFAULT_FILE_STORAGE", + default="django.core.files.storage.FileSystemStorage", + help_text="Default file storage backend, see https://docs.djangoproject.com/en/dev/ref/settings/#storages for " + "more information", +) +STATICFILES_STORAGE = env.str( + "DEFAULT_FILE_STORAGE", + default="django.core.files.storage.FileSystemStorage", + help_text="Default file storage for staticfiles, see https://docs.djangoproject.com/en/dev/ref/settings/#storages " + "for more information", +) STORAGES = { "default": { - "BACKEND": env("DEFAULT_FILE_STORAGE", default="django.core.files.storage.FileSystemStorage"), + "BACKEND": DEFAULT_FILE_STORAGE, }, "staticfiles": { - "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", + "BACKEND": STATICFILES_STORAGE, }, } if STORAGES["default"]["BACKEND"].endswith("MediaS3Storage") is True: - STORAGES["staticfiles"]["BACKEND"] = env("STATICFILES_STORAGE") - 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") + STORAGES["staticfiles"]["BACKEND"] = STATICFILES_STORAGE + AWS_ACCESS_KEY_ID = env.str("AWS_ACCESS_KEY_ID", help_text="AWS Access Key ID for S3 storage") + AWS_SECRET_ACCESS_KEY = env.str("AWS_SECRET_ACCESS_KEY", help_text="AWS Secret Access Key for S3 storage") + AWS_STORAGE_BUCKET_NAME = env.str("AWS_STORAGE_BUCKET_NAME", help_text="AWS S3 Bucket Name for storage") AWS_DEFAULT_ACL = "public-read" - AWS_S3_REGION = env("AWS_S3_REGION", default="us-east-2") + AWS_S3_REGION = env.str("AWS_S3_REGION", default="us-east-1", help_text="AWS S3 Region for storage") AWS_S3_CUSTOM_DOMAIN = f"s3.{AWS_S3_REGION}.amazonaws.com/{AWS_STORAGE_BUCKET_NAME}" AWS_S3_OBJECT_PARAMETERS = {"CacheControl": "max-age=86400"} STATIC_URL = f"https://{AWS_S3_CUSTOM_DOMAIN}/static/" @@ -181,13 +236,21 @@ STATIC_URL = "/public/static/" # Default primary key field type -# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field +# https://docs.djangoproject.com/en/dev/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" # CACHE SETTINGS -# Redis scheme docs: https://redis-py.readthedocs.io/en/stable/connections.html#redis.connection.ConnectionPool.from_url -REDIS_URL = env("REDIS_URL", "redis://redis:6379/0") -REDIS_PREFIX = env("REDIS_PREFIX", default="") +REDIS_URL = env.str( + "REDIS_URL", + default="redis://redis:6379/0", + help_text="URL used to connect to Redis, see https://docs.djangoproject.com/en/dev/ref/settings/#location for " + "more information", +) +REDIS_PREFIX = env.str( + "REDIS_PREFIX", + default="", + help_text="Prefix for all Redis keys, useful to avoid key collisions in shared Redis instances", +) CACHES = { "default": { "BACKEND": "django.core.cache.backends.redis.RedisCache", @@ -232,10 +295,10 @@ ACCOUNT_SIGNUP_OPEN = False ACCOUNT_SHOW_POST_LOGIN_MESSAGE = False -# See https://github.com/migonzalvar/dj-email-url for more examples on how to set the EMAIL_URL email = env.dj_email_url( "EMAIL_URL", default="smtp://skroob@planetspaceball.com:12345@smtp.planetspaceball.com:587/?ssl=True&_default_from_email=President%20Skroob%20%3Cskroob@planetspaceball.com%3E", + help_text="Email URL for sending emails, see https://github.com/migonzalvar/dj-email-url for more examples", ) DEFAULT_FROM_EMAIL = email["DEFAULT_FROM_EMAIL"] EMAIL_HOST = email["EMAIL_HOST"] @@ -266,7 +329,7 @@ def log_format() -> str: log_level = "WARNING" -IS_DEBUG_LOGGING_ON = env.bool("IS_DEBUG_LOGGING_ON", default=False) +IS_DEBUG_LOGGING_ON = env.bool("IS_DEBUG_LOGGING_ON", default=False, help_text="Set to `on` to enable debug logging") if IS_DEBUG_LOGGING_ON is True: log_level = "DEBUG" @@ -305,4 +368,6 @@ def log_format() -> str: MAINTENANCE_MODE_STATE_BACKEND = "maintenance_mode.backends.CacheBackend" MAINTENANCE_MODE_STATE_BACKEND_FALLBACK_VALUE = True -VITE_DEV_MODE = env.bool("VITE_DEV_MODE", default=DEBUG) +VITE_DEV_MODE = env.bool( + "VITE_DEV_MODE", default=DEBUG, help_text="Set to `on` to enable Vite development mode for HMR in the browser" +) diff --git a/pyproject.toml b/pyproject.toml index d8a6a49f..4538e3c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ dependencies = [ "django-alive~=2.0", "django-allauth~=65.0", "django-crispy-forms~=2.2", + "django-envtools~=0.2", "django-maintenance-mode~=0.19", "django-storages~=1.8", "environs[django]~=14.1", diff --git a/uv.lock b/uv.lock index 5354b63e..95917af0 100644 --- a/uv.lock +++ b/uv.lock @@ -429,6 +429,7 @@ dependencies = [ { name = "django-alive" }, { name = "django-allauth" }, { name = "django-crispy-forms" }, + { name = "django-envtools" }, { name = "django-maintenance-mode" }, { name = "django-storages" }, { name = "environs", extra = ["django"] }, @@ -468,6 +469,7 @@ requires-dist = [ { name = "django-alive", specifier = "~=2.0" }, { name = "django-allauth", specifier = "~=65.0" }, { name = "django-crispy-forms", specifier = "~=2.2" }, + { name = "django-envtools", specifier = "~=0.2" }, { name = "django-maintenance-mode", specifier = "~=0.19" }, { name = "django-storages", specifier = "~=1.8" }, { name = "environs", extras = ["django"], specifier = "~=14.1" }, @@ -532,6 +534,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2f/33/2036a472eedfbe49240dffea965242b3f444de4ea4fbeceb82ccea33a2ce/django_debug_toolbar-4.4.6-py3-none-any.whl", hash = "sha256:3beb671c9ec44ffb817fad2780667f172bd1c067dbcabad6268ce39a81335f45", size = 229621, upload-time = "2024-07-10T13:18:35.71Z" }, ] +[[package]] +name = "django-envtools" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "environs", extra = ["django"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4f/73/3c64364d62e692b13fd758ead7e83dc8a5f97db2364e5d20f571b3b561bc/django_envtools-0.2.0.tar.gz", hash = "sha256:2ae85797f34856f8b0da004fc9c6ee6e53d262f2f04ab8482fc8d1c97e52a45c", size = 32956, upload-time = "2025-06-07T19:42:36.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/52/9a1371b0eb470ad13860698486c01d830aefd948d47119bb7c2676d3fd21/django_envtools-0.2.0-py3-none-any.whl", hash = "sha256:b26d0834d00ec4cf8da810d62bb12750c28325b09ad1c570197ed33a7cddcebe", size = 7867, upload-time = "2025-06-07T19:42:35.098Z" }, +] + [[package]] name = "django-maintenance-mode" version = "0.22.0"