django-unfold 0.22.0__py3-none-any.whl → 0.24.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.22.0.dist-info → django_unfold-0.24.0.dist-info}/METADATA +102 -4
- {django_unfold-0.22.0.dist-info → django_unfold-0.24.0.dist-info}/RECORD +44 -43
- unfold/admin.py +29 -22
- unfold/apps.py +5 -0
- unfold/checks.py +1 -1
- unfold/contrib/filters/admin.py +116 -0
- unfold/contrib/filters/forms.py +29 -1
- unfold/contrib/filters/templates/unfold/filters/filters_date_range.html +1 -1
- unfold/contrib/filters/templates/unfold/filters/filters_datetime_range.html +1 -1
- unfold/contrib/filters/templates/unfold/filters/filters_field.html +7 -0
- unfold/contrib/filters/templates/unfold/filters/filters_numeric_range.html +1 -1
- unfold/contrib/filters/templates/unfold/filters/filters_numeric_single.html +1 -1
- unfold/contrib/filters/templates/unfold/filters/filters_numeric_slider.html +1 -1
- unfold/contrib/import_export/admin.py +1 -1
- unfold/contrib/import_export/forms.py +6 -7
- unfold/contrib/import_export/templates/admin/import_export/export.html +1 -1
- unfold/contrib/import_export/templates/admin/import_export/import_form.html +2 -2
- unfold/forms.py +6 -1
- unfold/sites.py +3 -3
- unfold/static/unfold/css/styles.css +1 -1
- unfold/styles.css +11 -4
- unfold/templates/admin/actions.html +9 -9
- unfold/templates/admin/app_list.html +6 -8
- unfold/templates/admin/base.html +1 -3
- unfold/templates/admin/change_form.html +1 -2
- unfold/templates/admin/change_list.html +2 -2
- unfold/templates/admin/change_list_results.html +15 -3
- unfold/templates/admin/edit_inline/tabular.html +3 -3
- unfold/templates/admin/filter.html +2 -2
- unfold/templates/admin/search_form.html +10 -12
- unfold/templates/unfold/helpers/display_header.html +16 -13
- unfold/templates/unfold/helpers/tab_list.html +1 -1
- unfold/templates/unfold/layouts/base_simple.html +1 -1
- unfold/templates/unfold/widgets/date.html +1 -1
- unfold/templates/{admin → unfold}/widgets/related_widget_wrapper.html +4 -4
- unfold/templates/unfold/widgets/time.html +1 -1
- unfold/templatetags/unfold_list.py +8 -6
- unfold/widgets.py +18 -5
- {django_unfold-0.22.0.dist-info → django_unfold-0.24.0.dist-info}/LICENSE.md +0 -0
- {django_unfold-0.22.0.dist-info → django_unfold-0.24.0.dist-info}/WHEEL +0 -0
- /unfold/templates/{admin → unfold}/widgets/clearable_file_input.html +0 -0
- /unfold/templates/{admin → unfold}/widgets/radio.html +0 -0
- /unfold/templates/{admin → unfold}/widgets/radio_option.html +0 -0
- /unfold/templates/{admin → unfold}/widgets/split_datetime.html +0 -0
@@ -8,10 +8,10 @@
|
|
8
8
|
|
9
9
|
{% if results %}
|
10
10
|
<table id="result_list" class="block border-gray-200 border-spacing-none border-separate text-gray-700 w-full dark:text-gray-400 lg:border lg:rounded-md lg:shadow-sm lg:table lg:dark:border-gray-800">
|
11
|
-
<thead
|
11
|
+
<thead>
|
12
12
|
<tr>
|
13
13
|
{% for header in result_headers %}
|
14
|
-
<th class="align-middle font-medium
|
14
|
+
<th class="align-middle font-medium py-2 text-left text-gray-400 text-sm {{ header.class_attrib }} {% if "action-toggle" in header.text and forloop.counter == 1 %}lg:px-3 lg:w-10{% else %}hidden px-3 lg:table-cell{% endif %}" scope="col">
|
15
15
|
<div class="flex items-center">
|
16
16
|
<div class="text">
|
17
17
|
{% if header.sortable %}
|
@@ -19,7 +19,19 @@
|
|
19
19
|
{{ header.text|capfirst }}
|
20
20
|
</a>
|
21
21
|
{% else %}
|
22
|
-
|
22
|
+
{% if "action-toggle" in header.text and forloop.counter == 1 %}
|
23
|
+
<label class="flex flex-row items-center gap-2">
|
24
|
+
{{ header.text|capfirst }}
|
25
|
+
|
26
|
+
<span class="block font-normal text-gray-500 dark:text-gray-400 lg:hidden">
|
27
|
+
{% trans "Select all rows"%}
|
28
|
+
</span>
|
29
|
+
</label>
|
30
|
+
{% else %}
|
31
|
+
<span>
|
32
|
+
{{ header.text|capfirst }}
|
33
|
+
</span>
|
34
|
+
{% endif %}
|
23
35
|
{% endif %}
|
24
36
|
</div>
|
25
37
|
|
@@ -109,11 +109,11 @@
|
|
109
109
|
{% with is_last_col=forloop.last %}
|
110
110
|
{% for field in line %}
|
111
111
|
{% if field.is_readonly or not field.field.is_hidden %}
|
112
|
-
<td{% if field.field.name %} class="field-{{ field.field.name }}{% if field.field.errors|length > 0 %} errors{% endif %}{% if inline_admin_form.original %} p-3 lg:py-3{% else %} py-3{% endif %}{% if field.is_checkbox %} align-middle{% else %} align-top{% endif %} {% if is_last_row and not inline_admin_formset.has_add_permission %}{% if is_last_col %}border-0 {% else %}border-b lg:border-0{% endif %}{% else %}border-b{% endif %} border-gray-200 flex items-center before:capitalize before:content-[attr(data-label)] before:mr-auto before:text-gray-500 before:w-72 lg:before:hidden font-normal px-3 text-left text-sm lg:table-cell dark:border-gray-800"{% endif %} data-label="{{ field.field.label }}">
|
112
|
+
<td{% if field.field.name %} class="field-{{ field.field.name }}{% if field.field.errors|length > 0 %} errors{% endif %}{% if inline_admin_form.original %} p-3 lg:py-3{% else %} py-3{% endif %}{% if field.is_checkbox %} align-middle{% else %} align-top{% endif %} {% if is_last_row and not inline_admin_formset.has_add_permission %}{% if is_last_col %}border-0 {% else %}border-b lg:border-0{% endif %}{% else %}border-b{% endif %} border-gray-200 flex items-center before:capitalize before:content-[attr(data-label)] before:mr-auto before:text-gray-500 before:w-72 lg:before:hidden font-normal px-3 text-left text-sm lg:table-cell dark:border-gray-800 {% if field.field.is_hidden %} !hidden{% endif %}"{% endif %} data-label="{{ field.field.label }}">
|
113
113
|
{% if field.is_readonly %}
|
114
|
-
<
|
114
|
+
<div class="bg-gray-50 border font-medium max-w-lg px-3 py-2 rounded-md shadow-sm text-gray-500 text-sm truncate whitespace-nowrap dark:border-gray-700 dark:text-gray-400 dark:bg-gray-800">
|
115
115
|
{{ field.contents }}
|
116
|
-
</
|
116
|
+
</div>
|
117
117
|
{% else %}
|
118
118
|
{{ field.field }}
|
119
119
|
|
@@ -1,12 +1,12 @@
|
|
1
1
|
{% load i18n unfold %}
|
2
2
|
|
3
3
|
<div class="mb-6">
|
4
|
-
<h3 class="font-medium mb-
|
4
|
+
<h3 class="font-medium mb-2 text-gray-700 text-sm dark:text-gray-200">
|
5
5
|
{% blocktranslate with filter_title=title %} By {{ filter_title }} {% endblocktranslate %}
|
6
6
|
</h3>
|
7
7
|
|
8
8
|
{% for choice in choices %}
|
9
|
-
{% if choice.selected %}
|
9
|
+
{% if choice.selected and spec.lookup_val.0 %}
|
10
10
|
<input type="hidden" name="{{ spec.lookup_kwarg }}" value="{{ spec.lookup_val.0 }}" />
|
11
11
|
{% endif %}
|
12
12
|
{% endfor %}
|
@@ -1,19 +1,17 @@
|
|
1
1
|
{% load i18n static %}
|
2
2
|
|
3
3
|
{% if cl.search_fields %}
|
4
|
-
<div id="toolbar"
|
5
|
-
<div>
|
6
|
-
<
|
7
|
-
<input class="font-medium h-9 px-3 text-gray-500 text-sm w-40 lg:w-60 focus:outline-none dark:bg-gray-900 dark:text-gray-400" type="text" name="{{ search_var }}" value="{{ cl.query }}" id="searchbar" placeholder="{% trans 'Type to search' %}" />
|
4
|
+
<div id="toolbar">
|
5
|
+
<div class="bg-white border flex rounded-md overflow-hidden shadow-sm lg:w-64 focus-within:ring focus-within:ring-primary-300 focus-within:border-primary-600 dark:bg-gray-900 dark:border-gray-700 dark:focus-within:border-primary-600 dark:focus-within:ring-primary-700 dark:focus-within:ring-opacity-50">
|
6
|
+
<input class="font-medium grow min-w-0 h-9 px-3 text-gray-500 text-sm focus:outline-none dark:bg-gray-900 dark:text-gray-400" type="text" name="{{ search_var }}" value="{{ cl.query }}" id="searchbar" placeholder="{% trans 'Type to search' %}" />
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
</div>
|
13
|
-
|
14
|
-
{% for pair in cl.params.items %}
|
15
|
-
{% if pair.0 != search_var %}<input type="hidden" name="{{ pair.0 }}" value="{{ pair.1 }}">{% endif %}
|
16
|
-
{% endfor %}
|
8
|
+
<button type="submit" class="flex items-center px-2 focus:outline-none" id="searchbar-submit">
|
9
|
+
<span class="material-symbols-outlined md-18 text-gray-400 dark:text-gray-500">search</span>
|
10
|
+
</button>
|
17
11
|
</div>
|
12
|
+
|
13
|
+
{% for pair in cl.params.items %}
|
14
|
+
{% if pair.0 != search_var %}<input type="hidden" name="{{ pair.0 }}" value="{{ pair.1 }}">{% endif %}
|
15
|
+
{% endfor %}
|
18
16
|
</div>
|
19
17
|
{% endif %}
|
@@ -1,17 +1,20 @@
|
|
1
1
|
<div class="flex gap-4 items-center">
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
2
|
+
{% if value.3 and value.3.path %}
|
3
|
+
<div class="bg-center bg-cover bg-white border flex font-medium h-8 w-8 dark:bg-gray-900 dark:border-gray-700 {% if value.3.squared %}rounded-sm{% else %}rounded-full{% endif %}" style="background-image: url('{{ value.3.path }}')">
|
4
|
+
</div>
|
5
|
+
{% elif value.2 %}
|
6
|
+
<div class="bg-white border flex font-medium h-8 justify-center items-center rounded-full text-xs uppercase w-8 dark:bg-gray-900 dark:border-gray-700">
|
7
|
+
{{ value.2 }}
|
8
|
+
</div>
|
9
|
+
{% endif %}
|
7
10
|
|
8
|
-
|
9
|
-
|
11
|
+
<div class="flex flex-col text-right lg:text-left">
|
12
|
+
<h3>{{ value.0 }}</h3>
|
10
13
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
{% if value.1 %}
|
15
|
+
<p class="text-gray-500">
|
16
|
+
{{ value.1 }}
|
17
|
+
</p>
|
18
|
+
{% endif %}
|
19
|
+
</div>
|
17
20
|
</div>
|
@@ -1,6 +1,6 @@
|
|
1
1
|
{% if not is_popup %}
|
2
2
|
{% if tab_list or actions_list or actions_items or nav_global %}
|
3
|
-
<div class="flex items-start flex-col mb-
|
3
|
+
<div class="flex items-start flex-col mb-4 text-gray-500 text-sm w-full md:border-b dark:md:border-gray-800 md:border-l-0 md:flex-row md:items-center md:justify-end dark:text-gray-400">
|
4
4
|
{% if tab_list %}
|
5
5
|
<ul class="border rounded-md flex flex-col w-full md:flex-row md:border-b-0 md:border-t-0 md:border-l-0 md:border-r-0 dark:border-gray-800">
|
6
6
|
{% for item in tab_list %}
|
@@ -9,7 +9,7 @@
|
|
9
9
|
{% if not is_hidden %}
|
10
10
|
{% if can_add_related or can_change_related or can_delete_related or can_view_related%}
|
11
11
|
{% if can_change_related %}
|
12
|
-
<a class="related-widget-wrapper-link change-related border cursor-pointer flex items-center h-9.5 justify-center ml-2 rounded shadow-sm shrink-0 text-gray-400 text-sm
|
12
|
+
<a class="related-widget-wrapper-link change-related bg-white border cursor-pointer flex items-center h-9.5 justify-center ml-2 rounded shadow-sm shrink-0 text-gray-400 text-sm w-9.5 hover:text-gray-700 dark:bg-gray-900 dark:border-gray-700 dark:text-gray-500 dark:hover:text-gray-200"
|
13
13
|
id="change_id_{{ name }}"
|
14
14
|
data-popup="yes"
|
15
15
|
data-href-template="{{ change_related_template_url }}?{{ url_params }}"
|
@@ -19,7 +19,7 @@
|
|
19
19
|
{% endif %}
|
20
20
|
|
21
21
|
{% if can_add_related %}
|
22
|
-
<a class="related-widget-wrapper-link add-related border cursor-pointer flex items-center h-9.5 justify-center ml-2 rounded shadow-sm shrink-0 text-gray-400 text-sm
|
22
|
+
<a class="related-widget-wrapper-link add-related bg-white border cursor-pointer flex items-center h-9.5 justify-center ml-2 rounded shadow-sm shrink-0 text-gray-400 text-sm w-9.5 hover:text-gray-700 dark:bg-gray-900 dark:border-gray-700 dark:text-gray-500 dark:hover:text-gray-200"
|
23
23
|
data-popup="yes"
|
24
24
|
id="add_id_{{ name }}"
|
25
25
|
href="{{ add_related_url }}?{{ url_params }}"
|
@@ -29,7 +29,7 @@
|
|
29
29
|
{% endif %}
|
30
30
|
|
31
31
|
{% if can_view_related %}
|
32
|
-
<a class="related-widget-wrapper-link view-related border cursor-pointer flex items-center h-9.5 justify-center ml-2 rounded shadow-sm shrink-0 text-gray-400 text-sm
|
32
|
+
<a class="related-widget-wrapper-link view-related bg-white border cursor-pointer flex items-center h-9.5 justify-center ml-2 rounded shadow-sm shrink-0 text-gray-400 text-sm w-9.5 hover:text-gray-700 dark:bg-gray-900 dark:border-gray-700 dark:text-gray-500 dark:hover:text-gray-200"
|
33
33
|
id="view_id_{{ name }}"
|
34
34
|
data-href-template="{{ change_related_template_url }}?{{ view_related_url_params }}"
|
35
35
|
title="{% blocktranslate %}View selected {{ model }}{% endblocktranslate %}">
|
@@ -38,7 +38,7 @@
|
|
38
38
|
{% endif %}
|
39
39
|
|
40
40
|
{% if can_delete_related %}
|
41
|
-
<a class="related-widget-wrapper-link delete-related border cursor-pointer flex items-center h-9.5 justify-center ml-2 rounded shadow-sm shrink-0 text-red-600 text-sm
|
41
|
+
<a class="related-widget-wrapper-link delete-related bg-white border cursor-pointer flex items-center h-9.5 justify-center ml-2 rounded shadow-sm shrink-0 text-red-600 text-sm w-9.5 dark:bg-gray-900 dark:border-gray-700 dark:text-red-500"
|
42
42
|
id="delete_id_{{ name }}"
|
43
43
|
data-href-template="{{ delete_related_template_url }}?{{ url_params }}"
|
44
44
|
data-popup="yes"
|
@@ -37,9 +37,9 @@ from ..widgets import UnfoldBooleanWidget
|
|
37
37
|
register = Library()
|
38
38
|
|
39
39
|
LINK_CLASSES = [
|
40
|
-
"text-gray-
|
40
|
+
"text-gray-600",
|
41
41
|
"truncate",
|
42
|
-
"dark:text-gray-
|
42
|
+
"dark:text-gray-300",
|
43
43
|
]
|
44
44
|
|
45
45
|
ROW_CLASSES = [
|
@@ -54,6 +54,7 @@ ROW_CLASSES = [
|
|
54
54
|
"px-3",
|
55
55
|
"py-2",
|
56
56
|
"text-left",
|
57
|
+
"text-gray-500",
|
57
58
|
"text-sm",
|
58
59
|
"before:flex",
|
59
60
|
"before:capitalize",
|
@@ -63,6 +64,7 @@ ROW_CLASSES = [
|
|
63
64
|
"before:text-gray-500",
|
64
65
|
"first:border-t-0",
|
65
66
|
"dark:before:text-gray-400",
|
67
|
+
"dark:text-gray-400",
|
66
68
|
"lg:before:hidden",
|
67
69
|
"lg:first:border-t",
|
68
70
|
"lg:py-3",
|
@@ -149,7 +151,7 @@ def result_headers(cl):
|
|
149
151
|
if is_sorted:
|
150
152
|
order_type = ordering_field_columns.get(i).lower()
|
151
153
|
sort_priority = list(ordering_field_columns).index(i) + 1
|
152
|
-
th_classes.append("sorted
|
154
|
+
th_classes.append(f"sorted {order_type}ending")
|
153
155
|
new_order_type = {"asc": "desc", "desc": "asc"}[order_type]
|
154
156
|
|
155
157
|
# build new ordering param
|
@@ -186,7 +188,7 @@ def result_headers(cl):
|
|
186
188
|
"url_primary": cl.get_query_string({ORDER_VAR: ".".join(o_list_primary)}),
|
187
189
|
"url_remove": cl.get_query_string({ORDER_VAR: ".".join(o_list_remove)}),
|
188
190
|
"url_toggle": cl.get_query_string({ORDER_VAR: ".".join(o_list_toggle)}),
|
189
|
-
"class_attrib": format_html(
|
191
|
+
"class_attrib": format_html("{}", " ".join(th_classes))
|
190
192
|
if th_classes
|
191
193
|
else "",
|
192
194
|
}
|
@@ -211,7 +213,7 @@ def items_for_result(cl: ChangeList, result: HttpRequest, form) -> SafeText:
|
|
211
213
|
for field_index, field_name in enumerate(cl.list_display):
|
212
214
|
empty_value_display = cl.model_admin.get_empty_value_display()
|
213
215
|
row_classes = [
|
214
|
-
"field
|
216
|
+
f"field-{_coerce_field_name(field_name, field_index)}",
|
215
217
|
*ROW_CLASSES,
|
216
218
|
]
|
217
219
|
|
@@ -252,7 +254,7 @@ def items_for_result(cl: ChangeList, result: HttpRequest, form) -> SafeText:
|
|
252
254
|
f, (models.DateField, models.TimeField, models.ForeignKey)
|
253
255
|
):
|
254
256
|
row_classes.append("nowrap")
|
255
|
-
row_class = mark_safe(' class="
|
257
|
+
row_class = mark_safe(f' class="{" ".join(row_classes)}"')
|
256
258
|
# If list_display_links not defined, add the link tag to the first field
|
257
259
|
|
258
260
|
if link_in_col(first, field_name, cl):
|
unfold/widgets.py
CHANGED
@@ -13,6 +13,7 @@ from django.contrib.admin.widgets import (
|
|
13
13
|
AdminTextInputWidget,
|
14
14
|
AdminTimeWidget,
|
15
15
|
AdminUUIDInputWidget,
|
16
|
+
RelatedFieldWidgetWrapper,
|
16
17
|
)
|
17
18
|
from django.forms import (
|
18
19
|
CheckboxInput,
|
@@ -291,7 +292,7 @@ class FileFieldMixin:
|
|
291
292
|
|
292
293
|
|
293
294
|
class UnfoldAdminImageFieldWidget(FileFieldMixin, AdminFileWidget):
|
294
|
-
|
295
|
+
template_name = "unfold/widgets/clearable_file_input.html"
|
295
296
|
|
296
297
|
|
297
298
|
class UnfoldAdminFileFieldWidget(FileFieldMixin, AdminFileWidget):
|
@@ -374,6 +375,8 @@ class UnfoldAdminTextareaWidget(AdminTextareaWidget):
|
|
374
375
|
|
375
376
|
|
376
377
|
class UnfoldAdminSplitDateTimeWidget(AdminSplitDateTime):
|
378
|
+
template_name = "unfold/widgets/split_datetime.html"
|
379
|
+
|
377
380
|
def __init__(self, attrs: Optional[Dict[str, Any]] = None) -> None:
|
378
381
|
widgets = [UnfoldAdminDateWidget, UnfoldAdminTimeWidget]
|
379
382
|
MultiWidget.__init__(self, widgets, attrs)
|
@@ -433,10 +436,15 @@ class UnfoldAdminBigIntegerFieldWidget(AdminBigIntegerFieldWidget):
|
|
433
436
|
|
434
437
|
|
435
438
|
class UnfoldAdminNullBooleanSelectWidget(NullBooleanSelect):
|
436
|
-
|
439
|
+
def __init__(self, attrs=None):
|
440
|
+
if attrs is None:
|
441
|
+
attrs = {}
|
437
442
|
|
443
|
+
attrs["class"] = " ".join(SELECT_CLASSES)
|
444
|
+
super().__init__(attrs)
|
438
445
|
|
439
|
-
|
446
|
+
|
447
|
+
class UnfoldAdminSelectWidget(Select):
|
440
448
|
def __init__(self, attrs=None, choices=()):
|
441
449
|
if attrs is None:
|
442
450
|
attrs = {}
|
@@ -446,7 +454,8 @@ class UnfoldAdminSelect(Select):
|
|
446
454
|
|
447
455
|
|
448
456
|
class UnfoldAdminRadioSelectWidget(AdminRadioSelect):
|
449
|
-
|
457
|
+
template_name = "unfold/widgets/radio.html"
|
458
|
+
option_template_name = "unfold/widgets/radio_option.html"
|
450
459
|
|
451
460
|
def __init__(self, radio_style: Optional[int] = None, *args, **kwargs):
|
452
461
|
super().__init__(*args, **kwargs)
|
@@ -473,7 +482,7 @@ try:
|
|
473
482
|
def __init__(self, *args, **kwargs):
|
474
483
|
super().__init__(
|
475
484
|
amount_widget=UnfoldAdminTextInputWidget,
|
476
|
-
currency_widget=
|
485
|
+
currency_widget=UnfoldAdminSelectWidget(choices=CURRENCY_CHOICES),
|
477
486
|
)
|
478
487
|
|
479
488
|
except ImportError:
|
@@ -506,3 +515,7 @@ class UnfoldBooleanSwitchWidget(CheckboxInput):
|
|
506
515
|
return super().__init__(
|
507
516
|
attrs={"class": " ".join(SWITCH_CLASSES), **(attrs or {})}, check_test=None
|
508
517
|
)
|
518
|
+
|
519
|
+
|
520
|
+
class UnfoldRelatedFieldWidgetWrapper(RelatedFieldWidgetWrapper):
|
521
|
+
template_name = "unfold/widgets/related_widget_wrapper.html"
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|