django-admin-react 0.1.0a1__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.1.0a1/LICENSE +21 -0
- django_admin_react-0.1.0a1/PKG-INFO +237 -0
- django_admin_react-0.1.0a1/README.md +206 -0
- django_admin_react-0.1.0a1/django_admin_react/README.md +57 -0
- django_admin_react-0.1.0a1/django_admin_react/__init__.py +10 -0
- django_admin_react-0.1.0a1/django_admin_react/api/README.md +31 -0
- django_admin_react-0.1.0a1/django_admin_react/api/__init__.py +5 -0
- django_admin_react-0.1.0a1/django_admin_react/api/permissions.py +80 -0
- django_admin_react-0.1.0a1/django_admin_react/api/registry.py +200 -0
- django_admin_react-0.1.0a1/django_admin_react/api/serializers.py +183 -0
- django_admin_react-0.1.0a1/django_admin_react/api/urls.py +84 -0
- django_admin_react-0.1.0a1/django_admin_react/api/views/README.md +21 -0
- django_admin_react-0.1.0a1/django_admin_react/api/views/__init__.py +6 -0
- django_admin_react-0.1.0a1/django_admin_react/api/views/create.py +141 -0
- django_admin_react-0.1.0a1/django_admin_react/api/views/destroy.py +89 -0
- django_admin_react-0.1.0a1/django_admin_react/api/views/detail.py +283 -0
- django_admin_react-0.1.0a1/django_admin_react/api/views/list.py +271 -0
- django_admin_react-0.1.0a1/django_admin_react/api/views/registry.py +51 -0
- django_admin_react-0.1.0a1/django_admin_react/api/views/update.py +121 -0
- django_admin_react-0.1.0a1/django_admin_react/api/writes.py +325 -0
- django_admin_react-0.1.0a1/django_admin_react/apps.py +27 -0
- django_admin_react-0.1.0a1/django_admin_react/conf.py +77 -0
- django_admin_react-0.1.0a1/django_admin_react/static/admin_react/.vite/manifest.json +11 -0
- django_admin_react-0.1.0a1/django_admin_react/static/admin_react/assets/index-CKxeWYBA.css +1 -0
- django_admin_react-0.1.0a1/django_admin_react/static/admin_react/assets/index-itk7hrnq.js +68 -0
- django_admin_react-0.1.0a1/django_admin_react/static/admin_react/assets/index-itk7hrnq.js.map +1 -0
- django_admin_react-0.1.0a1/django_admin_react/static/admin_react/index.html +13 -0
- django_admin_react-0.1.0a1/django_admin_react/templates/admin_react/README.md +10 -0
- django_admin_react-0.1.0a1/django_admin_react/templates/admin_react/index.html +33 -0
- django_admin_react-0.1.0a1/django_admin_react/urls.py +39 -0
- django_admin_react-0.1.0a1/django_admin_react/views.py +136 -0
- django_admin_react-0.1.0a1/pyproject.toml +215 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Martín Castro Álvarez and django-admin-react contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: django-admin-react
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: A drop-in React single-page admin for Django, driven entirely by ModelAdmin.
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: django,admin,react,spa,tailwind
|
|
7
|
+
Author: django-admin-react contributors
|
|
8
|
+
Requires-Python: >=3.10,<4.0
|
|
9
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
10
|
+
Classifier: Environment :: Web Environment
|
|
11
|
+
Classifier: Framework :: Django
|
|
12
|
+
Classifier: Framework :: Django :: 5.0
|
|
13
|
+
Classifier: Framework :: Django :: 5.1
|
|
14
|
+
Classifier: Framework :: Django :: 5.2
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Site Management
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Requires-Dist: django (>=5.0,<6.0)
|
|
26
|
+
Project-URL: Documentation, https://github.com/MartinCastroAlvarez/django-admin-react#readme
|
|
27
|
+
Project-URL: Homepage, https://github.com/MartinCastroAlvarez/django-admin-react
|
|
28
|
+
Project-URL: Repository, https://github.com/MartinCastroAlvarez/django-admin-react
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# django-admin-react
|
|
32
|
+
|
|
33
|
+
A drop-in **React single-page admin** for any Django 5+ project. Same
|
|
34
|
+
`pip install`, same `INSTALLED_APPS`, same `urls.py include()` — and
|
|
35
|
+
your `ModelAdmin` classes drive everything. No React code on your side.
|
|
36
|
+
|
|
37
|
+
> **Pre-alpha.** Not yet on PyPI. Install from source today (see below);
|
|
38
|
+
> a `pip install django-admin-react` release will follow. Track progress
|
|
39
|
+
> in [`PROGRESS.md`](PROGRESS.md).
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Install in your Django project
|
|
44
|
+
|
|
45
|
+
### Option A — once on PyPI (planned)
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install django-admin-react
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
# settings.py
|
|
53
|
+
INSTALLED_APPS = [
|
|
54
|
+
"django.contrib.admin",
|
|
55
|
+
"django.contrib.auth",
|
|
56
|
+
"django.contrib.contenttypes",
|
|
57
|
+
"django.contrib.sessions",
|
|
58
|
+
"django.contrib.messages",
|
|
59
|
+
"django.contrib.staticfiles",
|
|
60
|
+
"django_admin_react", # ← add this
|
|
61
|
+
# ... your own apps
|
|
62
|
+
]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
# urls.py
|
|
67
|
+
from django.urls import include, path
|
|
68
|
+
|
|
69
|
+
urlpatterns = [
|
|
70
|
+
path("admin/", include("django_admin_react.urls")),
|
|
71
|
+
# any prefix is fine:
|
|
72
|
+
# path("admin-react/", include("django_admin_react.urls")),
|
|
73
|
+
# path("staff/", include("django_admin_react.urls")),
|
|
74
|
+
]
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
That is the entire integration. Log in as a staff user → modern,
|
|
78
|
+
Tailwind-styled SPA driven by your existing `ModelAdmin` classes.
|
|
79
|
+
|
|
80
|
+
The wheel ships the **pre-built React bundle**. You do **not** need
|
|
81
|
+
Node, pnpm, or any frontend toolchain to install or run.
|
|
82
|
+
|
|
83
|
+
### Option B — install from source (today)
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
git clone https://github.com/MartinCastroAlvarez/django-admin-react.git
|
|
87
|
+
cd django-admin-react
|
|
88
|
+
./scripts/build.sh # builds the SPA + Python wheel
|
|
89
|
+
pip install dist/*.whl # install into your venv
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Then wire it into your project as in Option A.
|
|
93
|
+
|
|
94
|
+
### Optional configuration
|
|
95
|
+
|
|
96
|
+
All settings are optional. Defaults shown:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
DJANGO_ADMIN_REACT = {
|
|
100
|
+
"ADMIN_SITE": "django.contrib.admin.site", # dotted path
|
|
101
|
+
"DEFAULT_PAGE_SIZE": 25,
|
|
102
|
+
"MAX_PAGE_SIZE": 200,
|
|
103
|
+
"ENABLE_PROFILING": False,
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Extend without writing React
|
|
110
|
+
|
|
111
|
+
Just edit your `ModelAdmin` — the UI follows. No JS required.
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
# yourapp/admin.py
|
|
115
|
+
from django.contrib import admin
|
|
116
|
+
from .models import Invoice
|
|
117
|
+
|
|
118
|
+
@admin.register(Invoice)
|
|
119
|
+
class InvoiceAdmin(admin.ModelAdmin):
|
|
120
|
+
list_display = ("number", "customer", "total", "issued_at")
|
|
121
|
+
search_fields = ("number", "customer__name")
|
|
122
|
+
readonly_fields = ("total",) # ← UI hides the input
|
|
123
|
+
list_filter = ("status",) # ← UI surfaces the filter
|
|
124
|
+
|
|
125
|
+
def has_add_permission(self, request):
|
|
126
|
+
return request.user.has_perm("billing.create_invoice")
|
|
127
|
+
# ↑
|
|
128
|
+
# UI hides the Add button automatically when this returns False.
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Permissions, querysets, search, validation — all your `ModelAdmin`'s
|
|
132
|
+
answers, surfaced verbatim in the React UI. The package never invents
|
|
133
|
+
its own permission model.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Screenshots
|
|
138
|
+
|
|
139
|
+
> **Captured live** with `scripts/screenshots.sh` from
|
|
140
|
+
> `examples/project/` — real pixels, not mockups. The React SPA
|
|
141
|
+
> shell lands in PR #6 / #7; until then, the images below show the
|
|
142
|
+
> **legacy HTML admin** running against the example apps — i.e.,
|
|
143
|
+
> the experience `django-admin-react` modernises. Once the SPA
|
|
144
|
+
> renders, this section regenerates from the same script. Tracked
|
|
145
|
+
> in [`PROGRESS.md`](PROGRESS.md).
|
|
146
|
+
|
|
147
|
+
| Login (the entry door) | Admin index (legacy) |
|
|
148
|
+
| ------------------------------------------------- | ------------------------------------------------------- |
|
|
149
|
+
|  |  |
|
|
150
|
+
|
|
151
|
+
| Library / Authors — list view | Library / Author — detail view |
|
|
152
|
+
| -------------------------------------------------------------- | --------------------------------------------------------------- |
|
|
153
|
+
|  |  |
|
|
154
|
+
|
|
155
|
+
| Mobile (375 px) | API: `GET /api/v1/registry/` JSON |
|
|
156
|
+
| --------------------------------------------------------------------- | ------------------------------------------------------- |
|
|
157
|
+
|  |  |
|
|
158
|
+
|
|
159
|
+
Every screenshot uses a deterministic synthetic seed (no real
|
|
160
|
+
people, accounts, or PII). Regenerate with:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
bash scripts/screenshots.sh
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The script boots a one-off SQLite DB, seeds fixtures, captures via
|
|
167
|
+
Playwright (Chromium), and writes the six PNGs above.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## What you get
|
|
172
|
+
|
|
173
|
+
- **Plug-and-play**: works with any `ModelAdmin` you already have.
|
|
174
|
+
- **Shared auth**: Django sessions, CSRF, staff permissions. No new
|
|
175
|
+
user model, no parallel permission system.
|
|
176
|
+
- **Responsive, modern UI**: React + Tailwind + React Query, served
|
|
177
|
+
as a single bundle from `django_admin_react/static/admin_react/`.
|
|
178
|
+
- **Extensible by editing `ModelAdmin`**, not React.
|
|
179
|
+
- **Configurable URL prefix** — `/admin/`, `/admin-react/`, anywhere.
|
|
180
|
+
- **Conservative & secure-by-default** — never exposes models the
|
|
181
|
+
admin doesn't already expose; never writes fields the admin form
|
|
182
|
+
excludes; CSRF on every unsafe method.
|
|
183
|
+
- **Boring + auditable** — no parallel permission system, no
|
|
184
|
+
client-side workarounds for backend permissions, conservative
|
|
185
|
+
serializer with `str()` fallback.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Documentation map
|
|
190
|
+
|
|
191
|
+
| Doc | Topic |
|
|
192
|
+
| ---------------------------------------------------------------- | -------------------------------------------- |
|
|
193
|
+
| [`PROGRESS.md`](PROGRESS.md) | Live status of merged PRs / what's coming |
|
|
194
|
+
| [`ARCHITECTURE.md`](ARCHITECTURE.md) | Design contract |
|
|
195
|
+
| [`PLAN.md`](PLAN.md) | v1 scope, PR sequence, risks |
|
|
196
|
+
| [`SECURITY.md`](SECURITY.md) | Threat model, guarantees, required tests |
|
|
197
|
+
| [`CONTRIBUTING.md`](CONTRIBUTING.md) | Dev workflow |
|
|
198
|
+
| [`CLAUDE.md`](CLAUDE.md) | Rules for AI contributors |
|
|
199
|
+
| [`docs/api-contract.md`](docs/api-contract.md) | Full API spec |
|
|
200
|
+
| [`docs/data-layer.md`](docs/data-layer.md) | `@dar/data` design (SWR + debounce) |
|
|
201
|
+
| [`docs/agents/pr-workflow.md`](docs/agents/pr-workflow.md) | How agents send / review / merge PRs |
|
|
202
|
+
| [`docs/agents/autonomy-policy.md`](docs/agents/autonomy-policy.md) | What's auto-mergeable vs human-only |
|
|
203
|
+
| [`docs/agents/decisions.md`](docs/agents/decisions.md) | Decisions log |
|
|
204
|
+
| [`scripts/README.md`](scripts/README.md) | `lint.sh` / `build.sh` / `deploy.sh` |
|
|
205
|
+
| [`examples/README.md`](examples/README.md) | Demo Django apps |
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Developer scripts
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
./scripts/lint.sh # ruff + black + isort + flake8 + pylint + mypy + bandit + prettier + tsc
|
|
213
|
+
./scripts/build.sh # pnpm install + vite build + poetry build (wheel ships pre-built SPA)
|
|
214
|
+
./scripts/deploy.sh # poetry publish to PyPI (requires POETRY_PYPI_TOKEN_PYPI)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
The Merger runs `./scripts/lint.sh` before every merge — there is
|
|
218
|
+
no CI in this repo by design.
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## License
|
|
223
|
+
|
|
224
|
+
MIT — see [`LICENSE`](LICENSE).
|
|
225
|
+
|
|
226
|
+
## Security
|
|
227
|
+
|
|
228
|
+
Please report security issues privately. See
|
|
229
|
+
[`SECURITY.md`](SECURITY.md) §4. Do **not** open a public issue.
|
|
230
|
+
|
|
231
|
+
## Contributing
|
|
232
|
+
|
|
233
|
+
Humans and AI agents both welcome. Start with
|
|
234
|
+
[`CONTRIBUTING.md`](CONTRIBUTING.md). AI agents must also read
|
|
235
|
+
[`CLAUDE.md`](CLAUDE.md), [`docs/agents/pr-workflow.md`](docs/agents/pr-workflow.md),
|
|
236
|
+
and [`docs/agents/autonomy-policy.md`](docs/agents/autonomy-policy.md).
|
|
237
|
+
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# django-admin-react
|
|
2
|
+
|
|
3
|
+
A drop-in **React single-page admin** for any Django 5+ project. Same
|
|
4
|
+
`pip install`, same `INSTALLED_APPS`, same `urls.py include()` — and
|
|
5
|
+
your `ModelAdmin` classes drive everything. No React code on your side.
|
|
6
|
+
|
|
7
|
+
> **Pre-alpha.** Not yet on PyPI. Install from source today (see below);
|
|
8
|
+
> a `pip install django-admin-react` release will follow. Track progress
|
|
9
|
+
> in [`PROGRESS.md`](PROGRESS.md).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Install in your Django project
|
|
14
|
+
|
|
15
|
+
### Option A — once on PyPI (planned)
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install django-admin-react
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
# settings.py
|
|
23
|
+
INSTALLED_APPS = [
|
|
24
|
+
"django.contrib.admin",
|
|
25
|
+
"django.contrib.auth",
|
|
26
|
+
"django.contrib.contenttypes",
|
|
27
|
+
"django.contrib.sessions",
|
|
28
|
+
"django.contrib.messages",
|
|
29
|
+
"django.contrib.staticfiles",
|
|
30
|
+
"django_admin_react", # ← add this
|
|
31
|
+
# ... your own apps
|
|
32
|
+
]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
# urls.py
|
|
37
|
+
from django.urls import include, path
|
|
38
|
+
|
|
39
|
+
urlpatterns = [
|
|
40
|
+
path("admin/", include("django_admin_react.urls")),
|
|
41
|
+
# any prefix is fine:
|
|
42
|
+
# path("admin-react/", include("django_admin_react.urls")),
|
|
43
|
+
# path("staff/", include("django_admin_react.urls")),
|
|
44
|
+
]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
That is the entire integration. Log in as a staff user → modern,
|
|
48
|
+
Tailwind-styled SPA driven by your existing `ModelAdmin` classes.
|
|
49
|
+
|
|
50
|
+
The wheel ships the **pre-built React bundle**. You do **not** need
|
|
51
|
+
Node, pnpm, or any frontend toolchain to install or run.
|
|
52
|
+
|
|
53
|
+
### Option B — install from source (today)
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
git clone https://github.com/MartinCastroAlvarez/django-admin-react.git
|
|
57
|
+
cd django-admin-react
|
|
58
|
+
./scripts/build.sh # builds the SPA + Python wheel
|
|
59
|
+
pip install dist/*.whl # install into your venv
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Then wire it into your project as in Option A.
|
|
63
|
+
|
|
64
|
+
### Optional configuration
|
|
65
|
+
|
|
66
|
+
All settings are optional. Defaults shown:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
DJANGO_ADMIN_REACT = {
|
|
70
|
+
"ADMIN_SITE": "django.contrib.admin.site", # dotted path
|
|
71
|
+
"DEFAULT_PAGE_SIZE": 25,
|
|
72
|
+
"MAX_PAGE_SIZE": 200,
|
|
73
|
+
"ENABLE_PROFILING": False,
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Extend without writing React
|
|
80
|
+
|
|
81
|
+
Just edit your `ModelAdmin` — the UI follows. No JS required.
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
# yourapp/admin.py
|
|
85
|
+
from django.contrib import admin
|
|
86
|
+
from .models import Invoice
|
|
87
|
+
|
|
88
|
+
@admin.register(Invoice)
|
|
89
|
+
class InvoiceAdmin(admin.ModelAdmin):
|
|
90
|
+
list_display = ("number", "customer", "total", "issued_at")
|
|
91
|
+
search_fields = ("number", "customer__name")
|
|
92
|
+
readonly_fields = ("total",) # ← UI hides the input
|
|
93
|
+
list_filter = ("status",) # ← UI surfaces the filter
|
|
94
|
+
|
|
95
|
+
def has_add_permission(self, request):
|
|
96
|
+
return request.user.has_perm("billing.create_invoice")
|
|
97
|
+
# ↑
|
|
98
|
+
# UI hides the Add button automatically when this returns False.
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Permissions, querysets, search, validation — all your `ModelAdmin`'s
|
|
102
|
+
answers, surfaced verbatim in the React UI. The package never invents
|
|
103
|
+
its own permission model.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Screenshots
|
|
108
|
+
|
|
109
|
+
> **Captured live** with `scripts/screenshots.sh` from
|
|
110
|
+
> `examples/project/` — real pixels, not mockups. The React SPA
|
|
111
|
+
> shell lands in PR #6 / #7; until then, the images below show the
|
|
112
|
+
> **legacy HTML admin** running against the example apps — i.e.,
|
|
113
|
+
> the experience `django-admin-react` modernises. Once the SPA
|
|
114
|
+
> renders, this section regenerates from the same script. Tracked
|
|
115
|
+
> in [`PROGRESS.md`](PROGRESS.md).
|
|
116
|
+
|
|
117
|
+
| Login (the entry door) | Admin index (legacy) |
|
|
118
|
+
| ------------------------------------------------- | ------------------------------------------------------- |
|
|
119
|
+
|  |  |
|
|
120
|
+
|
|
121
|
+
| Library / Authors — list view | Library / Author — detail view |
|
|
122
|
+
| -------------------------------------------------------------- | --------------------------------------------------------------- |
|
|
123
|
+
|  |  |
|
|
124
|
+
|
|
125
|
+
| Mobile (375 px) | API: `GET /api/v1/registry/` JSON |
|
|
126
|
+
| --------------------------------------------------------------------- | ------------------------------------------------------- |
|
|
127
|
+
|  |  |
|
|
128
|
+
|
|
129
|
+
Every screenshot uses a deterministic synthetic seed (no real
|
|
130
|
+
people, accounts, or PII). Regenerate with:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
bash scripts/screenshots.sh
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The script boots a one-off SQLite DB, seeds fixtures, captures via
|
|
137
|
+
Playwright (Chromium), and writes the six PNGs above.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## What you get
|
|
142
|
+
|
|
143
|
+
- **Plug-and-play**: works with any `ModelAdmin` you already have.
|
|
144
|
+
- **Shared auth**: Django sessions, CSRF, staff permissions. No new
|
|
145
|
+
user model, no parallel permission system.
|
|
146
|
+
- **Responsive, modern UI**: React + Tailwind + React Query, served
|
|
147
|
+
as a single bundle from `django_admin_react/static/admin_react/`.
|
|
148
|
+
- **Extensible by editing `ModelAdmin`**, not React.
|
|
149
|
+
- **Configurable URL prefix** — `/admin/`, `/admin-react/`, anywhere.
|
|
150
|
+
- **Conservative & secure-by-default** — never exposes models the
|
|
151
|
+
admin doesn't already expose; never writes fields the admin form
|
|
152
|
+
excludes; CSRF on every unsafe method.
|
|
153
|
+
- **Boring + auditable** — no parallel permission system, no
|
|
154
|
+
client-side workarounds for backend permissions, conservative
|
|
155
|
+
serializer with `str()` fallback.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Documentation map
|
|
160
|
+
|
|
161
|
+
| Doc | Topic |
|
|
162
|
+
| ---------------------------------------------------------------- | -------------------------------------------- |
|
|
163
|
+
| [`PROGRESS.md`](PROGRESS.md) | Live status of merged PRs / what's coming |
|
|
164
|
+
| [`ARCHITECTURE.md`](ARCHITECTURE.md) | Design contract |
|
|
165
|
+
| [`PLAN.md`](PLAN.md) | v1 scope, PR sequence, risks |
|
|
166
|
+
| [`SECURITY.md`](SECURITY.md) | Threat model, guarantees, required tests |
|
|
167
|
+
| [`CONTRIBUTING.md`](CONTRIBUTING.md) | Dev workflow |
|
|
168
|
+
| [`CLAUDE.md`](CLAUDE.md) | Rules for AI contributors |
|
|
169
|
+
| [`docs/api-contract.md`](docs/api-contract.md) | Full API spec |
|
|
170
|
+
| [`docs/data-layer.md`](docs/data-layer.md) | `@dar/data` design (SWR + debounce) |
|
|
171
|
+
| [`docs/agents/pr-workflow.md`](docs/agents/pr-workflow.md) | How agents send / review / merge PRs |
|
|
172
|
+
| [`docs/agents/autonomy-policy.md`](docs/agents/autonomy-policy.md) | What's auto-mergeable vs human-only |
|
|
173
|
+
| [`docs/agents/decisions.md`](docs/agents/decisions.md) | Decisions log |
|
|
174
|
+
| [`scripts/README.md`](scripts/README.md) | `lint.sh` / `build.sh` / `deploy.sh` |
|
|
175
|
+
| [`examples/README.md`](examples/README.md) | Demo Django apps |
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Developer scripts
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
./scripts/lint.sh # ruff + black + isort + flake8 + pylint + mypy + bandit + prettier + tsc
|
|
183
|
+
./scripts/build.sh # pnpm install + vite build + poetry build (wheel ships pre-built SPA)
|
|
184
|
+
./scripts/deploy.sh # poetry publish to PyPI (requires POETRY_PYPI_TOKEN_PYPI)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The Merger runs `./scripts/lint.sh` before every merge — there is
|
|
188
|
+
no CI in this repo by design.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
MIT — see [`LICENSE`](LICENSE).
|
|
195
|
+
|
|
196
|
+
## Security
|
|
197
|
+
|
|
198
|
+
Please report security issues privately. See
|
|
199
|
+
[`SECURITY.md`](SECURITY.md) §4. Do **not** open a public issue.
|
|
200
|
+
|
|
201
|
+
## Contributing
|
|
202
|
+
|
|
203
|
+
Humans and AI agents both welcome. Start with
|
|
204
|
+
[`CONTRIBUTING.md`](CONTRIBUTING.md). AI agents must also read
|
|
205
|
+
[`CLAUDE.md`](CLAUDE.md), [`docs/agents/pr-workflow.md`](docs/agents/pr-workflow.md),
|
|
206
|
+
and [`docs/agents/autonomy-policy.md`](docs/agents/autonomy-policy.md).
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# django_admin_react/ — the Python package
|
|
2
|
+
|
|
3
|
+
This directory **is** the artifact published to PyPI. Everything else
|
|
4
|
+
in the repository supports it.
|
|
5
|
+
|
|
6
|
+
## Layout
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
django_admin_react/
|
|
10
|
+
├── __init__.py # __version__ and AppConfig entry point
|
|
11
|
+
├── apps.py # Django AppConfig
|
|
12
|
+
├── conf.py # settings.DJANGO_ADMIN_REACT lazy loader
|
|
13
|
+
├── urls.py # mountable URL patterns (api/v1/ + SPA fallback)
|
|
14
|
+
├── views.py # SPA index view
|
|
15
|
+
├── api/
|
|
16
|
+
│ ├── __init__.py
|
|
17
|
+
│ ├── urls.py # API URL patterns
|
|
18
|
+
│ ├── permissions.py # IsStaffUser + ModelAdmin gates
|
|
19
|
+
│ ├── registry.py # AdminSite introspection helpers
|
|
20
|
+
│ ├── serializers.py # conservative field serialization
|
|
21
|
+
│ └── views/ # one file per endpoint (registry/list/detail/...)
|
|
22
|
+
├── templates/admin_react/
|
|
23
|
+
│ └── index.html # SPA shell template
|
|
24
|
+
└── static/admin_react/ # built React bundle drops here
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Rules
|
|
28
|
+
|
|
29
|
+
- This package may **not** import from `frontend/`, `examples/`, or
|
|
30
|
+
`tests/`. It is self-contained.
|
|
31
|
+
- It may **not** import a consumer's models. All access goes through
|
|
32
|
+
`admin.site._registry` and `ModelAdmin` methods.
|
|
33
|
+
- It may **not** hardcode example model names (`Account`, `Book`,
|
|
34
|
+
`Transaction`, …). If you see one, fix it.
|
|
35
|
+
- Settings live under a single dict `settings.DJANGO_ADMIN_REACT`. Read
|
|
36
|
+
them through `django_admin_react.conf`, never via
|
|
37
|
+
`django.conf.settings.DJANGO_ADMIN_REACT` directly.
|
|
38
|
+
|
|
39
|
+
## Implementation status
|
|
40
|
+
|
|
41
|
+
| File | Status | Lands in PR |
|
|
42
|
+
| ----------------------------- | ------------------ | ----------- |
|
|
43
|
+
| `__init__.py`, `apps.py` | Stub | #1 |
|
|
44
|
+
| `conf.py` | Stub w/ defaults | #1 → flesh out in #2 |
|
|
45
|
+
| `urls.py` | Routes wired, views stubbed | #1 → #6 |
|
|
46
|
+
| `views.py` | SpaIndexView stub | #6 |
|
|
47
|
+
| `api/permissions.py` | Empty | #3 |
|
|
48
|
+
| `api/registry.py` | Empty | #3 |
|
|
49
|
+
| `api/serializers.py` | Empty | #4 |
|
|
50
|
+
| `api/views/registry.py` | Empty | #3 |
|
|
51
|
+
| `api/views/list.py` | Empty | #4 |
|
|
52
|
+
| `api/views/detail.py` | Empty | #4 |
|
|
53
|
+
| `api/views/create.py` | Empty | #5 |
|
|
54
|
+
| `api/views/update.py` | Empty | #5 |
|
|
55
|
+
| `api/views/delete.py` | Empty | #5 |
|
|
56
|
+
| `templates/admin_react/index.html` | Placeholder | #6 |
|
|
57
|
+
| `static/admin_react/` | `.gitkeep` | populated at build time |
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""django-admin-react — a React single-page admin for Django.
|
|
2
|
+
|
|
3
|
+
The actual implementation lands across PRs #2-#7. This package is
|
|
4
|
+
currently a skeleton that defines the layout and the Django AppConfig
|
|
5
|
+
entry point only. See `ARCHITECTURE.md` for the design contract.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.0.0"
|
|
9
|
+
|
|
10
|
+
default_app_config = "django_admin_react.apps.DjangoAdminReactConfig"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# django_admin_react/api/
|
|
2
|
+
|
|
3
|
+
JSON API package. See [`/docs/api-contract.md`](../../docs/api-contract.md)
|
|
4
|
+
for the wire format and [`/ARCHITECTURE.md`](../../ARCHITECTURE.md) §4 for
|
|
5
|
+
the design.
|
|
6
|
+
|
|
7
|
+
## Rules
|
|
8
|
+
|
|
9
|
+
- Every view consults `ModelAdmin` for permissions, queryset, form,
|
|
10
|
+
serialization. No exceptions.
|
|
11
|
+
- No direct `Model.objects.all()` — start from
|
|
12
|
+
`ModelAdmin.get_queryset(request)`.
|
|
13
|
+
- Client-provided `app_label`/`model_name` are resolved through
|
|
14
|
+
`admin.site._registry` only.
|
|
15
|
+
- CSRF on unsafe methods. Never exempt yourself.
|
|
16
|
+
- Conservative serializer with `str()` fallback (see
|
|
17
|
+
`serializers.py`).
|
|
18
|
+
- A denylist of sensitive-shaped field names is applied on top of the
|
|
19
|
+
admin form's own exclusion (defense in depth).
|
|
20
|
+
|
|
21
|
+
## Layout
|
|
22
|
+
|
|
23
|
+
| File | Purpose |
|
|
24
|
+
| ----------------- | ------------------------------------------------------------ |
|
|
25
|
+
| `urls.py` | URL patterns for all API endpoints. |
|
|
26
|
+
| `permissions.py` | Staff + AdminSite.has_permission gate; per-op delegation. |
|
|
27
|
+
| `registry.py` | AdminSite introspection helpers. |
|
|
28
|
+
| `serializers.py` | Conservative field serialization + denylist. |
|
|
29
|
+
| `views/` | One module per endpoint. |
|
|
30
|
+
|
|
31
|
+
Implementation status is tracked in `../README.md`.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Permission helpers.
|
|
2
|
+
|
|
3
|
+
The package's default permission gate is:
|
|
4
|
+
|
|
5
|
+
user.is_authenticated and user.is_active and user.is_staff
|
|
6
|
+
and admin_site.has_permission(request)
|
|
7
|
+
|
|
8
|
+
Per-operation gates always go through the relevant
|
|
9
|
+
``ModelAdmin.has_*_permission(request, obj=None)`` method.
|
|
10
|
+
|
|
11
|
+
See ``SECURITY.md`` §3 (rules 1 and 12) for the contract this enforces.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Final
|
|
17
|
+
|
|
18
|
+
from django.contrib.admin.sites import AdminSite
|
|
19
|
+
from django.http import HttpRequest
|
|
20
|
+
from django.http import HttpResponse
|
|
21
|
+
from django.http import JsonResponse
|
|
22
|
+
|
|
23
|
+
from django_admin_react.api.registry import get_admin_site
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _user_is_active_staff(request: HttpRequest) -> bool:
|
|
27
|
+
"""Return True iff the request user is authenticated, active, and staff.
|
|
28
|
+
|
|
29
|
+
The triple check is intentional and each part is load-bearing.
|
|
30
|
+
|
|
31
|
+
- ``is_authenticated`` rejects ``AnonymousUser``.
|
|
32
|
+
- ``is_active`` ensures a deactivated account loses access immediately;
|
|
33
|
+
relying on ``is_staff`` alone would still let a disabled superuser
|
|
34
|
+
through.
|
|
35
|
+
- ``is_staff`` is the standard Django admin gate.
|
|
36
|
+
|
|
37
|
+
``getattr(user, "is_active", False)`` (rather than ``user.is_active``)
|
|
38
|
+
is defensive: a custom user model might omit the attribute, and the
|
|
39
|
+
safe default is "no".
|
|
40
|
+
"""
|
|
41
|
+
user = getattr(request, "user", None)
|
|
42
|
+
return bool(
|
|
43
|
+
user is not None
|
|
44
|
+
and user.is_authenticated
|
|
45
|
+
and getattr(user, "is_active", False)
|
|
46
|
+
and getattr(user, "is_staff", False)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def is_admin_user(request: HttpRequest, admin_site: AdminSite | None = None) -> bool:
|
|
51
|
+
"""Return True iff the request may access the package's API.
|
|
52
|
+
|
|
53
|
+
The package's default policy is staff-only (rule 1 in ``SECURITY.md``
|
|
54
|
+
§3). ``AdminSite.has_permission`` is the consumer's escape hatch: if
|
|
55
|
+
a consumer's custom site allows non-staff users to access the admin,
|
|
56
|
+
this package follows that decision (`ARCHITECTURE.md` §4.2).
|
|
57
|
+
"""
|
|
58
|
+
if not _user_is_active_staff(request):
|
|
59
|
+
return False
|
|
60
|
+
site = admin_site if admin_site is not None else get_admin_site()
|
|
61
|
+
return bool(site.has_permission(request))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
_FORBIDDEN_BODY: Final[dict[str, dict[str, str]]] = {
|
|
65
|
+
"error": {"code": "forbidden", "message": "You do not have permission."}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def forbidden_response() -> HttpResponse:
|
|
70
|
+
"""Return the package's canonical 403 envelope.
|
|
71
|
+
|
|
72
|
+
The body never includes data identifying the resource (per ``SECURITY.md``
|
|
73
|
+
§3 rule 12). For unauthenticated requests, callers may also want to
|
|
74
|
+
redirect to ``settings.LOGIN_URL`` — that's a caller-level choice and
|
|
75
|
+
not encoded here.
|
|
76
|
+
"""
|
|
77
|
+
response = JsonResponse(_FORBIDDEN_BODY, status=403)
|
|
78
|
+
# Discourage caching of a permission decision.
|
|
79
|
+
response["Cache-Control"] = "no-store"
|
|
80
|
+
return response
|