django-admin-react 0.2.0a7__tar.gz → 0.2.0a8__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-0.2.0a7 → django_admin_react-0.2.0a8}/PKG-INFO +28 -13
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/README.md +27 -12
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/inlines.py +23 -1
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/detail.py +35 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/list.py +31 -6
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/conf.py +23 -10
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/static/admin_react/.vite/manifest.json +2 -2
- django_admin_react-0.2.0a8/django_admin_react/static/admin_react/assets/index-BSzI7RU6.css +1 -0
- django_admin_react-0.2.0a8/django_admin_react/static/admin_react/assets/index-CxlHfz-w.js +8 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/static/admin_react/index.html +2 -2
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/templates/admin_react/index.html +14 -3
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/views.py +74 -3
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/pyproject.toml +1 -1
- django_admin_react-0.2.0a7/django_admin_react/static/admin_react/assets/index-BK8mlqvA.js +0 -8
- django_admin_react-0.2.0a7/django_admin_react/static/admin_react/assets/index-BVqO3W_r.css +0 -1
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/LICENSE +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/README.md +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/__init__.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/README.md +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/__init__.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/dates.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/filters.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/inlines_write.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/panels.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/permissions.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/registry.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/serializers.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/urls.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/README.md +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/__init__.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/actions.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/auth.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/autocomplete.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/bulk.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/create.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/create_form.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/delete_preview.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/destroy.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/history.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/password.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/registry.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/schema.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/update.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/writes.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/apps.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/audit.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/pwa.py +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/templates/README.md +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/templates/admin_react/README.md +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/templates/admin_react/login.html +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/templates/admin_react/sw.js +0 -0
- {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/urls.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: django-admin-react
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.0a8
|
|
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
|
|
@@ -142,16 +142,19 @@ All settings are optional. Defaults shown:
|
|
|
142
142
|
```python
|
|
143
143
|
DJANGO_ADMIN_REACT = {
|
|
144
144
|
"ADMIN_SITE": "django.contrib.admin.site", # dotted path to AdminSite instance
|
|
145
|
-
"DEFAULT_PAGE_SIZE": 25,
|
|
145
|
+
"DEFAULT_PAGE_SIZE": 25, # fallback only; the list page size derives
|
|
146
|
+
# from ModelAdmin.list_per_page (Django parity).
|
|
146
147
|
"MAX_PAGE_SIZE": 200,
|
|
147
148
|
"ENABLE_PROFILING": False,
|
|
148
149
|
|
|
149
|
-
# Branding —
|
|
150
|
-
#
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
150
|
+
# Branding — all optional. The defaults derive from your AdminSite
|
|
151
|
+
# (site_header / site_title / site_logo), so if you already branded
|
|
152
|
+
# the HTML admin you need nothing here. Rendered server-side into the
|
|
153
|
+
# SPA shell, so title + favicon are present on first paint (no FOUC).
|
|
154
|
+
"BRAND_TITLE": None, # str | None — override for BOTH brand strings.
|
|
155
|
+
"BRAND_LOGO_URL": None, # str | None — favicon + sidebar logo;
|
|
156
|
+
# falls back to AdminSite.site_logo. Absolute
|
|
157
|
+
# URL or a path under your STATIC_URL.
|
|
155
158
|
"PRIMARY_COLOR": "#2563eb", # accent for primary buttons, links, and
|
|
156
159
|
# active states. Hex only (validated);
|
|
157
160
|
# injected as the --dar-primary CSS var, so
|
|
@@ -161,16 +164,28 @@ DJANGO_ADMIN_REACT = {
|
|
|
161
164
|
|
|
162
165
|
#### Branding (`BRAND_TITLE` + `BRAND_LOGO_URL`)
|
|
163
166
|
|
|
164
|
-
Both default to `None
|
|
167
|
+
Both default to `None` and **derive from your `AdminSite`**, mirroring
|
|
168
|
+
Django admin — so if you already customised the HTML admin's branding,
|
|
169
|
+
you need no settings here at all.
|
|
170
|
+
|
|
171
|
+
**Sidebar header** resolution:
|
|
165
172
|
|
|
166
173
|
1. `DJANGO_ADMIN_REACT["BRAND_TITLE"]` — explicit override.
|
|
167
|
-
2. `<your AdminSite>.site_header` —
|
|
168
|
-
on a custom `AdminSite`, the SPA reuses it automatically. No need
|
|
169
|
-
to repeat yourself.
|
|
174
|
+
2. `<your AdminSite>.site_header` — reused automatically.
|
|
170
175
|
3. `"Django Admin"` — last-resort fallback.
|
|
171
176
|
|
|
177
|
+
**Browser-tab `<title>`** resolution (Django uses `site_title` for the
|
|
178
|
+
tab, `site_header` for the on-page header):
|
|
179
|
+
|
|
180
|
+
1. `DJANGO_ADMIN_REACT["BRAND_TITLE"]` — explicit override.
|
|
181
|
+
2. `<your AdminSite>.site_title` — Django's tab-title source.
|
|
182
|
+
3. `<your AdminSite>.site_header` — fallback.
|
|
183
|
+
4. `"Django Admin"` — last-resort fallback.
|
|
184
|
+
|
|
172
185
|
`BRAND_LOGO_URL` accepts either an absolute URL or a path the browser
|
|
173
|
-
can resolve under your `STATIC_URL`.
|
|
186
|
+
can resolve under your `STATIC_URL`. When unset, a `site_logo` attribute
|
|
187
|
+
on your `AdminSite` is used (Django has no logo by default, so set it as
|
|
188
|
+
a constant on your custom site). It is used both as the favicon
|
|
174
189
|
(`<link rel="icon">` in the SPA shell) and as the small logo next to
|
|
175
190
|
the brand title in the sidebar.
|
|
176
191
|
|
|
@@ -111,16 +111,19 @@ All settings are optional. Defaults shown:
|
|
|
111
111
|
```python
|
|
112
112
|
DJANGO_ADMIN_REACT = {
|
|
113
113
|
"ADMIN_SITE": "django.contrib.admin.site", # dotted path to AdminSite instance
|
|
114
|
-
"DEFAULT_PAGE_SIZE": 25,
|
|
114
|
+
"DEFAULT_PAGE_SIZE": 25, # fallback only; the list page size derives
|
|
115
|
+
# from ModelAdmin.list_per_page (Django parity).
|
|
115
116
|
"MAX_PAGE_SIZE": 200,
|
|
116
117
|
"ENABLE_PROFILING": False,
|
|
117
118
|
|
|
118
|
-
# Branding —
|
|
119
|
-
#
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
119
|
+
# Branding — all optional. The defaults derive from your AdminSite
|
|
120
|
+
# (site_header / site_title / site_logo), so if you already branded
|
|
121
|
+
# the HTML admin you need nothing here. Rendered server-side into the
|
|
122
|
+
# SPA shell, so title + favicon are present on first paint (no FOUC).
|
|
123
|
+
"BRAND_TITLE": None, # str | None — override for BOTH brand strings.
|
|
124
|
+
"BRAND_LOGO_URL": None, # str | None — favicon + sidebar logo;
|
|
125
|
+
# falls back to AdminSite.site_logo. Absolute
|
|
126
|
+
# URL or a path under your STATIC_URL.
|
|
124
127
|
"PRIMARY_COLOR": "#2563eb", # accent for primary buttons, links, and
|
|
125
128
|
# active states. Hex only (validated);
|
|
126
129
|
# injected as the --dar-primary CSS var, so
|
|
@@ -130,16 +133,28 @@ DJANGO_ADMIN_REACT = {
|
|
|
130
133
|
|
|
131
134
|
#### Branding (`BRAND_TITLE` + `BRAND_LOGO_URL`)
|
|
132
135
|
|
|
133
|
-
Both default to `None
|
|
136
|
+
Both default to `None` and **derive from your `AdminSite`**, mirroring
|
|
137
|
+
Django admin — so if you already customised the HTML admin's branding,
|
|
138
|
+
you need no settings here at all.
|
|
139
|
+
|
|
140
|
+
**Sidebar header** resolution:
|
|
134
141
|
|
|
135
142
|
1. `DJANGO_ADMIN_REACT["BRAND_TITLE"]` — explicit override.
|
|
136
|
-
2. `<your AdminSite>.site_header` —
|
|
137
|
-
on a custom `AdminSite`, the SPA reuses it automatically. No need
|
|
138
|
-
to repeat yourself.
|
|
143
|
+
2. `<your AdminSite>.site_header` — reused automatically.
|
|
139
144
|
3. `"Django Admin"` — last-resort fallback.
|
|
140
145
|
|
|
146
|
+
**Browser-tab `<title>`** resolution (Django uses `site_title` for the
|
|
147
|
+
tab, `site_header` for the on-page header):
|
|
148
|
+
|
|
149
|
+
1. `DJANGO_ADMIN_REACT["BRAND_TITLE"]` — explicit override.
|
|
150
|
+
2. `<your AdminSite>.site_title` — Django's tab-title source.
|
|
151
|
+
3. `<your AdminSite>.site_header` — fallback.
|
|
152
|
+
4. `"Django Admin"` — last-resort fallback.
|
|
153
|
+
|
|
141
154
|
`BRAND_LOGO_URL` accepts either an absolute URL or a path the browser
|
|
142
|
-
can resolve under your `STATIC_URL`.
|
|
155
|
+
can resolve under your `STATIC_URL`. When unset, a `site_logo` attribute
|
|
156
|
+
on your `AdminSite` is used (Django has no logo by default, so set it as
|
|
157
|
+
a constant on your custom site). It is used both as the favicon
|
|
143
158
|
(`<link rel="icon">` in the SPA shell) and as the small logo next to
|
|
144
159
|
the brand title in the sidebar.
|
|
145
160
|
|
|
@@ -26,6 +26,7 @@ from typing import Any
|
|
|
26
26
|
|
|
27
27
|
from django.contrib.admin.options import InlineModelAdmin
|
|
28
28
|
from django.contrib.admin.options import ModelAdmin
|
|
29
|
+
from django.contrib.admin.options import TabularInline
|
|
29
30
|
from django.contrib.admin.utils import label_for_field
|
|
30
31
|
from django.contrib.admin.utils import lookup_field
|
|
31
32
|
from django.db.models import ForeignKey
|
|
@@ -120,6 +121,21 @@ def _show_change_link_allowed(
|
|
|
120
121
|
return bool(target_admin.has_view_permission(request))
|
|
121
122
|
|
|
122
123
|
|
|
124
|
+
def _inline_kind(inline: InlineModelAdmin) -> str:
|
|
125
|
+
"""Tabular vs stacked layout hint for the SPA.
|
|
126
|
+
|
|
127
|
+
Classified by the inline's **base class** (``admin.TabularInline``),
|
|
128
|
+
not its subclass *name*. The previous ``"Tabular" in
|
|
129
|
+
type(inline).__name__`` check mis-classified the common real-world
|
|
130
|
+
``class BookInline(admin.TabularInline)`` (no "Tabular" in the name)
|
|
131
|
+
as stacked, so a tabular inline rendered as a card list (#417).
|
|
132
|
+
``StackedInline`` — and a bare ``InlineModelAdmin`` — fall through to
|
|
133
|
+
the stacked layout, matching Django's own template selection
|
|
134
|
+
(``TabularInline`` is the only base that renders a table).
|
|
135
|
+
"""
|
|
136
|
+
return "tabular" if isinstance(inline, TabularInline) else "stacked"
|
|
137
|
+
|
|
138
|
+
|
|
123
139
|
def _spec_for_inline(
|
|
124
140
|
inline: InlineModelAdmin,
|
|
125
141
|
parent: Model,
|
|
@@ -146,7 +162,7 @@ def _spec_for_inline(
|
|
|
146
162
|
can_change = bool(inline.has_change_permission(request, parent))
|
|
147
163
|
can_delete = bool(inline.has_delete_permission(request, parent))
|
|
148
164
|
|
|
149
|
-
kind =
|
|
165
|
+
kind = _inline_kind(inline)
|
|
150
166
|
|
|
151
167
|
visible_fields = _visible_inline_fields(inline, parent, request)
|
|
152
168
|
fields_meta = _fields_meta(inline, child_model, visible_fields, request)
|
|
@@ -160,6 +176,12 @@ def _spec_for_inline(
|
|
|
160
176
|
"label": str(meta.verbose_name_plural),
|
|
161
177
|
"kind": kind,
|
|
162
178
|
"fk_name": fk_name,
|
|
179
|
+
# The child's pk field name (#418): when the pk is an explicit,
|
|
180
|
+
# non-auto field (e.g. a UUIDField) it shows up as an inline
|
|
181
|
+
# column, and the SPA must render it without ellipsis — the row's
|
|
182
|
+
# identity must stay fully readable/copyable (mirrors the list
|
|
183
|
+
# ``pk_field`` / #360).
|
|
184
|
+
"pk_field": meta.pk.name,
|
|
163
185
|
"child": {"app_label": meta.app_label, "model_name": meta.model_name},
|
|
164
186
|
"extra": int(getattr(inline, "extra", 0)),
|
|
165
187
|
"min_num": getattr(inline, "min_num", None),
|
{django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/detail.py
RENAMED
|
@@ -26,6 +26,8 @@ from django.db.models import FileField
|
|
|
26
26
|
from django.db.models import ForeignKey
|
|
27
27
|
from django.db.models import ManyToManyField
|
|
28
28
|
from django.db.models import Model
|
|
29
|
+
from django.forms.widgets import Textarea
|
|
30
|
+
from django.forms.widgets import TextInput
|
|
29
31
|
from django.http import HttpRequest
|
|
30
32
|
from django.http import HttpResponse
|
|
31
33
|
from django.http import JsonResponse
|
|
@@ -358,9 +360,42 @@ def _descriptor_for(
|
|
|
358
360
|
# relations). ``elif`` so ``radio_fields`` wins if a field is in both.
|
|
359
361
|
elif name in (getattr(model_admin, "raw_id_fields", None) or ()):
|
|
360
362
|
descriptor["widget"] = "raw_id"
|
|
363
|
+
# formfield_overrides (#446): the bound form field's widget already
|
|
364
|
+
# reflects the admin's ``formfield_overrides`` /
|
|
365
|
+
# ``formfield_for_dbfield`` — Django applied them in ``get_form``.
|
|
366
|
+
# Honour the one override the SPA can act on with the existing type
|
|
367
|
+
# vocabulary: a single-line string promoted to a ``Textarea`` becomes
|
|
368
|
+
# the multi-line ``text`` type (rendered as a ``<textarea>``), and a
|
|
369
|
+
# multi-line ``text`` forced to a single-line ``TextInput`` collapses
|
|
370
|
+
# back to ``string``. Other widget overrides (date pickers, FK
|
|
371
|
+
# autocomplete) the SPA already renders from the field type. Choice
|
|
372
|
+
# fields are untouched — their ``choice`` type wins above.
|
|
373
|
+
_apply_widget_override(descriptor, form_field)
|
|
361
374
|
return descriptor
|
|
362
375
|
|
|
363
376
|
|
|
377
|
+
def _apply_widget_override(descriptor: dict[str, Any], form_field: Any) -> None:
|
|
378
|
+
"""Reconcile the descriptor type with the bound form field's widget.
|
|
379
|
+
|
|
380
|
+
Reuses the form widget (source of truth) so ``formfield_overrides``
|
|
381
|
+
has a visible effect, mapping only to the existing ``string`` /
|
|
382
|
+
``text`` vocabulary so no new wire type is introduced (#446).
|
|
383
|
+
"""
|
|
384
|
+
if form_field is None:
|
|
385
|
+
return
|
|
386
|
+
widget = getattr(form_field, "widget", None)
|
|
387
|
+
if widget is None:
|
|
388
|
+
return
|
|
389
|
+
if descriptor["type"] == "string" and isinstance(widget, Textarea):
|
|
390
|
+
descriptor["type"] = "text"
|
|
391
|
+
elif (
|
|
392
|
+
descriptor["type"] == "text"
|
|
393
|
+
and isinstance(widget, TextInput)
|
|
394
|
+
and not isinstance(widget, Textarea)
|
|
395
|
+
):
|
|
396
|
+
descriptor["type"] = "string"
|
|
397
|
+
|
|
398
|
+
|
|
364
399
|
def _readonly_callable_descriptor(
|
|
365
400
|
model_admin: ModelAdmin,
|
|
366
401
|
model: type[Model],
|
{django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/list.py
RENAMED
|
@@ -157,7 +157,9 @@ class ListView(View):
|
|
|
157
157
|
page = 1
|
|
158
158
|
page_size = max(total, 1)
|
|
159
159
|
else:
|
|
160
|
-
page_size = _clamp_page_size(
|
|
160
|
+
page_size = _clamp_page_size(
|
|
161
|
+
request.GET.get("page_size"), _default_page_size(model_admin)
|
|
162
|
+
)
|
|
161
163
|
page = _clamp_page(request.GET.get("page"))
|
|
162
164
|
start = (page - 1) * page_size
|
|
163
165
|
end = start + page_size
|
|
@@ -253,14 +255,37 @@ def _clamp_page(raw: str | None) -> int:
|
|
|
253
255
|
return max(1, n)
|
|
254
256
|
|
|
255
257
|
|
|
256
|
-
def
|
|
258
|
+
def _default_page_size(model_admin: ModelAdmin) -> int:
|
|
259
|
+
"""The default page size when the client sends no ``?page_size=`` (#281).
|
|
260
|
+
|
|
261
|
+
Derived from ``ModelAdmin.list_per_page`` so the source of truth is the
|
|
262
|
+
admin (Rule #1), matching Django's changelist — a consumer who set
|
|
263
|
+
``list_per_page`` for the HTML admin gets the same page size in the SPA
|
|
264
|
+
with no extra setting. Falls back to ``conf.DEFAULT_PAGE_SIZE`` only when
|
|
265
|
+
``list_per_page`` is missing/invalid, and is capped at ``MAX_PAGE_SIZE``
|
|
266
|
+
so the per-request DoS ceiling still holds (a consumer wanting a bigger
|
|
267
|
+
default raises ``MAX_PAGE_SIZE`` too).
|
|
268
|
+
"""
|
|
269
|
+
fallback = int(conf.DEFAULT_PAGE_SIZE)
|
|
270
|
+
try:
|
|
271
|
+
n = int(getattr(model_admin, "list_per_page", fallback))
|
|
272
|
+
except (TypeError, ValueError):
|
|
273
|
+
n = fallback
|
|
274
|
+
if n < 1:
|
|
275
|
+
n = fallback
|
|
276
|
+
return min(n, int(conf.MAX_PAGE_SIZE))
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _clamp_page_size(raw: str | None, default: int) -> int:
|
|
257
280
|
"""Parse ``?page_size=`` and clamp to ``[1, conf.MAX_PAGE_SIZE]``.
|
|
258
281
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
282
|
+
``default`` (the model's ``list_per_page``-derived size, see
|
|
283
|
+
``_default_page_size``) is used when the param is absent or invalid.
|
|
284
|
+
|
|
285
|
+
The upper clamp is a denial-of-service guard: without it a client
|
|
286
|
+
could pass ``?page_size=10_000_000`` and force the database to
|
|
287
|
+
materialise ten million rows.
|
|
262
288
|
"""
|
|
263
|
-
default = int(conf.DEFAULT_PAGE_SIZE)
|
|
264
289
|
maximum = int(conf.MAX_PAGE_SIZE)
|
|
265
290
|
try:
|
|
266
291
|
n = int(raw) if raw is not None else default
|
|
@@ -22,6 +22,12 @@ from django.conf import settings as django_settings
|
|
|
22
22
|
|
|
23
23
|
DEFAULTS: dict[str, Any] = {
|
|
24
24
|
"ADMIN_SITE": "django.contrib.admin.site",
|
|
25
|
+
# The list page size derives from the model's
|
|
26
|
+
# ``ModelAdmin.list_per_page`` (Django's changelist source of truth,
|
|
27
|
+
# Rule #1 / #281), so the SPA pages like the HTML admin with no extra
|
|
28
|
+
# setting. ``DEFAULT_PAGE_SIZE`` is the fallback only when
|
|
29
|
+
# ``list_per_page`` is missing / invalid. ``MAX_PAGE_SIZE`` always caps
|
|
30
|
+
# ``?page_size`` (a DoS guard).
|
|
25
31
|
"DEFAULT_PAGE_SIZE": 25,
|
|
26
32
|
"MAX_PAGE_SIZE": 200,
|
|
27
33
|
"ENABLE_PROFILING": False,
|
|
@@ -29,16 +35,23 @@ DEFAULTS: dict[str, Any] = {
|
|
|
29
35
|
# rendered server-side into the SPA index template so the SPA
|
|
30
36
|
# picks them up on first paint (no FOUC).
|
|
31
37
|
#
|
|
32
|
-
# ``BRAND_TITLE`` —
|
|
33
|
-
# the browser
|
|
34
|
-
#
|
|
35
|
-
# ``site_header``
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
# ``
|
|
40
|
-
# ``
|
|
41
|
-
#
|
|
38
|
+
# ``BRAND_TITLE`` — optional override for *both* the sidebar
|
|
39
|
+
# header and the browser-tab title. ``None``
|
|
40
|
+
# (default) derives them from the AdminSite,
|
|
41
|
+
# mirroring Django admin: ``site_header`` →
|
|
42
|
+
# sidebar header, ``site_title`` → tab title
|
|
43
|
+
# (falling back to ``site_header``), else the
|
|
44
|
+
# package name (#281). A consumer who already
|
|
45
|
+
# set ``site_header`` / ``site_title`` on their
|
|
46
|
+
# ``AdminSite`` needs no branding setting at all.
|
|
47
|
+
# Plain text; no HTML.
|
|
48
|
+
# ``BRAND_LOGO_URL`` — optional override for the logo / favicon URL,
|
|
49
|
+
# written into the SPA's ``<link rel="icon">``.
|
|
50
|
+
# ``None`` (default) reads ``site_logo`` off the
|
|
51
|
+
# configured ``AdminSite`` if the consumer set
|
|
52
|
+
# that attribute (#281), else keeps the no-op
|
|
53
|
+
# ``data:,`` placeholder. Either an absolute URL
|
|
54
|
+
# or a path under your ``STATIC_URL``.
|
|
42
55
|
"BRAND_TITLE": None,
|
|
43
56
|
"BRAND_LOGO_URL": None,
|
|
44
57
|
# ``PRIMARY_COLOR`` — the accent color for primary buttons, links, and
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media(min-width:640px){.container{max-width:640px}}@media(min-width:768px){.container{max-width:768px}}@media(min-width:1024px){.container{max-width:1024px}}@media(min-width:1280px){.container{max-width:1280px}}@media(min-width:1536px){.container{max-width:1536px}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.visible{visibility:visible}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:0}.inset-x-0{left:0;right:0}.inset-y-0{top:0;bottom:0}.bottom-4{bottom:1rem}.left-0{left:0}.right-0{right:0}.right-2{right:.5rem}.right-4{right:1rem}.top-0{top:0}.top-2{top:.5rem}.isolate{isolation:isolate}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.z-\[100\]{z-index:100}.z-\[200\]{z-index:200}.col-span-2{grid-column:span 2 / span 2}.m-6{margin:1.5rem}.-ml-2{margin-left:-.5rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.ml-0\.5{margin-left:.125rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.block{display:block}.inline-block{display:inline-block}.\!inline{display:inline!important}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.contents{display:contents}.hidden{display:none}.h-10{height:2.5rem}.h-14{height:3.5rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-9{height:2.25rem}.h-full{height:100%}.max-h-48{max-height:12rem}.max-h-60{max-height:15rem}.max-h-72{max-height:18rem}.max-h-\[85vh\]{max-height:85vh}.min-h-screen{min-height:100vh}.w-1\.5{width:.375rem}.w-10{width:2.5rem}.w-2{width:.5rem}.w-20{width:5rem}.w-24{width:6rem}.w-28{width:7rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-3\/4{width:75%}.w-32{width:8rem}.w-4{width:1rem}.w-40{width:10rem}.w-48{width:12rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-64{width:16rem}.w-72{width:18rem}.w-80{width:20rem}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-48{min-width:12rem}.min-w-\[14rem\]{min-width:14rem}.min-w-full{min-width:100%}.max-w-\[12rem\]{max-width:12rem}.max-w-\[16rem\]{max-width:16rem}.max-w-\[calc\(100vw-2rem\)\]{max-width:calc(100vw - 2rem)}.max-w-full{max-width:100%}.max-w-prose{max-width:65ch}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.table-fixed{table-layout:fixed}.-translate-x-full{--tw-translate-x: -100%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-rotate-90{--tw-rotate: -90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-col-resize{cursor:col-resize}.cursor-grab{cursor:grab}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(2rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2rem * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-gray-100>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(243 244 246 / var(--tw-divide-opacity, 1))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(229 231 235 / var(--tw-divide-opacity, 1))}.divide-gray-800>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(31 41 55 / var(--tw-divide-opacity, 1))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-t-xl{border-top-left-radius:.75rem;border-top-right-radius:.75rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-amber-300{--tw-border-opacity: 1;border-color:rgb(252 211 77 / var(--tw-border-opacity, 1))}.border-blue-300{--tw-border-opacity: 1;border-color:rgb(147 197 253 / var(--tw-border-opacity, 1))}.border-blue-600{--tw-border-opacity: 1;border-color:rgb(37 99 235 / var(--tw-border-opacity, 1))}.border-gray-200{--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity, 1))}.border-gray-300{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity, 1))}.border-green-300{--tw-border-opacity: 1;border-color:rgb(134 239 172 / var(--tw-border-opacity, 1))}.border-primary{border-color:var(--dar-primary, #2563eb)}.border-red-300{--tw-border-opacity: 1;border-color:rgb(252 165 165 / var(--tw-border-opacity, 1))}.border-red-400{--tw-border-opacity: 1;border-color:rgb(248 113 113 / var(--tw-border-opacity, 1))}.border-red-500{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.border-red-600{--tw-border-opacity: 1;border-color:rgb(220 38 38 / var(--tw-border-opacity, 1))}.border-transparent{border-color:transparent}.bg-amber-50{--tw-bg-opacity: 1;background-color:rgb(255 251 235 / var(--tw-bg-opacity, 1))}.bg-black\/50{background-color:#00000080}.bg-blue-50{--tw-bg-opacity: 1;background-color:rgb(239 246 255 / var(--tw-bg-opacity, 1))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-gray-200{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity, 1))}.bg-gray-400{--tw-bg-opacity: 1;background-color:rgb(156 163 175 / var(--tw-bg-opacity, 1))}.bg-gray-50{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1))}.bg-gray-500{--tw-bg-opacity: 1;background-color:rgb(107 114 128 / var(--tw-bg-opacity, 1))}.bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity, 1))}.bg-green-50{--tw-bg-opacity: 1;background-color:rgb(240 253 244 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-primary{background-color:var(--dar-primary, #2563eb)}.bg-red-50{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity, 1))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.fill-amber-400{fill:#fbbf24}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pl-5{padding-left:1.25rem}.pr-3{padding-right:.75rem}.pt-1{padding-top:.25rem}.pt-20{padding-top:5rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-top{vertical-align:top}.text-2xl{font-size:1.5rem;line-height:2rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}.capitalize{text-transform:capitalize}.tracking-wide{letter-spacing:.025em}.text-amber-400{--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.text-amber-700{--tw-text-opacity: 1;color:rgb(180 83 9 / var(--tw-text-opacity, 1))}.text-amber-800{--tw-text-opacity: 1;color:rgb(146 64 14 / var(--tw-text-opacity, 1))}.text-blue-700{--tw-text-opacity: 1;color:rgb(29 78 216 / var(--tw-text-opacity, 1))}.text-gray-100{--tw-text-opacity: 1;color:rgb(243 244 246 / var(--tw-text-opacity, 1))}.text-gray-200{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.text-gray-700{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity, 1))}.text-gray-800{--tw-text-opacity: 1;color:rgb(31 41 55 / var(--tw-text-opacity, 1))}.text-gray-900{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity, 1))}.text-green-600{--tw-text-opacity: 1;color:rgb(22 163 74 / var(--tw-text-opacity, 1))}.text-green-700{--tw-text-opacity: 1;color:rgb(21 128 61 / var(--tw-text-opacity, 1))}.text-inherit{color:inherit}.text-primary{color:var(--dar-primary, #2563eb)}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-red-600{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity, 1))}.text-red-700{--tw-text-opacity: 1;color:rgb(185 28 28 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.underline{text-decoration-line:underline}.no-underline{text-decoration-line:none}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.placeholder-gray-500::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(107 114 128 / var(--tw-placeholder-opacity, 1))}.placeholder-gray-500::placeholder{--tw-placeholder-opacity: 1;color:rgb(107 114 128 / var(--tw-placeholder-opacity, 1))}.opacity-0{opacity:0}.opacity-25{opacity:.25}.opacity-40{opacity:.4}.opacity-50{opacity:.5}.opacity-75{opacity:.75}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.blur{--tw-blur: blur(8px);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}html,body,#root{height:100%}*{scrollbar-width:none;-ms-overflow-style:none}*::-webkit-scrollbar{display:none}.dark body{background-color:#0b1020;color:#e5e7eb}.dark .bg-white{background-color:#111827!important}.dark .bg-gray-50{background-color:#0b1020!important}.dark .bg-gray-200{background-color:#1f2937!important}.dark .text-gray-900,.dark .text-gray-800{color:#f3f4f6!important}.dark .text-gray-700,.dark .text-gray-600,.dark .text-gray-500{color:#9ca3af!important}.border-gray-200,.border-gray-300{border-color:#e5e7eb}.dark .border-gray-200,.dark .border-gray-300,.dark .border-gray-100{border-color:#2b3544!important}.dark .divide-gray-100>:not([hidden])~:not([hidden]),.dark .divide-gray-200>:not([hidden])~:not([hidden]){border-color:#1f2937!important}.dark .hover\:bg-gray-50:hover,.dark .hover\:bg-gray-100:hover{background-color:#1f2937!important}.dark input:not([type=checkbox],[type=radio],[type=range]),.dark select,.dark textarea{background-color:#111827!important;color:#e5e7eb!important;border-color:#2b3544!important}.dark input::-moz-placeholder,.dark textarea::-moz-placeholder{color:#6b7280!important}.dark input::placeholder,.dark textarea::placeholder{color:#6b7280!important}.dark .bg-blue-50{background-color:#1e293b!important}.dark .text-blue-700{color:#93c5fd!important}.dark .bg-red-50{background-color:#3f1d1d!important}.dark .border-red-300,.dark .border-red-400{border-color:#7f1d1d!important}.dark .text-red-700,.dark .text-red-600{color:#fca5a5!important}.dark .bg-green-50{background-color:#14321f!important}.dark .text-green-700,.dark .text-green-600{color:#86efac!important}.dark .bg-amber-50{background-color:#3a2e12!important}.dark .border-amber-300{border-color:#92400e!important}.dark .text-amber-800,.dark .text-amber-700{color:#fcd34d!important}.dark .bg-gray-100{background-color:#1f2937!important}table td>div>span,dl dd>span{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:#6b7280}.dark table td>div>span,.dark dl dd>span{color:#9ca3af}table td>div>span a,dl dd>span a{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;color:var(--dar-primary, #4f46e5)}table td>div>span a:hover,dl dd>span a:hover{text-decoration:underline}.dark table td>div>span a,.dark dl dd>span a{color:color-mix(in srgb,var(--dar-primary, #4f46e5) 65%,white)}.dark .text-primary{color:color-mix(in srgb,var(--dar-primary, #2563eb) 65%,white)}.first\:pt-0:first-child{padding-top:0}.hover\:border-gray-200:hover{--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity, 1))}.hover\:bg-blue-400\/60:hover{background-color:#60a5fa99}.hover\:bg-blue-50:hover{--tw-bg-opacity: 1;background-color:rgb(239 246 255 / var(--tw-bg-opacity, 1))}.hover\:bg-blue-700:hover{--tw-bg-opacity: 1;background-color:rgb(29 78 216 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-100:hover{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-50:hover{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-700:hover{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-800:hover{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.hover\:bg-red-700:hover{--tw-bg-opacity: 1;background-color:rgb(185 28 28 / var(--tw-bg-opacity, 1))}.hover\:text-amber-500:hover{--tw-text-opacity: 1;color:rgb(245 158 11 / var(--tw-text-opacity, 1))}.hover\:text-gray-200:hover{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.hover\:text-gray-600:hover{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.hover\:text-gray-700:hover{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity, 1))}.hover\:text-gray-800:hover{--tw-text-opacity: 1;color:rgb(31 41 55 / var(--tw-text-opacity, 1))}.hover\:text-red-600:hover{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity, 1))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:no-underline:hover{text-decoration-line:none}.hover\:opacity-90:hover{opacity:.9}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-gray-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(107 114 128 / var(--tw-ring-opacity, 1))}.focus\:ring-red-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(239 68 68 / var(--tw-ring-opacity, 1))}.focus-visible\:border-blue-500:focus-visible{--tw-border-opacity: 1;border-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-1:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-inset:focus-visible{--tw-ring-inset: inset}.focus-visible\:ring-blue-500:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1))}.focus-visible\:ring-gray-400:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(156 163 175 / var(--tw-ring-opacity, 1))}.focus-visible\:ring-gray-500:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(107 114 128 / var(--tw-ring-opacity, 1))}.focus-visible\:ring-offset-1:focus-visible{--tw-ring-offset-width: 1px}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:opacity-100{opacity:1}@media(min-width:640px){.sm\:max-w-md{max-width:28rem}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:items-start{align-items:flex-start}.sm\:items-center{align-items:center}.sm\:justify-between{justify-content:space-between}.sm\:gap-4{gap:1rem}.sm\:rounded-xl{border-radius:.75rem}}@media(min-width:1024px){.lg\:static{position:static}.lg\:z-auto{z-index:auto}.lg\:hidden{display:none}.lg\:translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:pt-6{padding-top:1.5rem}}
|