diff --git a/.idea/pySourceRootDetection.xml b/.idea/pySourceRootDetection.xml new file mode 100644 index 0000000..089a2ac --- /dev/null +++ b/.idea/pySourceRootDetection.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index 6111b3a..d34c184 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -67,6 +67,16 @@ docker compose run --rm web python manage.py check --deploy **off** in code, because defaulting them to `not DEBUG` would redirect every test request to https and break the suite anywhere `DEBUG` is unset. +### Keep DJANGO_DEBUG=False, even on the test server + +A test box is still a deployment: it is behind TLS, on a real domain, with real passkeys. +`DEBUG=True` there leaks tracebacks and settings to anyone who can reach a 500, and turns off +several of the protections in this document. Use it locally, not on a server. + +The app no longer *crashes* if you set it — `django_browser_reload` is a dev dependency that +the image installs with `--no-dev`, so settings guard on the module being importable rather +than assuming DEBUG implies it is there — but the reason to keep it off is not the crash. + ### One dependency comes from git `django-lucide` is our fork (`[tool.uv.sources]` in `pyproject.toml`, pinned by `uv.lock` to a diff --git a/rosterchief/settings.py b/rosterchief/settings.py index 46f88d9..941dffd 100644 --- a/rosterchief/settings.py +++ b/rosterchief/settings.py @@ -10,6 +10,7 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/6.0/ref/settings/ """ +from importlib.util import find_spec from pathlib import Path from decouple import Csv, config @@ -92,10 +93,17 @@ MIDDLEWARE = [ "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -# Reload the browser when templates, static files or Python change. Dev only: it -# injects a script tag into every HTML response and serves an open event stream, -# neither of which belongs in production. -if DEBUG: +# Reload the browser when templates, static files or Python change. Dev only: it injects a +# script tag into every HTML response and serves an open event stream, neither of which +# belongs in production. +# +# Guarded on the module being *importable*, not just on DEBUG: it is a dev dependency, and the +# production image installs with --no-dev. Without the guard, DEBUG=True in a deployed +# container does not merely turn on debugging — it stops the app from starting at all, with a +# ModuleNotFoundError that says nothing about the actual mistake. +BROWSER_RELOAD_AVAILABLE = find_spec("django_browser_reload") is not None + +if DEBUG and BROWSER_RELOAD_AVAILABLE: INSTALLED_APPS += ["django_browser_reload"] MIDDLEWARE += ["django_browser_reload.middleware.BrowserReloadMiddleware"] diff --git a/rosterchief/urls.py b/rosterchief/urls.py index cd1c84d..aa8fa5c 100644 --- a/rosterchief/urls.py +++ b/rosterchief/urls.py @@ -30,6 +30,11 @@ urlpatterns = [ ] if settings.DEBUG: - urlpatterns += [path("__reload__/", include("django_browser_reload.urls"))] + # Only when the app is actually installed. It is a dev dependency, and the production + # image installs with --no-dev, so DEBUG=True in a container must not take the whole + # site down over a package that is only there to refresh a browser tab. + if settings.BROWSER_RELOAD_AVAILABLE: + urlpatterns += [path("__reload__/", include("django_browser_reload.urls"))] + # Club logos are uploads: runserver has to serve MEDIA_ROOT itself. urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)