django-admin-react 1.1.0__tar.gz → 1.2.0__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 (27) hide show
  1. {django_admin_react-1.1.0 → django_admin_react-1.2.0}/PKG-INFO +44 -16
  2. {django_admin_react-1.1.0 → django_admin_react-1.2.0}/README.md +43 -15
  3. {django_admin_react-1.1.0 → django_admin_react-1.2.0}/django_admin_react/conf.py +12 -0
  4. {django_admin_react-1.1.0 → django_admin_react-1.2.0}/django_admin_react/static/admin_react/.vite/manifest.json +3 -3
  5. django_admin_react-1.1.0/django_admin_react/static/admin_react/assets/JsonViewer-7Pvk14Q0.js → django_admin_react-1.2.0/django_admin_react/static/admin_react/assets/JsonViewer-LPoiv-e5.js +1 -1
  6. django_admin_react-1.1.0/django_admin_react/static/admin_react/assets/index-BAk9ZaWt.js → django_admin_react-1.2.0/django_admin_react/static/admin_react/assets/index-BkdeALer.js +4 -4
  7. django_admin_react-1.2.0/django_admin_react/static/admin_react/assets/index-DMT_bXJw.css +1 -0
  8. {django_admin_react-1.1.0 → django_admin_react-1.2.0}/django_admin_react/static/admin_react/index.html +2 -2
  9. django_admin_react-1.2.0/django_admin_react/templates/admin/base_site.html +37 -0
  10. django_admin_react-1.2.0/django_admin_react/templates/django_admin_react/_experience_toggle_strip.html +22 -0
  11. django_admin_react-1.2.0/django_admin_react/templatetags/__init__.py +0 -0
  12. django_admin_react-1.2.0/django_admin_react/templatetags/experience_toggle.py +82 -0
  13. {django_admin_react-1.1.0 → django_admin_react-1.2.0}/pyproject.toml +1 -1
  14. django_admin_react-1.1.0/django_admin_react/static/admin_react/assets/index-CAPQqmPH.css +0 -1
  15. {django_admin_react-1.1.0 → django_admin_react-1.2.0}/LICENSE +0 -0
  16. {django_admin_react-1.1.0 → django_admin_react-1.2.0}/django_admin_react/README.md +0 -0
  17. {django_admin_react-1.1.0 → django_admin_react-1.2.0}/django_admin_react/__init__.py +0 -0
  18. {django_admin_react-1.1.0 → django_admin_react-1.2.0}/django_admin_react/apps.py +0 -0
  19. {django_admin_react-1.1.0 → django_admin_react-1.2.0}/django_admin_react/audit.py +0 -0
  20. {django_admin_react-1.1.0 → django_admin_react-1.2.0}/django_admin_react/pwa.py +0 -0
  21. {django_admin_react-1.1.0 → django_admin_react-1.2.0}/django_admin_react/templates/README.md +0 -0
  22. {django_admin_react-1.1.0 → django_admin_react-1.2.0}/django_admin_react/templates/admin_react/README.md +0 -0
  23. {django_admin_react-1.1.0 → django_admin_react-1.2.0}/django_admin_react/templates/admin_react/index.html +0 -0
  24. {django_admin_react-1.1.0 → django_admin_react-1.2.0}/django_admin_react/templates/admin_react/login.html +0 -0
  25. {django_admin_react-1.1.0 → django_admin_react-1.2.0}/django_admin_react/templates/admin_react/sw.js +0 -0
  26. {django_admin_react-1.1.0 → django_admin_react-1.2.0}/django_admin_react/urls.py +0 -0
  27. {django_admin_react-1.1.0 → django_admin_react-1.2.0}/django_admin_react/views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: django-admin-react
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: A drop-in React single-page admin for Django, driven entirely by ModelAdmin.
5
5
  License: MIT
6
6
  Keywords: django,admin,react,spa,tailwind
@@ -298,30 +298,58 @@ urlpatterns = [
298
298
  ]
299
299
  ```
300
300
 
301
- #### Legacy-admin escape hatch (optional)
301
+ #### Experience-toggle strip (optional)
302
302
 
303
- During the rollout, show an in-SPA banner on every page that links to
304
- **the same page** in the original admin. End-users with muscle memory
305
- for `/admin/` (or bookmarks pointing at it) can return to the classic
306
- surface in one click while you close feature gaps in the SPA:
303
+ During the rollout, show a thin **persistent** strip at the top of
304
+ every page on **both** admins that links to the same page on the
305
+ other admin. Users can switch surfaces in one click, regardless of
306
+ which one they're on:
307
307
 
308
308
  ```python
