django-unfold 0.56.0__py3-none-any.whl → 0.58.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 (45) hide show
  1. {django_unfold-0.56.0.dist-info → django_unfold-0.58.0.dist-info}/METADATA +1 -1
  2. {django_unfold-0.56.0.dist-info → django_unfold-0.58.0.dist-info}/RECORD +45 -41
  3. unfold/contrib/import_export/templates/admin/import_export/base.html +1 -5
  4. unfold/contrib/import_export/templates/admin/import_export/import_form.html +1 -1
  5. unfold/contrib/simple_history/templates/simple_history/object_history_form.html +1 -1
  6. unfold/decorators.py +1 -2
  7. unfold/forms.py +3 -2
  8. unfold/layout.py +19 -1
  9. unfold/sites.py +64 -41
  10. unfold/static/unfold/css/styles.css +2 -2
  11. unfold/styles.css +66 -117
  12. unfold/templates/admin/actions.html +1 -1
  13. unfold/templates/admin/auth/user/change_password.html +1 -1
  14. unfold/templates/admin/submit_line.html +7 -7
  15. unfold/templates/registration/password_change_done.html +1 -1
  16. unfold/templates/registration/password_change_form.html +1 -1
  17. unfold/templates/unfold/components/card.html +23 -23
  18. unfold/templates/unfold/components/progress.html +9 -7
  19. unfold/templates/unfold/components/table.html +5 -5
  20. unfold/templates/unfold/helpers/change_list_filter_actions.html +1 -1
  21. unfold/templates/unfold/helpers/edit_inline/tabular_field.html +1 -1
  22. unfold/templates/unfold/helpers/field.html +2 -2
  23. unfold/templates/unfold/helpers/field_readonly_value.html +1 -1
  24. unfold/templates/unfold/helpers/fieldset_row_checkbox.html +2 -2
  25. unfold/templates/unfold/helpers/fieldset_row_field.html +2 -2
  26. unfold/templates/unfold/helpers/form_errors.html +1 -1
  27. unfold/templates/unfold/helpers/messages/errornote.html +1 -5
  28. unfold/templates/unfold/helpers/navigation.html +1 -1
  29. unfold/templates/unfold/layouts/skeleton.html +1 -1
  30. unfold/templates/unfold/widgets/clearable_file_input.html +3 -3
  31. unfold/templates/unfold/widgets/clearable_file_input_small.html +1 -1
  32. unfold/templates/unfold/widgets/select.html +9 -0
  33. unfold/templates/unfold_crispy/errors_formset.html +3 -0
  34. unfold/templates/unfold_crispy/field.html +1 -1
  35. unfold/templates/unfold_crispy/inputs.html +1 -1
  36. unfold/templates/unfold_crispy/layout/checkbox.html +2 -2
  37. unfold/templates/unfold_crispy/layout/field_errors.html +5 -5
  38. unfold/templates/unfold_crispy/layout/fieldset.html +1 -1
  39. unfold/templates/unfold_crispy/layout/hr.html +13 -0
  40. unfold/templates/unfold_crispy/layout/table_inline_formset.html +23 -19
  41. unfold/templates/unfold_crispy/whole_uni_formset.html +30 -0
  42. unfold/templatetags/unfold.py +14 -0
  43. unfold/widgets.py +32 -2
  44. {django_unfold-0.56.0.dist-info → django_unfold-0.58.0.dist-info}/LICENSE.md +0 -0
  45. {django_unfold-0.56.0.dist-info → django_unfold-0.58.0.dist-info}/WHEEL +0 -0
@@ -58,7 +58,7 @@
58
58
  {% endblock %}
59
59
  </head>
60
60
 
61
- <body class="antialiased bg-base-50 font-sans text-font-default-light text-sm dark:bg-base-900 dark:text-font-default-dark {% if is_popup %}popup {% endif %}{% block bodyclass %}{% endblock %}" data-admin-utc-offset="{% now "Z" %}" x-data="{{% if opts %}activeTab: 'general',{% endif %} sidebarMobileOpen: false, sidebarDesktopOpen: {% if request.session.toggle_sidebar == False %}false{% else %}true{% endif %} }" x-init="activeTab = {% if opts %}window.location.hash?.replace('#', '') || 'general'{% else %}''{% endif %}">
61
+ <body class="antialiased bg-base-50 font-sans text-font-default-light text-sm dark:bg-base-900 dark:text-font-default-dark {% if is_popup %}popup {% endif %}{% block bodyclass %}{% endblock %}" data-admin-utc-offset="{% now "Z" %}" x-data="{ {% if opts %}activeTab: 'general',{% endif %} sidebarMobileOpen: false, sidebarDesktopOpen: {% if request.session.toggle_sidebar == False %}false{% else %}true{% endif %} }" x-init="activeTab = {% if opts %}window.location.hash?.replace('#', '') || 'general'{% else %}''{% endif %}">
62
62
  {% if colors %}
