django-admin-rest-api 0.1.0a0__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_rest_api-0.1.0a0/LICENSE +21 -0
- django_admin_rest_api-0.1.0a0/PKG-INFO +251 -0
- django_admin_rest_api-0.1.0a0/README.md +217 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/__init__.py +18 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/README.md +31 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/__init__.py +5 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/dates.py +215 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/filters.py +412 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/inlines.py +397 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/inlines_write.py +241 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/panels.py +113 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/permissions.py +132 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/registry.py +399 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/serializers.py +467 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/urls.py +170 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/views/README.md +22 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/views/__init__.py +6 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/views/actions.py +196 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/views/auth.py +185 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/views/autocomplete.py +163 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/views/bulk.py +248 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/views/create.py +214 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/views/create_form.py +229 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/views/delete_preview.py +107 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/views/destroy.py +93 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/views/detail.py +499 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/views/history.py +166 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/views/list.py +493 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/views/password.py +161 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/views/recent_actions.py +139 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/views/registry.py +53 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/views/schema.py +494 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/views/update.py +220 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/api/writes.py +541 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/apps.py +27 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/audit.py +58 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/conf.py +79 -0
- django_admin_rest_api-0.1.0a0/django_admin_rest_api/urls.py +28 -0
- django_admin_rest_api-0.1.0a0/pyproject.toml +212 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Martín Castro Álvarez and django-admin-rest-api 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,251 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-admin-rest-api
|
|
3
|
+
Version: 0.1.0a0
|
|
4
|
+
Summary: A JSON REST API for the Django admin — same permissions, same ModelAdmin, no new features. Powers django-admin-react and django-admin-mcp.
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: django,admin,rest,api,json,headless
|
|
8
|
+
Author: django-admin-rest-api contributors
|
|
9
|
+
Requires-Python: >=3.10,<4.0
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Web Environment
|
|
12
|
+
Classifier: Framework :: Django
|
|
13
|
+
Classifier: Framework :: Django :: 5.0
|
|
14
|
+
Classifier: Framework :: Django :: 5.1
|
|
15
|
+
Classifier: Framework :: Django :: 5.2
|
|
16
|
+
Classifier: Framework :: Django :: 6.0
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
26
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Site Management
|
|
27
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
28
|
+
Requires-Dist: django (>=5.0,<7.0)
|
|
29
|
+
Project-URL: Documentation, https://github.com/MartinCastroAlvarez/django-admin-api#readme
|
|
30
|
+
Project-URL: Homepage, https://github.com/MartinCastroAlvarez/django-admin-api
|
|
31
|
+
Project-URL: Repository, https://github.com/MartinCastroAlvarez/django-admin-api
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# django-admin-rest-api
|
|
35
|
+
|
|
36
|
+
> A JSON REST API for the Django admin — **same permissions, same `ModelAdmin`, no new features.**
|
|
37
|
+
|
|
38
|
+
[](https://pypi.org/project/django-admin-rest-api/)
|
|
39
|
+
[](https://pypi.org/project/django-admin-rest-api/)
|
|
40
|
+
[](https://www.djangoproject.com/)
|
|
41
|
+
[](LICENSE)
|
|
42
|
+
|
|
43
|
+
`django-admin-rest-api` exposes every `ModelAdmin` you've already
|
|
44
|
+
registered on `django.contrib.admin.site` (or your own `AdminSite`)
|
|
45
|
+
through a JSON REST API — **without** introducing a parallel
|
|
46
|
+
permission system, a parallel form layer, or any features the Django
|
|
47
|
+
admin itself doesn't have.
|
|
48
|
+
|
|
49
|
+
It is the wire surface that lets these projects drive your admin:
|
|
50
|
+
|
|
51
|
+
| Project | Role | PyPI |
|
|
52
|
+
| --- | --- | --- |
|
|
53
|
+
| 🟦 [`django-admin-react`](https://github.com/MartinCastroAlvarez/django-admin-react) | React single-page admin frontend | [`django-admin-react`](https://pypi.org/project/django-admin-react/) |
|
|
54
|
+
| 🟩 **`django-admin-rest-api`** *(this repo)* | JSON REST API over `ModelAdmin` | [`django-admin-rest-api`](https://pypi.org/project/django-admin-rest-api/) |
|
|
55
|
+
| 🟪 [`django-admin-mcp`](https://github.com/MartinCastroAlvarez/django-admin-mcp) | MCP server exposing the same API to LLMs | *(coming soon)* |
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## ✨ The one design principle
|
|
60
|
+
|
|
61
|
+
**This package adds no new behavior. It is a JSON wrapper.**
|
|
62
|
+
|
|
63
|
+
That means every one of these is owned by your existing Django setup —
|
|
64
|
+
not by this library:
|
|
65
|
+
|
|
66
|
+
- 🔐 **Authentication** — Django's session + login. The API enforces the
|
|
67
|
+
same `is_active` + `is_staff` + `AdminSite.has_permission` gate the
|
|
68
|
+
HTML admin uses. No tokens, no custom auth backends, no JWTs.
|
|
69
|
+
- 🛡️ **Authorization / permissions** — every endpoint calls the
|
|
70
|
+
matching `ModelAdmin.has_view_permission` / `has_add_permission` /
|
|
71
|
+
`has_change_permission` / `has_delete_permission`. If your admin
|
|
72
|
+
says no, the API says 403.
|
|
73
|
+
- 📋 **Field validation** — `POST` / `PATCH` route the payload through
|
|
74
|
+
the same `ModelForm` Django would render in the HTML admin
|
|
75
|
+
(`ModelAdmin.get_form(request, obj)`), so every clean method, every
|
|
76
|
+
`unique_together` constraint, every custom widget validator runs
|
|
77
|
+
exactly once and exactly the same way.
|
|
78
|
+
- ⚙️ **Actions** — the action registry comes from
|
|
79
|
+
`ModelAdmin.get_actions(request)`. Your custom action functions run
|
|
80
|
+
unmodified.
|
|
81
|
+
- 🔎 **Search & filters** — search uses
|
|
82
|
+
`ModelAdmin.get_search_results(request, queryset, term)`; filters
|
|
83
|
+
use `ModelAdmin.list_filter`. No parallel implementation.
|
|
84
|
+
- 📜 **Audit log** — writes go through Django's `LogEntry` so your
|
|
85
|
+
history page (and every other consumer of `LogEntry`) keeps working.
|
|
86
|
+
- 🌐 **CSRF & sessions** — Django's middleware. Nothing is
|
|
87
|
+
`@csrf_exempt`.
|
|
88
|
+
|
|
89
|
+
If a behavior isn't in the HTML admin, it isn't here. If it is in the
|
|
90
|
+
HTML admin, this library exposes it over JSON.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 🚀 Plug-and-play install
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
pip install django-admin-rest-api
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Two changes to your project:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
# settings.py
|
|
104
|
+
INSTALLED_APPS = [
|
|
105
|
+
# ... your existing apps ...
|
|
106
|
+
"django.contrib.admin",
|
|
107
|
+
"django_admin_rest_api", # ← add
|
|
108
|
+
]
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
# urls.py
|
|
113
|
+
from django.contrib import admin
|
|
114
|
+
from django.urls import include, path
|
|
115
|
+
|
|
116
|
+
urlpatterns = [
|
|
117
|
+
path("admin/", admin.site.urls),
|
|
118
|
+
path("admin-api/", include("django_admin_rest_api.urls")), # ← add
|
|
119
|
+
]
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
That's it. Your admin is now also a JSON API at `/admin-api/api/v1/...`.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 📡 The endpoints
|
|
127
|
+
|
|
128
|
+
| Method | Path | What it returns |
|
|
129
|
+
| ------- | ---------------------------------------------- | -------------------------------------------------------- |
|
|
130
|
+
| `GET` | `/api/v1/registry/` | The same app/model tree Django renders in the admin index |
|
|
131
|
+
| `GET` | `/api/v1/schema/` | OpenAPI 3.1 schema of every endpoint below |
|
|
132
|
+
| `GET` | `/api/v1/<app>/<model>/` | List + pagination + filters + search |
|
|
133
|
+
| `POST` | `/api/v1/<app>/<model>/` | Create (runs the same `ModelForm`) |
|
|
134
|
+
| `GET` | `/api/v1/<app>/<model>/<pk>/` | Detail (read view as the HTML admin renders it) |
|
|
135
|
+
| `PATCH` | `/api/v1/<app>/<model>/<pk>/` | Update |
|
|
136
|
+
| `DELETE`| `/api/v1/<app>/<model>/<pk>/` | Destroy (with `LogEntry`) |
|
|
137
|
+
| `POST` | `/api/v1/<app>/<model>/bulk-update/` | Bulk patch |
|
|
138
|
+
| `POST` | `/api/v1/<app>/<model>/delete-preview/` | Cascade preview (like the HTML admin's confirm page) |
|
|
139
|
+
| `GET` | `/api/v1/<app>/<model>/autocomplete/?q=…` | `ModelAdmin.autocomplete_fields` source |
|
|
140
|
+
| `POST` | `/api/v1/<app>/<model>/actions/` | Run a `ModelAdmin` action on a selection |
|
|
141
|
+
| `GET` | `/api/v1/<app>/<model>/<pk>/history/` | The `LogEntry` history for one object |
|
|
142
|
+
| `GET` | `/api/v1/recent-actions/` | The dashboard's "Recent Actions" feed |
|
|
143
|
+
| `POST` | `/api/v1/login/` | Same `authenticate` + `login` as the HTML admin |
|
|
144
|
+
| `POST` | `/api/v1/logout/` | Same `logout` |
|
|
145
|
+
| `POST` | `/api/v1/<app>/<model>/<pk>/set-password/` | Auth-app `set_password` permission gate (User model) |
|
|
146
|
+
|
|
147
|
+
Every endpoint enforces the same permission gates as the HTML admin.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 📸 Screenshots
|
|
152
|
+
|
|
153
|
+
The JSON `registry` endpoint — the source-of-truth for any consumer
|
|
154
|
+
frontend:
|
|
155
|
+
|
|
156
|
+

|
|
157
|
+
|
|
158
|
+
And here is the same admin rendered by
|
|
159
|
+
[`django-admin-react`](https://github.com/MartinCastroAlvarez/django-admin-react)
|
|
160
|
+
on top of this API, to give you an idea of what a consumer can build:
|
|
161
|
+
|
|
162
|
+
| | |
|
|
163
|
+
|:-:|:-:|
|
|
164
|
+
|  |  |
|
|
165
|
+
|  |  |
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## ⚙️ Configuration
|
|
170
|
+
|
|
171
|
+
All settings live under a single optional dict — defaults are sane,
|
|
172
|
+
so most projects need no entry at all.
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
# settings.py (all keys optional)
|
|
176
|
+
DJANGO_ADMIN_REST_API = {
|
|
177
|
+
# Dotted path to the AdminSite whose ModelAdmin registry the API
|
|
178
|
+
# mirrors. Default exposes django.contrib.admin.site.
|
|
179
|
+
"ADMIN_SITE": "django.contrib.admin.site",
|
|
180
|
+
|
|
181
|
+
# Pagination. List endpoints use ModelAdmin.list_per_page as the
|
|
182
|
+
# source of truth; DEFAULT_PAGE_SIZE is the fallback. MAX_PAGE_SIZE
|
|
183
|
+
# caps ?page_size from the client (DoS guard).
|
|
184
|
+
"DEFAULT_PAGE_SIZE": 25,
|
|
185
|
+
"MAX_PAGE_SIZE": 200,
|
|
186
|
+
|
|
187
|
+
# When True, list responses include per-query timing in a debug
|
|
188
|
+
# block. Off by default — only enable in development.
|
|
189
|
+
"ENABLE_PROFILING": False,
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## 🔒 Security
|
|
196
|
+
|
|
197
|
+
- The API is **not** a parallel auth surface. It refuses any caller
|
|
198
|
+
the HTML admin would refuse, with the same gate
|
|
199
|
+
(`AdminSite.has_permission`, plus the per-model `ModelAdmin.has_*_permission`).
|
|
200
|
+
- Anonymous → `403` for every data endpoint.
|
|
201
|
+
- Authenticated but non-staff → `403`. Cookie present but resolved
|
|
202
|
+
user is anonymous → `403 not_authenticated`.
|
|
203
|
+
- Writes always go through `ModelForm.is_valid()` —
|
|
204
|
+
`unique_together`, `clean()`, field validators all run.
|
|
205
|
+
- Per-object guards run **before** the form does anything. The
|
|
206
|
+
`delete-preview` and `delete` endpoints both check `has_delete_permission(obj)`.
|
|
207
|
+
- CSRF is enforced everywhere. No view in this package is
|
|
208
|
+
`@csrf_exempt`. The login endpoint requires the CSRF cookie set
|
|
209
|
+
by the consumer's shell.
|
|
210
|
+
|
|
211
|
+
See the upstream
|
|
212
|
+
[`django-admin-react` SECURITY.md](https://github.com/MartinCastroAlvarez/django-admin-react/blob/main/SECURITY.md)
|
|
213
|
+
for the full threat model — the API surface is identical and the
|
|
214
|
+
guarantees transfer 1:1.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## 🧪 Local development
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
git clone https://github.com/MartinCastroAlvarez/django-admin-api
|
|
222
|
+
cd django-admin-api
|
|
223
|
+
poetry install
|
|
224
|
+
poetry run pytest
|
|
225
|
+
poetry run ruff check .
|
|
226
|
+
poetry run black --check .
|
|
227
|
+
poetry run mypy django_admin_rest_api
|
|
228
|
+
poetry run bandit -c pyproject.toml -r django_admin_rest_api
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
The test suite uses `pytest-django` + an in-memory SQLite database, so
|
|
232
|
+
no setup beyond `poetry install`.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## 🤝 Contributing
|
|
237
|
+
|
|
238
|
+
Issues, PRs, and Discussions are welcome on GitHub:
|
|
239
|
+
<https://github.com/MartinCastroAlvarez/django-admin-api>.
|
|
240
|
+
|
|
241
|
+
The lint + security gate is the same set the upstream
|
|
242
|
+
`django-admin-react` repo uses: **ruff, black, isort, flake8,
|
|
243
|
+
pylint, mypy, bandit, pip-audit, gitleaks.** Every change must pass
|
|
244
|
+
all of them before merge.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## 📜 License
|
|
249
|
+
|
|
250
|
+
MIT. See [LICENSE](LICENSE).
|
|
251
|
+
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# django-admin-rest-api
|
|
2
|
+
|
|
3
|
+
> A JSON REST API for the Django admin — **same permissions, same `ModelAdmin`, no new features.**
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/django-admin-rest-api/)
|
|
6
|
+
[](https://pypi.org/project/django-admin-rest-api/)
|
|
7
|
+
[](https://www.djangoproject.com/)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
|
|
10
|
+
`django-admin-rest-api` exposes every `ModelAdmin` you've already
|
|
11
|
+
registered on `django.contrib.admin.site` (or your own `AdminSite`)
|
|
12
|
+
through a JSON REST API — **without** introducing a parallel
|
|
13
|
+
permission system, a parallel form layer, or any features the Django
|
|
14
|
+
admin itself doesn't have.
|
|
15
|
+
|
|
16
|
+
It is the wire surface that lets these projects drive your admin:
|
|
17
|
+
|
|
18
|
+
| Project | Role | PyPI |
|
|
19
|
+
| --- | --- | --- |
|
|
20
|
+
| 🟦 [`django-admin-react`](https://github.com/MartinCastroAlvarez/django-admin-react) | React single-page admin frontend | [`django-admin-react`](https://pypi.org/project/django-admin-react/) |
|
|
21
|
+
| 🟩 **`django-admin-rest-api`** *(this repo)* | JSON REST API over `ModelAdmin` | [`django-admin-rest-api`](https://pypi.org/project/django-admin-rest-api/) |
|
|
22
|
+
| 🟪 [`django-admin-mcp`](https://github.com/MartinCastroAlvarez/django-admin-mcp) | MCP server exposing the same API to LLMs | *(coming soon)* |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## ✨ The one design principle
|
|
27
|
+
|
|
28
|
+
**This package adds no new behavior. It is a JSON wrapper.**
|
|
29
|
+
|
|
30
|
+
That means every one of these is owned by your existing Django setup —
|
|
31
|
+
not by this library:
|
|
32
|
+
|
|
33
|
+
- 🔐 **Authentication** — Django's session + login. The API enforces the
|
|
34
|
+
same `is_active` + `is_staff` + `AdminSite.has_permission` gate the
|
|
35
|
+
HTML admin uses. No tokens, no custom auth backends, no JWTs.
|
|
36
|
+
- 🛡️ **Authorization / permissions** — every endpoint calls the
|
|
37
|
+
matching `ModelAdmin.has_view_permission` / `has_add_permission` /
|
|
38
|
+
`has_change_permission` / `has_delete_permission`. If your admin
|
|
39
|
+
says no, the API says 403.
|
|
40
|
+
- 📋 **Field validation** — `POST` / `PATCH` route the payload through
|
|
41
|
+
the same `ModelForm` Django would render in the HTML admin
|
|
42
|
+
(`ModelAdmin.get_form(request, obj)`), so every clean method, every
|
|
43
|
+
`unique_together` constraint, every custom widget validator runs
|
|
44
|
+
exactly once and exactly the same way.
|
|
45
|
+
- ⚙️ **Actions** — the action registry comes from
|
|
46
|
+
`ModelAdmin.get_actions(request)`. Your custom action functions run
|
|
47
|
+
unmodified.
|
|
48
|
+
- 🔎 **Search & filters** — search uses
|
|
49
|
+
`ModelAdmin.get_search_results(request, queryset, term)`; filters
|
|
50
|
+
use `ModelAdmin.list_filter`. No parallel implementation.
|
|
51
|
+
- 📜 **Audit log** — writes go through Django's `LogEntry` so your
|
|
52
|
+
history page (and every other consumer of `LogEntry`) keeps working.
|
|
53
|
+
- 🌐 **CSRF & sessions** — Django's middleware. Nothing is
|
|
54
|
+
`@csrf_exempt`.
|
|
55
|
+
|
|
56
|
+
If a behavior isn't in the HTML admin, it isn't here. If it is in the
|
|
57
|
+
HTML admin, this library exposes it over JSON.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 🚀 Plug-and-play install
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install django-admin-rest-api
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Two changes to your project:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
# settings.py
|
|
71
|
+
INSTALLED_APPS = [
|
|
72
|
+
# ... your existing apps ...
|
|
73
|
+
"django.contrib.admin",
|
|
74
|
+
"django_admin_rest_api", # ← add
|
|
75
|
+
]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
# urls.py
|
|
80
|
+
from django.contrib import admin
|
|
81
|
+
from django.urls import include, path
|
|
82
|
+
|
|
83
|
+
urlpatterns = [
|
|
84
|
+
path("admin/", admin.site.urls),
|
|
85
|
+
path("admin-api/", include("django_admin_rest_api.urls")), # ← add
|
|
86
|
+
]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
That's it. Your admin is now also a JSON API at `/admin-api/api/v1/...`.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 📡 The endpoints
|
|
94
|
+
|
|
95
|
+
| Method | Path | What it returns |
|
|
96
|
+
| ------- | ---------------------------------------------- | -------------------------------------------------------- |
|
|
97
|
+
| `GET` | `/api/v1/registry/` | The same app/model tree Django renders in the admin index |
|
|
98
|
+
| `GET` | `/api/v1/schema/` | OpenAPI 3.1 schema of every endpoint below |
|
|
99
|
+
| `GET` | `/api/v1/<app>/<model>/` | List + pagination + filters + search |
|
|
100
|
+
| `POST` | `/api/v1/<app>/<model>/` | Create (runs the same `ModelForm`) |
|
|
101
|
+
| `GET` | `/api/v1/<app>/<model>/<pk>/` | Detail (read view as the HTML admin renders it) |
|
|
102
|
+
| `PATCH` | `/api/v1/<app>/<model>/<pk>/` | Update |
|
|
103
|
+
| `DELETE`| `/api/v1/<app>/<model>/<pk>/` | Destroy (with `LogEntry`) |
|
|
104
|
+
| `POST` | `/api/v1/<app>/<model>/bulk-update/` | Bulk patch |
|
|
105
|
+
| `POST` | `/api/v1/<app>/<model>/delete-preview/` | Cascade preview (like the HTML admin's confirm page) |
|
|
106
|
+
| `GET` | `/api/v1/<app>/<model>/autocomplete/?q=…` | `ModelAdmin.autocomplete_fields` source |
|
|
107
|
+
| `POST` | `/api/v1/<app>/<model>/actions/` | Run a `ModelAdmin` action on a selection |
|
|
108
|
+
| `GET` | `/api/v1/<app>/<model>/<pk>/history/` | The `LogEntry` history for one object |
|
|
109
|
+
| `GET` | `/api/v1/recent-actions/` | The dashboard's "Recent Actions" feed |
|
|
110
|
+
| `POST` | `/api/v1/login/` | Same `authenticate` + `login` as the HTML admin |
|
|
111
|
+
| `POST` | `/api/v1/logout/` | Same `logout` |
|
|
112
|
+
| `POST` | `/api/v1/<app>/<model>/<pk>/set-password/` | Auth-app `set_password` permission gate (User model) |
|
|
113
|
+
|
|
114
|
+
Every endpoint enforces the same permission gates as the HTML admin.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 📸 Screenshots
|
|
119
|
+
|
|
120
|
+
The JSON `registry` endpoint — the source-of-truth for any consumer
|
|
121
|
+
frontend:
|
|
122
|
+
|
|
123
|
+

|
|
124
|
+
|
|
125
|
+
And here is the same admin rendered by
|
|
126
|
+
[`django-admin-react`](https://github.com/MartinCastroAlvarez/django-admin-react)
|
|
127
|
+
on top of this API, to give you an idea of what a consumer can build:
|
|
128
|
+
|
|
129
|
+
| | |
|
|
130
|
+
|:-:|:-:|
|
|
131
|
+
|  |  |
|
|
132
|
+
|  |  |
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## ⚙️ Configuration
|
|
137
|
+
|
|
138
|
+
All settings live under a single optional dict — defaults are sane,
|
|
139
|
+
so most projects need no entry at all.
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
# settings.py (all keys optional)
|
|
143
|
+
DJANGO_ADMIN_REST_API = {
|
|
144
|
+
# Dotted path to the AdminSite whose ModelAdmin registry the API
|
|
145
|
+
# mirrors. Default exposes django.contrib.admin.site.
|
|
146
|
+
"ADMIN_SITE": "django.contrib.admin.site",
|
|
147
|
+
|
|
148
|
+
# Pagination. List endpoints use ModelAdmin.list_per_page as the
|
|
149
|
+
# source of truth; DEFAULT_PAGE_SIZE is the fallback. MAX_PAGE_SIZE
|
|
150
|
+
# caps ?page_size from the client (DoS guard).
|
|
151
|
+
"DEFAULT_PAGE_SIZE": 25,
|
|
152
|
+
"MAX_PAGE_SIZE": 200,
|
|
153
|
+
|
|
154
|
+
# When True, list responses include per-query timing in a debug
|
|
155
|
+
# block. Off by default — only enable in development.
|
|
156
|
+
"ENABLE_PROFILING": False,
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 🔒 Security
|
|
163
|
+
|
|
164
|
+
- The API is **not** a parallel auth surface. It refuses any caller
|
|
165
|
+
the HTML admin would refuse, with the same gate
|
|
166
|
+
(`AdminSite.has_permission`, plus the per-model `ModelAdmin.has_*_permission`).
|
|
167
|
+
- Anonymous → `403` for every data endpoint.
|
|
168
|
+
- Authenticated but non-staff → `403`. Cookie present but resolved
|
|
169
|
+
user is anonymous → `403 not_authenticated`.
|
|
170
|
+
- Writes always go through `ModelForm.is_valid()` —
|
|
171
|
+
`unique_together`, `clean()`, field validators all run.
|
|
172
|
+
- Per-object guards run **before** the form does anything. The
|
|
173
|
+
`delete-preview` and `delete` endpoints both check `has_delete_permission(obj)`.
|
|
174
|
+
- CSRF is enforced everywhere. No view in this package is
|
|
175
|
+
`@csrf_exempt`. The login endpoint requires the CSRF cookie set
|
|
176
|
+
by the consumer's shell.
|
|
177
|
+
|
|
178
|
+
See the upstream
|
|
179
|
+
[`django-admin-react` SECURITY.md](https://github.com/MartinCastroAlvarez/django-admin-react/blob/main/SECURITY.md)
|
|
180
|
+
for the full threat model — the API surface is identical and the
|
|
181
|
+
guarantees transfer 1:1.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 🧪 Local development
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
git clone https://github.com/MartinCastroAlvarez/django-admin-api
|
|
189
|
+
cd django-admin-api
|
|
190
|
+
poetry install
|
|
191
|
+
poetry run pytest
|
|
192
|
+
poetry run ruff check .
|
|
193
|
+
poetry run black --check .
|
|
194
|
+
poetry run mypy django_admin_rest_api
|
|
195
|
+
poetry run bandit -c pyproject.toml -r django_admin_rest_api
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The test suite uses `pytest-django` + an in-memory SQLite database, so
|
|
199
|
+
no setup beyond `poetry install`.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## 🤝 Contributing
|
|
204
|
+
|
|
205
|
+
Issues, PRs, and Discussions are welcome on GitHub:
|
|
206
|
+
<https://github.com/MartinCastroAlvarez/django-admin-api>.
|
|
207
|
+
|
|
208
|
+
The lint + security gate is the same set the upstream
|
|
209
|
+
`django-admin-react` repo uses: **ruff, black, isort, flake8,
|
|
210
|
+
pylint, mypy, bandit, pip-audit, gitleaks.** Every change must pass
|
|
211
|
+
all of them before merge.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## 📜 License
|
|
216
|
+
|
|
217
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""django-admin-rest-api — a JSON REST API for the Django admin.
|
|
2
|
+
|
|
3
|
+
A frontend-agnostic Django app that exposes every ``ModelAdmin``
|
|
4
|
+
through JSON endpoints (list, detail, create, update, delete, actions,
|
|
5
|
+
history, autocomplete, …) using the **same permissions and the same
|
|
6
|
+
ModelAdmin source of truth** as the HTML admin.
|
|
7
|
+
|
|
8
|
+
This package adds no new features and no new permissions: it is the
|
|
9
|
+
wire surface that lets clients like
|
|
10
|
+
`django-admin-react <https://pypi.org/project/django-admin-react/>`_
|
|
11
|
+
and the forthcoming ``django-admin-mcp`` drive the Django admin over a
|
|
12
|
+
clean JSON contract.
|
|
13
|
+
|
|
14
|
+
See ``README.md`` for the install + URL wiring, and
|
|
15
|
+
``docs/api-contract.md`` (in the repo) for the wire shape.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
__version__ = "0.1.0a0"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# django_admin_rest_api/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`.
|