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.
Files changed (44) hide show
  1. {django_unfold-0.22.0.dist-info → django_unfold-0.24.0.dist-info}/METADATA +102 -4
  2. {django_unfold-0.22.0.dist-info → django_unfold-0.24.0.dist-info}/RECORD +44 -43
  3. unfold/admin.py +29 -22
  4. unfold/apps.py +5 -0
  5. unfold/checks.py +1 -1
  6. unfold/contrib/filters/admin.py +116 -0
  7. unfold/contrib/filters/forms.py +29 -1
  8. unfold/contrib/filters/templates/unfold/filters/filters_date_range.html +1 -1
  9. unfold/contrib/filters/templates/unfold/filters/filters_datetime_range.html +1 -1
  10. unfold/contrib/filters/templates/unfold/filters/filters_field.html +7 -0
  11. unfold/contrib/filters/templates/unfold/filters/filters_numeric_range.html +1 -1
  12. unfold/contrib/filters/templates/unfold/filters/filters_numeric_single.html +1 -1
  13. unfold/contrib/filters/templates/unfold/filters/filters_numeric_slider.html +1 -1
  14. unfold/contrib/import_export/admin.py +1 -1
  15. unfold/contrib/import_export/forms.py +6 -7
  16. unfold/contrib/import_export/templates/admin/import_export/export.html +1 -1
  17. unfold/contrib/import_export/templates/admin/import_export/import_form.html +2 -2
  18. unfold/forms.py +6 -1
  19. unfold/sites.py +3 -3
  20. unfold/static/unfold/css/styles.css +1 -1
  21. unfold/styles.css +11 -4
  22. unfold/templates/admin/actions.html +9 -9
  23. unfold/templates/admin/app_list.html +6 -8
  24. unfold/templates/admin/base.html +1 -3
  25. unfold/templates/admin/change_form.html +1 -2
  26. unfold/templates/admin/change_list.html +2 -2
  27. unfold/templates/admin/change_list_results.html +15 -3
  28. unfold/templates/admin/edit_inline/tabular.html +3 -3
  29. unfold/templates/admin/filter.html +2 -2
  30. unfold/templates/admin/search_form.html +10 -12
  31. unfold/templates/unfold/helpers/display_header.html +16 -13
  32. unfold/templates/unfold/helpers/tab_list.html +1 -1
  33. unfold/templates/unfold/layouts/base_simple.html +1 -1
  34. unfold/templates/unfold/widgets/date.html +1 -1
  35. unfold/templates/{admin → unfold}/widgets/related_widget_wrapper.html +4 -4
  36. unfold/templates/unfold/widgets/time.html +1 -1
  37. unfold/templatetags/unfold_list.py +8 -6
  38. unfold/widgets.py +18 -5
  39. {django_unfold-0.22.0.dist-info → django_unfold-0.24.0.dist-info}/LICENSE.md +0 -0
  40. {django_unfold-0.22.0.dist-info → django_unfold-0.24.0.dist-info}/WHEEL +0 -0
  41. /unfold/templates/{admin → unfold}/widgets/clearable_file_input.html +0 -0
  42. /unfold/templates/{admin → unfold}/widgets/radio.html +0 -0
  43. /unfold/templates/{admin → unfold}/widgets/radio_option.html +0 -0
  44. /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 class="hidden lg:table-header-group">
11
+ <thead>
12
12
  <tr>
13
13
  {% for header in result_headers %}
14
- <th class="align-middle font-medium px-3 py-2 text-left text-gray-400 text-sm {{ header.class_attrib }} {% if "action-toggle" in header.text and forloop.counter == 1 %}w-10{% endif %}" scope="col">
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
- <span>{{ header.text|capfirst }}</span>
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
- <p 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">
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
- </p>
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 text-gray-700 text-sm dark:text-gray-200">
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" class="lg:w-72">
5
- <div>
6
- <div class="bg-white border flex rounded-md overflow-hidden shadow-sm 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">
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
- <button type="submit" class="flex items-center ml-auto px-2 focus:outline-none" id="searchbar-submit">
10
- <span class="material-symbols-outlined md-18 text-gray-400 dark:text-gray-500">search</span>
11
- </button>
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
- {% if value.2 %}
3
- <div class="border flex font-medium h-8 justify-center items-center rounded-full text-xs uppercase w-8 dark:border-gray-700">
4
- {{ value.2 }}
5
- </div>
6
- {% endif %}
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
- <div class="flex flex-col text-right lg:text-left">
9
- <h3>{{ value.0 }}</h3>
11
+ <div class="flex flex-col text-right lg:text-left">
12
+ <h3>{{ value.0 }}</h3>
10
13
 
11
- {% if value.1 %}
12
- <p class="text-gray-500">
13
- {{ value.1 }}
14
- </p>
15
- {% endif %}
16
- </div>
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-2 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">
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 %}
@@ -10,7 +10,7 @@
10
10
  {% endblock %}
11
11
  {% endif %}
12
12
 
13
- <div id="main" class="shadow flex-grow">
13
+ <div id="main" class="shadow flex-grow min-w-0">
14
14
  {% block content_before %}
15
15
  {% include "unfold/helpers/header.html" %}
16
16
  {% endblock %}
@@ -1,3 +1,3 @@
1
- <div class="flex flex-col max-w-2xl relative">
1
+ <div class="flex flex-col max-w-2xl min-w-48 relative w-full">
2
2
  {% include "django/forms/widgets/input.html" %}
3
3
  </div>
@@ -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 transition-colors w-9.5 hover:text-gray-700 dark:border-gray-700 dark:text-gray-500 dark:hover:text-gray-200"
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 transition-colors w-9.5 hover:text-gray-700 dark:border-gray-700 dark:text-gray-500 dark:hover:text-gray-200"
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 transition-colors w-9.5 hover:text-gray-700 dark:border-gray-700 dark:text-gray-500 dark:hover:text-gray-200"
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 transition-colors w-9.5 dark:border-gray-700 dark:text-red-500"
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"
@@ -1,3 +1,3 @@
1
- <div class="flex flex-col max-w-2xl relative">
1
+ <div class="flex flex-col max-w-2xl min-w-48 relative w-full">
2
2
  {% include "django/forms/widgets/input.html" %}
3
3
  </div>
@@ -37,9 +37,9 @@ from ..widgets import UnfoldBooleanWidget
37
37
  register = Library()
38
38
 
39
39
  LINK_CLASSES = [
40
- "text-gray-700",
40
+ "text-gray-600",
41
41
  "truncate",
42
- "dark:text-gray-200",
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 %sending" % order_type)
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(' class="{}"', " ".join(th_classes))
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-%s" % _coerce_field_name(field_name, field_index),
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="%s"' % " ".join(row_classes))
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
- pass
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
- pass
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
- class UnfoldAdminSelect(Select):
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
- option_template_name = "admin/widgets/radio_option.html"
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=UnfoldAdminSelect(choices=CURRENCY_CHOICES),
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