diff --git a/pyproject.toml b/pyproject.toml index d394a250..869437d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,24 +8,25 @@ license = { text = "AGPL-3.0-only" } classifiers = ["Private :: Do Not Upload"] requires-python = ">=3.12,<3.13" dependencies = [ - "django==3.2.25", - "pillow>=11.0.0", "cssmin==0.2.0", "cssselect==1.2.0", "defusedxml>=0.7.1", + "dj-database-url>=2.2.0", "django-allauth==0.51", "django-bootstrap-form==3.4", "django-compressor==3.1", - "django-environ==0.9.0", "django-invitations==1.9.3", "django-tastypie==0.14.7", + "django==3.2.25", "feedparser>=6.0.11", "httpx>=0.28.1", "jsmin==3.0.1", "lxml>=5.3.0", + "pillow>=11.0.0", "pytz>=2024.2", "sentry-sdk>=2.19.2", "setuptools>=75.6.0", # Needed by django-bootstrap-form + "typenv>=0.2.0", ] [project.optional-dependencies] diff --git a/src/comics/settings.py b/src/comics/settings.py index e5985510..66dc2757 100644 --- a/src/comics/settings.py +++ b/src/comics/settings.py @@ -1,8 +1,11 @@ from pathlib import Path +from urllib.parse import urlsplit -import environ +import dj_database_url import sentry_sdk +from django.core.management.utils import get_random_secret_key from sentry_sdk.integrations.django import DjangoIntegration +from typenv import Env # Paths ROOT_DIR = Path(__file__).parents[2] @@ -11,27 +14,31 @@ # Environment variables -env = environ.Env() -env.read_env(ROOT_DIR / ".env") +env = Env() +env.read_env(".env") #: The Django secret key SECRET_KEY = env.str( "DJANGO_SECRET_KEY", - default="django-insecure-xe=$sh@*pt#46(_q(zy89quq&n1rut&o9b(qk1+o(^7exqqnj=", + default=get_random_secret_key(), ) #: Debug mode. Keep off in production. -DEBUG = env.bool("DJANGO_DEBUG", default=False) +DEBUG = env.bool( + "DJANGO_DEBUG", + default=False, +) #: Site admins ADMINS = [] -if "DJANGO_ADMIN" in env: - ADMINS.append(("Site admin", env.str("DJANGO_ADMIN"))) +if admin_email := env.str("DJANGO_ADMIN", default=None): + ADMINS.append(("Site admin", admin_email)) #: Default from email DEFAULT_FROM_EMAIL = env.str( - "DJANGO_DEFAULT_FROM_EMAIL", default="webmaster@example.com" + "DJANGO_DEFAULT_FROM_EMAIL", + default="webmaster@example.com", ) SQLITE_FILE = str(RUN_DIR / "db.sqlite3") @@ -40,7 +47,12 @@ #: Database settings. You will want to change this for production. See the #: Django docs for details. DATABASES = { - "default": env.db("DATABASE_URL", default=SQLITE_URL), + "default": dj_database_url.parse( + env.str( + "DATABASE_URL", + default=SQLITE_URL, + ), + ), } DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" @@ -55,18 +67,30 @@ USE_TZ = True #: Path on disk to where downloaded media will be stored and served from -MEDIA_ROOT = env.str("DJANGO_MEDIA_ROOT", default=str(RUN_DIR / "media")) +MEDIA_ROOT = env.str( + "DJANGO_MEDIA_ROOT", + default=str(RUN_DIR / "media"), +) Path(MEDIA_ROOT).mkdir(parents=True, exist_ok=True) #: URL to where downloaded media will be stored and served from -MEDIA_URL = env.str("DJANGO_MEDIA_URL", default="/media/") +MEDIA_URL = env.str( + "DJANGO_MEDIA_URL", + default="/media/", +) #: Path on disk to where static files will be served from -STATIC_ROOT = env.str("DJANGO_STATIC_ROOT", default=str(RUN_DIR / "static")) +STATIC_ROOT = env.str( + "DJANGO_STATIC_ROOT", + default=str(RUN_DIR / "static"), +) Path(STATIC_ROOT).mkdir(parents=True, exist_ok=True) #: URL to where static files will be served from -STATIC_URL = env.str("DJANGO_STATIC_URL", default="/static/") +STATIC_URL = env.str( + "DJANGO_STATIC_URL", + default="/static/", +) STATICFILES_DIRS = [ str(SRC_DIR / "comics" / "static"), @@ -164,11 +188,14 @@ "OPTIONS": {"MAX_ENTRIES": 1000}, } } -if "CACHE_URL" in env: - CACHES["default"] = env.cache( - "CACHE_URL", backend="django.core.cache.backends.memcached.PyMemcacheCache" - ) - SESSION_ENGINE = "django.contrib.sessions.backends.cached_db" +if cache_url := env.str("CACHE_URL", default=None): + parts = urlsplit(cache_url) + if parts.scheme == "memcache": + CACHES["default"] = { + "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache", + "LOCATION": parts.netloc, + } + SESSION_ENGINE = "django.contrib.sessions.backends.cached_db" CACHE_MIDDLEWARE_SECONDS = 300 CACHE_MIDDLEWARE_KEY_PREFIX = "comics" @@ -185,7 +212,7 @@ WSGI_APPLICATION = "comics.wsgi.application" -ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default="*") +ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=["*"]) # ### djang-debug-toolbar settings diff --git a/uv.lock b/uv.lock index 6f84ad38..23d6c01a 100644 --- a/uv.lock +++ b/uv.lock @@ -118,11 +118,11 @@ dependencies = [ { name = "cssmin" }, { name = "cssselect" }, { name = "defusedxml" }, + { name = "dj-database-url" }, { name = "django" }, { name = "django-allauth" }, { name = "django-bootstrap-form" }, { name = "django-compressor" }, - { name = "django-environ" }, { name = "django-invitations" }, { name = "django-tastypie" }, { name = "feedparser" }, @@ -133,6 +133,7 @@ dependencies = [ { name = "pytz" }, { name = "sentry-sdk" }, { name = "setuptools" }, + { name = "typenv" }, ] [package.optional-dependencies] @@ -179,11 +180,11 @@ requires-dist = [ { name = "cssmin", specifier = "==0.2.0" }, { name = "cssselect", specifier = "==1.2.0" }, { name = "defusedxml", specifier = ">=0.7.1" }, + { name = "dj-database-url", specifier = ">=2.2.0" }, { name = "django", specifier = "==3.2.25" }, { name = "django-allauth", specifier = "==0.51" }, { name = "django-bootstrap-form", specifier = "==3.4" }, { name = "django-compressor", specifier = "==3.1" }, - { name = "django-environ", specifier = "==0.9.0" }, { name = "django-invitations", specifier = "==1.9.3" }, { name = "django-tastypie", specifier = "==0.14.7" }, { name = "feedparser", specifier = ">=6.0.11" }, @@ -198,6 +199,7 @@ requires-dist = [ { name = "pyyaml", marker = "extra == 'api'", specifier = ">=6.0.1,<7" }, { name = "sentry-sdk", specifier = ">=2.19.2" }, { name = "setuptools", specifier = ">=75.6.0" }, + { name = "typenv", specifier = ">=0.2.0" }, ] [package.metadata.requires-dev] @@ -303,6 +305,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, ] +[[package]] +name = "dj-database-url" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/48/51f398a47c197f584b3445de886986ddc40de18bdb6e168f325a8d2c7bca/dj_database_url-2.2.0.tar.gz", hash = "sha256:9f9b05058ddf888f1e6f840048b8d705ff9395e3b52a07165daa3d8b9360551b", size = 10874 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/9a/13f173c716d07283661e821f7e1624d0904835151b4f099687455dbef81e/dj_database_url-2.2.0-py3-none-any.whl", hash = "sha256:3e792567b0aa9a4884860af05fe2aa4968071ad351e033b6db632f97ac6db9de", size = 7764 }, +] + [[package]] name = "django" version = "3.2.25" @@ -378,15 +393,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/c3/fd81e84ce86904019112428c690a06ab19c66742c917a898dcf9da6ae166/django_debug_toolbar-3.8.1-py3-none-any.whl", hash = "sha256:879f8a4672d41621c06a4d322dcffa630fc4df056cada6e417ed01db0e5e0478", size = 221071 }, ] -[[package]] -name = "django-environ" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bf/5a/0325376b74279c89cfbdfd134d044094dfce91c1e8f21ea4088f43f56f68/django-environ-0.9.0.tar.gz", hash = "sha256:bff5381533056328c9ac02f71790bd5bf1cea81b1beeb648f28b81c9e83e0a21", size = 49835 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/60/5a/f231f5d3e15217cdf0164019cff5c4a7e5ba6bb5fd502759f94972786e17/django_environ-0.9.0-py2.py3-none-any.whl", hash = "sha256:f21a5ef8cc603da1870bbf9a09b7e5577ab5f6da451b843dbcc721a7bca6b3d9", size = 17918 }, -] - [[package]] name = "django-extensions" version = "3.2.3" @@ -775,6 +781,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, ] +[[package]] +name = "python-dotenv" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, +] + [[package]] name = "python-mimeparse" version = "2.0.0" @@ -995,6 +1010,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/09/92/07ad2cbe8fbf80d2ba7a0cf246e1f62e48bbd8d59fb08e4c38bc94d9ca9d/tox_uv-1.16.2-py3-none-any.whl", hash = "sha256:57d4c00cfd7973f2d3ebb1ca2ecbf18599c47aa080253355a742de44430cc58b", size = 13693 }, ] +[[package]] +name = "typenv" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dotenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/62/3da6e385d6e400e72edbee43b1b11c0c446d84db175a4a9174fefb463c50/typenv-0.2.0.tar.gz", hash = "sha256:6fd1e6964fe5bb4a9aefcd28557ee520913b51208d1afff33598fc677bc855fb", size = 10027 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/bb/1ca83f95829c1e0a36a8a1e9845b8d240f77547c8802654149e59eb6ce1c/typenv-0.2.0-py3-none-any.whl", hash = "sha256:6cd2a00a0ce4ff03f8645f1b6da1f64e60a1f260a732ba7a716c17e3b0946915", size = 8340 }, +] + [[package]] name = "types-pytz" version = "2024.2.0.20241221"