django-admin-react 0.1.0a1__py3-none-any.whl
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.
- LICENSE +21 -0
- django_admin_react/README.md +57 -0
- django_admin_react/__init__.py +10 -0
- django_admin_react/api/README.md +31 -0
- django_admin_react/api/__init__.py +5 -0
- django_admin_react/api/permissions.py +80 -0
- django_admin_react/api/registry.py +200 -0
- django_admin_react/api/serializers.py +183 -0
- django_admin_react/api/urls.py +84 -0
- django_admin_react/api/views/README.md +21 -0
- django_admin_react/api/views/__init__.py +6 -0
- django_admin_react/api/views/create.py +141 -0
- django_admin_react/api/views/destroy.py +89 -0
- django_admin_react/api/views/detail.py +283 -0
- django_admin_react/api/views/list.py +271 -0
- django_admin_react/api/views/registry.py +51 -0
- django_admin_react/api/views/update.py +121 -0
- django_admin_react/api/writes.py +325 -0
- django_admin_react/apps.py +27 -0
- django_admin_react/conf.py +77 -0
- django_admin_react/static/admin_react/.vite/manifest.json +11 -0
- django_admin_react/static/admin_react/assets/index-CKxeWYBA.css +1 -0
- django_admin_react/static/admin_react/assets/index-itk7hrnq.js +68 -0
- django_admin_react/static/admin_react/assets/index-itk7hrnq.js.map +1 -0
- django_admin_react/static/admin_react/index.html +13 -0
- django_admin_react/templates/admin_react/README.md +10 -0
- django_admin_react/templates/admin_react/index.html +33 -0
- django_admin_react/urls.py +39 -0
- django_admin_react/views.py +136 -0
- django_admin_react-0.1.0a1.dist-info/LICENSE +21 -0
- django_admin_react-0.1.0a1.dist-info/METADATA +237 -0
- django_admin_react-0.1.0a1.dist-info/RECORD +33 -0
- django_admin_react-0.1.0a1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>Django Admin React</title>
|
|
7
|
+
<script type="module" crossorigin src="./assets/index-itk7hrnq.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="./assets/index-CKxeWYBA.css">
|
|
9
|
+
</head>
|
|
10
|
+
<body class="bg-gray-50 text-gray-900 antialiased">
|
|
11
|
+
<div id="root"></div>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# django_admin_react/templates/admin_react/
|
|
2
|
+
|
|
3
|
+
Django template directory for the SPA shell.
|
|
4
|
+
|
|
5
|
+
- `index.html` — the SPA entry point. Renders the React root element
|
|
6
|
+
and exposes the mount point via a `<meta name="dar-mount" ...>` tag.
|
|
7
|
+
|
|
8
|
+
Only one HTML file lives here. All routing happens client-side via
|
|
9
|
+
React Router. The view that renders this template is
|
|
10
|
+
`django_admin_react.views.SpaIndexView` (PR #6).
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{% load static %}<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<meta name="dar-mount" content="{{ mount_point }}" />
|
|
7
|
+
<title>Django Admin</title>
|
|
8
|
+
<link rel="icon" href="data:," />
|
|
9
|
+
{% csrf_token %}
|
|
10
|
+
{% if bundle %}
|
|
11
|
+
{% for css_path in bundle.css %}
|
|
12
|
+
<link rel="stylesheet" href="{% static 'admin_react/' %}{{ css_path }}" />
|
|
13
|
+
{% endfor %}
|
|
14
|
+
<script type="module" src="{% static 'admin_react/' %}{{ bundle.file }}"></script>
|
|
15
|
+
{% endif %}
|
|
16
|
+
</head>
|
|
17
|
+
<body class="bg-gray-50 text-gray-900 antialiased">
|
|
18
|
+
<div id="root">
|
|
19
|
+
{% if not bundle %}
|
|
20
|
+
<main style="max-width: 60ch; margin: 4rem auto; font-family: system-ui, sans-serif; line-height: 1.5;">
|
|
21
|
+
<h1>django-admin-react</h1>
|
|
22
|
+
<p>The React bundle is not built yet.</p>
|
|
23
|
+
<p>From the repo root, run:</p>
|
|
24
|
+
<pre style="background:#f3f4f6;padding:.5rem .75rem;border-radius:.25rem;">pnpm install
|
|
25
|
+
pnpm run build:vite</pre>
|
|
26
|
+
<p>Then refresh this page.</p>
|
|
27
|
+
</main>
|
|
28
|
+
{% else %}
|
|
29
|
+
<noscript>This page requires JavaScript.</noscript>
|
|
30
|
+
{% endif %}
|
|
31
|
+
</div>
|
|
32
|
+
</body>
|
|
33
|
+
</html>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Top-level URL configuration for django_admin_react.
|
|
2
|
+
|
|
3
|
+
This module is intended to be `include()`d by the consumer at any
|
|
4
|
+
prefix:
|
|
5
|
+
|
|
6
|
+
from django.urls import include, path
|
|
7
|
+
urlpatterns = [
|
|
8
|
+
path("admin-react/", include("django_admin_react.urls")),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
The patterns below are split into two groups:
|
|
12
|
+
|
|
13
|
+
1. `api/v1/...` — JSON endpoints documented in `docs/api-contract.md`.
|
|
14
|
+
2. Everything else under the mount point falls through to the SPA
|
|
15
|
+
shell view, which serves `index.html` and lets React Router handle
|
|
16
|
+
client-side routes.
|
|
17
|
+
|
|
18
|
+
Implementation lands in PRs #3-#5 (backend) and #6 (frontend index).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from django.urls import include
|
|
24
|
+
from django.urls import path
|
|
25
|
+
from django.urls import re_path
|
|
26
|
+
|
|
27
|
+
from django_admin_react import views
|
|
28
|
+
|
|
29
|
+
app_name = "django_admin_react"
|
|
30
|
+
|
|
31
|
+
urlpatterns: list = [
|
|
32
|
+
# API endpoints. No URL namespace: the SPA builds these URLs from
|
|
33
|
+
# the wire contract (see ``docs/api-contract.md``), not via Django's
|
|
34
|
+
# ``reverse()``, so a namespace would be dead weight.
|
|
35
|
+
path("api/v1/", include("django_admin_react.api.urls")),
|
|
36
|
+
# SPA fallback — implemented in PR #6. The catch-all is intentionally
|
|
37
|
+
# last so any future server-rendered route can take precedence.
|
|
38
|
+
re_path(r"^.*$", views.SpaIndexView.as_view(), name="spa_index"),
|
|
39
|
+
]
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""SPA entry point view.
|
|
2
|
+
|
|
3
|
+
Serves the built React single-page app to authenticated staff. The
|
|
4
|
+
SPA's bundled assets ship with the package under
|
|
5
|
+
``django_admin_react/static/admin_react/`` and a Django template at
|
|
6
|
+
``django_admin_react/templates/admin_react/index.html`` references
|
|
7
|
+
them via a Vite-emitted manifest.
|
|
8
|
+
|
|
9
|
+
The view's only jobs are:
|
|
10
|
+
|
|
11
|
+
1. Enforce the same authentication gate as the rest of the package
|
|
12
|
+
(active + staff, or whatever ``AdminSite.has_permission`` says).
|
|
13
|
+
2. Render ``index.html`` with the resolved mount point and the
|
|
14
|
+
bundle filenames from the Vite manifest so the SPA can construct
|
|
15
|
+
API URLs without hardcoding (`ARCHITECTURE.md` §4.5).
|
|
16
|
+
|
|
17
|
+
This view never returns 404 for "no manifest yet" — a consumer
|
|
18
|
+
who installed the wheel always has a manifest. In development the
|
|
19
|
+
helper :func:`_load_manifest` returns ``None`` and the template
|
|
20
|
+
falls back to a friendly "the SPA is not built; run pnpm build:vite"
|
|
21
|
+
message, so a contributor running ``runserver`` without having built
|
|
22
|
+
the frontend gets a clear next step instead of a JS error.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import json
|
|
28
|
+
from functools import lru_cache
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
from django.conf import settings
|
|
33
|
+
from django.http import HttpRequest
|
|
34
|
+
from django.http import HttpResponse
|
|
35
|
+
from django.middleware.csrf import get_token
|
|
36
|
+
from django.shortcuts import redirect
|
|
37
|
+
from django.shortcuts import render
|
|
38
|
+
from django.urls import reverse_lazy
|
|
39
|
+
from django.views.generic import View
|
|
40
|
+
|
|
41
|
+
from django_admin_react.api.permissions import is_admin_user
|
|
42
|
+
from django_admin_react.api.registry import get_admin_site
|
|
43
|
+
|
|
44
|
+
# Path the Vite build writes its manifest to (matches
|
|
45
|
+
# ``frontend/apps/web/vite.config.ts``'s build.outDir + manifest).
|
|
46
|
+
_STATIC_ROOT: Path = Path(__file__).resolve().parent / "static" / "admin_react"
|
|
47
|
+
_MANIFEST_PATH: Path = _STATIC_ROOT / ".vite" / "manifest.json"
|
|
48
|
+
_ENTRY_KEY: str = "index.html"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class SpaIndexView(View):
|
|
52
|
+
"""Serves the built React SPA at any URL the consumer mounts.
|
|
53
|
+
|
|
54
|
+
Per the wire contract, the same auth gate that protects the API
|
|
55
|
+
protects this view too — there's no point serving a SPA shell to
|
|
56
|
+
a non-staff user only to have every API call return 403.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
http_method_names = ["get"]
|
|
60
|
+
|
|
61
|
+
def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
|
|
62
|
+
# noqa: ARG002 — args/kwargs only present to satisfy CBV signature.
|
|
63
|
+
admin_site = get_admin_site()
|
|
64
|
+
if not is_admin_user(request, admin_site=admin_site):
|
|
65
|
+
return _redirect_to_login(request)
|
|
66
|
+
|
|
67
|
+
# Force CSRF cookie so the SPA can read it before any unsafe
|
|
68
|
+
# method (the fetch client attaches it as ``X-CSRFToken``).
|
|
69
|
+
get_token(request)
|
|
70
|
+
|
|
71
|
+
return render(
|
|
72
|
+
request,
|
|
73
|
+
"admin_react/index.html",
|
|
74
|
+
{
|
|
75
|
+
"mount_point": _mount_from_request(request),
|
|
76
|
+
"bundle": _load_manifest_entry(),
|
|
77
|
+
},
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# --------------------------------------------------------------------------- #
|
|
82
|
+
# Manifest loading #
|
|
83
|
+
# --------------------------------------------------------------------------- #
|
|
84
|
+
@lru_cache(maxsize=1)
|
|
85
|
+
def _load_manifest_entry() -> dict[str, Any] | None:
|
|
86
|
+
"""Read the Vite manifest and return the entry record, or ``None``.
|
|
87
|
+
|
|
88
|
+
Cached in-process — the manifest file is immutable for a given
|
|
89
|
+
deploy. If you need to test a rebuilt SPA in the same Django
|
|
90
|
+
process, call ``_load_manifest_entry.cache_clear()``.
|
|
91
|
+
"""
|
|
92
|
+
if not _MANIFEST_PATH.is_file():
|
|
93
|
+
return None
|
|
94
|
+
try:
|
|
95
|
+
manifest: dict[str, Any] = json.loads(_MANIFEST_PATH.read_text("utf-8"))
|
|
96
|
+
except (OSError, json.JSONDecodeError):
|
|
97
|
+
return None
|
|
98
|
+
entry = manifest.get(_ENTRY_KEY)
|
|
99
|
+
if not isinstance(entry, dict):
|
|
100
|
+
return None
|
|
101
|
+
return entry
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# --------------------------------------------------------------------------- #
|
|
105
|
+
# Helpers #
|
|
106
|
+
# --------------------------------------------------------------------------- #
|
|
107
|
+
def _mount_from_request(request: HttpRequest) -> str:
|
|
108
|
+
"""Reconstruct the consumer-chosen mount prefix from ``request.path``.
|
|
109
|
+
|
|
110
|
+
The SPA index view is the catch-all under the mount, so
|
|
111
|
+
``request.path`` is *already* a path under the mount. We can
|
|
112
|
+
take everything up to the last path segment that the SPA itself
|
|
113
|
+
serves and treat it as the mount. The simplest stable signal is
|
|
114
|
+
the URL that ``urls.py`` registered — but to stay loose-coupled,
|
|
115
|
+
we just return everything up to and including the FIRST segment.
|
|
116
|
+
The SPA's React Router uses this as its basename.
|
|
117
|
+
"""
|
|
118
|
+
path = request.path
|
|
119
|
+
if not path.startswith("/"):
|
|
120
|
+
return "/"
|
|
121
|
+
# First path component after the leading slash.
|
|
122
|
+
parts = path.split("/", 2)
|
|
123
|
+
if len(parts) < 3:
|
|
124
|
+
return path if path.endswith("/") else path + "/"
|
|
125
|
+
return f"/{parts[1]}/"
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _redirect_to_login(request: HttpRequest) -> HttpResponse:
|
|
129
|
+
"""Redirect unauthenticated requests to the configured login URL.
|
|
130
|
+
|
|
131
|
+
Uses ``settings.LOGIN_URL`` if set (Django's standard), else
|
|
132
|
+
falls back to the standard admin login. The ``next`` query
|
|
133
|
+
parameter brings the user back to the SPA after login.
|
|
134
|
+
"""
|
|
135
|
+
login_url = getattr(settings, "LOGIN_URL", reverse_lazy("admin:login"))
|
|
136
|
+
return redirect(f"{login_url}?next={request.path}")
|
|
@@ -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,33 @@
|
|
|
1
|
+
LICENSE,sha256=vbkFSjylBXFGOBG4pUYgXw6RkgBDuw9w9DBacGhDNKA,1116
|
|
2
|
+
django_admin_react/README.md,sha256=nCl2QmRdauW1lWjMwbPhjfmY03s1iXWm9dGSQ5EseEo,2793
|
|
3
|
+
django_admin_react/__init__.py,sha256=KXYG1yTFWGr4mKUvmiT1rniQtB0qoUdaXn1AZ7RM40o,364
|
|
4
|
+
django_admin_react/api/README.md,sha256=hT7ou_1WTRZC5OYA0qPF3f6G4KUpkB0RbCAuBfc4UyA,1386
|
|
5
|
+
django_admin_react/api/__init__.py,sha256=Oh7tZ8DkFP1bP-vB2T7ZJiW6KWlRdpY40ma2-8_oZfA,166
|
|
6
|
+
django_admin_react/api/permissions.py,sha256=fi6VZArADuzE11mXjD_CNzP4ao4Yl5WrwHlnIIeEe9Y,2808
|
|
7
|
+
django_admin_react/api/registry.py,sha256=QQ0Pgw_nmUEyrHB6MggWx9AXM2w100_A6ldQnOczQLg,7378
|
|
8
|
+
django_admin_react/api/serializers.py,sha256=X4v09qZXzEXdNZN0fun0nRdMjqweZ3aIDL7rcScteoQ,5845
|
|
9
|
+
django_admin_react/api/urls.py,sha256=7naic2qgjaFU_3PRCUsywoymOKB8AFDhAVjOs03IW0M,3030
|
|
10
|
+
django_admin_react/api/views/README.md,sha256=k4nalY18vtxtIYYMc5seBfkrCv1CPffJINhK0yOmdC0,1124
|
|
11
|
+
django_admin_react/api/views/__init__.py,sha256=88xF9wjBYX2jQUKaJGG8EH3zubcpCGCnURbJhDIvGDI,192
|
|
12
|
+
django_admin_react/api/views/create.py,sha256=LAAck3s9QVsD8juUeHcvn042Xjk1Mzks84ZEnYWuUFE,5107
|
|
13
|
+
django_admin_react/api/views/destroy.py,sha256=2XK_TGTGSjXaHMPqAkqbG9WA1TY78VnQzb29xCsmoRo,3218
|
|
14
|
+
django_admin_react/api/views/detail.py,sha256=WheM7HkGbvK1Maf3PJUhCNHsRCneK_vAimGgeMkMB-A,10068
|
|
15
|
+
django_admin_react/api/views/list.py,sha256=18F_G_bYF_LbCjYPp5ZMtQ0uGtKZ8pOjPAVqXgwLcS0,9797
|
|
16
|
+
django_admin_react/api/views/registry.py,sha256=rmgis5zyvsiMCEwFcIIIeJEt94-pSJ4gBQNfokYN1kc,2199
|
|
17
|
+
django_admin_react/api/views/update.py,sha256=ae8hHmDuvUHjtG8tW4MEoeAW6Eccuslh-H-JGiD6iuw,4697
|
|
18
|
+
django_admin_react/api/writes.py,sha256=rn-2Ktn1kCsJddaLFQdLnj7kgAWJH4Pl8BcdoMU4Ju4,12995
|
|
19
|
+
django_admin_react/apps.py,sha256=jzc-OhkXzOIs-ECIiLvPzvrn5RlwAmzUO0C7mRT_g3w,1082
|
|
20
|
+
django_admin_react/conf.py,sha256=ge2qjEZpgkjlsPI64Q3QlhZHX3OtRjD6n49o2s9MVYc,2531
|
|
21
|
+
django_admin_react/static/admin_react/.vite/manifest.json,sha256=pi7U9ZjOBtmzXf8M-7wHytHZHjg9-UKdqMzAArtMXTk,185
|
|
22
|
+
django_admin_react/static/admin_react/assets/index-CKxeWYBA.css,sha256=g2T-JHJD1DxinEjk5m04OZhvIpVaW_hMSCwmVL8Pe6Q,12732
|
|
23
|
+
django_admin_react/static/admin_react/assets/index-itk7hrnq.js,sha256=o5sqG3_tSta2J4sCRpGs5LlV0cctaS8GebinChvU7gs,177490
|
|
24
|
+
django_admin_react/static/admin_react/assets/index-itk7hrnq.js.map,sha256=hb1tk5b7Ngn_I1gKu5fQfDw2gb2MYHzJIsJ2mxQJE8c,758262
|
|
25
|
+
django_admin_react/static/admin_react/index.html,sha256=q7ph2QOkKCZUOjOLAuYNyCM2UWaX60UH39zKTNVjAnU,448
|
|
26
|
+
django_admin_react/templates/admin_react/README.md,sha256=pq5twRCaqYruNXJJRXmvch6ho0O5F4TF17_2I6Mg6R8,403
|
|
27
|
+
django_admin_react/templates/admin_react/index.html,sha256=ra-YJnNZVKNQMt0XKT441FA47qWLytE-Wnwp49Lkm5k,1238
|
|
28
|
+
django_admin_react/urls.py,sha256=ZmWFV0XznqIIEY0ZCWvD3xM88bZ-CX_jiS96gT55zl0,1321
|
|
29
|
+
django_admin_react/views.py,sha256=bgDUUUhs2wSRbxeabQWmADAvDtBK0wRmLETKxL4jVjI,5427
|
|
30
|
+
django_admin_react-0.1.0a1.dist-info/LICENSE,sha256=vbkFSjylBXFGOBG4pUYgXw6RkgBDuw9w9DBacGhDNKA,1116
|
|
31
|
+
django_admin_react-0.1.0a1.dist-info/METADATA,sha256=wMASTHKXuhhwRIUmYS_RKXvm3RMMTy2xHzKURVQrt1M,9588
|
|
32
|
+
django_admin_react-0.1.0a1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
33
|
+
django_admin_react-0.1.0a1.dist-info/RECORD,,
|