django-unfold 0.22.0__py3-none-any.whl → 0.24.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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