A JSON REST API for the Django admin — same permissions, same ModelAdmin, no new features. Powers django-admin-react and django-admin-mcp.
Project description
django-admin-rest-api
A JSON REST API for the Django admin — same permissions, same
ModelAdmin, no new features.
django-admin-rest-api exposes every ModelAdmin you've already
registered on django.contrib.admin.site (or your own AdminSite)
through a JSON REST API — without introducing a parallel
permission system, a parallel form layer, or any features the Django
admin itself doesn't have.
It is the wire surface that lets these projects drive your admin:
| Project | Role | PyPI |
|---|---|---|
🟦 django-admin-react |
React single-page admin frontend | django-admin-react |
🟩 django-admin-rest-api (this repo) |
JSON REST API over ModelAdmin |
django-admin-rest-api |
🟪 django-admin-mcp |
MCP server exposing the same API to LLMs | (coming soon) |
✨ The one design principle
This package adds no new behavior. It is a JSON wrapper.
That means every one of these is owned by your existing Django setup — not by this library:
- 🔐 Authentication — Django's session + login. The API enforces the
same
is_active+is_staff+AdminSite.has_permissiongate the HTML admin uses. No tokens, no custom auth backends, no JWTs. - 🛡️ Authorization / permissions — every endpoint calls the
matching
ModelAdmin.has_view_permission/has_add_permission/has_change_permission/has_delete_permission. If your admin says no, the API says 403. - 📋 Field validation —
POST/PATCHroute the payload through the sameModelFormDjango would render in the HTML admin (ModelAdmin.get_form(request, obj)), so every clean method, everyunique_togetherconstraint, every custom widget validator runs exactly once and exactly the same way. - ⚙️ Actions — the action registry comes from
ModelAdmin.get_actions(request). Your custom action functions run unmodified. - 🔎 Search & filters — search uses
ModelAdmin.get_search_results(request, queryset, term); filters useModelAdmin.list_filter. No parallel implementation. - 📜 Audit log — writes go through Django's
LogEntryso your history page (and every other consumer ofLogEntry) keeps working. - 🌐 CSRF & sessions — Django's middleware. Nothing is
@csrf_exempt.
If a behavior isn't in the HTML admin, it isn't here. If it is in the HTML admin, this library exposes it over JSON.
🚀 Plug-and-play install
pip install django-admin-rest-api
Two changes to your project:
# settings.py
INSTALLED_APPS = [
# ... your existing apps ...
"django.contrib.admin",
"django_admin_rest_api", # ← add
]
# urls.py
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path("admin/", admin.site.urls),
path("admin-api/", include("django_admin_rest_api.urls")), # ← add
]
That's it. Your admin is now also a JSON API at /admin-api/api/v1/....
📡 The endpoints
| Method | Path | What it returns |
|---|---|---|
GET |
/api/v1/registry/ |
The same app/model tree Django renders in the admin index |
GET |
/api/v1/schema/ |
OpenAPI 3.1 schema of every endpoint below |
GET |
/api/v1/<app>/<model>/ |
List + pagination + filters + search |
POST |
/api/v1/<app>/<model>/ |
Create (runs the same ModelForm) |
GET |
/api/v1/<app>/<model>/<pk>/ |
Detail (read view as the HTML admin renders it) |
PATCH |
/api/v1/<app>/<model>/<pk>/ |
Update |
DELETE |
/api/v1/<app>/<model>/<pk>/ |
Destroy (with LogEntry) |
POST |
/api/v1/<app>/<model>/bulk-update/ |
Bulk patch |
POST |
/api/v1/<app>/<model>/delete-preview/ |
Cascade preview (like the HTML admin's confirm page) |
GET |
/api/v1/<app>/<model>/autocomplete/?q=… |
ModelAdmin.autocomplete_fields source |
POST |
/api/v1/<app>/<model>/actions/ |
Run a ModelAdmin action on a selection |
GET |
/api/v1/<app>/<model>/<pk>/history/ |
The LogEntry history for one object |
GET |
/api/v1/recent-actions/ |
The dashboard's "Recent Actions" feed |
POST |
/api/v1/login/ |
Same authenticate + login as the HTML admin |
POST |
/api/v1/logout/ |
Same logout |
POST |
/api/v1/<app>/<model>/<pk>/password/ |
JSON mirror of UserAdmin's password-change page (AdminPasswordChangeForm + AUTH_PASSWORD_VALIDATORS + set_password); 404 unless the model's admin declares change_password_form; gated by has_change_permission |
Every endpoint enforces the same permission gates as the HTML admin.
📸 Screenshots
The JSON registry endpoint — the source-of-truth for any consumer
frontend:
And here is the same admin rendered by
django-admin-react
on top of this API, to give you an idea of what a consumer can build:
⚙️ Configuration
All settings live under a single optional dict — defaults are sane, so most projects need no entry at all.
# settings.py (all keys optional)
DJANGO_ADMIN_REST_API = {
# Dotted path to the AdminSite whose ModelAdmin registry the API
# mirrors. Default exposes django.contrib.admin.site.
"ADMIN_SITE": "django.contrib.admin.site",
# Pagination. List endpoints use ModelAdmin.list_per_page as the
# source of truth; DEFAULT_PAGE_SIZE is the fallback. MAX_PAGE_SIZE
# caps ?page_size from the client (DoS guard).
"DEFAULT_PAGE_SIZE": 25,
"MAX_PAGE_SIZE": 200,
# When True, list responses include per-query timing in a debug
# block. Off by default — only enable in development.
"ENABLE_PROFILING": False,
}
🔒 Security
- The API is not a parallel auth surface. It refuses any caller
the HTML admin would refuse, with the same gate
(
AdminSite.has_permission, plus the per-modelModelAdmin.has_*_permission). - Anonymous →
403for every data endpoint. - Authenticated but non-staff →
403. Cookie present but resolved user is anonymous →403 not_authenticated. - Writes always go through
ModelForm.is_valid()—unique_together,clean(), field validators all run. - Per-object guards run before the form does anything. The
delete-previewanddeleteendpoints both checkhas_delete_permission(obj). - CSRF is enforced everywhere. No view in this package is
@csrf_exempt. The login endpoint requires the CSRF cookie set by the consumer's shell.
See the upstream
django-admin-react SECURITY.md
for the full threat model — the API surface is identical and the
guarantees transfer 1:1.
🧪 Local development
git clone https://github.com/MartinCastroAlvarez/django-admin-api
cd django-admin-api
poetry install
poetry run pytest
poetry run ruff check .
poetry run black --check .
poetry run mypy django_admin_rest_api
poetry run bandit -c pyproject.toml -r django_admin_rest_api
The test suite uses pytest-django + an in-memory SQLite database, so
no setup beyond poetry install.
🤝 Contributing
Issues, PRs, and Discussions are welcome on GitHub: https://github.com/MartinCastroAlvarez/django-admin-api.
The lint + security gate is the same set the upstream
django-admin-react repo uses: ruff, black, isort, flake8,
pylint, mypy, bandit, pip-audit, gitleaks. Every change must pass
all of them before merge.
📜 License
MIT. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_admin_rest_api-1.0.2.tar.gz.
File metadata
- Download URL: django_admin_rest_api-1.0.2.tar.gz
- Upload date:
- Size: 88.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
94c6dd8cb8aad49a9f86df0fce265fbf5c91190db61a330b5825e212649c2410
|
|
| MD5 |
9801eb4709f2ac6802bb1e6c931807bb
|
|
| BLAKE2b-256 |
ec5eeef040d3327a5b5be9f939f5a1fb2938a385363e0964df1214a471493c3f
|
File details
Details for the file django_admin_rest_api-1.0.2-py3-none-any.whl.
File metadata
- Download URL: django_admin_rest_api-1.0.2-py3-none-any.whl
- Upload date:
- Size: 113.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bf1f9279619a8e18b06b6d6e8438168ac46c6c31011ebbb9fce4f5e09c5c472b
|
|
| MD5 |
ab67f9dd903ae0a1f574fac2baddb46d
|
|
| BLAKE2b-256 |
2705c1d0724faa8864d39029a570c759e058d1435d646d826561d233294b15a1
|