Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,28 @@ concurrency:

jobs:
backend:
name: Backend (pytest)
name: Backend (pytest py${{ matrix.python }} / Django ${{ matrix.django }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Django 4.2 LTS is supported through April 2026 (#622). Add it
# alongside 5.x and 6.0 so the package keeps working for the
# majority of installed Django by deployment count.
python: ["3.12"]
django: ["4.2", "5.2"]
# 4.2 added Python 3.13 support in 4.2.16 but pip may resolve to
# an earlier 4.2.x; skip 3.13 + 4.2 conservatively. (We only
# exercise py3.12 here today; the matrix is set up for an easy
# future expansion.)
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Set up Python
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: "3.12"
python-version: ${{ matrix.python }}

- name: Install Poetry
# Poetry is pinned to the version that generated poetry.lock (see
Expand All @@ -72,6 +84,9 @@ jobs:
- name: Install dependencies (locked)
run: poetry install --no-interaction

- name: Pin Django to the matrix version
run: poetry run pip install "django~=${{ matrix.django }}.0"

# pytest with coverage (per pyproject `addopts`), including
# tests/test_security.py. `filterwarnings = ["error"]` means a new
# warning fails the run.
Expand Down
40 changes: 36 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,12 @@ DJANGO_ADMIN_REACT = {
"BRAND_LOGO_URL": None, # str | None — favicon + sidebar logo;
# falls back to AdminSite.site_logo. Absolute
# URL or a path under your STATIC_URL.
"PRIMARY_COLOR": "#2563eb", # accent for primary buttons, links, and
# active states. Hex only (validated);
# injected as the --dar-primary CSS var, so
"PRIMARY_COLOR": None, # accent for primary buttons, links, and
# active states (#437 / #631). Hex only
# (validated). None → reads
# `site_primary_color` off your AdminSite;
# fallback default is "#2563eb". Injected
# as the --dar-primary CSS var, so
# rebranding needs no React rebuild.

# Auth + API mount
Expand Down Expand Up @@ -212,10 +215,39 @@ Both values are written into the SPA index template as standard
reads them at boot, so the first paint already carries the consumer's
brand. No flash of the package's defaults.

#### Accent colour (`PRIMARY_COLOR` + `AdminSite.site_primary_color`)

`PRIMARY_COLOR` defaults to `None` so a custom `AdminSite` subclass can
own the brand colour the same way it owns `site_header` / `site_logo`
(#631). Resolution order — explicit setting wins, AdminSite is the
structural default, built-in fallback last:

1. `DJANGO_ADMIN_REACT["PRIMARY_COLOR"]` — explicit per-deployment override.
2. `<your AdminSite>.site_primary_color` — convention attribute on your
custom `AdminSite` subclass (Django has no such attribute by default;
add it as a constant alongside `site_header` / `site_logo`).
3. `"#2563eb"` — the package's last-resort fallback.

Every layer runs through a strict hex-colour regex (`#rgb` / `#rgba` /
`#rrggbb` / `#rrggbbaa`) before being injected into the SPA's `<style>`
block, so a non-hex value at any layer falls through to the next — CSS
injection is impossible at any source.

```python
# myproject/admin.py
from django.contrib.admin import AdminSite

class AcmeAdminSite(AdminSite):
site_header = "Acme"
site_title = "Acme Admin"
site_logo = "/static/acme/logo.svg"
site_primary_color = "#10b981" # emerald — used by legacy admin AND the SPA
```

### Requirements

- **Python**: 3.10+
- **Django**: 5.0, 5.1, 5.2, 6.0 (and any later 6.x)
- **Django**: 4.2 LTS, 5.0, 5.1, 5.2 LTS, 6.0 (and any later 6.x)
- **Database**: anything Django supports — the package is ORM-only,
no direct SQL.
- **Auth**: Django's built-in session + CSRF. Works with custom
Expand Down
20 changes: 10 additions & 10 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 13 additions & 10 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "django-admin-react"
version = "1.4.13"
version = "1.5.0"
description = "A drop-in React single-page admin for Django, driven entirely by ModelAdmin."
authors = ["django-admin-react contributors"]
license = "MIT"
Expand All @@ -13,6 +13,7 @@ classifiers = [
"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
"Framework :: Django",
"Framework :: Django :: 4.2",
"Framework :: Django :: 5.0",
"Framework :: Django :: 5.1",
"Framework :: Django :: 5.2",
Expand All @@ -39,26 +40,28 @@ include = [

[tool.poetry.dependencies]
python = "^3.10"
# Django 5.0 → 5.2 LTS → 6.x. Bump the upper bound when 7.0 ships and
# we've verified the package still passes its test matrix on it.
django = ">=5.0,<7.0"
# Django 4.2 LTS → 5.2 LTS → 6.x (#622). Bump the upper bound when 7.0
# ships and we've verified the package still passes its test matrix on
# it. The dependent ``django-admin-rest-api`` package also supports 4.2
# (since its 1.1.0).
django = ">=4.2,<7.0"
# The JSON REST API surface (every list / detail / create / update /
# delete / action / history / autocomplete endpoint) lives in this
# sibling package — same `ModelAdmin` source of truth, same permissions,
# no new features. **This repo implements no API of its own**; it is the
# React SPA super-layer over `django-admin-rest-api`. The package's URLs
# are included by `django_admin_react.urls`, and consumers add
# `"django_admin_rest_api"` to `INSTALLED_APPS` alongside this package.
django-admin-rest-api = "^1.0.6"
django-admin-rest-api = "^1.1.0"
# `django-admin-mcp-api` — MCP-protocol adapter over the same REST API
# so agents reach the SAME `ModelAdmin`-driven surface. Wire-protocol-only
# layer; adds NO new functionality, permissions, or validation.
# Constraint widened in v1.4.4 (#605) from the original `^0.1.0a0`
# (which caps at `<0.2.0`) to `>=1.0.0,<2.0.0` so consumers can pin
# the stable `1.x` mcp line. The wire contract is unchanged across
# the 0.1.0a0 → 1.x bump; the version bump is a stability signal,
# not an API break.
django-admin-mcp-api = ">=1.0.0,<2.0.0"
# to `>=1.0.0,<2.0.0`; raised again in v1.5.0 (#622) to `>=1.1.0,<2.0.0`
# so a consumer on Django 4.2 actually gets a Django-4.2-compatible MCP
# (the 1.0.x line pins ``django>=5.0``, which would refuse to install
# alongside Django 4.2).
django-admin-mcp-api = ">=1.1.0,<2.0.0"

[tool.poetry.group.dev.dependencies]
# Dependabot #5: pytest's tmpdir handled symlinks unsafely
Expand Down
Loading