django-unfold 0.34.0__py3-none-any.whl → 0.35.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.34.0.dist-info → django_unfold-0.35.0.dist-info}/METADATA +17 -5
- {django_unfold-0.34.0.dist-info → django_unfold-0.35.0.dist-info}/RECORD +13 -12
- unfold/admin.py +7 -5
- unfold/contrib/filters/admin.py +34 -1
- unfold/contrib/filters/forms.py +36 -3
- unfold/decorators.py +2 -2
- unfold/static/unfold/js/app.js +23 -0
- unfold/static/unfold/js/select2.init.js +15 -0
- unfold/templates/admin/change_form.html +1 -1
- unfold/templates/admin/submit_line.html +1 -1
- unfold/widgets.py +11 -1
- {django_unfold-0.34.0.dist-info → django_unfold-0.35.0.dist-info}/LICENSE.md +0 -0
- {django_unfold-0.34.0.dist-info → django_unfold-0.35.0.dist-info}/WHEEL +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: django-unfold
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.35.0
|
4
4
|
Summary: Modern Django admin theme for seamless interface development
|
5
5
|
Home-page: https://unfoldadmin.com
|
6
6
|
License: MIT
|
@@ -179,7 +179,7 @@ class CustomAdminClass(ModelAdmin):
|
|
179
179
|
from django.contrib import admin
|
180
180
|
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
181
181
|
from django.contrib.auth.admin import GroupAdmin as BaseGroupAdmin
|
182
|
-
from django.contrib.auth.models import User
|
182
|
+
from django.contrib.auth.models import User, Group
|
183
183
|
|
184
184
|
from unfold.admin import ModelAdmin
|
185
185
|
|
@@ -360,6 +360,9 @@ class CustomAdminClass(ModelAdmin):
|
|
360
360
|
# Display fields in changeform in compressed mode
|
361
361
|
compressed_fields = True # Default: False
|
362
362
|
|
363
|
+
# Warn before leaving unsaved changes in changeform
|
364
|
+
warn_unsaved_form = True # Default: False
|
365
|
+
|
363
366
|
# Preprocess content of readonly fields before render
|
364
367
|
readonly_preprocess_fields = {
|
365
368
|
"model_field_name": "html.unescape",
|
@@ -645,7 +648,14 @@ The difference between them is that `ChoicesDropdownFilter` will collect a list
|
|
645
648
|
from django.contrib import admin
|
646
649
|
from django.contrib.auth.models import User
|
647
650
|
from unfold.admin import ModelAdmin
|
648
|
-
from unfold.contrib.filters.admin import
|
651
|
+
from unfold.contrib.filters.admin import (
|
652
|
+
ChoicesDropdownFilter,
|
653
|
+
MultipleChoicesDropdownFilter,
|
654
|
+
RelatedDropdownFilter,
|
655
|
+
MultipleRelatedDropdownFilter,
|
656
|
+
DropdownFilter,
|
657
|
+
MultipleDropdownFilter
|
658
|
+
)
|
649
659
|
|
650
660
|
|
651
661
|
class CustomDropdownFilter(DropdownFilter):
|
@@ -672,7 +682,9 @@ class MyAdmin(ModelAdmin):
|
|
672
682
|
list_filter = [
|
673
683
|
CustomDropdownFilter,
|
674
684
|
("modelfield_with_choices", ChoicesDropdownFilter),
|
685
|
+
("modelfield_with_choices_multiple", MultipleChoicesDropdownFilter),
|
675
686
|
("modelfield_with_foreign_key", RelatedDropdownFilter)
|
687
|
+
("modelfield_with_foreign_key_multiple", MultipleRelatedDropdownFilter)
|
676
688
|
]
|
677
689
|
```
|
678
690
|
|
@@ -1363,8 +1375,8 @@ def dashboard_callback(request: HttpRequest) -> Dict:
|
|
1363
1375
|
```
|
1364
1376
|
|
1365
1377
|
```django-html
|
1366
|
-
{% component "unfold/components/card" with title="Card title" %}
|
1367
|
-
{% component "unfold/components/table.html" with table=table_data card_included=1 striped=1 %}{%
|
1378
|
+
{% component "unfold/components/card.html" with title="Card title" %}
|
1379
|
+
{% component "unfold/components/table.html" with table=table_data card_included=1 striped=1 %}{% endcomponent %}
|
1368
1380
|
{% endcomponent %}
|
1369
1381
|
```
|
1370
1382
|
|
@@ -1,12 +1,12 @@
|
|
1
1
|
unfold/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
unfold/admin.py,sha256=
|
2
|
+
unfold/admin.py,sha256=nUAouLLyo57CE3EpOREHDMjOaj68P7MZO6KEPYlutYU,19587
|
3
3
|
unfold/apps.py,sha256=SlBXPYrUd2uXn67qFbRvbXSUk3XFWrF4-5WELgDCvho,381
|
4
4
|
unfold/checks.py,sha256=Smgji9w19hnYjJElJ_FJnnyTEAE-E-OUB6otHu7lasY,1670
|
5
5
|
unfold/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
6
|
unfold/contrib/filters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
|
-
unfold/contrib/filters/admin.py,sha256=
|
7
|
+
unfold/contrib/filters/admin.py,sha256=4dwK-LCXaopQeqW3818wUiZJhkJEDLNwbDLJboVJPHk,21138
|
8
8
|
unfold/contrib/filters/apps.py,sha256=wEySJy0gMLzFLb9XNKE-RexiO05X7NaQ5QmxZyziJ_k,136
|
9
|
-
unfold/contrib/filters/forms.py,sha256=
|
9
|
+
unfold/contrib/filters/forms.py,sha256=N25YiD7YfBxg57-goSnaXc-eyEQFp1XYgDmmQwg7jQ0,5696
|
10
10
|
unfold/contrib/filters/static/unfold/filters/css/nouislider.min.css,sha256=rddL_jOGGVEY6wR-aw0VYovAfz5fPeAIsulrlSNb1hc,4221
|
11
11
|
unfold/contrib/filters/static/unfold/filters/js/DateTimeShortcuts.js,sha256=jgFNBDf6aHvUlyv0LEDHggXO-xA8pWOCWFWcVupdA30,19332
|
12
12
|
unfold/contrib/filters/static/unfold/filters/js/admin-numeric-filter.js,sha256=nTkiiJk4Abn9d6KigxPSEsereFhFq-v5n_boiiY1eII,1668
|
@@ -65,7 +65,7 @@ unfold/contrib/simple_history/templates/simple_history/object_history_form.html,
|
|
65
65
|
unfold/contrib/simple_history/templates/simple_history/object_history_list.html,sha256=f0RxTiPfC957uQzNfSkKoyMUzu8LRw4-MS6d3xZ2gew,6821
|
66
66
|
unfold/contrib/simple_history/templates/simple_history/submit_line.html,sha256=9v7PfS8M6N0NccTEb_9QyTG28NgDBiIkjuBOfWGIQ18,1703
|
67
67
|
unfold/dataclasses.py,sha256=4PAWLVlUhyGMq1Mwai7afCgrRO0Inulw0opFUKVEOBk,483
|
68
|
-
unfold/decorators.py,sha256=
|
68
|
+
unfold/decorators.py,sha256=jyjE2sunazp8gK1QIsNRJGEEDwO7RDKWDKnOI3GDWZE,3338
|
69
69
|
unfold/exceptions.py,sha256=gcCj1ox61E137bk_0Cqy4YC3SttdPgB-fiJUqpmyHSE,43
|
70
70
|
unfold/fields.py,sha256=yhtpDfqycpOxqdQlbncCg9qhELxGk3AtXizkZyzzH0g,7410
|
71
71
|
unfold/forms.py,sha256=8-IoFZLwUNJKTNdev_oJwdxm-_xekvrn_84IXKse4Q8,4100
|
@@ -83,9 +83,10 @@ unfold/static/unfold/fonts/material-symbols/styles.css,sha256=U0oiGd2DS7LXnbuqgs
|
|
83
83
|
unfold/static/unfold/js/alpine.anchor.js,sha256=2Jg9aUq749pjFynzr_H1NK3lf-nXrbHMtO9wvlWJyIQ,15524
|
84
84
|
unfold/static/unfold/js/alpine.js,sha256=NY2a-7GrW--i9IBhowd25bzXcH9BCmBrqYX5i8OxwDQ,44659
|
85
85
|
unfold/static/unfold/js/alpine.persist.js,sha256=jFBwr6faTqqhp3sVi4_VTxJ0FpaF9YGZN1ZGLl_5QYM,837
|
86
|
-
unfold/static/unfold/js/app.js,sha256=
|
86
|
+
unfold/static/unfold/js/app.js,sha256=kL7gJu4bDcY7v61KYtYtjn7KxWd-rHVvL49GTD_q770,6884
|
87
87
|
unfold/static/unfold/js/chart.js,sha256=22W6cFERR-CElMOKRgMMicueMVP0Vf7FBEBYH8Z8tCk,200633
|
88
88
|
unfold/static/unfold/js/htmx.js,sha256=XOLqvnZiyEx46EW9vaJTBUaaWg8CGVVfXJkVsUmJbpI,42820
|
89
|
+
unfold/static/unfold/js/select2.init.js,sha256=SzpjSL1mD9tHedjSeXlmEwHjuuyqlnpilpSeEEKeYLE,265
|
89
90
|
unfold/static/unfold/js/simplebar.js,sha256=t-uG1FAD6ZoiMeN--wac0XRS7SxoDVG6zvRnGuEp7X8,27176
|
90
91
|
unfold/styles.css,sha256=egNdWhCzaGS5Y_tCiceIU6BTNn1MkrMMooX5zDv_r6I,18223
|
91
92
|
unfold/templates/admin/actions.html,sha256=8GqELAxzywGyfQJiQeuhGIQUakdK_WeneILY06C7mEo,2746
|
@@ -95,7 +96,7 @@ unfold/templates/admin/auth/user/add_form.html,sha256=iLig-vd2YExXsj0xGBwYhZ4kGU
|
|
95
96
|
unfold/templates/admin/auth/user/change_password.html,sha256=-Wa9ml3yss-kDz0YQxCiwoxs91KQD8eetCt5l6xekWM,2892
|
96
97
|
unfold/templates/admin/base.html,sha256=RptiEpgXZ6seYrPu-1Stoe8ECHpi9PjkJjFheGecYF0,2358
|
97
98
|
unfold/templates/admin/base_site.html,sha256=3ckWrcAdd7Pw1hk6Zwyknab_Qb-rteV9-mXhMnfo6VI,361
|
98
|
-
unfold/templates/admin/change_form.html,sha256=
|
99
|
+
unfold/templates/admin/change_form.html,sha256=FiVfbB1j1wb6qGfaLukdMUrLRXsO_sJ4wirKq1bit60,4528
|
99
100
|
unfold/templates/admin/change_form_object_tools.html,sha256=eyeH-i2HgEM0Yi-OJA2D1VnKJyC19A_my1IDGxxoP8Y,593
|
100
101
|
unfold/templates/admin/change_list.html,sha256=o8XnKa1xFot53-BxGeB8afJ0TF8N7TcJhyTA4vPnop0,5124
|
101
102
|
unfold/templates/admin/change_list_object_tools.html,sha256=cmMiT2nT20Ph5yfpj9aHPr76Z-JP4aSXp0o-Rnad28s,147
|
@@ -114,7 +115,7 @@ unfold/templates/admin/nav_sidebar.html,sha256=cKuC28XU9NT17lrCVugn2cHf39kgkoX49
|
|
114
115
|
unfold/templates/admin/object_history.html,sha256=mRD6nbIVmnsz2OG6UsuXdI55bflY3Vs8zbIE9X-Oi74,4816
|
115
116
|
unfold/templates/admin/pagination.html,sha256=KrwOM6gRizfpTMP59TESQaNGcQzkga2k02opl_7BxLo,1130
|
116
117
|
unfold/templates/admin/search_form.html,sha256=vKuruVUEMarx8FPrBp86yqqFtUXfFPzR_9GxhnOCs4U,1173
|
117
|
-
unfold/templates/admin/submit_line.html,sha256=
|
118
|
+
unfold/templates/admin/submit_line.html,sha256=6V9qtE9BecN905-YkI5ZOLY3ZYyb_mhN_p9Zkhv4h8Q,4248
|
118
119
|
unfold/templates/auth/widgets/read_only_password_hash.html,sha256=I08wFPcDr0Gjrg64nlcKiB12Tz2g_nnJXemiy8tkoZg,785
|
119
120
|
unfold/templates/registration/logged_out.html,sha256=4rjAazcxt8ZCqA5bConKRgAk4VrljNTksdtg7D_btrU,1028
|
120
121
|
unfold/templates/registration/password_change_done.html,sha256=i1ZzfTwZHWNWoN9_xHZDdcgLdTOVbTFFD1HUSuG0LkY,1062
|
@@ -194,8 +195,8 @@ unfold/templatetags/unfold_list.py,sha256=q02u7jbMWyDi_6_rOk17W-jW6Yqe9dkuz7Cc_m
|
|
194
195
|
unfold/typing.py,sha256=1P8PWM2oeaceUJtA5j071RbKEBpHYaux441u7Hd6wv4,643
|
195
196
|
unfold/utils.py,sha256=zZdJE4FmwRd7p5a7sJiAoZjBOJitXJduOq7BulyppWM,4803
|
196
197
|
unfold/views.py,sha256=hQCyeeMa9kcJV1IZeeYqj8PGW7J4QWME8n-5n0UGmiU,1003
|
197
|
-
unfold/widgets.py,sha256=
|
198
|
-
django_unfold-0.
|
199
|
-
django_unfold-0.
|
200
|
-
django_unfold-0.
|
201
|
-
django_unfold-0.
|
198
|
+
unfold/widgets.py,sha256=6hQp13LmuhDJ41YxTG70SPJEg0agmHcmUhCtCUwI6SU,16369
|
199
|
+
django_unfold-0.35.0.dist-info/LICENSE.md,sha256=Ltk_quRyyvV3J5v3brtOqmibeZSw2Hrb8bY1W3ya0Ik,1077
|
200
|
+
django_unfold-0.35.0.dist-info/METADATA,sha256=_lkCvpMErEFx3xEWaKWziYEDzsvy4jxiLlfVkJav5u0,57888
|
201
|
+
django_unfold-0.35.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
202
|
+
django_unfold-0.35.0.dist-info/RECORD,,
|
unfold/admin.py
CHANGED
@@ -236,6 +236,7 @@ class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
|
|
236
236
|
list_disable_select_all = False
|
237
237
|
compressed_fields = False
|
238
238
|
readonly_preprocess_fields = {}
|
239
|
+
warn_unsaved_form = False
|
239
240
|
checks_class = UnfoldModelAdminChecks
|
240
241
|
|
241
242
|
@property
|
@@ -244,13 +245,14 @@ class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
|
|
244
245
|
additional_media = forms.Media()
|
245
246
|
|
246
247
|
for filter in self.list_filter:
|
247
|
-
if
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
filter[1].form_class, "Media"
|
248
|
+
if (
|
249
|
+
isinstance(filter, (tuple, list))
|
250
|
+
and hasattr(filter[1], "form_class")
|
251
|
+
and hasattr(filter[1].form_class, "Media")
|
252
252
|
):
|
253
253
|
additional_media += forms.Media(filter[1].form_class.Media)
|
254
|
+
elif hasattr(filter, "form_class") and hasattr(filter.form_class, "Media"):
|
255
|
+
additional_media += forms.Media(filter.form_class.Media)
|
254
256
|
|
255
257
|
return media + additional_media
|
256
258
|
|
unfold/contrib/filters/admin.py
CHANGED
@@ -41,12 +41,23 @@ class ValueMixin:
|
|
41
41
|
)
|
42
42
|
|
43
43
|
|
44
|
+
class MultiValueMixin:
|
45
|
+
def value(self) -> Optional[List[str]]:
|
46
|
+
return (
|
47
|
+
self.lookup_val
|
48
|
+
if self.lookup_val not in EMPTY_VALUES
|
49
|
+
and isinstance(self.lookup_val, List)
|
50
|
+
and len(self.lookup_val) > 0
|
51
|
+
else self.lookup_val
|
52
|
+
)
|
53
|
+
|
54
|
+
|
44
55
|
class DropdownMixin:
|
45
56
|
template = "unfold/filters/filters_field.html"
|
46
57
|
form_class = DropdownForm
|
47
58
|
all_option = ["", _("All")]
|
48
59
|
|
49
|
-
def queryset(self, request, queryset) -> QuerySet:
|
60
|
+
def queryset(self, request: HttpRequest, queryset: QuerySet) -> QuerySet:
|
50
61
|
if self.value() not in EMPTY_VALUES:
|
51
62
|
return super().queryset(request, queryset)
|
52
63
|
|
@@ -112,11 +123,23 @@ class DropdownFilter(admin.SimpleListFilter):
|
|
112
123
|
name=self.parameter_name,
|
113
124
|
choices=[self.all_option, *self.lookup_choices],
|
114
125
|
data={self.parameter_name: self.value()},
|
126
|
+
multiple=self.multiple if hasattr(self, "multiple") else False,
|
115
127
|
),
|
116
128
|
},
|
117
129
|
)
|
118
130
|
|
119
131
|
|
132
|
+
class MultipleDropdownFilter(DropdownFilter):
|
133
|
+
multiple = True
|
134
|
+
|
135
|
+
def __init__(self, request, params, model, model_admin):
|
136
|
+
self.request = request
|
137
|
+
super().__init__(request, params, model, model_admin)
|
138
|
+
|
139
|
+
def value(self):
|
140
|
+
return self.request.GET.getlist(self.parameter_name)
|
141
|
+
|
142
|
+
|
120
143
|
class ChoicesDropdownFilter(ValueMixin, DropdownMixin, admin.ChoicesFieldListFilter):
|
121
144
|
def choices(self, changelist: ChangeList):
|
122
145
|
choices = [self.all_option, *self.field.flatchoices]
|
@@ -127,10 +150,15 @@ class ChoicesDropdownFilter(ValueMixin, DropdownMixin, admin.ChoicesFieldListFil
|
|
127
150
|
name=self.lookup_kwarg,
|
128
151
|
choices=choices,
|
129
152
|
data={self.lookup_kwarg: self.value()},
|
153
|
+
multiple=self.multiple if hasattr(self, "multiple") else False,
|
130
154
|
),
|
131
155
|
}
|
132
156
|
|
133
157
|
|
158
|
+
class MultipleChoicesDropdownFilter(MultiValueMixin, ChoicesDropdownFilter):
|
159
|
+
multiple = True
|
160
|
+
|
161
|
+
|
134
162
|
class RelatedDropdownFilter(ValueMixin, DropdownMixin, admin.RelatedFieldListFilter):
|
135
163
|
def choices(self, changelist: ChangeList):
|
136
164
|
yield {
|
@@ -139,10 +167,15 @@ class RelatedDropdownFilter(ValueMixin, DropdownMixin, admin.RelatedFieldListFil
|
|
139
167
|
name=self.lookup_kwarg,
|
140
168
|
choices=[self.all_option, *self.lookup_choices],
|
141
169
|
data={self.lookup_kwarg: self.value()},
|
170
|
+
multiple=self.multiple if hasattr(self, "multiple") else False,
|
142
171
|
),
|
143
172
|
}
|
144
173
|
|
145
174
|
|
175
|
+
class MultipleRelatedDropdownFilter(MultiValueMixin, RelatedDropdownFilter):
|
176
|
+
multiple = True
|
177
|
+
|
178
|
+
|
146
179
|
class SingleNumericFilter(admin.FieldListFilter):
|
147
180
|
request = None
|
148
181
|
parameter_name = None
|
unfold/contrib/filters/forms.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
from django import forms
|
2
|
+
from django.forms import ChoiceField, MultipleChoiceField
|
2
3
|
from django.utils.translation import gettext_lazy as _
|
3
4
|
|
4
5
|
from ...widgets import (
|
5
6
|
INPUT_CLASSES,
|
7
|
+
UnfoldAdminSelectMultipleWidget,
|
6
8
|
UnfoldAdminSelectWidget,
|
7
9
|
UnfoldAdminSplitDateTimeVerticalWidget,
|
8
10
|
UnfoldAdminTextInputWidget,
|
@@ -21,15 +23,46 @@ class SearchForm(forms.Form):
|
|
21
23
|
|
22
24
|
|
23
25
|
class DropdownForm(forms.Form):
|
24
|
-
|
26
|
+
widget = UnfoldAdminSelectWidget(
|
27
|
+
attrs={
|
28
|
+
"data-theme": "admin-autocomplete",
|
29
|
+
"class": "admin-autocomplete",
|
30
|
+
}
|
31
|
+
)
|
32
|
+
field = ChoiceField
|
33
|
+
|
34
|
+
def __init__(self, name, label, choices, multiple=False, *args, **kwargs):
|
25
35
|
super().__init__(*args, **kwargs)
|
26
36
|
|
27
|
-
|
37
|
+
if multiple:
|
38
|
+
self.widget = UnfoldAdminSelectMultipleWidget(
|
39
|
+
attrs={
|
40
|
+
"data-theme": "admin-autocomplete",
|
41
|
+
"class": "admin-autocomplete",
|
42
|
+
}
|
43
|
+
)
|
44
|
+
self.field = MultipleChoiceField
|
45
|
+
|
46
|
+
self.fields[name] = self.field(
|
28
47
|
label=label,
|
29
48
|
required=False,
|
30
49
|
choices=choices,
|
31
|
-
widget=
|
50
|
+
widget=self.widget,
|
51
|
+
)
|
52
|
+
|
53
|
+
class Media:
|
54
|
+
js = (
|
55
|
+
"admin/js/vendor/jquery/jquery.js",
|
56
|
+
"admin/js/vendor/select2/select2.full.js",
|
57
|
+
"admin/js/jquery.init.js",
|
58
|
+
"unfold/js/select2.init.js",
|
32
59
|
)
|
60
|
+
css = {
|
61
|
+
"screen": (
|
62
|
+
"admin/css/vendor/select2/select2.css",
|
63
|
+
"admin/css/autocomplete.css",
|
64
|
+
),
|
65
|
+
}
|
33
66
|
|
34
67
|
|
35
68
|
class SingleNumericForm(forms.Form):
|
unfold/decorators.py
CHANGED
@@ -29,12 +29,12 @@ def action(
|
|
29
29
|
getattr(model_admin, f"has_{permission}_permission")
|
30
30
|
for permission in permissions
|
31
31
|
)
|
32
|
-
# TODO add obj parameter into has_permission method call.
|
33
32
|
# Permissions methods have following syntax: has_<some>_permission(self, request, obj=None):
|
34
33
|
# But obj is not examined by default in django admin and it would also require additional
|
35
34
|
# fetch from database, therefore it is not supported yet
|
36
35
|
if not any(
|
37
|
-
has_permission(request)
|
36
|
+
has_permission(request, kwargs.get("object_id"))
|
37
|
+
for has_permission in permission_checks
|
38
38
|
):
|
39
39
|
raise PermissionDenied
|
40
40
|
return func(model_admin, request, *args, **kwargs)
|
unfold/static/unfold/js/app.js
CHANGED
@@ -8,8 +8,31 @@ window.addEventListener("load", (e) => {
|
|
8
8
|
renderCharts();
|
9
9
|
|
10
10
|
filterForm();
|
11
|
+
|
12
|
+
warnWithoutSaving();
|
11
13
|
});
|
12
14
|
|
15
|
+
/*************************************************************
|
16
|
+
* Warn without saving
|
17
|
+
*************************************************************/
|
18
|
+
const warnWithoutSaving = () => {
|
19
|
+
let formChanged = false;
|
20
|
+
|
21
|
+
Array.from(
|
22
|
+
document.querySelectorAll(
|
23
|
+
"form.warn-unsaved-form input, form.warn-unsaved-form select"
|
24
|
+
)
|
25
|
+
).forEach((field) => {
|
26
|
+
field.addEventListener("change", (e) => (formChanged = true));
|
27
|
+
});
|
28
|
+
|
29
|
+
window.addEventListener("beforeunload", (e) => {
|
30
|
+
if (formChanged) {
|
31
|
+
e.preventDefault();
|
32
|
+
}
|
33
|
+
});
|
34
|
+
};
|
35
|
+
|
13
36
|
/*************************************************************
|
14
37
|
* Filter form
|
15
38
|
*************************************************************/
|
@@ -0,0 +1,15 @@
|
|
1
|
+
"use strict";
|
2
|
+
{
|
3
|
+
const $ = django.jQuery;
|
4
|
+
|
5
|
+
$.fn.djangoCustomSelect2 = function () {
|
6
|
+
$.each(this, function (i, element) {
|
7
|
+
$(element).select2();
|
8
|
+
});
|
9
|
+
return this;
|
10
|
+
};
|
11
|
+
|
12
|
+
$(function () {
|
13
|
+
$(".admin-autocomplete").djangoCustomSelect2();
|
14
|
+
});
|
15
|
+
}
|
@@ -59,7 +59,7 @@
|
|
59
59
|
|
60
60
|
{% block content %}
|
61
61
|
<div id="content-main">
|
62
|
-
<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}{% if form_url %}action="{{ form_url }}" {% endif %}method="post" id="{{ opts.model_name }}_form" novalidate>
|
62
|
+
<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}{% if form_url %}action="{{ form_url }}" {% endif %}method="post" id="{{ opts.model_name }}_form" {% if adminform.model_admin.warn_unsaved_form %}class="warn-unsaved-form"{% endif %} novalidate>
|
63
63
|
{% csrf_token %}
|
64
64
|
|
65
65
|
{% block form_top %}{% endblock %}
|
@@ -42,7 +42,7 @@
|
|
42
42
|
{% endif %}
|
43
43
|
|
44
44
|
{% if show_save_as_new %}
|
45
|
-
<button type="submit" name="_saveasnew" class="border font-medium px-3 py-2 rounded-md transition-all w-full hover:bg-gray-50 dark:border-gray-700 dark:hover:text-gray-200 dark:hover:bg-gray-900">
|
45
|
+
<button type="submit" name="_saveasnew" class="border font-medium hidden px-3 py-2 rounded-md transition-all w-full hover:bg-gray-50 lg:block lg:w-auto dark:border-gray-700 dark:hover:text-gray-200 dark:hover:bg-gray-900">
|
46
46
|
{% translate 'Save as new' %}
|
47
47
|
</button>
|
48
48
|
{% endif %}
|
unfold/widgets.py
CHANGED
@@ -25,6 +25,7 @@ from django.forms import (
|
|
25
25
|
NumberInput,
|
26
26
|
PasswordInput,
|
27
27
|
Select,
|
28
|
+
SelectMultiple,
|
28
29
|
)
|
29
30
|
from django.utils.translation import gettext_lazy as _
|
30
31
|
|
@@ -480,7 +481,16 @@ class UnfoldAdminSelectWidget(Select):
|
|
480
481
|
if attrs is None:
|
481
482
|
attrs = {}
|
482
483
|
|
483
|
-
attrs["class"] = " ".join(SELECT_CLASSES)
|
484
|
+
attrs["class"] = " ".join([*SELECT_CLASSES, attrs.get("class", "")])
|
485
|
+
super().__init__(attrs, choices)
|
486
|
+
|
487
|
+
|
488
|
+
class UnfoldAdminSelectMultipleWidget(SelectMultiple):
|
489
|
+
def __init__(self, attrs=None, choices=()):
|
490
|
+
if attrs is None:
|
491
|
+
attrs = {}
|
492
|
+
|
493
|
+
attrs["class"] = " ".join([*SELECT_CLASSES, attrs.get("class", "")])
|
484
494
|
super().__init__(attrs, choices)
|
485
495
|
|
486
496
|
|
File without changes
|
File without changes
|