django-admin-react 1.4.12__tar.gz → 1.4.13__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_react-1.4.12 → django_admin_react-1.4.13}/PKG-INFO +119 -2
- {django_admin_react-1.4.12 → django_admin_react-1.4.13}/README.md +118 -1
- {django_admin_react-1.4.12 → django_admin_react-1.4.13}/django_admin_react/conf.py +17 -4
- {django_admin_react-1.4.12 → django_admin_react-1.4.13}/django_admin_react/static/admin_react/.vite/manifest.json +4 -4
- django_admin_react-1.4.12/django_admin_react/static/admin_react/assets/ColumnLayoutModal-CLIzQjlm.js → django_admin_react-1.4.13/django_admin_react/static/admin_react/assets/ColumnLayoutModal-Bqf6vN1j.js +1 -1
- django_admin_react-1.4.12/django_admin_react/static/admin_react/assets/JsonViewer-B0UvsOqx.js → django_admin_react-1.4.13/django_admin_react/static/admin_react/assets/JsonViewer-DkeJkG12.js +1 -1
- django_admin_react-1.4.12/django_admin_react/static/admin_react/assets/index-C3ZSD987.js → django_admin_react-1.4.13/django_admin_react/static/admin_react/assets/index-CGh8t5dj.js +6 -6
- django_admin_react-1.4.13/django_admin_react/static/admin_react/assets/index-CpxUkcdm.css +1 -0
- {django_admin_react-1.4.12 → django_admin_react-1.4.13}/django_admin_react/static/admin_react/index.html +2 -2
- {django_admin_react-1.4.12 → django_admin_react-1.4.13}/django_admin_react/views.py +21 -6
- {django_admin_react-1.4.12 → django_admin_react-1.4.13}/pyproject.toml +1 -1
- django_admin_react-1.4.12/django_admin_react/static/admin_react/assets/index-DRQ2gAuA.css +0 -1
- {django_admin_react-1.4.12 → django_admin_react-1.4.13}/LICENSE +0 -0
- {django_admin_react-1.4.12 → django_admin_react-1.4.13}/django_admin_react/README.md +0 -0
- {django_admin_react-1.4.12 → django_admin_react-1.4.13}/django_admin_react/__init__.py +0 -0
- {django_admin_react-1.4.12 → django_admin_react-1.4.13}/django_admin_react/apps.py +0 -0
- {django_admin_react-1.4.12 → django_admin_react-1.4.13}/django_admin_react/audit.py +0 -0
- {django_admin_react-1.4.12 → django_admin_react-1.4.13}/django_admin_react/pwa.py +0 -0
- {django_admin_react-1.4.12 → django_admin_react-1.4.13}/django_admin_react/templates/README.md +0 -0
- {django_admin_react-1.4.12 → django_admin_react-1.4.13}/django_admin_react/templates/admin/base_site.html +0 -0
- {django_admin_react-1.4.12 → django_admin_react-1.4.13}/django_admin_react/templates/admin_react/README.md +0 -0
- {django_admin_react-1.4.12 → django_admin_react-1.4.13}/django_admin_react/templates/admin_react/index.html +0 -0
- {django_admin_react-1.4.12 → django_admin_react-1.4.13}/django_admin_react/templates/admin_react/login.html +0 -0
- {django_admin_react-1.4.12 → django_admin_react-1.4.13}/django_admin_react/templates/admin_react/sw.js +0 -0
- {django_admin_react-1.4.12 → django_admin_react-1.4.13}/django_admin_react/templates/django_admin_react/_experience_toggle_strip.html +0 -0
- {django_admin_react-1.4.12 → django_admin_react-1.4.13}/django_admin_react/templatetags/__init__.py +0 -0
- {django_admin_react-1.4.12 → django_admin_react-1.4.13}/django_admin_react/templatetags/experience_toggle.py +0 -0
- {django_admin_react-1.4.12 → django_admin_react-1.4.13}/django_admin_react/urls.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-admin-react
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.13
|
|
4
4
|
Summary: A drop-in React single-page admin for Django, driven entirely by ModelAdmin.
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -620,7 +620,9 @@ all share the v1 wire contract. Per-feature live status below.
|
|
|
620
620
|
| `date_hierarchy` | ✅ |
|
|
621
621
|
| `list_editable` + bulk PATCH | ✅ |
|
|
622
622
|
| `actions` — batch + detail (signature-classified) | ✅ |
|
|
623
|
-
| `autocomplete_fields`
|
|
623
|
+
| `autocomplete_fields` | ✅ |
|
|
624
|
+
| `raw_id_fields` (pk text input + lookup popup) | 🟡 [#626](https://github.com/MartinCastroAlvarez/django-admin-react/issues/626) (API emits the hint; SPA still renders autocomplete) |
|
|
625
|
+
| `radio_fields` (inline radio buttons vs `<select>`) | 🟡 [#626](https://github.com/MartinCastroAlvarez/django-admin-react/issues/626) (API emits the hint; SPA still renders dropdown) |
|
|
624
626
|
| `ManyToManyField` read + write | ✅ |
|
|
625
627
|
| `inlines` (TabularInline / StackedInline) — read + write | ✅ |
|
|
626
628
|
| `FileField` / `ImageField` — read | ✅ |
|
|
@@ -636,6 +638,121 @@ all share the v1 wire contract. Per-feature live status below.
|
|
|
636
638
|
|
|
637
639
|
✅ = shipped. 🟡 = not yet built (tracked).
|
|
638
640
|
|
|
641
|
+
### Stock-Django `ModelAdmin` hooks that do NOT carry through to the SPA
|
|
642
|
+
|
|
643
|
+
The SPA renders from the JSON wire — it never sees the consumer's
|
|
644
|
+
Django HTML templates, custom widgets, or `get_urls()` views. The
|
|
645
|
+
hooks below are stock-Django extension points the SPA cannot honour
|
|
646
|
+
today; if your admin uses any of them, the surface behaves
|
|
647
|
+
differently on the SPA than on the legacy `/admin/`. Tracking
|
|
648
|
+
issues link the work to close each gap.
|
|
649
|
+
|
|
650
|
+
| Stock-Django hook | SPA behaviour | Tracked |
|
|
651
|
+
|---|---|---|
|
|
652
|
+
| `change_form_template` / `change_list_template` / `add_form_template` / `change_password_template` / `object_history_template` overrides | Silently ignored — the SPA renders entirely from the JSON wire. | [#624](https://github.com/MartinCastroAlvarez/django-admin-react/issues/624) |
|
|
653
|
+
| `formfield_overrides = {Field: {"widget": CustomWidget}}` | Custom widget invisible — the SPA picks its own control from the field's `type`. No React-side widget-registration API yet. | [#625](https://github.com/MartinCastroAlvarez/django-admin-react/issues/625) |
|
|
654
|
+
| `raw_id_fields` | Falls back to the autocomplete picker (same as `autocomplete_fields`). Defeats the purpose for FKs with 10M+ rows where autocomplete `get_search_results` is too expensive. | [#626](https://github.com/MartinCastroAlvarez/django-admin-react/issues/626) |
|
|
655
|
+
| `radio_fields = {"status": admin.HORIZONTAL}` | Renders a `<select>` (default choice control) instead of inline radio buttons. | [#626](https://github.com/MartinCastroAlvarez/django-admin-react/issues/626) |
|
|
656
|
+
| `filter_horizontal` / `filter_vertical` (M2M shuttle widget) | Renders the generic multi-select checkbox list, not Django's two-pane shuttle. Switch the field to `autocomplete_fields` for a workable SPA UX. | [#627](https://github.com/MartinCastroAlvarez/django-admin-react/issues/627) |
|
|
657
|
+
| `GenericForeignKey` / `GenericInlineModelAdmin` | Support gap — verify per-model before relying on the SPA. | [#628](https://github.com/MartinCastroAlvarez/django-admin-react/issues/628) |
|
|
658
|
+
| `LANGUAGE_CODE` / `gettext` / `Accept-Language` | The SPA chrome stays English; translated `verbose_name` / `help_text` / `@admin.action(description=_("..."))` are not surfaced per-request. | [#630](https://github.com/MartinCastroAlvarez/django-admin-react/issues/630) |
|
|
659
|
+
| `ModelAdmin.get_urls()` custom views | Opens as a popout (`<a target="_blank">`) into the Django-rendered HTML page — no SPA chrome, no breadcrumb. The link IS surfaced; the UX is just outside the SPA. | [#623](https://github.com/MartinCastroAlvarez/django-admin-react/issues/623) |
|
|
660
|
+
| Django 4.2 LTS support | Not yet — the package pins `django >= 5.0,<7.0`. | [#622](https://github.com/MartinCastroAlvarez/django-admin-react/issues/622) |
|
|
661
|
+
|
|
662
|
+
If your admin relies on any "silently ignored" hook above, the
|
|
663
|
+
typical workaround is to keep that model on the legacy
|
|
664
|
+
`/admin/` surface via the
|
|
665
|
+
[experience-toggle strip](#experience-toggle-strip-optional) — the
|
|
666
|
+
SPA + legacy admin happily coexist.
|
|
667
|
+
|
|
668
|
+
---
|
|
669
|
+
|
|
670
|
+
## Writing safe `list_display` callables
|
|
671
|
+
|
|
672
|
+
This applies on **both** the legacy `/admin/` and the SPA — but the
|
|
673
|
+
SPA renders any `format_html` / `mark_safe` value via React's
|
|
674
|
+
`dangerouslySetInnerHTML`, so misuse is reflected XSS the same way
|
|
675
|
+
the legacy admin would be.
|
|
676
|
+
|
|
677
|
+
**Do not** interpolate user-controlled data into a `mark_safe(...)`
|
|
678
|
+
string. The whole point of `mark_safe` is "I have already escaped
|
|
679
|
+
this," and `f"<span>{obj.user_input}</span>"` has not — so a
|
|
680
|
+
`user_input` of `<script>alert(1)</script>` runs.
|
|
681
|
+
|
|
682
|
+
```python
|
|
683
|
+
# WRONG — copy-paste-from-StackOverflow XSS hazard.
|
|
684
|
+
@admin.display(description="Status")
|
|
685
|
+
def status_badge(self, obj):
|
|
686
|
+
return mark_safe(f'<span class="badge">{obj.user_input}</span>')
|
|
687
|
+
|
|
688
|
+
# RIGHT — format_html auto-escapes every interpolated arg.
|
|
689
|
+
@admin.display(description="Status")
|
|
690
|
+
def status_badge(self, obj):
|
|
691
|
+
return format_html('<span class="badge">{}</span>', obj.user_input)
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
Same rule for `readonly_fields` callables. See
|
|
695
|
+
[#633](https://github.com/MartinCastroAlvarez/django-admin-react/issues/633)
|
|
696
|
+
for the optional defense-in-depth `STRICT_HTML` setting tracking
|
|
697
|
+
issue (bleach-clean every rendered HTML value with a tight allow-list).
|
|
698
|
+
|
|
699
|
+
---
|
|
700
|
+
|
|
701
|
+
## Hardening
|
|
702
|
+
|
|
703
|
+
### Brute-force defense on `/api/v1/login/`
|
|
704
|
+
|
|
705
|
+
The package's React login endpoint (`<mount>/api/v1/login/`) reuses
|
|
706
|
+
Django's session auth, so the canonical brute-force defenses work
|
|
707
|
+
unchanged. The recommended layer is
|
|
708
|
+
[`django-axes`](https://pypi.org/project/django-axes/):
|
|
709
|
+
|
|
710
|
+
```python
|
|
711
|
+
# settings.py
|
|
712
|
+
INSTALLED_APPS = [..., "axes", "django_admin_react", "django_admin_rest_api"]
|
|
713
|
+
|
|
714
|
+
AUTHENTICATION_BACKENDS = [
|
|
715
|
+
"axes.backends.AxesStandaloneBackend",
|
|
716
|
+
"django.contrib.auth.backends.ModelBackend",
|
|
717
|
+
]
|
|
718
|
+
MIDDLEWARE = [..., "axes.middleware.AxesMiddleware"]
|
|
719
|
+
|
|
720
|
+
AXES_FAILURE_LIMIT = 5
|
|
721
|
+
AXES_COOLOFF_TIME = 1 # hour
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
Axes intercepts via `AUTHENTICATION_BACKENDS`, not URL middleware, so
|
|
725
|
+
lockouts apply to both the legacy admin login and the SPA's JSON
|
|
726
|
+
login automatically. Tracked: [#634](https://github.com/MartinCastroAlvarez/django-admin-react/issues/634).
|
|
727
|
+
|
|
728
|
+
### Mounting the API on a different origin (CORS + cookies)
|
|
729
|
+
|
|
730
|
+
`DJANGO_ADMIN_REACT["API_URL_PREFIX"]` lets the SPA point at a
|
|
731
|
+
separately-mounted REST API — e.g. SPA at `admin.example.com`
|
|
732
|
+
talking to an API at `api.example.com`. The session-cookie auth
|
|
733
|
+
across origins needs three settings configured together; if any
|
|
734
|
+
one is missing, every API call silently 401s after login.
|
|
735
|
+
|
|
736
|
+
```python
|
|
737
|
+
# settings.py — required when SPA and API are on different origins.
|
|
738
|
+
SESSION_COOKIE_SAMESITE = "None" # default "Lax" drops cookies cross-origin
|
|
739
|
+
SESSION_COOKIE_SECURE = True # required by browsers when SameSite=None
|
|
740
|
+
CSRF_COOKIE_SAMESITE = "None"
|
|
741
|
+
CSRF_COOKIE_SECURE = True
|
|
742
|
+
|
|
743
|
+
# pip install django-cors-headers
|
|
744
|
+
INSTALLED_APPS = [..., "corsheaders", ...]
|
|
745
|
+
MIDDLEWARE = ["corsheaders.middleware.CorsMiddleware", ...]
|
|
746
|
+
|
|
747
|
+
CORS_ALLOW_CREDENTIALS = True
|
|
748
|
+
CORS_ALLOWED_ORIGINS = ["https://admin.example.com"] # NEVER "*" with credentials
|
|
749
|
+
CSRF_TRUSTED_ORIGINS = ["https://admin.example.com"]
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
The SPA's HTTP client already sends `credentials: "include"`, so no
|
|
753
|
+
frontend change is needed — only the Django-side cookie + CORS
|
|
754
|
+
config above. Tracked: [#635](https://github.com/MartinCastroAlvarez/django-admin-react/issues/635).
|
|
755
|
+
|
|
639
756
|
---
|
|
640
757
|
|
|
641
758
|
## The API surface
|
|
@@ -585,7 +585,9 @@ all share the v1 wire contract. Per-feature live status below.
|
|
|
585
585
|
| `date_hierarchy` | ✅ |
|
|
586
586
|
| `list_editable` + bulk PATCH | ✅ |
|
|
587
587
|
| `actions` — batch + detail (signature-classified) | ✅ |
|
|
588
|
-
| `autocomplete_fields`
|
|
588
|
+
| `autocomplete_fields` | ✅ |
|
|
589
|
+
| `raw_id_fields` (pk text input + lookup popup) | 🟡 [#626](https://github.com/MartinCastroAlvarez/django-admin-react/issues/626) (API emits the hint; SPA still renders autocomplete) |
|
|
590
|
+
| `radio_fields` (inline radio buttons vs `<select>`) | 🟡 [#626](https://github.com/MartinCastroAlvarez/django-admin-react/issues/626) (API emits the hint; SPA still renders dropdown) |
|
|
589
591
|
| `ManyToManyField` read + write | ✅ |
|
|
590
592
|
| `inlines` (TabularInline / StackedInline) — read + write | ✅ |
|
|
591
593
|
| `FileField` / `ImageField` — read | ✅ |
|
|
@@ -601,6 +603,121 @@ all share the v1 wire contract. Per-feature live status below.
|
|
|
601
603
|
|
|
602
604
|
✅ = shipped. 🟡 = not yet built (tracked).
|
|
603
605
|
|
|
606
|
+
### Stock-Django `ModelAdmin` hooks that do NOT carry through to the SPA
|
|
607
|
+
|
|
608
|
+
The SPA renders from the JSON wire — it never sees the consumer's
|
|
609
|
+
Django HTML templates, custom widgets, or `get_urls()` views. The
|
|
610
|
+
hooks below are stock-Django extension points the SPA cannot honour
|
|
611
|
+
today; if your admin uses any of them, the surface behaves
|
|
612
|
+
differently on the SPA than on the legacy `/admin/`. Tracking
|
|
613
|
+
issues link the work to close each gap.
|
|
614
|
+
|
|
615
|
+
| Stock-Django hook | SPA behaviour | Tracked |
|
|
616
|
+
|---|---|---|
|
|
617
|
+
| `change_form_template` / `change_list_template` / `add_form_template` / `change_password_template` / `object_history_template` overrides | Silently ignored — the SPA renders entirely from the JSON wire. | [#624](https://github.com/MartinCastroAlvarez/django-admin-react/issues/624) |
|
|
618
|
+
| `formfield_overrides = {Field: {"widget": CustomWidget}}` | Custom widget invisible — the SPA picks its own control from the field's `type`. No React-side widget-registration API yet. | [#625](https://github.com/MartinCastroAlvarez/django-admin-react/issues/625) |
|
|
619
|
+
| `raw_id_fields` | Falls back to the autocomplete picker (same as `autocomplete_fields`). Defeats the purpose for FKs with 10M+ rows where autocomplete `get_search_results` is too expensive. | [#626](https://github.com/MartinCastroAlvarez/django-admin-react/issues/626) |
|
|
620
|
+
| `radio_fields = {"status": admin.HORIZONTAL}` | Renders a `<select>` (default choice control) instead of inline radio buttons. | [#626](https://github.com/MartinCastroAlvarez/django-admin-react/issues/626) |
|
|
621
|
+
| `filter_horizontal` / `filter_vertical` (M2M shuttle widget) | Renders the generic multi-select checkbox list, not Django's two-pane shuttle. Switch the field to `autocomplete_fields` for a workable SPA UX. | [#627](https://github.com/MartinCastroAlvarez/django-admin-react/issues/627) |
|
|
622
|
+
| `GenericForeignKey` / `GenericInlineModelAdmin` | Support gap — verify per-model before relying on the SPA. | [#628](https://github.com/MartinCastroAlvarez/django-admin-react/issues/628) |
|
|
623
|
+
| `LANGUAGE_CODE` / `gettext` / `Accept-Language` | The SPA chrome stays English; translated `verbose_name` / `help_text` / `@admin.action(description=_("..."))` are not surfaced per-request. | [#630](https://github.com/MartinCastroAlvarez/django-admin-react/issues/630) |
|
|
624
|
+
| `ModelAdmin.get_urls()` custom views | Opens as a popout (`<a target="_blank">`) into the Django-rendered HTML page — no SPA chrome, no breadcrumb. The link IS surfaced; the UX is just outside the SPA. | [#623](https://github.com/MartinCastroAlvarez/django-admin-react/issues/623) |
|
|
625
|
+
| Django 4.2 LTS support | Not yet — the package pins `django >= 5.0,<7.0`. | [#622](https://github.com/MartinCastroAlvarez/django-admin-react/issues/622) |
|
|
626
|
+
|
|
627
|
+
If your admin relies on any "silently ignored" hook above, the
|
|
628
|
+
typical workaround is to keep that model on the legacy
|
|
629
|
+
`/admin/` surface via the
|
|
630
|
+
[experience-toggle strip](#experience-toggle-strip-optional) — the
|
|
631
|
+
SPA + legacy admin happily coexist.
|
|
632
|
+
|
|
633
|
+
---
|
|
634
|
+
|
|
635
|
+
## Writing safe `list_display` callables
|
|
636
|
+
|
|
637
|
+
This applies on **both** the legacy `/admin/` and the SPA — but the
|
|
638
|
+
SPA renders any `format_html` / `mark_safe` value via React's
|
|
639
|
+
`dangerouslySetInnerHTML`, so misuse is reflected XSS the same way
|
|
640
|
+
the legacy admin would be.
|
|
641
|
+
|
|
642
|
+
**Do not** interpolate user-controlled data into a `mark_safe(...)`
|
|
643
|
+
string. The whole point of `mark_safe` is "I have already escaped
|
|
644
|
+
this," and `f"<span>{obj.user_input}</span>"` has not — so a
|
|
645
|
+
`user_input` of `<script>alert(1)</script>` runs.
|
|
646
|
+
|
|
647
|
+
```python
|
|
648
|
+
# WRONG — copy-paste-from-StackOverflow XSS hazard.
|
|
649
|
+
@admin.display(description="Status")
|
|
650
|
+
def status_badge(self, obj):
|
|
651
|
+
return mark_safe(f'<span class="badge">{obj.user_input}</span>')
|
|
652
|
+
|
|
653
|
+
# RIGHT — format_html auto-escapes every interpolated arg.
|
|
654
|
+
@admin.display(description="Status")
|
|
655
|
+
def status_badge(self, obj):
|
|
656
|
+
return format_html('<span class="badge">{}</span>', obj.user_input)
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
Same rule for `readonly_fields` callables. See
|
|
660
|
+
[#633](https://github.com/MartinCastroAlvarez/django-admin-react/issues/633)
|
|
661
|
+
for the optional defense-in-depth `STRICT_HTML` setting tracking
|
|
662
|
+
issue (bleach-clean every rendered HTML value with a tight allow-list).
|
|
663
|
+
|
|
664
|
+
---
|
|
665
|
+
|
|
666
|
+
## Hardening
|
|
667
|
+
|
|
668
|
+
### Brute-force defense on `/api/v1/login/`
|
|
669
|
+
|
|
670
|
+
The package's React login endpoint (`<mount>/api/v1/login/`) reuses
|
|
671
|
+
Django's session auth, so the canonical brute-force defenses work
|
|
672
|
+
unchanged. The recommended layer is
|
|
673
|
+
[`django-axes`](https://pypi.org/project/django-axes/):
|
|
674
|
+
|
|
675
|
+
```python
|
|
676
|
+
# settings.py
|
|
677
|
+
INSTALLED_APPS = [..., "axes", "django_admin_react", "django_admin_rest_api"]
|
|
678
|
+
|
|
679
|
+
AUTHENTICATION_BACKENDS = [
|
|
680
|
+
"axes.backends.AxesStandaloneBackend",
|
|
681
|
+
"django.contrib.auth.backends.ModelBackend",
|
|
682
|
+
]
|
|
683
|
+
MIDDLEWARE = [..., "axes.middleware.AxesMiddleware"]
|
|
684
|
+
|
|
685
|
+
AXES_FAILURE_LIMIT = 5
|
|
686
|
+
AXES_COOLOFF_TIME = 1 # hour
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
Axes intercepts via `AUTHENTICATION_BACKENDS`, not URL middleware, so
|
|
690
|
+
lockouts apply to both the legacy admin login and the SPA's JSON
|
|
691
|
+
login automatically. Tracked: [#634](https://github.com/MartinCastroAlvarez/django-admin-react/issues/634).
|
|
692
|
+
|
|
693
|
+
### Mounting the API on a different origin (CORS + cookies)
|
|
694
|
+
|
|
695
|
+
`DJANGO_ADMIN_REACT["API_URL_PREFIX"]` lets the SPA point at a
|
|
696
|
+
separately-mounted REST API — e.g. SPA at `admin.example.com`
|
|
697
|
+
talking to an API at `api.example.com`. The session-cookie auth
|
|
698
|
+
across origins needs three settings configured together; if any
|
|
699
|
+
one is missing, every API call silently 401s after login.
|
|
700
|
+
|
|
701
|
+
```python
|
|
702
|
+
# settings.py — required when SPA and API are on different origins.
|
|
703
|
+
SESSION_COOKIE_SAMESITE = "None" # default "Lax" drops cookies cross-origin
|
|
704
|
+
SESSION_COOKIE_SECURE = True # required by browsers when SameSite=None
|
|
705
|
+
CSRF_COOKIE_SAMESITE = "None"
|
|
706
|
+
CSRF_COOKIE_SECURE = True
|
|
707
|
+
|
|
708
|
+
# pip install django-cors-headers
|
|
709
|
+
INSTALLED_APPS = [..., "corsheaders", ...]
|
|
710
|
+
MIDDLEWARE = ["corsheaders.middleware.CorsMiddleware", ...]
|
|
711
|
+
|
|
712
|
+
CORS_ALLOW_CREDENTIALS = True
|
|
713
|
+
CORS_ALLOWED_ORIGINS = ["https://admin.example.com"] # NEVER "*" with credentials
|
|
714
|
+
CSRF_TRUSTED_ORIGINS = ["https://admin.example.com"]
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
The SPA's HTTP client already sends `credentials: "include"`, so no
|
|
718
|
+
frontend change is needed — only the Django-side cookie + CORS
|
|
719
|
+
config above. Tracked: [#635](https://github.com/MartinCastroAlvarez/django-admin-react/issues/635).
|
|
720
|
+
|
|
604
721
|
---
|
|
605
722
|
|
|
606
723
|
## The API surface
|
|
@@ -20,6 +20,12 @@ from typing import Any
|
|
|
20
20
|
|
|
21
21
|
from django.conf import settings as django_settings
|
|
22
22
|
|
|
23
|
+
# Built-in fallback for the ``--dar-primary`` accent color when the
|
|
24
|
+
# consumer hasn't set ``PRIMARY_COLOR`` AND their ``AdminSite`` has no
|
|
25
|
+
# ``site_primary_color`` attribute. Re-exported so ``views.py`` can
|
|
26
|
+
# pick up the same constant instead of stringifying its own.
|
|
27
|
+
DEFAULT_PRIMARY_COLOR = "#2563eb"
|
|
28
|
+
|
|
23
29
|
DEFAULTS: dict[str, Any] = {
|
|
24
30
|
"ADMIN_SITE": "django.contrib.admin.site",
|
|
25
31
|
# The list page size derives from the model's
|
|
@@ -59,9 +65,16 @@ DEFAULTS: dict[str, Any] = {
|
|
|
59
65
|
# ``--dar-primary`` CSS variable so a consumer can brand the admin with
|
|
60
66
|
# no React rebuild. Must be a hex color (``#rgb`` / ``#rgba`` /
|
|
61
67
|
# ``#rrggbb`` / ``#rrggbbaa``); anything else is rejected at render and
|
|
62
|
-
# falls back to
|
|
63
|
-
# ``<style>`` block and must not be able to inject CSS.
|
|
64
|
-
|
|
68
|
+
# falls back to ``DEFAULT_PRIMARY_COLOR`` below, since the value is
|
|
69
|
+
# written into a ``<style>`` block and must not be able to inject CSS.
|
|
70
|
+
#
|
|
71
|
+
# ``None`` (default) means "consumer didn't explicitly set this" — the
|
|
72
|
+
# SPA reads ``site_primary_color`` off the configured ``AdminSite``
|
|
73
|
+
# next, then falls back to ``DEFAULT_PRIMARY_COLOR``. Mirrors
|
|
74
|
+
# ``BRAND_TITLE`` / ``BRAND_LOGO_URL``: setting wins as the
|
|
75
|
+
# per-deployment override, AdminSite attr is the structural default,
|
|
76
|
+
# built-in default last (#631).
|
|
77
|
+
"PRIMARY_COLOR": None,
|
|
65
78
|
# ``REACT_LOGIN`` — React-rendered login is the **default** so the
|
|
66
79
|
# SPA fully replaces the Django admin URL surface end-to-end (owner
|
|
67
80
|
# directive 2026-05-28). ``SpaIndexView`` serves the React shell to
|
|
@@ -149,7 +162,7 @@ class _PackageSettings:
|
|
|
149
162
|
ENABLE_PROFILING: bool = DEFAULTS["ENABLE_PROFILING"]
|
|
150
163
|
BRAND_TITLE: str | None = DEFAULTS["BRAND_TITLE"]
|
|
151
164
|
BRAND_LOGO_URL: str | None = DEFAULTS["BRAND_LOGO_URL"]
|
|
152
|
-
PRIMARY_COLOR: str = DEFAULTS["PRIMARY_COLOR"]
|
|
165
|
+
PRIMARY_COLOR: str | None = DEFAULTS["PRIMARY_COLOR"]
|
|
153
166
|
REACT_LOGIN: bool = DEFAULTS["REACT_LOGIN"]
|
|
154
167
|
API_URL_PREFIX: str | None = DEFAULTS["API_URL_PREFIX"]
|
|
155
168
|
LEGACY_ADMIN_URL_PREFIX: str | None = DEFAULTS["LEGACY_ADMIN_URL_PREFIX"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"../../packages/details/src/JsonViewer.tsx": {
|
|
3
|
-
"file": "assets/JsonViewer-
|
|
3
|
+
"file": "assets/JsonViewer-DkeJkG12.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-
|
|
12
|
+
"file": "assets/index-CGh8t5dj.js",
|
|
13
13
|
"name": "index",
|
|
14
14
|
"src": "index.html",
|
|
15
15
|
"isEntry": true,
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
"src/ColumnLayoutModal.tsx"
|
|
19
19
|
],
|
|
20
20
|
"css": [
|
|
21
|
-
"assets/index-
|
|
21
|
+
"assets/index-CpxUkcdm.css"
|
|
22
22
|
]
|
|
23
23
|
},
|
|
24
24
|
"src/ColumnLayoutModal.tsx": {
|
|
25
|
-
"file": "assets/ColumnLayoutModal-
|
|
25
|
+
"file": "assets/ColumnLayoutModal-Bqf6vN1j.js",
|
|
26
26
|
"name": "ColumnLayoutModal",
|
|
27
27
|
"src": "src/ColumnLayoutModal.tsx",
|
|
28
28
|
"isDynamicEntry": true,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{c as tt,d as c,R as $,r as ke,j as I,M as kn,b as On,a as Jt}from"./index-
|
|
1
|
+
import{c as tt,d as c,R as $,r as ke,j as I,M as kn,b as On,a as Jt}from"./index-CGh8t5dj.js";const Tn=[["circle",{cx:"9",cy:"12",r:"1",key:"1vctgf"}],["circle",{cx:"9",cy:"5",r:"1",key:"hp0tcf"}],["circle",{cx:"9",cy:"19",r:"1",key:"fkjjf6"}],["circle",{cx:"15",cy:"12",r:"1",key:"1tmaij"}],["circle",{cx:"15",cy:"5",r:"1",key:"19l28e"}],["circle",{cx:"15",cy:"19",r:"1",key:"f4zoj3"}]],Qt=tt("grip-vertical",Tn);const Ln=[["rect",{width:"18",height:"11",x:"3",y:"11",rx:"2",ry:"2",key:"1w4ew1"}],["path",{d:"M7 11V7a5 5 0 0 1 9.9-1",key:"1mm8w8"}]],jn=tt("lock-open",Ln);const zn=[["rect",{width:"18",height:"11",x:"3",y:"11",rx:"2",ry:"2",key:"1w4ew1"}],["path",{d:"M7 11V7a5 5 0 0 1 10 0v4",key:"fwvmzm"}]],jt=tt("lock",zn);const $n=[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"1357e3"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}]],Bn=tt("rotate-ccw",$n);function Pn(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return c.useMemo(()=>r=>{t.forEach(o=>o(r))},t)}const nt=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function xe(e){const t=Object.prototype.toString.call(e);return t==="[object Window]"||t==="[object global]"}function mt(e){return"nodeType"in e}function B(e){var t,n;return e?xe(e)?e:mt(e)&&(t=(n=e.ownerDocument)==null?void 0:n.defaultView)!=null?t:window:window}function yt(e){const{Document:t}=B(e);return e instanceof t}function Pe(e){return xe(e)?!1:e instanceof B(e).HTMLElement}function Zt(e){return e instanceof B(e).SVGElement}function we(e){return e?xe(e)?e.document:mt(e)?yt(e)?e:Pe(e)||Zt(e)?e.ownerDocument:document:document:document}const Z=nt?c.useLayoutEffect:c.useEffect;function xt(e){const t=c.useRef(e);return Z(()=>{t.current=e}),c.useCallback(function(){for(var n=arguments.length,r=new Array(n),o=0;o<n;o++)r[o]=arguments[o];return t.current==null?void 0:t.current(...r)},[])}function Fn(){const e=c.useRef(null),t=c.useCallback((r,o)=>{e.current=setInterval(r,o)},[]),n=c.useCallback(()=>{e.current!==null&&(clearInterval(e.current),e.current=null)},[]);return[t,n]}function je(e,t){t===void 0&&(t=[e]);const n=c.useRef(e);return Z(()=>{n.current!==e&&(n.current=e)},t),n}function Fe(e,t){const n=c.useRef();return c.useMemo(()=>{const r=e(n.current);return n.current=r,r},[...t])}function Je(e){const t=xt(e),n=c.useRef(null),r=c.useCallback(o=>{o!==n.current&&t?.(o,n.current),n.current=o},[]);return[n,r]}function ht(e){const t=c.useRef();return c.useEffect(()=>{t.current=e},[e]),t.current}let lt={};function Xe(e,t){return c.useMemo(()=>{if(t)return t;const n=lt[e]==null?0:lt[e]+1;return lt[e]=n,e+"-"+n},[e,t])}function en(e){return function(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o<n;o++)r[o-1]=arguments[o];return r.reduce((i,s)=>{const a=Object.entries(s);for(const[l,u]of a){const f=i[l];f!=null&&(i[l]=f+e*u)}return i},{...t})}}const ye=en(1),ze=en(-1);function Xn(e){return"clientX"in e&&"clientY"in e}function wt(e){if(!e)return!1;const{KeyboardEvent:t}=B(e.target);return t&&e instanceof t}function Un(e){if(!e)return!1;const{TouchEvent:t}=B(e.target);return t&&e instanceof t}function gt(e){if(Un(e)){if(e.touches&&e.touches.length){const{clientX:t,clientY:n}=e.touches[0];return{x:t,y:n}}else if(e.changedTouches&&e.changedTouches.length){const{clientX:t,clientY:n}=e.changedTouches[0];return{x:t,y:n}}}return Xn(e)?{x:e.clientX,y:e.clientY}:null}const $e=Object.freeze({Translate:{toString(e){if(!e)return;const{x:t,y:n}=e;return"translate3d("+(t?Math.round(t):0)+"px, "+(n?Math.round(n):0)+"px, 0)"}},Scale:{toString(e){if(!e)return;const{scaleX:t,scaleY:n}=e;return"scaleX("+t+") scaleY("+n+")"}},Transform:{toString(e){if(e)return[$e.Translate.toString(e),$e.Scale.toString(e)].join(" ")}},Transition:{toString(e){let{property:t,duration:n,easing:r}=e;return t+" "+n+"ms "+r}}}),zt="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function Yn(e){return e.matches(zt)?e:e.querySelector(zt)}const Wn={display:"none"};function Vn(e){let{id:t,value:n}=e;return $.createElement("div",{id:t,style:Wn},n)}function Hn(e){let{id:t,announcement:n,ariaLiveType:r="assertive"}=e;const o={position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"};return $.createElement("div",{id:t,style:o,role:"status","aria-live":r,"aria-atomic":!0},n)}function Kn(){const[e,t]=c.useState("");return{announce:c.useCallback(r=>{r!=null&&t(r)},[]),announcement:e}}const tn=c.createContext(null);function _n(e){const t=c.useContext(tn);c.useEffect(()=>{if(!t)throw new Error("useDndMonitor must be used within a children of <DndContext>");return t(e)},[e,t])}function qn(){const[e]=c.useState(()=>new Set),t=c.useCallback(r=>(e.add(r),()=>e.delete(r)),[e]);return[c.useCallback(r=>{let{type:o,event:i}=r;e.forEach(s=>{var a;return(a=s[o])==null?void 0:a.call(s,i)})},[e]),t]}const Gn={draggable:`
|
|
2
2
|
To pick up a draggable item, press the space bar.
|
|
3
3
|
While dragging, use the arrow keys to move the item.
|
|
4
4
|
Press space again to drop the item in its new position, or press escape to cancel.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{c as d,d as l,j as e,C as h}from"./index-
|
|
1
|
+
import{c as d,d as l,j as e,C as h}from"./index-CGh8t5dj.js";const p=[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]],g=d("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=d("copy",y);function N({raw:t,parsed:r}){const[s,c]=l.useState(!1);async function a(){try{await navigator.clipboard.writeText(t),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:r,depth:0})]})}function o({value:t,depth:r}){return t===null?e.jsx("span",{className:"text-purple-600 dark:text-purple-400",children:"null"}):typeof t=="boolean"?e.jsx("span",{className:"text-purple-600 dark:text-purple-400",children:t?"true":"false"}):typeof t=="number"?e.jsx("span",{className:"text-blue-700",children:String(t)}):typeof t=="string"?e.jsxs("span",{className:"text-green-700 dark:text-green-400",children:['"',t,'"']}):Array.isArray(t)?e.jsx(u,{value:t,depth:r}):typeof t=="object"?e.jsx(m,{value:t,depth:r}):e.jsx("span",{className:"text-gray-500",children:String(t)})}function m({value:t,depth:r}){const s=Object.keys(t),[c,a]=l.useState(r<2);return s.length===0?e.jsx("span",{className:"text-gray-500",children:"{}"}):e.jsx(x,{open:c,onToggle:()=>a(n=>!n),collapsedLabel:`{…} ${s.length} ${s.length===1?"key":"keys"}`,openBracket:"{",closeBracket:"}",depth:r,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:t[n],depth:r+1}),i<s.length-1?e.jsx("span",{className:"text-gray-500",children:","}):null]},n))})}function u({value:t,depth:r}){const[s,c]=l.useState(r<2);return t.length===0?e.jsx("span",{className:"text-gray-500",children:"[]"}):e.jsx(x,{open:s,onToggle:()=>c(a=>!a),collapsedLabel:`[…] ${t.length} ${t.length===1?"item":"items"}`,openBracket:"[",closeBracket:"]",depth:r,children:t.map((a,n)=>e.jsxs("div",{className:"pl-4",children:[e.jsx(o,{value:a,depth:r+1}),n<t.length-1?e.jsx("span",{className:"text-gray-500",children:","}):null]},n))})}function x({open:t,onToggle:r,collapsedLabel:s,openBracket:c,closeBracket:a,depth:n,children:i}){return e.jsxs("span",{children:[e.jsx("button",{type:"button",onClick:r,"aria-expanded":t,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 ${t?"rotate-90":""}`,"aria-hidden":!0})}),e.jsx("span",{className:"text-gray-500",children:c}),t?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};
|