309
309
  # settings.py
310
310
  DJANGO_ADMIN_REACT = {
311
- "LEGACY_ADMIN_URL_PREFIX": "admin/", # the legacy admin's mount
311
+ "LEGACY_ADMIN_URL_PREFIX": "admin/", # the legacy admin's mount
312
+ "REACT_ADMIN_URL_PREFIX": "admin2/", # this package's mount
312
313
  }
313
314
  ```
314
315
 
315
- The value must match the prefix you mounted the legacy admin under in
316
- `urls.py`. When set, the SPA renders a thin notice banner at the top
317
- of every page linking the matching legacy URL (computed by swapping
318
- the prefix — both admins honour the same `app_label/model_name/...`
319
- URL shape). The banner is dismissible per session (`sessionStorage`),
320
- so each session shows it once and a user who forgot the escape hatch
321
- exists is reminded the next time they log in.
316
+ Both values must match the prefixes you used in `urls.py`. When set:
322
317
 
323
- When you remove the setting (or set it to `None`), the banner
324
- disappears on the next page load completing the migration.
318
+ - The **React SPA** renders a strip linking the same path under the
319
+ legacy admin's mount (with `?query=string` preserved and a trailing
320
+ slash, since Django admin URLs require one).
321
+ - The **legacy Django admin** renders the mirror strip linking the
322
+ matching React URL.
323
+
324
+ Set `LEGACY_ADMIN_URL_PREFIX` alone if you only want the SPA → legacy
325
+ direction (reverse direction stays off).
326
+
327
+ ##### `INSTALLED_APPS` ordering
328
+
329
+ For the legacy-side strip, list `django_admin_react` **before**
330
+ `django.contrib.admin`. Django's template loader resolves
331
+ `admin/base_site.html` left-to-right and the first match wins —
332
+ the package's override of that template injects the strip:
333
+
334
+ ```python
335
+ INSTALLED_APPS = [
336
+ "django_admin_react", # ← BEFORE django.contrib.admin
337
+ "django.contrib.admin",
338
+ # ...
339
+ ]
340
+ ```
341
+
342
+ If you don't enable the legacy-side strip (`REACT_ADMIN_URL_PREFIX`
343
+ unset) the ordering doesn't matter — the override is a no-op for
344
+ consumers who haven't opted in.
345
+
346
+ ##### UX contract
347
+
348
+ The strip is **subtle and persistent**: one line tall, neutral
349
+ chrome, no dismiss control. Operators turn it on/off via the
350
+ settings; end-users do not. When you remove the settings (or set
351
+ them to `None`), the strips disappear on the next page load —
352
+ completing the migration.
325
353
 
326
354
  ---
327
355
 
@@ -265,30 +265,58 @@ urlpatterns = [
265
265
  ]
266
266
  ```
267
267
 
268
- #### Legacy-admin escape hatch (optional)
268
+ #### Experience-toggle strip (optional)
269
269
 
270
- During the rollout, show an in-SPA banner on every page that links to
271
- **the same page** in the original admin. End-users with muscle memory
272
- for `/admin/` (or bookmarks pointing at it) can return to the classic
273
- surface in one click while you close feature gaps in the SPA:
270
+ During the rollout, show a thin **persistent** strip at the top of
271
+ every page on **both** admins that links to the same page on the
272
+ other admin. Users can switch surfaces in one click, regardless of
273
+ which one they're on:
274
274
 
275
275
  ```python
276
276
  # settings.py
277
277
  DJANGO_ADMIN_REACT = {
278
- "LEGACY_ADMIN_URL_PREFIX": "admin/", # the legacy admin's mount
278
+ "LEGACY_ADMIN_URL_PREFIX": "admin/", # the legacy admin's mount
279
+ "REACT_ADMIN_URL_PREFIX": "admin2/", # this package's mount
279
280
  }
280
281
  ```
281
282
 
282
- The value must match the prefix you mounted the legacy admin under in
283
- `urls.py`. When set, the SPA renders a thin notice banner at the top
284
- of every page linking the matching legacy URL (computed by swapping
285
- the prefix — both admins honour the same `app_label/model_name/...`
286
- URL shape). The banner is dismissible per session (`sessionStorage`),
287
- so each session shows it once and a user who forgot the escape hatch
288
- exists is reminded the next time they log in.
283
+ Both values must match the prefixes you used in `urls.py`. When set:
289
284
 
