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.
Files changed (32) hide show
  1. django_admin_react-0.1.0a1/LICENSE +21 -0
  2. django_admin_react-0.1.0a1/PKG-INFO +237 -0
  3. django_admin_react-0.1.0a1/README.md +206 -0
  4. django_admin_react-0.1.0a1/django_admin_react/README.md +57 -0
  5. django_admin_react-0.1.0a1/django_admin_react/__init__.py +10 -0
  6. django_admin_react-0.1.0a1/django_admin_react/api/README.md +31 -0
  7. django_admin_react-0.1.0a1/django_admin_react/api/__init__.py +5 -0
  8. django_admin_react-0.1.0a1/django_admin_react/api/permissions.py +80 -0
  9. django_admin_react-0.1.0a1/django_admin_react/api/registry.py +200 -0
  10. django_admin_react-0.1.0a1/django_admin_react/api/serializers.py +183 -0
  11. django_admin_react-0.1.0a1/django_admin_react/api/urls.py +84 -0
  12. django_admin_react-0.1.0a1/django_admin_react/api/views/README.md +21 -0
  13. django_admin_react-0.1.0a1/django_admin_react/api/views/__init__.py +6 -0
  14. django_admin_react-0.1.0a1/django_admin_react/api/views/create.py +141 -0
  15. django_admin_react-0.1.0a1/django_admin_react/api/views/destroy.py +89 -0
  16. django_admin_react-0.1.0a1/django_admin_react/api/views/detail.py +283 -0
  17. django_admin_react-0.1.0a1/django_admin_react/api/views/list.py +271 -0
  18. django_admin_react-0.1.0a1/django_admin_react/api/views/registry.py +51 -0
  19. django_admin_react-0.1.0a1/django_admin_react/api/views/update.py +121 -0
  20. django_admin_react-0.1.0a1/django_admin_react/api/writes.py +325 -0
  21. django_admin_react-0.1.0a1/django_admin_react/apps.py +27 -0
  22. django_admin_react-0.1.0a1/django_admin_react/conf.py +77 -0
  23. django_admin_react-0.1.0a1/django_admin_react/static/admin_react/.vite/manifest.json +11 -0
  24. django_admin_react-0.1.0a1/django_admin_react/static/admin_react/assets/index-CKxeWYBA.css +1 -0
  25. django_admin_react-0.1.0a1/django_admin_react/static/admin_react/assets/index-itk7hrnq.js +68 -0
  26. django_admin_react-0.1.0a1/django_admin_react/static/admin_react/assets/index-itk7hrnq.js.map +1 -0
  27. django_admin_react-0.1.0a1/django_admin_react/static/admin_react/index.html +13 -0
  28. django_admin_react-0.1.0a1/django_admin_react/templates/admin_react/README.md +10 -0
  29. django_admin_react-0.1.0a1/django_admin_react/templates/admin_react/index.html +33 -0
  30. django_admin_react-0.1.0a1/django_admin_react/urls.py +39 -0
  31. django_admin_react-0.1.0a1/django_admin_react/views.py +136 -0
  32. 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
+ | ![Login](docs/screenshots/01-admin-login.png) | ![Admin index](docs/screenshots/02-admin-index.png) |
150
+
151
+ | Library / Authors — list view | Library / Author — detail view |
152
+ | -------------------------------------------------------------- | --------------------------------------------------------------- |
153
+ | ![Author list](docs/screenshots/03-admin-library-list.png) | ![Author detail](docs/screenshots/05-admin-library-detail.png) |
154
+
155
+ | Mobile (375 px) | API: `GET /api/v1/registry/` JSON |
156
+ | --------------------------------------------------------------------- | ------------------------------------------------------- |
157
+ | ![Mobile list](docs/screenshots/04-admin-library-list-mobile.png) | ![Registry JSON](docs/screenshots/06-registry-api-json.png) |
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
+ | ![Login](docs/screenshots/01-admin-login.png) | ![Admin index](docs/screenshots/02-admin-index.png) |
120
+
121
+ | Library / Authors — list view | Library / Author — detail view |
122
+ | -------------------------------------------------------------- | --------------------------------------------------------------- |
123
+ | ![Author list](docs/screenshots/03-admin-library-list.png) | ![Author detail](docs/screenshots/05-admin-library-detail.png) |
124
+
125
+ | Mobile (375 px) | API: `GET /api/v1/registry/` JSON |
126
+ | --------------------------------------------------------------------- | ------------------------------------------------------- |
127
+ | ![Mobile list](docs/screenshots/04-admin-library-list-mobile.png) | ![Registry JSON](docs/screenshots/06-registry-api-json.png) |
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,5 @@
1
+ """JSON API for django_admin_react.
2
+
3
+ See `docs/api-contract.md` for the wire contract. All endpoints live
4
+ here; nothing in this subpackage may bypass ModelAdmin.
5
+ """
@@ -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