django-admin-react 0.2.0a7__tar.gz → 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/PKG-INFO +97 -52
- {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/README.md +93 -50
- {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/audit.py +16 -0
- {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/conf.py +49 -23
- {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/pwa.py +5 -1
- {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/static/admin_react/.vite/manifest.json +2 -2
- django_admin_react-1.0.0/django_admin_react/static/admin_react/assets/index-CcvXnwTT.js +8 -0
- django_admin_react-1.0.0/django_admin_react/static/admin_react/assets/index-Rr7dwEhe.css +1 -0
- {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/static/admin_react/index.html +2 -2
- {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/templates/admin_react/index.html +21 -3
- {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/urls.py +33 -11
- {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/views.py +115 -6
- {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/pyproject.toml +14 -2
- django_admin_react-0.2.0a7/django_admin_react/api/README.md +0 -31
- django_admin_react-0.2.0a7/django_admin_react/api/__init__.py +0 -5
- django_admin_react-0.2.0a7/django_admin_react/api/dates.py +0 -215
- django_admin_react-0.2.0a7/django_admin_react/api/filters.py +0 -412
- django_admin_react-0.2.0a7/django_admin_react/api/inlines.py +0 -336
- django_admin_react-0.2.0a7/django_admin_react/api/inlines_write.py +0 -241
- django_admin_react-0.2.0a7/django_admin_react/api/panels.py +0 -113
- django_admin_react-0.2.0a7/django_admin_react/api/permissions.py +0 -132
- django_admin_react-0.2.0a7/django_admin_react/api/registry.py +0 -399
- django_admin_react-0.2.0a7/django_admin_react/api/serializers.py +0 -467
- django_admin_react-0.2.0a7/django_admin_react/api/urls.py +0 -164
- django_admin_react-0.2.0a7/django_admin_react/api/views/README.md +0 -22
- django_admin_react-0.2.0a7/django_admin_react/api/views/__init__.py +0 -6
- django_admin_react-0.2.0a7/django_admin_react/api/views/actions.py +0 -196
- django_admin_react-0.2.0a7/django_admin_react/api/views/auth.py +0 -192
- django_admin_react-0.2.0a7/django_admin_react/api/views/autocomplete.py +0 -163
- django_admin_react-0.2.0a7/django_admin_react/api/views/bulk.py +0 -248
- django_admin_react-0.2.0a7/django_admin_react/api/views/create.py +0 -213
- django_admin_react-0.2.0a7/django_admin_react/api/views/create_form.py +0 -229
- django_admin_react-0.2.0a7/django_admin_react/api/views/delete_preview.py +0 -107
- django_admin_react-0.2.0a7/django_admin_react/api/views/destroy.py +0 -93
- django_admin_react-0.2.0a7/django_admin_react/api/views/detail.py +0 -447
- django_admin_react-0.2.0a7/django_admin_react/api/views/history.py +0 -166
- django_admin_react-0.2.0a7/django_admin_react/api/views/list.py +0 -468
- django_admin_react-0.2.0a7/django_admin_react/api/views/password.py +0 -161
- django_admin_react-0.2.0a7/django_admin_react/api/views/registry.py +0 -53
- django_admin_react-0.2.0a7/django_admin_react/api/views/schema.py +0 -494
- django_admin_react-0.2.0a7/django_admin_react/api/views/update.py +0 -220
- django_admin_react-0.2.0a7/django_admin_react/api/writes.py +0 -457
- django_admin_react-0.2.0a7/django_admin_react/static/admin_react/assets/index-BK8mlqvA.js +0 -8
- django_admin_react-0.2.0a7/django_admin_react/static/admin_react/assets/index-BVqO3W_r.css +0 -1
- {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/LICENSE +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/README.md +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/__init__.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/apps.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/templates/README.md +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/templates/admin_react/README.md +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/templates/admin_react/login.html +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/templates/admin_react/sw.js +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: django-admin-react
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: A drop-in React single-page admin for Django, driven entirely by ModelAdmin.
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: django,admin,react,spa,tailwind
|
|
7
7
|
Author: django-admin-react contributors
|
|
8
8
|
Requires-Python: >=3.10,<4.0
|
|
9
|
-
Classifier: Development Status ::
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
10
|
Classifier: Environment :: Web Environment
|
|
11
11
|
Classifier: Framework :: Django
|
|
12
12
|
Classifier: Framework :: Django :: 5.0
|
|
@@ -24,6 +24,8 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
24
24
|
Classifier: Topic :: Internet :: WWW/HTTP :: Site Management
|
|
25
25
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
26
|
Requires-Dist: django (>=5.0,<7.0)
|
|
27
|
+
Requires-Dist: django-admin-mcp-api (>=0.1.0a0,<0.2.0)
|
|
28
|
+
Requires-Dist: django-admin-rest-api (>=1.0.1,<2.0.0)
|
|
27
29
|
Project-URL: Documentation, https://github.com/MartinCastroAlvarez/django-admin-react#readme
|
|
28
30
|
Project-URL: Homepage, https://github.com/MartinCastroAlvarez/django-admin-react
|
|
29
31
|
Project-URL: Repository, https://github.com/MartinCastroAlvarez/django-admin-react
|
|
@@ -35,11 +37,42 @@ A drop-in **React single-page admin** for any Django 5+ project. Same
|
|
|
35
37
|
`pip install`, same `INSTALLED_APPS`, same `urls.py include()` — and
|
|
36
38
|
your `ModelAdmin` classes drive everything. No React code on your side.
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
```python
|
|
41
|
+
# settings.py
|
|
42
|
+
INSTALLED_APPS = [
|
|
43
|
+
# ...
|
|
44
|
+
"django.contrib.admin",
|
|
45
|
+
"django_admin_rest_api", # the JSON REST API (sibling package — pulled in as a dependency)
|
|
46
|
+
"django_admin_react", # this package — the React SPA on top of it
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
# urls.py
|
|
50
|
+
urlpatterns = [
|
|
51
|
+
path("admin/", admin.site.urls),
|
|
52
|
+
path("admin-react/", include("django_admin_react.urls")), # SPA + its API include
|
|
53
|
+
]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
> **Beta — v1.0.0.** Available on PyPI; the SPA + the API
|
|
57
|
+
> ([`django-admin-rest-api`](https://pypi.org/project/django-admin-rest-api/))
|
|
58
|
+
> + the MCP adapter
|
|
59
|
+
> ([`django-admin-mcp-api`](https://pypi.org/project/django-admin-mcp-api/))
|
|
60
|
+
> all share the v1 wire contract. Track progress on the
|
|
40
61
|
> [Project board](https://github.com/users/MartinCastroAlvarez/projects/3)
|
|
41
62
|
> and the [Issues list](https://github.com/MartinCastroAlvarez/django-admin-react/issues).
|
|
42
63
|
|
|
64
|
+
## Three repos, one product
|
|
65
|
+
|
|
66
|
+
The project is split into three independently-published, cross-referenced repos so each piece can be consumed on its own merits:
|
|
67
|
+
|
|
68
|
+
| Repo | PyPI | Role |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| **[`django-admin-rest-api`](https://github.com/MartinCastroAlvarez/django-admin-api)** | [`django-admin-rest-api`](https://pypi.org/project/django-admin-rest-api/) | The JSON REST API for the Django admin — same permissions, same `ModelAdmin`, no new features. The wire surface. |
|
|
71
|
+
| **`django-admin-react`** *(this repo)* | [`django-admin-react`](https://pypi.org/project/django-admin-react/) | The React SPA frontend. A **super-layer** that depends on `django-admin-rest-api` for every wire call. |
|
|
72
|
+
| **[`django-admin-mcp-api`](https://github.com/MartinCastroAlvarez/django-admin-mcp)** | [`django-admin-mcp-api`](https://pypi.org/project/django-admin-mcp-api/) | Wire-protocol-only **MCP** adapter (call, manifest, …) over `django-admin-rest-api` — lets agents reach the same `ModelAdmin`-driven REST surface, no new functionality / permissions / validation. |
|
|
73
|
+
|
|
74
|
+
The wire contract itself lives in the **API repo** (`docs/api-contract.md` there). This README is about the SPA. The migration from "self-contained" to the 3-repo split is tracked in [META #544](https://github.com/MartinCastroAlvarez/django-admin-react/issues/544).
|
|
75
|
+
|
|
43
76
|
---
|
|
44
77
|
|
|
45
78
|
## Why django-admin-react
|
|
@@ -77,8 +110,8 @@ permissions at runtime from `GET /api/v1/registry/`. Add a new
|
|
|
77
110
|
|
|
78
111
|
Real captures of the **django-admin-react SPA** rendering the bundled
|
|
79
112
|
`examples/` apps — driven entirely by each app's `ModelAdmin`.
|
|
80
|
-
|
|
81
|
-
|
|
113
|
+
Captured **manually** against a local dev server (no Playwright / Cypress /
|
|
114
|
+
e2e tooling required).
|
|
82
115
|
|
|
83
116
|
| Sign in (package login) | Registry / home |
|
|
84
117
|
| -------------------------------------------------- | ----------------------------------------------------- |
|
|
@@ -103,34 +136,16 @@ emails, account numbers, or PII).
|
|
|
103
136
|
pip install django-admin-react
|
|
104
137
|
```
|
|
105
138
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
INSTALLED_APPS
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
"django.contrib.messages",
|
|
114
|
-
"django.contrib.staticfiles",
|
|
115
|
-
"django_admin_react", # ← add this
|
|
116
|
-
# ... your own apps
|
|
117
|
-
]
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
```python
|
|
121
|
-
# urls.py
|
|
122
|
-
from django.urls import include, path
|
|
139
|
+
This pulls in the JSON API ([`django-admin-rest-api`](https://pypi.org/project/django-admin-rest-api/))
|
|
140
|
+
and the MCP adapter ([`django-admin-mcp-api`](https://pypi.org/project/django-admin-mcp-api/))
|
|
141
|
+
as transitive dependencies. The **two-line `INSTALLED_APPS` + one-line
|
|
142
|
+
URL include** at the top of this README is the *entire* integration.
|
|
143
|
+
Mount at any prefix you like — `/admin-react/`, `/staff/`,
|
|
144
|
+
`/back-office/` — just don't collide with `django.contrib.admin`'s
|
|
145
|
+
own mount.
|
|
123
146
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
# any prefix is fine:
|
|
127
|
-
# path("admin-react/", include("django_admin_react.urls")),
|
|
128
|
-
# path("staff/", include("django_admin_react.urls")),
|
|
129
|
-
]
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
That is the entire integration. Log in as a staff user → modern,
|
|
133
|
-
Tailwind-styled SPA driven by your existing `ModelAdmin` classes.
|
|
147
|
+
Log in as a staff user → modern, Tailwind-styled SPA driven by your
|
|
148
|
+
existing `ModelAdmin` classes.
|
|
134
149
|
|
|
135
150
|
The wheel ships the **pre-built React bundle**. You do **not** need
|
|
136
151
|
Node, pnpm, or any frontend toolchain to install or run.
|
|
@@ -142,35 +157,60 @@ All settings are optional. Defaults shown:
|
|
|
142
157
|
```python
|
|
143
158
|
DJANGO_ADMIN_REACT = {
|
|
144
159
|
"ADMIN_SITE": "django.contrib.admin.site", # dotted path to AdminSite instance
|
|
145
|
-
"DEFAULT_PAGE_SIZE": 25,
|
|
160
|
+
"DEFAULT_PAGE_SIZE": 25, # fallback only; the list page size derives
|
|
161
|
+
# from ModelAdmin.list_per_page (Django parity).
|
|
146
162
|
"MAX_PAGE_SIZE": 200,
|
|
147
163
|
"ENABLE_PROFILING": False,
|
|
148
164
|
|
|
149
|
-
# Branding —
|
|
150
|
-
#
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
165
|
+
# Branding — all optional. The defaults derive from your AdminSite
|
|
166
|
+
# (site_header / site_title / site_logo), so if you already branded
|
|
167
|
+
# the HTML admin you need nothing here. Rendered server-side into the
|
|
168
|
+
# SPA shell, so title + favicon are present on first paint (no FOUC).
|
|
169
|
+
"BRAND_TITLE": None, # str | None — override for BOTH brand strings.
|
|
170
|
+
"BRAND_LOGO_URL": None, # str | None — favicon + sidebar logo;
|
|
171
|
+
# falls back to AdminSite.site_logo. Absolute
|
|
172
|
+
# URL or a path under your STATIC_URL.
|
|
155
173
|
"PRIMARY_COLOR": "#2563eb", # accent for primary buttons, links, and
|
|
156
174
|
# active states. Hex only (validated);
|
|
157
175
|
# injected as the --dar-primary CSS var, so
|
|
158
176
|
# rebranding needs no React rebuild.
|
|
177
|
+
|
|
178
|
+
# Auth + API mount
|
|
179
|
+
"REACT_LOGIN": True, # bool — React-rendered login is the default;
|
|
180
|
+
# the SPA shell is served to anonymous users
|
|
181
|
+
# and posts to /api/v1/login/. Set False to
|
|
182
|
+
# opt back into the legacy admin HTML login.
|
|
183
|
+
"API_URL_PREFIX": None, # str | None — point the SPA at a separately-
|
|
184
|
+
# mounted django-admin-rest-api (e.g.
|
|
185
|
+
# "/api/api/v1/"). Default None keeps the
|
|
186
|
+
# inline include the package ships today.
|
|
159
187
|
}
|
|
160
188
|
```
|
|
161
189
|
|
|
162
190
|
#### Branding (`BRAND_TITLE` + `BRAND_LOGO_URL`)
|
|
163
191
|
|
|
164
|
-
Both default to `None
|
|
192
|
+
Both default to `None` and **derive from your `AdminSite`**, mirroring
|
|
193
|
+
Django admin — so if you already customised the HTML admin's branding,
|
|
194
|
+
you need no settings here at all.
|
|
195
|
+
|
|
196
|
+
**Sidebar header** resolution:
|
|
165
197
|
|
|
166
198
|
1. `DJANGO_ADMIN_REACT["BRAND_TITLE"]` — explicit override.
|
|
167
|
-
2. `<your AdminSite>.site_header` —
|
|
168
|
-
on a custom `AdminSite`, the SPA reuses it automatically. No need
|
|
169
|
-
to repeat yourself.
|
|
199
|
+
2. `<your AdminSite>.site_header` — reused automatically.
|
|
170
200
|
3. `"Django Admin"` — last-resort fallback.
|
|
171
201
|
|
|
202
|
+
**Browser-tab `<title>`** resolution (Django uses `site_title` for the
|
|
203
|
+
tab, `site_header` for the on-page header):
|
|
204
|
+
|
|
205
|
+
1. `DJANGO_ADMIN_REACT["BRAND_TITLE"]` — explicit override.
|
|
206
|
+
2. `<your AdminSite>.site_title` — Django's tab-title source.
|
|
207
|
+
3. `<your AdminSite>.site_header` — fallback.
|
|
208
|
+
4. `"Django Admin"` — last-resort fallback.
|
|
209
|
+
|
|
172
210
|
`BRAND_LOGO_URL` accepts either an absolute URL or a path the browser
|
|
173
|
-
can resolve under your `STATIC_URL`.
|
|
211
|
+
can resolve under your `STATIC_URL`. When unset, a `site_logo` attribute
|
|
212
|
+
on your `AdminSite` is used (Django has no logo by default, so set it as
|
|
213
|
+
a constant on your custom site). It is used both as the favicon
|
|
174
214
|
(`<link rel="icon">` in the SPA shell) and as the small logo next to
|
|
175
215
|
the brand title in the sidebar.
|
|
176
216
|
|
|
@@ -447,8 +487,9 @@ register_field_type(MoneyField, vocab_type="decimal")
|
|
|
447
487
|
# code required.
|
|
448
488
|
```
|
|
449
489
|
|
|
450
|
-
|
|
451
|
-
|
|
490
|
+
Coining a brand-new `vocab_type` (with a matching SPA widget) is an
|
|
491
|
+
**API-repo** concern — open the issue at
|
|
492
|
+
[`MartinCastroAlvarez/django-admin-api`](https://github.com/MartinCastroAlvarez/django-admin-api).
|
|
452
493
|
|
|
453
494
|
### Pre-built `get_*` overrides still work
|
|
454
495
|
|
|
@@ -495,7 +536,6 @@ and the [project board](https://github.com/users/MartinCastroAlvarez/projects/3)
|
|
|
495
536
|
✅ = shipped in the current alpha. 🟡 = not yet built (tracked). This
|
|
496
537
|
column is the **backend** capability only — for which surfaces the React
|
|
497
538
|
UI renders today, see the [frontend tracker (#160)](https://github.com/MartinCastroAlvarez/django-admin-react/issues/160).
|
|
498
|
-
[`ACCEPTANCE.md`](ACCEPTANCE.md) carries the full criterion-by-criterion list.
|
|
499
539
|
|
|
500
540
|
---
|
|
501
541
|
|
|
@@ -520,8 +560,9 @@ frontend, a script).
|
|
|
520
560
|
|
|
521
561
|
Every endpoint is **staff-only by default** (or whatever
|
|
522
562
|
`AdminSite.has_permission` returns), CSRF-required on unsafe
|
|
523
|
-
methods, and emits `Cache-Control: no-store`. Full wire contract
|
|
524
|
-
|
|
563
|
+
methods, and emits `Cache-Control: no-store`. Full wire contract
|
|
564
|
+
lives in the API repo:
|
|
565
|
+
[`MartinCastroAlvarez/django-admin-api`](https://github.com/MartinCastroAlvarez/django-admin-api).
|
|
525
566
|
|
|
526
567
|
---
|
|
527
568
|
|
|
@@ -585,6 +626,10 @@ See [`SECURITY.md`](SECURITY.md). Do **not** open a public issue.
|
|
|
585
626
|
|
|
586
627
|
## Contributing
|
|
587
628
|
|
|
588
|
-
|
|
589
|
-
[
|
|
629
|
+
Open an [Issue](https://github.com/MartinCastroAlvarez/django-admin-react/issues/new)
|
|
630
|
+
or a [Discussion](https://github.com/MartinCastroAlvarez/django-admin-react/discussions)
|
|
631
|
+
before sending a PR for anything non-trivial. **API-side contributions** (any
|
|
632
|
+
`/api/v1/...` endpoint, the wire contract, permission gates, serializer
|
|
633
|
+
denylist) go to [`MartinCastroAlvarez/django-admin-api`](https://github.com/MartinCastroAlvarez/django-admin-api)
|
|
634
|
+
— this repo owns only the **React SPA super-layer** on top.
|
|
590
635
|
|
|
@@ -4,11 +4,42 @@ A drop-in **React single-page admin** for any Django 5+ project. Same
|
|
|
4
4
|
`pip install`, same `INSTALLED_APPS`, same `urls.py include()` — and
|
|
5
5
|
your `ModelAdmin` classes drive everything. No React code on your side.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
```python
|
|
8
|
+
# settings.py
|
|
9
|
+
INSTALLED_APPS = [
|
|
10
|
+
# ...
|
|
11
|
+
"django.contrib.admin",
|
|
12
|
+
"django_admin_rest_api", # the JSON REST API (sibling package — pulled in as a dependency)
|
|
13
|
+
"django_admin_react", # this package — the React SPA on top of it
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
# urls.py
|
|
17
|
+
urlpatterns = [
|
|
18
|
+
path("admin/", admin.site.urls),
|
|
19
|
+
path("admin-react/", include("django_admin_react.urls")), # SPA + its API include
|
|
20
|
+
]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
> **Beta — v1.0.0.** Available on PyPI; the SPA + the API
|
|
24
|
+
> ([`django-admin-rest-api`](https://pypi.org/project/django-admin-rest-api/))
|
|
25
|
+
> + the MCP adapter
|
|
26
|
+
> ([`django-admin-mcp-api`](https://pypi.org/project/django-admin-mcp-api/))
|
|
27
|
+
> all share the v1 wire contract. Track progress on the
|
|
9
28
|
> [Project board](https://github.com/users/MartinCastroAlvarez/projects/3)
|
|
10
29
|
> and the [Issues list](https://github.com/MartinCastroAlvarez/django-admin-react/issues).
|
|
11
30
|
|
|
31
|
+
## Three repos, one product
|
|
32
|
+
|
|
33
|
+
The project is split into three independently-published, cross-referenced repos so each piece can be consumed on its own merits:
|
|
34
|
+
|
|
35
|
+
| Repo | PyPI | Role |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| **[`django-admin-rest-api`](https://github.com/MartinCastroAlvarez/django-admin-api)** | [`django-admin-rest-api`](https://pypi.org/project/django-admin-rest-api/) | The JSON REST API for the Django admin — same permissions, same `ModelAdmin`, no new features. The wire surface. |
|
|
38
|
+
| **`django-admin-react`** *(this repo)* | [`django-admin-react`](https://pypi.org/project/django-admin-react/) | The React SPA frontend. A **super-layer** that depends on `django-admin-rest-api` for every wire call. |
|
|
39
|
+
| **[`django-admin-mcp-api`](https://github.com/MartinCastroAlvarez/django-admin-mcp)** | [`django-admin-mcp-api`](https://pypi.org/project/django-admin-mcp-api/) | Wire-protocol-only **MCP** adapter (call, manifest, …) over `django-admin-rest-api` — lets agents reach the same `ModelAdmin`-driven REST surface, no new functionality / permissions / validation. |
|
|
40
|
+
|
|
41
|
+
The wire contract itself lives in the **API repo** (`docs/api-contract.md` there). This README is about the SPA. The migration from "self-contained" to the 3-repo split is tracked in [META #544](https://github.com/MartinCastroAlvarez/django-admin-react/issues/544).
|
|
42
|
+
|
|
12
43
|
---
|
|
13
44
|
|
|
14
45
|
## Why django-admin-react
|
|
@@ -46,8 +77,8 @@ permissions at runtime from `GET /api/v1/registry/`. Add a new
|
|
|
46
77
|
|
|
47
78
|
Real captures of the **django-admin-react SPA** rendering the bundled
|
|
48
79
|
`examples/` apps — driven entirely by each app's `ModelAdmin`.
|
|
49
|
-
|
|
50
|
-
|
|
80
|
+
Captured **manually** against a local dev server (no Playwright / Cypress /
|
|
81
|
+
e2e tooling required).
|
|
51
82
|
|
|
52
83
|
| Sign in (package login) | Registry / home |
|
|
53
84
|
| -------------------------------------------------- | ----------------------------------------------------- |
|
|
@@ -72,34 +103,16 @@ emails, account numbers, or PII).
|
|
|
72
103
|
pip install django-admin-react
|
|
73
104
|
```
|
|
74
105
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
INSTALLED_APPS
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
"django.contrib.messages",
|
|
83
|
-
"django.contrib.staticfiles",
|
|
84
|
-
"django_admin_react", # ← add this
|
|
85
|
-
# ... your own apps
|
|
86
|
-
]
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
```python
|
|
90
|
-
# urls.py
|
|
91
|
-
from django.urls import include, path
|
|
106
|
+
This pulls in the JSON API ([`django-admin-rest-api`](https://pypi.org/project/django-admin-rest-api/))
|
|
107
|
+
and the MCP adapter ([`django-admin-mcp-api`](https://pypi.org/project/django-admin-mcp-api/))
|
|
108
|
+
as transitive dependencies. The **two-line `INSTALLED_APPS` + one-line
|
|
109
|
+
URL include** at the top of this README is the *entire* integration.
|
|
110
|
+
Mount at any prefix you like — `/admin-react/`, `/staff/`,
|
|
111
|
+
`/back-office/` — just don't collide with `django.contrib.admin`'s
|
|
112
|
+
own mount.
|
|
92
113
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
# any prefix is fine:
|
|
96
|
-
# path("admin-react/", include("django_admin_react.urls")),
|
|
97
|
-
# path("staff/", include("django_admin_react.urls")),
|
|
98
|
-
]
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
That is the entire integration. Log in as a staff user → modern,
|
|
102
|
-
Tailwind-styled SPA driven by your existing `ModelAdmin` classes.
|
|
114
|
+
Log in as a staff user → modern, Tailwind-styled SPA driven by your
|
|
115
|
+
existing `ModelAdmin` classes.
|
|
103
116
|
|
|
104
117
|
The wheel ships the **pre-built React bundle**. You do **not** need
|
|
105
118
|
Node, pnpm, or any frontend toolchain to install or run.
|
|
@@ -111,35 +124,60 @@ All settings are optional. Defaults shown:
|
|
|
111
124
|
```python
|
|
112
125
|
DJANGO_ADMIN_REACT = {
|
|
113
126
|
"ADMIN_SITE": "django.contrib.admin.site", # dotted path to AdminSite instance
|
|
114
|
-
"DEFAULT_PAGE_SIZE": 25,
|
|
127
|
+
"DEFAULT_PAGE_SIZE": 25, # fallback only; the list page size derives
|
|
128
|
+
# from ModelAdmin.list_per_page (Django parity).
|
|
115
129
|
"MAX_PAGE_SIZE": 200,
|
|
116
130
|
"ENABLE_PROFILING": False,
|
|
117
131
|
|
|
118
|
-
# Branding —
|
|
119
|
-
#
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
132
|
+
# Branding — all optional. The defaults derive from your AdminSite
|
|
133
|
+
# (site_header / site_title / site_logo), so if you already branded
|
|
134
|
+
# the HTML admin you need nothing here. Rendered server-side into the
|
|
135
|
+
# SPA shell, so title + favicon are present on first paint (no FOUC).
|
|
136
|
+
"BRAND_TITLE": None, # str | None — override for BOTH brand strings.
|
|
137
|
+
"BRAND_LOGO_URL": None, # str | None — favicon + sidebar logo;
|
|
138
|
+
# falls back to AdminSite.site_logo. Absolute
|
|
139
|
+
# URL or a path under your STATIC_URL.
|
|
124
140
|
"PRIMARY_COLOR": "#2563eb", # accent for primary buttons, links, and
|
|
125
141
|
# active states. Hex only (validated);
|
|
126
142
|
# injected as the --dar-primary CSS var, so
|
|
127
143
|
# rebranding needs no React rebuild.
|
|
144
|
+
|
|
145
|
+
# Auth + API mount
|
|
146
|
+
"REACT_LOGIN": True, # bool — React-rendered login is the default;
|
|
147
|
+
# the SPA shell is served to anonymous users
|
|
148
|
+
# and posts to /api/v1/login/. Set False to
|
|
149
|
+
# opt back into the legacy admin HTML login.
|
|
150
|
+
"API_URL_PREFIX": None, # str | None — point the SPA at a separately-
|
|
151
|
+
# mounted django-admin-rest-api (e.g.
|
|
152
|
+
# "/api/api/v1/"). Default None keeps the
|
|
153
|
+
# inline include the package ships today.
|
|
128
154
|
}
|
|
129
155
|
```
|
|
130
156
|
|
|
131
157
|
#### Branding (`BRAND_TITLE` + `BRAND_LOGO_URL`)
|
|
132
158
|
|
|
133
|
-
Both default to `None
|
|
159
|
+
Both default to `None` and **derive from your `AdminSite`**, mirroring
|
|
160
|
+
Django admin — so if you already customised the HTML admin's branding,
|
|
161
|
+
you need no settings here at all.
|
|
162
|
+
|
|
163
|
+
**Sidebar header** resolution:
|
|
134
164
|
|
|
135
165
|
1. `DJANGO_ADMIN_REACT["BRAND_TITLE"]` — explicit override.
|
|
136
|
-
2. `<your AdminSite>.site_header` —
|
|
137
|
-
on a custom `AdminSite`, the SPA reuses it automatically. No need
|
|
138
|
-
to repeat yourself.
|
|
166
|
+
2. `<your AdminSite>.site_header` — reused automatically.
|
|
139
167
|
3. `"Django Admin"` — last-resort fallback.
|
|
140
168
|
|
|
169
|
+
**Browser-tab `<title>`** resolution (Django uses `site_title` for the
|
|
170
|
+
tab, `site_header` for the on-page header):
|
|
171
|
+
|
|
172
|
+
1. `DJANGO_ADMIN_REACT["BRAND_TITLE"]` — explicit override.
|
|
173
|
+
2. `<your AdminSite>.site_title` — Django's tab-title source.
|
|
174
|
+
3. `<your AdminSite>.site_header` — fallback.
|
|
175
|
+
4. `"Django Admin"` — last-resort fallback.
|
|
176
|
+
|
|
141
177
|
`BRAND_LOGO_URL` accepts either an absolute URL or a path the browser
|
|
142
|
-
can resolve under your `STATIC_URL`.
|
|
178
|
+
can resolve under your `STATIC_URL`. When unset, a `site_logo` attribute
|
|
179
|
+
on your `AdminSite` is used (Django has no logo by default, so set it as
|
|
180
|
+
a constant on your custom site). It is used both as the favicon
|
|
143
181
|
(`<link rel="icon">` in the SPA shell) and as the small logo next to
|
|
144
182
|
the brand title in the sidebar.
|
|
145
183
|
|
|
@@ -416,8 +454,9 @@ register_field_type(MoneyField, vocab_type="decimal")
|
|
|
416
454
|
# code required.
|
|
417
455
|
```
|
|
418
456
|
|
|
419
|
-
|
|
420
|
-
|
|
457
|
+
Coining a brand-new `vocab_type` (with a matching SPA widget) is an
|
|
458
|
+
**API-repo** concern — open the issue at
|
|
459
|
+
[`MartinCastroAlvarez/django-admin-api`](https://github.com/MartinCastroAlvarez/django-admin-api).
|
|
421
460
|
|
|
422
461
|
### Pre-built `get_*` overrides still work
|
|
423
462
|
|
|
@@ -464,7 +503,6 @@ and the [project board](https://github.com/users/MartinCastroAlvarez/projects/3)
|
|
|
464
503
|
✅ = shipped in the current alpha. 🟡 = not yet built (tracked). This
|
|
465
504
|
column is the **backend** capability only — for which surfaces the React
|
|
466
505
|
UI renders today, see the [frontend tracker (#160)](https://github.com/MartinCastroAlvarez/django-admin-react/issues/160).
|
|
467
|
-
[`ACCEPTANCE.md`](ACCEPTANCE.md) carries the full criterion-by-criterion list.
|
|
468
506
|
|
|
469
507
|
---
|
|
470
508
|
|
|
@@ -489,8 +527,9 @@ frontend, a script).
|
|
|
489
527
|
|
|
490
528
|
Every endpoint is **staff-only by default** (or whatever
|
|
491
529
|
`AdminSite.has_permission` returns), CSRF-required on unsafe
|
|
492
|
-
methods, and emits `Cache-Control: no-store`. Full wire contract
|
|
493
|
-
|
|
530
|
+
methods, and emits `Cache-Control: no-store`. Full wire contract
|
|
531
|
+
lives in the API repo:
|
|
532
|
+
[`MartinCastroAlvarez/django-admin-api`](https://github.com/MartinCastroAlvarez/django-admin-api).
|
|
494
533
|
|
|
495
534
|
---
|
|
496
535
|
|
|
@@ -554,5 +593,9 @@ See [`SECURITY.md`](SECURITY.md). Do **not** open a public issue.
|
|
|
554
593
|
|
|
555
594
|
## Contributing
|
|
556
595
|
|
|
557
|
-
|
|
558
|
-
[
|
|
596
|
+
Open an [Issue](https://github.com/MartinCastroAlvarez/django-admin-react/issues/new)
|
|
597
|
+
or a [Discussion](https://github.com/MartinCastroAlvarez/django-admin-react/discussions)
|
|
598
|
+
before sending a PR for anything non-trivial. **API-side contributions** (any
|
|
599
|
+
`/api/v1/...` endpoint, the wire contract, permission gates, serializer
|
|
600
|
+
denylist) go to [`MartinCastroAlvarez/django-admin-api`](https://github.com/MartinCastroAlvarez/django-admin-api)
|
|
601
|
+
— this repo owns only the **React SPA super-layer** on top.
|
|
@@ -17,6 +17,8 @@ Public surface:
|
|
|
17
17
|
|
|
18
18
|
- :func:`object_log_entries` — the ``LogEntry`` queryset for one object,
|
|
19
19
|
newest-first, with the acting user pre-fetched.
|
|
20
|
+
- :func:`recent_actions_for_user` — the most recent ``LogEntry`` rows for
|
|
21
|
+
one user (the index "Recent actions" panel), newest-first.
|
|
20
22
|
"""
|
|
21
23
|
|
|
22
24
|
from __future__ import annotations
|
|
@@ -40,3 +42,17 @@ def object_log_entries(obj: Model) -> QuerySet[LogEntry]:
|
|
|
40
42
|
.select_related("user")
|
|
41
43
|
.order_by("-action_time")
|
|
42
44
|
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def recent_actions_for_user(user_pk: str | int, limit: int) -> QuerySet[LogEntry]:
|
|
48
|
+
"""Return the most recent ``LogEntry`` rows for one user, newest first.
|
|
49
|
+
|
|
50
|
+
The user-scoped counterpart of :func:`object_log_entries`: filtered by
|
|
51
|
+
the acting user and capped at ``limit`` — exactly how Django's admin
|
|
52
|
+
index "Recent actions" panel reads the log
|
|
53
|
+
(``LogEntry.objects.filter(user=...)``). Same get_queryset-rule
|
|
54
|
+
rationale as the module docstring: LogEntry is a framework audit
|
|
55
|
+
table, not a consumer model, so it is read directly here (outside
|
|
56
|
+
``api/``) rather than via ``ModelAdmin.get_queryset``.
|
|
57
|
+
"""
|
|
58
|
+
return LogEntry.objects.filter(user__pk=user_pk).order_by("-action_time")[:limit]
|
|
@@ -22,6 +22,12 @@ from django.conf import settings as django_settings
|
|
|
22
22
|
|
|
23
23
|
DEFAULTS: dict[str, Any] = {
|
|
24
24
|
"ADMIN_SITE": "django.contrib.admin.site",
|
|
25
|
+
# The list page size derives from the model's
|
|
26
|
+
# ``ModelAdmin.list_per_page`` (Django's changelist source of truth,
|
|
27
|
+
# Rule #1 / #281), so the SPA pages like the HTML admin with no extra
|
|
28
|
+
# setting. ``DEFAULT_PAGE_SIZE`` is the fallback only when
|
|
29
|
+
# ``list_per_page`` is missing / invalid. ``MAX_PAGE_SIZE`` always caps
|
|
30
|
+
# ``?page_size`` (a DoS guard).
|
|
25
31
|
"DEFAULT_PAGE_SIZE": 25,
|
|
26
32
|
"MAX_PAGE_SIZE": 200,
|
|
27
33
|
"ENABLE_PROFILING": False,
|
|
@@ -29,16 +35,23 @@ DEFAULTS: dict[str, Any] = {
|
|
|
29
35
|
# rendered server-side into the SPA index template so the SPA
|
|
30
36
|
# picks them up on first paint (no FOUC).
|
|
31
37
|
#
|
|
32
|
-
# ``BRAND_TITLE`` —
|
|
33
|
-
# the browser
|
|
34
|
-
#
|
|
35
|
-
# ``site_header``
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
# ``
|
|
40
|
-
# ``
|
|
41
|
-
#
|
|
38
|
+
# ``BRAND_TITLE`` — optional override for *both* the sidebar
|
|
39
|
+
# header and the browser-tab title. ``None``
|
|
40
|
+
# (default) derives them from the AdminSite,
|
|
41
|
+
# mirroring Django admin: ``site_header`` →
|
|
42
|
+
# sidebar header, ``site_title`` → tab title
|
|
43
|
+
# (falling back to ``site_header``), else the
|
|
44
|
+
# package name (#281). A consumer who already
|
|
45
|
+
# set ``site_header`` / ``site_title`` on their
|
|
46
|
+
# ``AdminSite`` needs no branding setting at all.
|
|
47
|
+
# Plain text; no HTML.
|
|
48
|
+
# ``BRAND_LOGO_URL`` — optional override for the logo / favicon URL,
|
|
49
|
+
# written into the SPA's ``<link rel="icon">``.
|
|
50
|
+
# ``None`` (default) reads ``site_logo`` off the
|
|
51
|
+
# configured ``AdminSite`` if the consumer set
|
|
52
|
+
# that attribute (#281), else keeps the no-op
|
|
53
|
+
# ``data:,`` placeholder. Either an absolute URL
|
|
54
|
+
# or a path under your ``STATIC_URL``.
|
|
42
55
|
"BRAND_TITLE": None,
|
|
43
56
|
"BRAND_LOGO_URL": None,
|
|
44
57
|
# ``PRIMARY_COLOR`` — the accent color for primary buttons, links, and
|
|
@@ -49,19 +62,20 @@ DEFAULTS: dict[str, Any] = {
|
|
|
49
62
|
# falls back to this default, since the value is written into a
|
|
50
63
|
# ``<style>`` block and must not be able to inject CSS.
|
|
51
64
|
"PRIMARY_COLOR": "#2563eb",
|
|
52
|
-
# ``REACT_LOGIN`` —
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
# (
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
+
# ``REACT_LOGIN`` — React-rendered login is the **default** so the
|
|
66
|
+
# SPA fully replaces the Django admin URL surface end-to-end (owner
|
|
67
|
+
# directive 2026-05-28). ``SpaIndexView`` serves the React shell to
|
|
68
|
+
# anonymous users (with the CSRF cookie set) and the in-SPA login
|
|
69
|
+
# form POSTs to the API package's ``/api/v1/login/``. A consumer
|
|
70
|
+
# who wants the legacy HTML-admin login back can opt out with
|
|
71
|
+
# ``"REACT_LOGIN": False`` — the package's own ``<mount>/login/``
|
|
72
|
+
# endpoint is still mounted in either mode. The auth *mechanism* is
|
|
73
|
+
# unchanged in both modes (Django's ``authenticate``/``login``
|
|
74
|
+
# behind the JSON endpoint); only the UI surface differs. The
|
|
75
|
+
# shell carries no user data — serving it to anonymous users
|
|
76
|
+
# discloses nothing the static bundle wouldn't, and every data API
|
|
77
|
+
# call still returns 403 until the user authenticates.
|
|
78
|
+
"REACT_LOGIN": True,
|
|
65
79
|
# PWA (Issue #86) — all optional; sane defaults make the manifest
|
|
66
80
|
# work with zero config. See ``django_admin_react/pwa.py`` +
|
|
67
81
|
# ``docs/ux/pwa.md``.
|
|
@@ -74,6 +88,17 @@ DEFAULTS: dict[str, Any] = {
|
|
|
74
88
|
# dicts. ``None`` (default) uses the shipped
|
|
75
89
|
# 192/512/maskable set under
|
|
76
90
|
# ``static/dar/icons/``.
|
|
91
|
+
# ``API_URL_PREFIX`` — absolute URL prefix the SPA calls for every
|
|
92
|
+
# JSON request (#559). Default ``None`` keeps the inline include the
|
|
93
|
+
# package ships today (`<spa-mount>/api/v1/`), so existing consumers
|
|
94
|
+
# are unaffected. Override when the consumer mounts
|
|
95
|
+
# ``django_admin_rest_api.urls`` separately and the SPA should talk
|
|
96
|
+
# to **that** mount instead — for example
|
|
97
|
+
# ``DJANGO_ADMIN_REACT = {"API_URL_PREFIX": "/api/api/v1/"}`` lets
|
|
98
|
+
# the SPA and any other client share a single REST mount. When set,
|
|
99
|
+
# `django_admin_react.urls` skips the inline `api/v1/` include so
|
|
100
|
+
# there is no double-mount.
|
|
101
|
+
"API_URL_PREFIX": None,
|
|
77
102
|
"PWA_NAME": None,
|
|
78
103
|
"PWA_SHORT_NAME": None,
|
|
79
104
|
"PWA_ICONS": None,
|
|
@@ -96,6 +121,7 @@ class _PackageSettings:
|
|
|
96
121
|
BRAND_LOGO_URL: str | None = DEFAULTS["BRAND_LOGO_URL"]
|
|
97
122
|
PRIMARY_COLOR: str = DEFAULTS["PRIMARY_COLOR"]
|
|
98
123
|
REACT_LOGIN: bool = DEFAULTS["REACT_LOGIN"]
|
|
124
|
+
API_URL_PREFIX: str | None = DEFAULTS["API_URL_PREFIX"]
|
|
99
125
|
PWA_NAME: str | None = DEFAULTS["PWA_NAME"]
|
|
100
126
|
PWA_SHORT_NAME: str | None = DEFAULTS["PWA_SHORT_NAME"]
|
|
101
127
|
PWA_ICONS: list[dict[str, str]] | None = DEFAULTS["PWA_ICONS"]
|
|
@@ -34,7 +34,11 @@ from django.shortcuts import render
|
|
|
34
34
|
from django.views.generic import View
|
|
35
35
|
|
|
36
36
|
from django_admin_react import conf as dar_conf
|
|
37
|
-
|
|
37
|
+
|
|
38
|
+
# Re-use the API package's admin-site lookup (this repo implements no
|
|
39
|
+
# API; the registry helper lives there). The PWA only needs the active
|
|
40
|
+
# `AdminSite.name` for the manifest's start URL.
|
|
41
|
+
from django_admin_rest_api.api.registry import get_admin_site
|
|
38
42
|
|
|
39
43
|
# Theme colours keyed by the resolved colour scheme. Kept here (not in
|
|
40
44
|
# the SPA's CSS-var system) because the manifest is rendered server-side
|