From c5bc2cccb671d193680267320028a422143e9883 Mon Sep 17 00:00:00 2001 From: Martin Castro Laminrs Date: Sun, 31 May 2026 12:24:10 +0200 Subject: [PATCH] feat(spa): read --dar-primary from AdminSite.site_primary_color (#631) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors how `BRAND_TITLE` / `BRAND_LOGO_URL` already fall through to `AdminSite.site_header` / `site_logo`: a consumer with a custom `AdminSite` subclass can now brand the whole admin (legacy + SPA) from one place — set `site_primary_color` on the AdminSite, skip the `settings.DJANGO_ADMIN_REACT["PRIMARY_COLOR"]` dance. Resolution order (per-deployment override → structural default → built-in fallback): 1. `DJANGO_ADMIN_REACT["PRIMARY_COLOR"]` setting, when set. 2. `admin_site.site_primary_color` attr. 3. `DEFAULT_PRIMARY_COLOR` (= `#2563eb`, unchanged). Every layer runs through the existing hex-regex gate, so CSS injection is impossible at any source — same trust boundary as before. DEFAULTS["PRIMARY_COLOR"] flipped from `"#2563eb"` to `None` so the resolver can distinguish "consumer set this" from "default is in effect"; the actual fallback hex now lives in a re-exported `DEFAULT_PRIMARY_COLOR` constant. Locked by three new tests in `test_spa_index.py`: - AdminSite attr is honoured when no setting is configured. - Explicit setting wins over the AdminSite attr. - Non-hex AdminSite attr can't inject CSS — falls through to default. Closes #631. Co-Authored-By: Claude Opus 4.7 (1M context) --- django_admin_react/conf.py | 21 ++++++++++--- django_admin_react/views.py | 27 +++++++++++++---- tests/test_spa_index.py | 59 +++++++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 10 deletions(-) diff --git a/django_admin_react/conf.py b/django_admin_react/conf.py index ec8819d..5a123ab 100644 --- a/django_admin_react/conf.py +++ b/django_admin_react/conf.py @@ -20,6 +20,12 @@ from django.conf import settings as django_settings +# Built-in fallback for the ``--dar-primary`` accent color when the +# consumer hasn't set ``PRIMARY_COLOR`` AND their ``AdminSite`` has no +# ``site_primary_color`` attribute. Re-exported so ``views.py`` can +# pick up the same constant instead of stringifying its own. +DEFAULT_PRIMARY_COLOR = "#2563eb" + DEFAULTS: dict[str, Any] = { "ADMIN_SITE": "django.contrib.admin.site", # The list page size derives from the model's @@ -59,9 +65,16 @@ # ``--dar-primary`` CSS variable so a consumer can brand the admin with # no React rebuild. Must be a hex color (``#rgb`` / ``#rgba`` / # ``#rrggbb`` / ``#rrggbbaa``); anything else is rejected at render and - # falls back to this default, since the value is written into a - # ``