63
63
  <style id="unfold-theme-colors">
64
64
  :root {
@@ -24,18 +24,18 @@
24
24
  <input type="text" aria-label="{% trans 'Choose file to upload' %}" value="{% if widget.value %}{{ widget.value.url }}{% else %}{% trans 'Choose file to upload' %}{% endif %}" disabled class="bg-white grow font-medium min-w-0 px-3 py-2 text-ellipsis dark:bg-base-900">
25
25
 
26
26
  <div class="bg-white flex flex-none items-center leading-none self-stretch dark:bg-base-900">
27
- <div class="opacity-0 w-[0px]">
27
+ <div class="opacity-0 overflow-hidden w-[0px]">
28
28
  <input type="{{ widget.type }}" name="{{ widget.name }}" {% include "django/forms/widgets/attrs.html" %} />
29
29
  </div>
30
30
 
31
31
  {% if widget.is_initial %}
32
32
  <a href="{{ widget.value.url }}" class="border-r border-base-200 cursor-pointer text-base-400 px-3 hover:text-base-700 dark:border-base-700 dark:text-base-500 dark:hover:text-base-200" target="_blank">
33
- <span class="material-symbols-outlined">download</span>
33
+ <span class="block material-symbols-outlined">download</span>
34
34
  </a>
35
35
  {% endif %}
36
36
 
37
37
  <label for="{{ widget.attrs.id }}" class="cursor-pointer text-base-400 px-3 hover:text-base-700 dark:text-base-500 dark:hover:text-base-200">
38
- <span class="material-symbols-outlined">upload</span>
38
+ <span class="block material-symbols-outlined">upload</span>
39
39
  </label>
40
40
  </div>
41
41
  </div>
@@ -17,7 +17,7 @@
17
17
  <input type="text" aria-label="{% trans 'Choose file to upload' %}" value="{% if widget.value %}{{ widget.value.url }}{% else %}{% trans 'Choose file to upload' %}{% endif %}" disabled class="bg-white grow font-medium min-w-0 px-3 py-2 text-ellipsis dark:bg-base-900">
18
18
 
19
19
  <div class="flex flex-none items-center leading-none self-stretch">
20
- <div class="opacity-0 w-[0px]">
20
+ <div class="opacity-0 overflow-hidden w-[0px]">
21
21
  <input type="{{ widget.type }}" name="{{ widget.name }}" {% include "django/forms/widgets/attrs.html" %} />
22
22
  </div>
23
23
 
@@ -0,0 +1,9 @@
1
+ <div class="grow relative max-w-2xl">
2
+ <select name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>{% for group_name, group_choices, group_index in widget.optgroups %}{% if group_name %}
3
+ <optgroup label="{{ group_name }}">{% endif %}{% for option in group_choices %}
4
+ {% include option.template_name with widget=option %}{% endfor %}{% if group_name %}
5
+ </optgroup>{% endif %}{% endfor %}
6
+ </select>
7
+
8
+ <span class="material-symbols-outlined absolute group-[.primary]:text-white -ml-[31px] pointer-events-none text-base-400 top-0 top-1/2 hover:text-base-700 dark:text-base-500 dark:hover:text-base-200 -translate-y-1/2">expand_more</span>
9
+ </div>
@@ -0,0 +1,3 @@
1
+ {% if formset.non_form_errors %}
2
+ {% include "unfold/helpers/messages/error.html" with errors=formset.non_form_errors %}
3
+ {% endif %}
@@ -3,7 +3,7 @@
3
3
  {% if field.is_hidden %}
4
4
  {{ field }}
5
5
  {% else %}
6
- <{% if tag %}{{ tag }}{% else %}div{% endif %} id="div_{{ field.auto_id }}" class="group {% if tag == "td" %}align-top border-t border-base-200 font-normal gap-4 min-w-0 overflow-hidden px-3 py-3 text-left dark:border-base-800 dark:before:text-font-important-dark{% endif%} {% if field.errors %}errors{% endif %} {% if field_class %} {{ field_class }}{% endif %} {% if field|is_checkbox and tag == "td" %}flex flex-row gap-2 h-[38px] items-center{% else %}{% if 'form-horizontal' in form_class %} row{% endif %}{% endif %}{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}">
6
+ <{% if tag %}{{ tag }}{% else %}div{% endif %} id="div_{{ field.auto_id }}" class="group {% if tag == "td" %}align-top border-t border-base-200 font-normal gap-4 min-w-0 overflow-hidden px-3 py-3 text-left dark:border-base-800 dark:before:text-font-important-dark {% if form_show_labels and forloop.parentloop.first %}border-t-0{% endif %}{% endif %} {% if field.errors %}errors{% endif %} {% if field_class %} {{ field_class }}{% endif %} {% if field|is_checkbox and tag == "td" %}flex flex-row gap-2 items-center{% else %}{% if 'form-horizontal' in form_class %} row{% endif %}{% endif %}{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}">
7
7
  {% if field.label and not field|is_checkbox and form_show_labels %}
8
8
  <label {% if field.id_for_label %}for="{{ field.id_for_label }}"{% endif %} class="block font-semibold mb-2 text-font-important-light text-sm dark:text-font-important-dark {% if label_class %} {{ label_class }}{% endif %}">
9
9
  {{ field.label }}{% if field.field.required %} <span class="asteriskField">*</span>{% endif %}
@@ -1,5 +1,5 @@
1
1
  {% if inputs %}
2
- <div class="border border-base-200 rounded-default flex flex-col gap-2 justify-end p-3 lg:flex-row dark:border-base-800 {{ field_class }}">
2
+ <div class="flex flex-col gap-2 justify-end lg:flex-row {{ field_class }}">
3
3
  {% for input in inputs %}
4
4
  {% include "unfold_crispy/layout/baseinput.html" %}
5
5
  {% endfor %}
@@ -1,13 +1,13 @@
1
1
  {% load crispy_forms_field unfold %}
2
2
 
3
3
  {% if form_show_labels %}
4
- <label for="{{ field.id_for_label }}" class="flex flex-row gap-3 items-center">
4
+ <label for="{{ field.id_for_label }}" class="flex flex-row gap-3 items-center {% if tag == "td" %}mt-9{% endif %}">
5
5
  {% crispy_field field 'class' form_classes.switch %} <span>{{ field.label }}{% if field.field.required %} <span class="asteriskField">*</span>{% endif %}</span>
6
6
  </label>
7
7
 
8
8
  {% include 'unfold_crispy/layout/help_text_and_errors.html' %}
9
9
  {% else %}
10
- {% with field=field|add_css_class:form_classes.checkbox %}
10
+ {% with field=field|add_css_class:form_classes.switch %}
11
11
  {% if tag == "td" %}
12
12
  <div class="flex items-center justify-center h-[38px]">
13
13
  {% crispy_field field %}
@@ -1,7 +1,7 @@
1
1
  {% if form_show_errors and field.errors %}
2
- {% for error in field.errors %}
3
- <span id="error_{{ forloop.counter }}_{{ field.auto_id }}" class="block text-red-600 text-xs mt-1 dark:text-red-500">
4
- {{ error }}
5
- </span>
6
- {% endfor %}
2
+ <ul class="errorlist">
3
+ {% for error in field.errors %}
4
+ <li id="error_{{ forloop.counter }}_{{ field.auto_id }}" class="mt-2 text-red-600 text-xs dark:text-red-500">{{ error }}</li>
5
+ {% endfor %}
6
+ </ul>
7
7
  {% endif %}
@@ -1,6 +1,6 @@
1
1
  <fieldset {% if fieldset.css_id %}id="{{ fieldset.css_id }}"{% endif %} class="fieldset group flex flex-col gap-5 grow rounded-default border-base-200 shadow-xs aligned border p-3 relative dark:border-base-800 {% if fieldset.css_class %} {{ fieldset.css_class }}{% endif %}" {{ fieldset.flat_attrs }}>
2
2
  {% if legend %}
3
- <legend class="border-b border-base-200 font-semibold float-left pb-3 -mx-3 px-3 text-font-important-light dark:text-font-important-dark dark:border-base-800">
3
+ <legend class="border-b border-base-200 font-semibold float-left pb-3 -mx-3 px-3 text-[15px] text-font-important-light dark:text-font-important-dark dark:border-base-800">
4
4
  {{ legend|safe }}
5
5
  </legend>
6
6
  {% endif %}
@@ -0,0 +1,13 @@
1
+ <div class="flex flex-row justify-center items-center gap-4 my-4">
2
+ {% if title %}
3
+ <span class="bg-base-200 grow h-px"></span>
4
+
5
+ {% if title %}
6
+ <span>
7
+ {{ title }}
8
+ </span>
9
+ {% endif %}
10
+ {% endif %}
11
+
12
+ <span class="bg-base-200 grow h-px"></span>
13
+ </div>
@@ -9,24 +9,28 @@
9
9
  {% csrf_token %}
10
10
  {% endif %}
11
11
 
12
- <div class="overflow-x-auto border border-base-200 rounded-default shadow-xs dark:border-base-800" {% if form_id %} id="{{ form_id }}"{% endif %}>
12
+ {% include "unfold_crispy/errors_formset.html" %}
13
+
14
+ <div class="overflow-x-auto border border-base-200 mb-8 rounded-default shadow-xs dark:border-base-800" {% if form_id %} id="{{ form_id }}"{% endif %}>
13
15
  {{ formset.management_form|crispy }}
14
16
 
15
17
  <table class="w-full">
16
- <thead>
17
- {% if formset.readonly and not formset.queryset.exists %}
18
- {% else %}
19
- <tr>
20
- {% for field in formset.forms.0 %}
21
- {% if field.label and not field.is_hidden %}
22
- <th for="{{ field.auto_id }}" class="align-middle font-semibold py-2 text-left text-font-important-light dark:text-font-important-dark whitespace-nowrap px-3 {% if field.name == "DELETE" %}w-0{% endif %}">
23
- {{ field.label }}{% if field.field.required and not field|is_checkbox %} <span class="asteriskField">*</span>{% endif %}
24
- </th>
25
- {% endif %}
26
- {% endfor %}
27
- </tr>
28
- {% endif %}
29
- </thead>
18
+ {% if not form_show_labels %}
19
+ <thead>
20
+ {% if formset.readonly and not formset.queryset.exists %}
21
+ {% else %}
22
+ <tr>
23
+ {% for field in formset.forms.0 %}
24
+ {% if field.label and not field.is_hidden %}
25
+ <th for="{{ field.auto_id }}" class="align-middle font-semibold py-2 text-left text-font-important-light dark:text-font-important-dark whitespace-nowrap px-3 {% if field.name == "DELETE" %}w-0{% endif %}">
26
+ {{ field.label }}{% if field.field.required and not field|is_checkbox %} <span class="asteriskField">*</span>{% endif %}
27
+ </th>
28
+ {% endif %}
29
+ {% endfor %}
30
+ </tr>
31
+ {% endif %}
32
+ </thead>
33
+ {% endif %}
30
34
 
31
35
  <tbody {% if formset_id %}id="{{ formset_id }}-rows"{% endif %}>
32
36
  {% for form in formset %}
@@ -40,7 +44,7 @@
40
44
 
41
45
  <tr>
42
46
  {% for field in form %}
43
- {% include 'unfold_crispy/field.html' with tag="td" form_show_labels=False %}
47
+ {% include 'unfold_crispy/field.html' with tag="td" %}
44
48
  {% endfor %}
45
49
  </tr>
46
50
  {% endfor %}
@@ -50,7 +54,7 @@
50
54
  <tbody>
51
55
  <tr class="empty-form">
52
56
  {% for field in formset.empty_form %}
53
- {% include 'unfold_crispy/field.html' with tag="td" form_show_labels=False %}
57
+ {% include 'unfold_crispy/field.html' with tag="td" %}
54
58
  {% endfor %}
55
59
  </tr>
56
60
 
@@ -66,10 +70,10 @@
66
70
  </tbody>
67
71
  {% endif %}
68
72
  </table>
69
-
70
- {% include "unfold_crispy/inputs.html" %}
71
73
  </div>
72
74
 
75
+ {% include "unfold_crispy/inputs.html" %}
76
+
73
77
  {% if formset_tag %}
74
78
  </form>
75
79
  {% endif %}
@@ -0,0 +1,30 @@
1
+ {% load crispy_forms_tags %}
2
+ {% load crispy_forms_utils %}
3
+
4
+ {% specialspaceless %}
5
+ {% if formset_tag %}
6
+ <form {{ flat_attrs }} method="{{ form_method }}" {% if formset.is_multipart %} enctype="multipart/form-data"{% endif %}>
7
+ {% endif %}
8
+ {% if formset_method|lower == 'post' and not disable_csrf %}
9
+ {% csrf_token %}
10
+ {% endif %}
11
+
12
+ <div>
13
+ {{ formset.management_form|crispy }}
14
+ </div>
15
+
16
+ {% include "unfold_crispy/errors_formset.html" %}
17
+
18
+ {% for form in formset %}
19
+ {% include "unfold_crispy/display_form.html" %}
20
+ {% endfor %}
21
+
22
+ {% if inputs %}
23
+ <div class="form-actions">
24
+ {% for input in inputs %}
25
+ {% include "unfold_crispy/layout/baseinput.html" %}
26
+ {% endfor %}
27
+ </div>
28
+ {% endif %}
29
+ {% if formset_tag %}</form>{% endif %}
30
+ {% endspecialspaceless %}
@@ -108,6 +108,20 @@ def class_name(value: Any) -> str:
108
108
  return value.__class__.__name__
109
109
 
110
110
 
111
+ @register.filter
112
+ def is_list(value: Any) -> str:
113
+ return isinstance(value, list)
114
+
115
+
116
+ @register.filter
117
+ def has_active_item(items: list[dict]) -> bool:
118
+ for item in items:
119
+ if "active" in item and item["active"]:
120
+ return True
121
+
122
+ return False
123
+
124
+
111
125
  @register.filter
112
126
  def index(indexable: Mapping[int, Any], i: int) -> Any:
113
127
  try:
unfold/widgets.py CHANGED
@@ -86,6 +86,7 @@ BASE_CLASSES = [
86
86
  "dark:group-[.errors]:border-red-500",
87
87
  "dark:focus:group-[.errors]:outline-red-500",
88
88
  "dark:scheme-dark",
89
+ "group-[.primary]:border-transparent",
89
90
  ]
90
91
 
91
92
  BASE_INPUT_CLASSES = [
@@ -167,7 +168,7 @@ CHECKBOX_CLASSES = [
167
168
  "dark:border-base-500",
168
169
  "dark:checked:after:text-white",
169
170
  "focus:outline",
170
- "focus:outline-1",
171
+ "focus:outline-2",
171
172
  "focus:outline-offset-2",
172
173
  "focus:outline-primary-500",
173
174
  "after:absolute",
@@ -209,7 +210,7 @@ RADIO_CLASSES = [
209
210
  "dark:border-base-500",
210
211
  "hover:border-base-400",
211
212
  "focus:outline",
212
- "focus:outline-1",
213
+ "focus:outline-2",
213
214
  "focus:outline-offset-2",
214
215
  "focus:outline-primary-500",
215
216
  "after:absolute",
@@ -633,6 +634,8 @@ class UnfoldAdminBigIntegerFieldWidget(AdminBigIntegerFieldWidget):
633
634
 
634
635
 
635
636
  class UnfoldAdminNullBooleanSelectWidget(NullBooleanSelect):
637
+ template_name = "unfold/widgets/select.html"
638
+
636
639
  def __init__(self, attrs=None):
637
640
  if attrs is None:
638
641
  attrs = {}
@@ -644,6 +647,8 @@ class UnfoldAdminNullBooleanSelectWidget(NullBooleanSelect):
644
647
 
645
648
 
646
649
  class UnfoldAdminSelectWidget(Select):
650
+ template_name = "unfold/widgets/select.html"
651
+
647
652
  def __init__(self, attrs=None, choices=()):
648
653
  if attrs is None:
649
654
  attrs = {}
@@ -690,6 +695,31 @@ class UnfoldAdminSelectMultipleWidget(SelectMultiple):
690
695
  super().__init__(attrs, choices)
691
696
 
692
697
 
698
+ class UnfoldAdminSelect2MultipleWidget(SelectMultiple):
699
+ def __init__(self, attrs=None, choices=()):
700
+ if attrs is None:
701
+ attrs = {}
702
+
703
+ attrs["data-theme"] = "admin-autocomplete"
704
+ attrs["class"] = "unfold-admin-autocomplete admin-autocomplete"
705
+
706
+ super().__init__(attrs, choices)
707
+
708
+ class Media:
709
+ js = (
710
+ "admin/js/vendor/jquery/jquery.js",
711
+ "admin/js/vendor/select2/select2.full.js",
712
+ "admin/js/jquery.init.js",
713
+ "unfold/js/select2.init.js",
714
+ )
715
+ css = {
716
+ "screen": (
717
+ "admin/css/vendor/select2/select2.css",
718
+ "admin/css/autocomplete.css",
719
+ ),
720
+ }
721
+
722
+
693
723
  class UnfoldAdminRadioSelectWidget(AdminRadioSelect):
694
724
  template_name = "unfold/widgets/radio.html"
695
725
  option_template_name = "unfold/widgets/radio_option.html"