290
- When you remove the setting (or set it to `None`), the banner
291
- disappears on the next page load completing the migration.
285
+ - The **React SPA** renders a strip linking the same path under the
286
+ legacy admin's mount (with `?query=string` preserved and a trailing
287
+ slash, since Django admin URLs require one).
288
+ - The **legacy Django admin** renders the mirror strip linking the
289
+ matching React URL.
290
+
291
+ Set `LEGACY_ADMIN_URL_PREFIX` alone if you only want the SPA → legacy
292
+ direction (reverse direction stays off).
293
+
294
+ ##### `INSTALLED_APPS` ordering
295
+
296
+ For the legacy-side strip, list `django_admin_react` **before**
297
+ `django.contrib.admin`. Django's template loader resolves
298
+ `admin/base_site.html` left-to-right and the first match wins —
299
+ the package's override of that template injects the strip:
300
+
301
+ ```python
302
+ INSTALLED_APPS = [
303
+ "django_admin_react", # ← BEFORE django.contrib.admin
304
+ "django.contrib.admin",
305
+ # ...
306
+ ]
307
+ ```
308
+
309
+ If you don't enable the legacy-side strip (`REACT_ADMIN_URL_PREFIX`
310
+ unset) the ordering doesn't matter — the override is a no-op for
311
+ consumers who haven't opted in.
312
+
313
+ ##### UX contract
314
+
315
+ The strip is **subtle and persistent**: one line tall, neutral
316
+ chrome, no dismiss control. Operators turn it on/off via the
317
+ settings; end-users do not. When you remove the settings (or set
318
+ them to `None`), the strips disappear on the next page load —
319
+ completing the migration.
292
320
 
293
321
  ---
294
322
 
@@ -118,6 +118,17 @@ DEFAULTS: dict[str, Any] = {
118
118
  # mapping). When the matching legacy route doesn't exist, the
119
119
  # legacy admin 404s — same outcome as visiting that URL directly.
120
120
  "LEGACY_ADMIN_URL_PREFIX": None,
121
+ # ``REACT_ADMIN_URL_PREFIX`` — sibling of ``LEGACY_ADMIN_URL_PREFIX``
122
+ # (#584). When **both** are set, the legacy Django admin renders a
123
+ # mirror strip at the top of every page linking the same path under
124
+ # the React admin's mount — so a user on either surface can swap
125
+ # to the other in one click. The value is the prefix the SPA was
126
+ # mounted at in ``urls.py`` — e.g. ``"admin2/"`` for
127
+ # ``urlpatterns = [path("admin2/", include("django_admin_react.urls")), ...]``.
128
+ # When only ``LEGACY_ADMIN_URL_PREFIX`` is set, only the SPA-side
129
+ # strip renders (reverse direction stays off — no implicit guess
130
+ # at the consumer's chosen mount).
131
+ "REACT_ADMIN_URL_PREFIX": None,
121
132
  "PWA_NAME": None,
122
133
  "PWA_SHORT_NAME": None,
123
134
  "PWA_ICONS": None,
@@ -142,6 +153,7 @@ class _PackageSettings:
142
153
  REACT_LOGIN: bool = DEFAULTS["REACT_LOGIN"]
143
154
  API_URL_PREFIX: str | None = DEFAULTS["API_URL_PREFIX"]
144
155
  LEGACY_ADMIN_URL_PREFIX: str | None = DEFAULTS["LEGACY_ADMIN_URL_PREFIX"]
156
+ REACT_ADMIN_URL_PREFIX: str | None = DEFAULTS["REACT_ADMIN_URL_PREFIX"]
145
157
  PWA_NAME: str | None = DEFAULTS["PWA_NAME"]
146
158
  PWA_SHORT_NAME: str | None = DEFAULTS["PWA_SHORT_NAME"]
147
159
  PWA_ICONS: list[dict[str, str]] | None = DEFAULTS["PWA_ICONS"]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "../../packages/details/src/JsonViewer.tsx": {
3
- "file": "assets/JsonViewer-7Pvk14Q0.js",
3
+ "file": "assets/JsonViewer-LPoiv-e5.js",
4
4
  "name": "JsonViewer",
5
5
  "src": "../../packages/details/src/JsonViewer.tsx",
6
6
  "isDynamicEntry": true,
@@ -9,7 +9,7 @@
9
9
  ]
10
10
  },
