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.
Files changed (52) hide show
  1. {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/PKG-INFO +97 -52
  2. {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/README.md +93 -50
  3. {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/audit.py +16 -0
  4. {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/conf.py +49 -23
  5. {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/pwa.py +5 -1
  6. {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/static/admin_react/.vite/manifest.json +2 -2
  7. django_admin_react-1.0.0/django_admin_react/static/admin_react/assets/index-CcvXnwTT.js +8 -0
  8. django_admin_react-1.0.0/django_admin_react/static/admin_react/assets/index-Rr7dwEhe.css +1 -0
  9. {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/static/admin_react/index.html +2 -2
  10. {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/templates/admin_react/index.html +21 -3
  11. {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/urls.py +33 -11
  12. {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/views.py +115 -6
  13. {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/pyproject.toml +14 -2
  14. django_admin_react-0.2.0a7/django_admin_react/api/README.md +0 -31
  15. django_admin_react-0.2.0a7/django_admin_react/api/__init__.py +0 -5
  16. django_admin_react-0.2.0a7/django_admin_react/api/dates.py +0 -215
  17. django_admin_react-0.2.0a7/django_admin_react/api/filters.py +0 -412
  18. django_admin_react-0.2.0a7/django_admin_react/api/inlines.py +0 -336
  19. django_admin_react-0.2.0a7/django_admin_react/api/inlines_write.py +0 -241
  20. django_admin_react-0.2.0a7/django_admin_react/api/panels.py +0 -113
  21. django_admin_react-0.2.0a7/django_admin_react/api/permissions.py +0 -132
  22. django_admin_react-0.2.0a7/django_admin_react/api/registry.py +0 -399
  23. django_admin_react-0.2.0a7/django_admin_react/api/serializers.py +0 -467
  24. django_admin_react-0.2.0a7/django_admin_react/api/urls.py +0 -164
  25. django_admin_react-0.2.0a7/django_admin_react/api/views/README.md +0 -22
  26. django_admin_react-0.2.0a7/django_admin_react/api/views/__init__.py +0 -6
  27. django_admin_react-0.2.0a7/django_admin_react/api/views/actions.py +0 -196
  28. django_admin_react-0.2.0a7/django_admin_react/api/views/auth.py +0 -192
  29. django_admin_react-0.2.0a7/django_admin_react/api/views/autocomplete.py +0 -163
  30. django_admin_react-0.2.0a7/django_admin_react/api/views/bulk.py +0 -248
  31. django_admin_react-0.2.0a7/django_admin_react/api/views/create.py +0 -213
  32. django_admin_react-0.2.0a7/django_admin_react/api/views/create_form.py +0 -229
  33. django_admin_react-0.2.0a7/django_admin_react/api/views/delete_preview.py +0 -107
  34. django_admin_react-0.2.0a7/django_admin_react/api/views/destroy.py +0 -93
  35. django_admin_react-0.2.0a7/django_admin_react/api/views/detail.py +0 -447
  36. django_admin_react-0.2.0a7/django_admin_react/api/views/history.py +0 -166
  37. django_admin_react-0.2.0a7/django_admin_react/api/views/list.py +0 -468
  38. django_admin_react-0.2.0a7/django_admin_react/api/views/password.py +0 -161
  39. django_admin_react-0.2.0a7/django_admin_react/api/views/registry.py +0 -53
  40. django_admin_react-0.2.0a7/django_admin_react/api/views/schema.py +0 -494
  41. django_admin_react-0.2.0a7/django_admin_react/api/views/update.py +0 -220
  42. django_admin_react-0.2.0a7/django_admin_react/api/writes.py +0 -457
  43. django_admin_react-0.2.0a7/django_admin_react/static/admin_react/assets/index-BK8mlqvA.js +0 -8
  44. django_admin_react-0.2.0a7/django_admin_react/static/admin_react/assets/index-BVqO3W_r.css +0 -1
  45. {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/LICENSE +0 -0
  46. {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/README.md +0 -0
  47. {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/__init__.py +0 -0
  48. {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/apps.py +0 -0
  49. {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/templates/README.md +0 -0
  50. {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/templates/admin_react/README.md +0 -0
  51. {django_admin_react-0.2.0a7 → django_admin_react-1.0.0}/django_admin_react/templates/admin_react/login.html +0 -0
  52. {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.2.0a7
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 :: 2 - Pre-Alpha
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
- > **Pre-alpha.** Available on PyPI as an alpha. Pin tightly; expect
39
- > breaking changes between alpha releases. Track progress on the
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
- Regenerate any time with `scripts/screenshots.sh` (Playwright against a
81
- throwaway example server).
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
- ```python
107
- # settings.py
108
- INSTALLED_APPS = [
109
- "django.contrib.admin",
110
- "django.contrib.auth",
111
- "django.contrib.contenttypes",
112
- "django.contrib.sessions",
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
- urlpatterns = [
125
- path("admin/", include("django_admin_react.urls")),
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 — rendered server-side into the SPA shell, so the
150
- # consumer's title + favicon are present on first paint (no FOUC).
151
- "BRAND_TITLE": None, # str | None sidebar header + browser tab.
152
- "BRAND_LOGO_URL": None, # str | None used as the favicon and
153
- # the sidebar logo. Absolute URL or a
154
- # path under your STATIC_URL.
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`. Resolution order for the title:
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` — if you already set `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`. It is used both as the favicon
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
- For coining a brand-new `vocab_type` (with a matching SPA widget)
451
- see [`docs/extensions.md`](docs/extensions.md).
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
- [`docs/api-contract.md`](docs/api-contract.md).
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
- Humans and AI agents both welcome. Start with
589
- [`CONTRIBUTING.md`](CONTRIBUTING.md).
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
- > **Pre-alpha.** Available on PyPI as an alpha. Pin tightly; expect
8
- > breaking changes between alpha releases. Track progress on the
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
- Regenerate any time with `scripts/screenshots.sh` (Playwright against a
50
- throwaway example server).
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
- ```python
76
- # settings.py
77
- INSTALLED_APPS = [
78
- "django.contrib.admin",
79
- "django.contrib.auth",
80
- "django.contrib.contenttypes",
81
- "django.contrib.sessions",
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
- urlpatterns = [
94
- path("admin/", include("django_admin_react.urls")),
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 — rendered server-side into the SPA shell, so the
119
- # consumer's title + favicon are present on first paint (no FOUC).
120
- "BRAND_TITLE": None, # str | None sidebar header + browser tab.
121
- "BRAND_LOGO_URL": None, # str | None used as the favicon and
122
- # the sidebar logo. Absolute URL or a
123
- # path under your STATIC_URL.
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`. Resolution order for the title:
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` — if you already set `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`. It is used both as the favicon
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
- For coining a brand-new `vocab_type` (with a matching SPA widget)
420
- see [`docs/extensions.md`](docs/extensions.md).
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
- [`docs/api-contract.md`](docs/api-contract.md).
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
- Humans and AI agents both welcome. Start with
558
- [`CONTRIBUTING.md`](CONTRIBUTING.md).
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`` — string shown in the sidebar header *and*
33
- # the browser tab title. ``None`` (default)
34
- # falls back to the configured AdminSite's
35
- # ``site_header`` if set, else the package
36
- # name. Plain text; no HTML.
37
- # ``BRAND_LOGO_URL`` — URL to a square logo / favicon. Written
38
- # into the SPA's ``<link rel="icon">``.
39
- # ``None`` (default) keeps the no-op
40
- # ``data:,`` placeholder. Either an absolute
41
- # URL or a path under your ``STATIC_URL``.
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`` — opt-in React-rendered login (Issue #167).
53
- # Default ``False`` keeps today's behavior: ``SpaIndexView``
54
- # redirects anonymous / unauthorized users to Django's HTML login
55
- # (or the package's own ``<mount>/login/`` page). When ``True``,
56
- # the SPA shell is served to anonymous users (with the CSRF cookie
57
- # set) so the React app can render its own login form, which POSTs
58
- # to ``/api/v1/login/``. The auth *mechanism* is unchanged — still
59
- # Django's ``authenticate``/``login`` behind the JSON endpoint
60
- # (`api/views/auth.py`); only the UI surface differs. The shell
61
- # carries no user data, so serving it to anonymous users discloses
62
- # nothing the static bundle wouldn't, and every data API call still
63
- # returns 403 until the user authenticates.
64
- "REACT_LOGIN": False,
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
- from django_admin_react.api.registry import get_admin_site
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
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "index.html": {
3
- "file": "assets/index-BK8mlqvA.js",
3
+ "file": "assets/index-CcvXnwTT.js",
4
4
  "name": "index",
5
5
  "src": "index.html",
6
6
  "isEntry": true,
7
7
  "css": [
8
- "assets/index-BVqO3W_r.css"
8
+ "assets/index-Rr7dwEhe.css"
9
9
  ]
10
10
  }
11
11
  }