django-unfold 0.59.0__py3-none-any.whl → 0.61.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_unfold-0.59.0.dist-info → django_unfold-0.61.0.dist-info}/METADATA +17 -7
- {django_unfold-0.59.0.dist-info → django_unfold-0.61.0.dist-info}/RECORD +58 -47
- unfold/admin.py +45 -13
- unfold/contrib/import_export/templates/admin/import_export/change_list_import.html +5 -0
- unfold/contrib/inlines/admin.py +11 -6
- unfold/contrib/inlines/forms.py +3 -1
- unfold/contrib/location_field/__init__.py +0 -0
- unfold/contrib/location_field/apps.py +6 -0
- unfold/contrib/location_field/templates/location_field/map_widget.html +5 -0
- unfold/contrib/simple_history/templates/simple_history/submit_line.html +16 -18
- unfold/fields.py +17 -19
- unfold/forms.py +96 -2
- unfold/mixins/base_model_admin.py +21 -2
- unfold/sites.py +18 -31
- unfold/static/admin/js/admin/RelatedObjectLookups.js +2 -2
- unfold/static/unfold/css/styles.css +1 -1
- unfold/static/unfold/js/app.js +61 -0
- unfold/styles.css +6 -8
- unfold/templates/admin/app_list.html +4 -1
- unfold/templates/admin/auth/user/add_form.html +1 -5
- unfold/templates/admin/change_form.html +3 -1
- unfold/templates/admin/change_list.html +4 -2
- unfold/templates/admin/change_list_results.html +3 -3
- unfold/templates/admin/edit_inline/stacked.html +18 -8
- unfold/templates/admin/edit_inline/tabular.html +2 -0
- unfold/templates/admin/includes/fieldset.html +9 -3
- unfold/templates/admin/login.html +45 -88
- unfold/templates/admin/search_form.html +14 -5
- unfold/templates/admin/submit_line.html +1 -1
- unfold/templates/unfold/components/button.html +10 -1
- unfold/templates/unfold/components/card.html +4 -4
- unfold/templates/unfold/helpers/add_link.html +3 -1
- unfold/templates/unfold/helpers/app_list.html +5 -2
- unfold/templates/unfold/helpers/app_list_default.html +2 -1
- unfold/templates/unfold/helpers/field.html +5 -3
- unfold/templates/unfold/helpers/fieldsets_tabs.html +3 -3
- unfold/templates/unfold/helpers/help_text.html +1 -1
- unfold/templates/unfold/helpers/messages.html +4 -4
- unfold/templates/unfold/helpers/pagination.html +1 -1
- unfold/templates/unfold/helpers/pagination_inline.html +28 -0
- unfold/templates/unfold/helpers/popup_header.html +27 -0
- unfold/templates/unfold/helpers/search.html +16 -12
- unfold/templates/unfold/helpers/search_results.html +10 -9
- unfold/templates/unfold/helpers/shortcut.html +3 -0
- unfold/templates/unfold/helpers/site_branding.html +2 -2
- unfold/templates/unfold/helpers/tab_action.html +4 -3
- unfold/templates/unfold/helpers/unauthenticated_header.html +15 -0
- unfold/templates/unfold/helpers/unauthenticated_title.html +11 -0
- unfold/templates/unfold/layouts/unauthenticated.html +37 -0
- unfold/templates/unfold/widgets/text.html +28 -0
- unfold/templates/unfold_crispy/field.html +12 -10
- unfold/templates/unfold_crispy/layout/checkbox.html +20 -4
- unfold/templates/unfold_crispy/layout/fieldset.html +3 -3
- unfold/templatetags/unfold.py +34 -4
- unfold/templatetags/unfold_list.py +4 -1
- unfold/widgets.py +42 -1
- {django_unfold-0.59.0.dist-info → django_unfold-0.61.0.dist-info}/LICENSE.md +0 -0
- {django_unfold-0.59.0.dist-info → django_unfold-0.61.0.dist-info}/WHEEL +0 -0
unfold/forms.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
from
|
1
|
+
from collections.abc import Generator
|
2
|
+
from typing import Optional, Union
|
2
3
|
|
3
4
|
from django import forms
|
4
5
|
from django.contrib.admin.forms import (
|
@@ -11,14 +12,23 @@ from django.contrib.auth.forms import (
|
|
11
12
|
AdminPasswordChangeForm as BaseAdminPasswordChangeForm,
|
12
13
|
)
|
13
14
|
from django.contrib.auth.models import User
|
15
|
+
from django.contrib.contenttypes.forms import BaseGenericInlineFormSet
|
16
|
+
from django.core.paginator import Page, Paginator
|
17
|
+
from django.db.models import QuerySet
|
18
|
+
from django.forms import BaseInlineFormSet
|
19
|
+
from django.http import HttpRequest
|
20
|
+
|
21
|
+
from unfold.fields import UnfoldAdminField, UnfoldAdminReadonlyField
|
14
22
|
|
15
23
|
try:
|
16
24
|
from django.contrib.auth.forms import AdminUserCreationForm as BaseUserCreationForm
|
17
25
|
except ImportError:
|
18
26
|
from django.contrib.auth.forms import UserCreationForm as BaseUserCreationForm
|
27
|
+
from django.contrib.admin.helpers import AdminForm as BaseAdminForm
|
28
|
+
from django.contrib.admin.helpers import Fieldline as BaseFieldline
|
29
|
+
from django.contrib.admin.helpers import Fieldset as BaseFieldset
|
19
30
|
from django.contrib.auth.forms import ReadOnlyPasswordHashWidget
|
20
31
|
from django.contrib.auth.forms import UserChangeForm as BaseUserChangeForm
|
21
|
-
from django.http import HttpRequest
|
22
32
|
from django.utils.safestring import mark_safe
|
23
33
|
from django.utils.translation import gettext_lazy as _
|
24
34
|
|
@@ -146,3 +156,87 @@ class AdminOwnPasswordChangeForm(BaseAdminOwnPasswordChangeForm):
|
|
146
156
|
self.fields["old_password"].widget.attrs["class"] = " ".join(INPUT_CLASSES)
|
147
157
|
self.fields["new_password1"].widget.attrs["class"] = " ".join(INPUT_CLASSES)
|
148
158
|
self.fields["new_password2"].widget.attrs["class"] = " ".join(INPUT_CLASSES)
|
159
|
+
|
160
|
+
|
161
|
+
class AdminForm(BaseAdminForm):
|
162
|
+
def __iter__(self) -> Generator["Fieldset", None, None]:
|
163
|
+
for name, options in self.fieldsets:
|
164
|
+
yield Fieldset(
|
165
|
+
self.form,
|
166
|
+
name,
|
167
|
+
readonly_fields=self.readonly_fields,
|
168
|
+
model_admin=self.model_admin,
|
169
|
+
**options,
|
170
|
+
)
|
171
|
+
|
172
|
+
|
173
|
+
class Fieldset(BaseFieldset):
|
174
|
+
def __iter__(self) -> Generator["Fieldline", None, None]:
|
175
|
+
for field in self.fields:
|
176
|
+
yield Fieldline(
|
177
|
+
self.form, field, self.readonly_fields, model_admin=self.model_admin
|
178
|
+
)
|
179
|
+
|
180
|
+
|
181
|
+
class Fieldline(BaseFieldline):
|
182
|
+
def __iter__(
|
183
|
+
self,
|
184
|
+
) -> Generator[Union["UnfoldAdminReadonlyField", "UnfoldAdminField"], None, None]:
|
185
|
+
for i, field in enumerate(self.fields):
|
186
|
+
if field in self.readonly_fields:
|
187
|
+
yield UnfoldAdminReadonlyField(
|
188
|
+
self.form, field, is_first=(i == 0), model_admin=self.model_admin
|
189
|
+
)
|
190
|
+
else:
|
191
|
+
yield UnfoldAdminField(self.form, field, is_first=(i == 0))
|
192
|
+
|
193
|
+
|
194
|
+
class PaginationFormSetMixin:
|
195
|
+
queryset: Optional[QuerySet] = None
|
196
|
+
request: Optional[HttpRequest] = None
|
197
|
+
per_page: Optional[int] = None
|
198
|
+
|
199
|
+
def __init__(
|
200
|
+
self,
|
201
|
+
request: Optional[HttpRequest] = None,
|
202
|
+
per_page: Optional[int] = None,
|
203
|
+
*args,
|
204
|
+
**kwargs,
|
205
|
+
):
|
206
|
+
self.request = request
|
207
|
+
self.per_page = per_page
|
208
|
+
|
209
|
+
super().__init__(*args, **kwargs)
|
210
|
+
|
211
|
+
if self.per_page:
|
212
|
+
self.paginator = Paginator(self.queryset, self.per_page)
|
213
|
+
self.page = self.get_page(self.paginator, self.get_page_num())
|
214
|
+
self._queryset = self.page.object_list
|
215
|
+
|
216
|
+
def get_pagination_key(self) -> str:
|
217
|
+
return f"{self.prefix}-page"
|
218
|
+
|
219
|
+
def get_page_num(self) -> int:
|
220
|
+
page = self.request.GET.get(self.get_pagination_key())
|
221
|
+
if page and page.isnumeric() and page > "0":
|
222
|
+
return int(page)
|
223
|
+
|
224
|
+
page = self.request.POST.get(self.get_pagination_key())
|
225
|
+
if page and page.isnumeric() and page > "0":
|
226
|
+
return int(page)
|
227
|
+
|
228
|
+
return 1
|
229
|
+
|
230
|
+
def get_page(self, paginator: Paginator, page: int) -> Page:
|
231
|
+
if page <= paginator.num_pages:
|
232
|
+
return paginator.page(page)
|
233
|
+
|
234
|
+
return paginator.page(1)
|
235
|
+
|
236
|
+
|
237
|
+
class PaginationInlineFormSet(PaginationFormSetMixin, BaseInlineFormSet):
|
238
|
+
pass
|
239
|
+
|
240
|
+
|
241
|
+
class PaginationGenericInlineFormSet(PaginationFormSetMixin, BaseGenericInlineFormSet):
|
242
|
+
pass
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import copy
|
2
|
-
from typing import Optional
|
2
|
+
from typing import Any, Optional
|
3
3
|
|
4
|
+
from django.contrib.admin import helpers
|
4
5
|
from django.contrib.admin.sites import AdminSite
|
5
6
|
from django.contrib.admin.widgets import RelatedFieldWidgetWrapper
|
6
7
|
from django.db import models
|
@@ -27,6 +28,19 @@ class BaseModelAdminMixin:
|
|
27
28
|
|
28
29
|
super().__init__(model, admin_site)
|
29
30
|
|
31
|
+
def changeform_view(
|
32
|
+
self,
|
33
|
+
request: HttpRequest,
|
34
|
+
object_id: Optional[str] = None,
|
35
|
+
form_url: str = "",
|
36
|
+
extra_context: Optional[dict[str, Any]] = None,
|
37
|
+
) -> Any:
|
38
|
+
from unfold.forms import AdminForm, Fieldline
|
39
|
+
|
40
|
+
helpers.AdminForm = AdminForm
|
41
|
+
helpers.Fieldline = Fieldline
|
42
|
+
return super().changeform_view(request, object_id, form_url, extra_context)
|
43
|
+
|
30
44
|
def formfield_for_choice_field(
|
31
45
|
self, db_field: Field, request: HttpRequest, **kwargs
|
32
46
|
) -> TypedChoiceField:
|
@@ -90,7 +104,12 @@ class BaseModelAdminMixin:
|
|
90
104
|
self, db_field: Field, request: HttpRequest, **kwargs
|
91
105
|
) -> Optional[Field]:
|
92
106
|
if "widget" not in kwargs:
|
93
|
-
|
107
|
+
if db_field.choices:
|
108
|
+
kwargs["widget"] = widgets.UnfoldAdminSelectWidget(
|
109
|
+
choices=db_field.choices
|
110
|
+
)
|
111
|
+
else:
|
112
|
+
kwargs["widget"] = widgets.UnfoldAdminNullBooleanSelectWidget()
|
94
113
|
|
95
114
|
return db_field.formfield(**kwargs)
|
96
115
|
|
unfold/sites.py
CHANGED
@@ -4,15 +4,12 @@ from typing import Any, Callable, Optional, Union
|
|
4
4
|
from urllib.parse import parse_qs, urlparse
|
5
5
|
|
6
6
|
from django.contrib.admin import AdminSite
|
7
|
-
from django.contrib.auth import REDIRECT_FIELD_NAME
|
8
7
|
from django.core.validators import EMPTY_VALUES
|
9
8
|
from django.http import HttpRequest, HttpResponse
|
10
9
|
from django.template.response import TemplateResponse
|
11
10
|
from django.urls import URLPattern, path, reverse, reverse_lazy
|
12
|
-
from django.utils.decorators import method_decorator
|
13
11
|
from django.utils.functional import lazy
|
14
12
|
from django.utils.module_loading import import_string
|
15
|
-
from django.views.decorators.cache import never_cache
|
16
13
|
|
17
14
|
from unfold.dataclasses import DropdownItem, Favicon
|
18
15
|
|
@@ -91,6 +88,9 @@ class UnfoldAdminSite(AdminSite):
|
|
91
88
|
"site_icon": self._get_theme_images("SITE_ICON", request),
|
92
89
|
"site_symbol": self._get_config("SITE_SYMBOL", request),
|
93
90
|
"site_favicons": self._get_favicons("SITE_FAVICONS", request),
|
91
|
+
"login_image": self._get_value(
|
92
|
+
get_config(self.settings_name)["LOGIN"].get("image"), request
|
93
|
+
),
|
94
94
|
"show_history": self._get_config("SHOW_HISTORY", request),
|
95
95
|
"show_view_on_site": self._get_config("SHOW_VIEW_ON_SITE", request),
|
96
96
|
"show_languages": self._get_config("SHOW_LANGUAGES", request),
|
@@ -170,6 +170,7 @@ class UnfoldAdminSite(AdminSite):
|
|
170
170
|
) -> TemplateResponse:
|
171
171
|
query = request.GET.get("s").lower()
|
172
172
|
app_list = super().get_app_list(request)
|
173
|
+
apps = []
|
173
174
|
results = []
|
174
175
|
|
175
176
|
if query in EMPTY_VALUES:
|
@@ -177,7 +178,7 @@ class UnfoldAdminSite(AdminSite):
|
|
177
178
|
|
178
179
|
for app in app_list:
|
179
180
|
if query in app["name"].lower():
|
180
|
-
|
181
|
+
apps.append(app)
|
181
182
|
continue
|
182
183
|
|
183
184
|
models = []
|
@@ -188,7 +189,16 @@ class UnfoldAdminSite(AdminSite):
|
|
188
189
|
|
189
190
|
if len(models) > 0:
|
190
191
|
app["models"] = models
|
191
|
-
|
192
|
+
apps.append(app)
|
193
|
+
|
194
|
+
for app in apps:
|
195
|
+
for model in app["models"]:
|
196
|
+
results.append(
|
197
|
+
{
|
198
|
+
"app": app,
|
199
|
+
"model": model,
|
200
|
+
}
|
201
|
+
)
|
192
202
|
|
193
203
|
return TemplateResponse(
|
194
204
|
request,
|
@@ -196,34 +206,11 @@ class UnfoldAdminSite(AdminSite):
|
|
196
206
|
context={
|
197
207
|
"results": results,
|
198
208
|
},
|
209
|
+
headers={
|
210
|
+
"HX-Trigger": "search",
|
211
|
+
},
|
199
212
|
)
|
200
213
|
|
201
|
-
@method_decorator(never_cache)
|
202
|
-
@login_not_required
|
203
|
-
def login(
|
204
|
-
self, request: HttpRequest, extra_context: Optional[dict[str, Any]] = None
|
205
|
-
) -> HttpResponse:
|
206
|
-
extra_context = {} if extra_context is None else extra_context
|
207
|
-
image = self._get_value(
|
208
|
-
get_config(self.settings_name)["LOGIN"].get("image"), request
|
209
|
-
)
|
210
|
-
|
211
|
-
redirect_field_name = self._get_value(
|
212
|
-
get_config(self.settings_name)["LOGIN"].get("redirect_after"), request
|
213
|
-
)
|
214
|
-
|
215
|
-
if image not in EMPTY_VALUES:
|
216
|
-
extra_context.update(
|
217
|
-
{
|
218
|
-
"image": image,
|
219
|
-
}
|
220
|
-
)
|
221
|
-
|
222
|
-
if redirect_field_name not in EMPTY_VALUES:
|
223
|
-
extra_context.update({REDIRECT_FIELD_NAME: redirect_field_name})
|
224
|
-
|
225
|
-
return super().login(request, extra_context)
|
226
|
-
|
227
214
|
def password_change(
|
228
215
|
self, request: HttpRequest, extra_context: Optional[dict[str, Any]] = None
|
229
216
|
) -> HttpResponse:
|