11
11
  "index.html": {
12
- "file": "assets/index-BAk9ZaWt.js",
12
+ "file": "assets/index-BkdeALer.js",
13
13
  "name": "index",
14
14
  "src": "index.html",
15
15
  "isEntry": true,
@@ -17,7 +17,7 @@
17
17
  "../../packages/details/src/JsonViewer.tsx"
18
18
  ],
19
19
  "css": [
20
- "assets/index-CAPQqmPH.css"
20
+ "assets/index-DMT_bXJw.css"
21
21
  ]
22
22
  }
23
23
  }
@@ -1 +1 @@
1
- import{c as x,r as l,j as e,C as h}from"./index-BAk9ZaWt.js";const p=[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]],g=x("chevron-right",p);const y=[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]],j=x("copy",y);function N({raw:r,parsed:t}){const[s,c]=l.useState(!1);async function a(){try{await navigator.clipboard.writeText(r),c(!0),setTimeout(()=>c(!1),2e3)}catch{}}return e.jsxs("div",{className:"relative w-full overflow-x-auto rounded border border-gray-200 bg-gray-50 p-3 font-mono text-xs leading-relaxed text-gray-800 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-200",children:[e.jsx("button",{type:"button",onClick:a,"aria-label":"Copy JSON",title:s?"Copied":"Copy",className:"absolute right-2 top-2 inline-flex h-6 w-6 items-center justify-center rounded border border-gray-300 bg-white text-gray-600 hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700",children:s?e.jsx(h,{className:"h-3.5 w-3.5 text-green-600","aria-hidden":!0}):e.jsx(j,{className:"h-3.5 w-3.5","aria-hidden":!0})}),e.jsx(o,{value:t,depth:0})]})}function o({value:r,depth:t}){return r===null?e.jsx("span",{className:"text-purple-600 dark:text-purple-400",children:"null"}):typeof r=="boolean"?e.jsx("span",{className:"text-purple-600 dark:text-purple-400",children:r?"true":"false"}):typeof r=="number"?e.jsx("span",{className:"text-blue-700",children:String(r)}):typeof r=="string"?e.jsxs("span",{className:"text-green-700 dark:text-green-400",children:['"',r,'"']}):Array.isArray(r)?e.jsx(u,{value:r,depth:t}):typeof r=="object"?e.jsx(m,{value:r,depth:t}):e.jsx("span",{className:"text-gray-500",children:String(r)})}function m({value:r,depth:t}){const s=Object.keys(r),[c,a]=l.useState(t<2);return s.length===0?e.jsx("span",{className:"text-gray-500",children:"{}"}):e.jsx(d,{open:c,onToggle:()=>a(n=>!n),collapsedLabel:`{…} ${s.length} ${s.length===1?"key":"keys"}`,openBracket:"{",closeBracket:"}",depth:t,children:s.map((n,i)=>e.jsxs("div",{className:"pl-4",children:[e.jsxs("span",{className:"text-rose-700 dark:text-rose-400",children:['"',n,'"']}),e.jsx("span",{className:"text-gray-500",children:": "}),e.jsx(o,{value:r[n],depth:t+1}),i<s.length-1?e.jsx("span",{className:"text-gray-500",children:","}):null]},n))})}function u({value:r,depth:t}){const[s,c]=l.useState(t<2);return r.length===0?e.jsx("span",{className:"text-gray-500",children:"[]"}):e.jsx(d,{open:s,onToggle:()=>c(a=>!a),collapsedLabel:`[…] ${r.length} ${r.length===1?"item":"items"}`,openBracket:"[",closeBracket:"]",depth:t,children:r.map((a,n)=>e.jsxs("div",{className:"pl-4",children:[e.jsx(o,{value:a,depth:t+1}),n<r.length-1?e.jsx("span",{className:"text-gray-500",children:","}):null]},n))})}function d({open:r,onToggle:t,collapsedLabel:s,openBracket:c,closeBracket:a,depth:n,children:i}){return e.jsxs("span",{children:[e.jsx("button",{type:"button",onClick:t,"aria-expanded":r,className:"inline-flex items-center align-baseline text-gray-500 hover:text-gray-800 dark:hover:text-gray-200",children:e.jsx(g,{className:`h-3 w-3 shrink-0 transition-transform ${r?"rotate-90":""}`,"aria-hidden":!0})}),e.jsx("span",{className:"text-gray-500",children:c}),r?e.jsxs(e.Fragment,{children:[i,e.jsx("div",{className:n===0?"":"pl-0",children:e.jsx("span",{className:"text-gray-500",children:a})})]}):e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"px-1 text-gray-500",children:s}),e.jsx("span",{className:"text-gray-500",children:a})]})]})}export{N as default};
1
+ import{c as x,r as l,j as e,C as h}from"./index-BkdeALer.js";const p=[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]],g=x("chevron-right",p);const y=[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]],j=x("copy",y);function N({raw:r,parsed:t}){const[s,c]=l.useState(!1);async function a(){try{await navigator.clipboard.writeText(r),c(!0),setTimeout(()=>c(!1),2e3)}catch{}}return e.jsxs("div",{className:"relative w-full overflow-x-auto rounded border border-gray-200 bg-gray-50 p-3 font-mono text-xs leading-relaxed text-gray-800 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-200",children:[e.jsx("button",{type:"button",onClick:a,"aria-label":"Copy JSON",title:s?"Copied":"Copy",className:"absolute right-2 top-2 inline-flex h-6 w-6 items-center justify-center rounded border border-gray-300 bg-white text-gray-600 hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700",children:s?e.jsx(h,{className:"h-3.5 w-3.5 text-green-600","aria-hidden":!0}):e.jsx(j,{className:"h-3.5 w-3.5","aria-hidden":!0})}),e.jsx(o,{value:t,depth:0})]})}function o({value:r,depth:t}){return r===null?e.jsx("span",{className:"text-purple-600 dark:text-purple-400",children:"null"}):typeof r=="boolean"?e.jsx("span",{className:"text-purple-600 dark:text-purple-400",children:r?"true":"false"}):typeof r=="number"?e.jsx("span",{className:"text-blue-700",children:String(r)}):typeof r=="string"?e.jsxs("span",{className:"text-green-700 dark:text-green-400",children:['"',r,'"']}):Array.isArray(r)?e.jsx(u,{value:r,depth:t}):typeof r=="object"?e.jsx(m,{value:r,depth:t}):e.jsx("span",{className:"text-gray-500",children:String(r)})}function m({value:r,depth:t}){const s=Object.keys(r),[c,a]=l.useState(t<2);return s.length===0?e.jsx("span",{className:"text-gray-500",children:"{}"}):e.jsx(d,{open:c,onToggle:()=>a(n=>!n),collapsedLabel:`{…} ${s.length} ${s.length===1?"key":"keys"}`,openBracket:"{",closeBracket:"}",depth:t,children:s.map((n,i)=>e.jsxs("div",{className:"pl-4",children:[e.jsxs("span",{className:"text-rose-700 dark:text-rose-400",children:['"',n,'"']}),e.jsx("span",{className:"text-gray-500",children:": "}),e.jsx(o,{value:r[n],depth:t+1}),i<s.length-1?e.jsx("span",{className:"text-gray-500",children:","}):null]},n))})}function u({value:r,depth:t}){const[s,c]=l.useState(t<2);return r.length===0?e.jsx("span",{className:"text-gray-500",children:"[]"}):e.jsx(d,{open:s,onToggle:()=>c(a=>!a),collapsedLabel:`[…] ${r.length} ${r.length===1?"item":"items"}`,openBracket:"[",closeBracket:"]",depth:t,children:r.map((a,n)=>e.jsxs("div",{className:"pl-4",children:[e.jsx(o,{value:a,depth:t+1}),n<r.length-1?e.jsx("span",{className:"text-gray-500",children:","}):null]},n))})}function d({open:r,onToggle:t,collapsedLabel:s,openBracket:c,closeBracket:a,depth:n,children:i}){return e.jsxs("span",{children:[e.jsx("button",{type:"button",onClick:t,"aria-expanded":r,className:"inline-flex items-center align-baseline text-gray-500 hover:text-gray-800 dark:hover:text-gray-200",children:e.jsx(g,{className:`h-3 w-3 shrink-0 transition-transform ${r?"rotate-90":""}`,"aria-hidden":!0})}),e.jsx("span",{className:"text-gray-500",children:c}),r?e.jsxs(e.Fragment,{children:[i,e.jsx("div",{className:n===0?"":"pl-0",children:e.jsx("span",{className:"text-gray-500",children:a})})]}):e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"px-1 text-gray-500",children:s}),e.jsx("span",{className:"text-gray-500",children:a})]})]})}export{N as default};