feat: production settings from MDB_SETTINGS, DEBUG off, whitenoise
This commit is contained in:
6 files changed
+104
-7
No files matched your search
@@ -20,6 +20,18 @@ Run migrations and start the development server:
|
||||
uv run python manage.py migrate
|
||||
uv run python manage.py runserver
|
||||
|
||||
`mise.toml` sets `DJANGO_DEBUG=1`; without it `DEBUG` is off. Local overrides go in
|
||||
`mdbsite/local_settings.py`.
|
||||
|
||||
## Production
|
||||
|
||||
MDB runs on sega under edb-infra's `mdb` role (`roles/mdb.md` there): gunicorn from a
|
||||
release directory per commit, its settings in the file `MDB_SETTINGS` names
|
||||
(`/etc/mdb/settings.py`), which `mdbsite/settings.py` executes last. A push to `main` that
|
||||
passes the checks deploys itself while the repository variable `DEPLOY_SEGA` is `true`
|
||||
(`.gitea/workflows/deploy.yaml`). The DNS and DHCP sync runs as root on sega:
|
||||
`sudo mdb-sync`.
|
||||
|
||||
## Development tasks
|
||||
|
||||
Dump test data:
|
||||
|
||||
+18
-6
@@ -2,9 +2,10 @@ import os
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||
|
||||
DEBUG = os.getenv("DJANGO_DEBUG", True)
|
||||
# Off unless DJANGO_DEBUG says otherwise; mise.toml turns it on for development.
|
||||
DEBUG = os.getenv("DJANGO_DEBUG", "").lower() in ("1", "true", "yes")
|
||||
|
||||
ADMINS = (("System Administrator", "sysadmin@example.com"),)
|
||||
ADMINS = ("sysadmin@example.com",)
|
||||
|
||||
MANAGERS = ADMINS
|
||||
|
||||
@@ -35,10 +36,12 @@ STATICFILES_FINDERS = (
|
||||
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
|
||||
)
|
||||
|
||||
# Make this unique, and don't share it with anybody.
|
||||
# For development only: the repository is public. Production sets its own (MDB_SETTINGS).
|
||||
SECRET_KEY = "@4ps=ebofcpv8sb#y7gm896=l&l*u0u-pgo7bcdj%+(k+2lqnl"
|
||||
|
||||
MIDDLEWARE = (
|
||||
# Serves STATIC_ROOT, so production needs nothing in front but a proxy.
|
||||
"whitenoise.middleware.WhiteNoiseMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
@@ -132,7 +135,16 @@ SENTRY_ENVIRONMENT = os.getenv("SENTRY_ENVIRONENT", "dev")
|
||||
|
||||
ADMIN_SITE_NAME = "MDB"
|
||||
|
||||
# Production's settings live outside the code, in the file MDB_SETTINGS names; the mdb role
|
||||
# in edb-infra writes it. An error in it stops MDB rather than leaving the defaults above.
|
||||
if os.getenv("MDB_SETTINGS"):
|
||||
with open(os.environ["MDB_SETTINGS"]) as f:
|
||||
exec(compile(f.read(), os.environ["MDB_SETTINGS"], "exec"))
|
||||
else:
|
||||
# Development. Only a missing local_settings is skipped: a local_settings whose own
|
||||
# imports fail must not leave MDB running on the defaults.
|
||||
try:
|
||||
from .local_settings import *
|
||||
except ImportError:
|
||||
pass
|
||||
from .local_settings import * # noqa: F403
|
||||
except ModuleNotFoundError as e:
|
||||
if e.name != "mdbsite.local_settings":
|
||||
raise
|
||||
@@ -1,3 +1,7 @@
|
||||
[tools]
|
||||
python = "3.14"
|
||||
uv = "latest"
|
||||
|
||||
[env]
|
||||
# settings.py leaves DEBUG off unless this says otherwise.
|
||||
DJANGO_DEBUG = "1"
|
||||
+2
-1
@@ -15,10 +15,11 @@ dependencies = [
|
||||
"ipaddress>=1.0.23,<2.0.0",
|
||||
"requests>=2.32.5",
|
||||
"sentry-sdk>=2.55.0",
|
||||
"whitenoise>=6.12,<7",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
# Only needed on the server (LDAP is configured in local_settings.py).
|
||||
# Only needed on the server (LDAP is configured in production's settings).
|
||||
# Kept separate so CI can skip building python-ldap from source.
|
||||
prod = [
|
||||
"django-auth-ldap>=5.3.0",
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
"""How mdbsite.settings loads production's settings, in a fresh interpreter each time,
|
||||
since the settings module is imported once per process."""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
PRINT = (
|
||||
"import django, os; os.environ['DJANGO_SETTINGS_MODULE'] = 'mdbsite.settings'; "
|
||||
"django.setup(); from django.conf import settings as s; print(s.ALLOWED_HOSTS, s.DEBUG)"
|
||||
)
|
||||
|
||||
|
||||
def run(env):
|
||||
return subprocess.run(
|
||||
[sys.executable, "-c", PRINT],
|
||||
cwd=ROOT,
|
||||
env={
|
||||
k: v
|
||||
for k, v in os.environ.items()
|
||||
if k not in ("MDB_SETTINGS", "DJANGO_DEBUG")
|
||||
}
|
||||
| env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
|
||||
def test_settings_file_overrides(tmp_path):
|
||||
f = tmp_path / "settings.py"
|
||||
f.write_text("ALLOWED_HOSTS = ['mdb.example.org']\n")
|
||||
result = run({"MDB_SETTINGS": str(f)})
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert result.stdout.strip() == "['mdb.example.org'] False"
|
||||
|
||||
|
||||
def test_failing_settings_file_stops_mdb(tmp_path):
|
||||
# As on sega after the 24.04 upgrade: python-ldap would not load, and the old
|
||||
# `except ImportError: pass` left MDB running on the defaults.
|
||||
f = tmp_path / "settings.py"
|
||||
f.write_text("import a_module_that_does_not_exist\n")
|
||||
result = run({"MDB_SETTINGS": str(f)})
|
||||
assert result.returncode != 0
|
||||
assert "a_module_that_does_not_exist" in result.stderr
|
||||
|
||||
|
||||
def test_missing_settings_file_stops_mdb(tmp_path):
|
||||
result = run({"MDB_SETTINGS": str(tmp_path / "missing.py")})
|
||||
assert result.returncode != 0
|
||||
|
||||
|
||||
def test_debug_is_off_unless_asked():
|
||||
assert run({}).stdout.strip().endswith("False")
|
||||
assert run({"DJANGO_DEBUG": "1"}).stdout.strip().endswith("True")
|
||||
@@ -185,6 +185,7 @@ dependencies = [
|
||||
{ name = "ipaddress" },
|
||||
{ name = "requests" },
|
||||
{ name = "sentry-sdk" },
|
||||
{ name = "whitenoise" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
@@ -207,6 +208,7 @@ requires-dist = [
|
||||
{ name = "ipaddress", specifier = ">=1.0.23,<2.0.0" },
|
||||
{ name = "requests", specifier = ">=2.32.5" },
|
||||
{ name = "sentry-sdk", specifier = ">=2.55.0" },
|
||||
{ name = "whitenoise", specifier = ">=6.12,<7" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
@@ -404,3 +406,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "whitenoise"
|
||||
version = "6.12.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cb/2a/55b3f3a4ec326cd077c1c3defeee656b9298372a69229134d930151acd01/whitenoise-6.12.0.tar.gz", hash = "sha256:f723ebb76a112e98816ff80fcea0a6c9b8ecde835f8ddda25df7a30a3c2db6ad", size = 26841, upload-time = "2026-02-27T00:05:42.028Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/db/eb/d5583a11486211f3ebd4b385545ae787f32363d453c19fffd81106c9c138/whitenoise-6.12.0-py3-none-any.whl", hash = "sha256:fc5e8c572e33ebf24795b47b6a7da8da3c00cff2349f5b04c02f28d0cc5a3cc2", size = 20302, upload-time = "2026-02-27T00:05:40.086Z" },
|
||||
]
|
||||
Reference in new issue
Block a user