django-unfold 0.53.0__py3-none-any.whl → 0.55.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 (56) hide show
  1. {django_unfold-0.53.0.dist-info → django_unfold-0.55.0.dist-info}/METADATA +24 -11
  2. {django_unfold-0.53.0.dist-info → django_unfold-0.55.0.dist-info}/RECORD +56 -33
  3. {django_unfold-0.53.0.dist-info → django_unfold-0.55.0.dist-info}/WHEEL +1 -1
  4. unfold/contrib/filters/admin/__init__.py +17 -0
  5. unfold/contrib/filters/admin/choice_filters.py +173 -0
  6. unfold/contrib/filters/admin/dropdown_filters.py +15 -1
  7. unfold/contrib/filters/admin/mixins.py +25 -0
  8. unfold/contrib/filters/forms.py +41 -3
  9. unfold/contrib/filters/templates/unfold/filters/filters_date_range.html +1 -1
  10. unfold/contrib/filters/templates/unfold/filters/filters_datetime_range.html +1 -1
  11. unfold/layout.py +23 -0
  12. unfold/sites.py +17 -6
  13. unfold/static/admin/js/inlines.js +23 -3
  14. unfold/static/unfold/css/styles.css +1 -1
  15. unfold/static/unfold/fonts/material-symbols/LICENSE +202 -0
  16. unfold/static/unfold/fonts/material-symbols/Material-Symbols-Outlined.woff2 +0 -0
  17. unfold/static/unfold/js/alpine.js +2 -2
  18. unfold/static/unfold/js/select2.init.js +4 -0
  19. unfold/styles.css +2 -1
  20. unfold/templates/admin/change_list.html +15 -13
  21. unfold/templates/admin/change_list_results.html +1 -1
  22. unfold/templates/admin/edit_inline/tabular.html +1 -2
  23. unfold/templates/unfold/components/button.html +1 -1
  24. unfold/templates/unfold/components/chart/cohort.html +2 -2
  25. unfold/templates/unfold/helpers/change_list_filter.html +2 -2
  26. unfold/templates/unfold/helpers/change_list_filter_actions.html +28 -26
  27. unfold/templates/unfold/helpers/edit_inline/tabular_field.html +10 -8
  28. unfold/templates/unfold/helpers/field.html +4 -2
  29. unfold/templates/unfold/helpers/form_label.html +1 -1
  30. unfold/templates/unfold/helpers/tab_action.html +17 -2
  31. unfold/templates/unfold/widgets/clearable_file_input.html +1 -1
  32. unfold/templates/unfold/widgets/clearable_file_input_small.html +1 -1
  33. unfold/templates/unfold/widgets/radio_option.html +1 -1
  34. unfold/templates/unfold_crispy/display_form.html +9 -0
  35. unfold/templates/unfold_crispy/errors.html +3 -0
  36. unfold/templates/unfold_crispy/field.html +23 -0
  37. unfold/templates/unfold_crispy/inputs.html +7 -0
  38. unfold/templates/unfold_crispy/layout/attrs.html +1 -0
  39. unfold/templates/unfold_crispy/layout/baseinput.html +9 -0
  40. unfold/templates/unfold_crispy/layout/button.html +1 -0
  41. unfold/templates/unfold_crispy/layout/buttonholder.html +3 -0
  42. unfold/templates/unfold_crispy/layout/checkbox.html +19 -0
  43. unfold/templates/unfold_crispy/layout/column.html +3 -0
  44. unfold/templates/unfold_crispy/layout/div.html +4 -0
  45. unfold/templates/unfold_crispy/layout/field_errors.html +7 -0
  46. unfold/templates/unfold_crispy/layout/fieldset.html +9 -0
  47. unfold/templates/unfold_crispy/layout/help_text.html +5 -0
  48. unfold/templates/unfold_crispy/layout/help_text_and_errors.html +3 -0
  49. unfold/templates/unfold_crispy/layout/radio_checkbox_select.html +21 -0
  50. unfold/templates/unfold_crispy/layout/row.html +3 -0
  51. unfold/templates/unfold_crispy/layout/table_inline_formset.html +100 -0
  52. unfold/templates/unfold_crispy/uni_form.html +11 -0
  53. unfold/templates/unfold_crispy/whole_uni_form.html +14 -0
  54. unfold/templatetags/unfold.py +26 -14
  55. unfold/widgets.py +88 -1
  56. {django_unfold-0.53.0.dist-info → django_unfold-0.55.0.dist-info}/LICENSE.md +0 -0
@@ -1,31 +1,33 @@
1
1
  {% load i18n %}
2
2
 
3
- <div class="bg-white border-t border-base-200 p-3 py-2.5 dark:bg-base-800 dark:border-base-700{% if not cl.model_admin.list_filter_sheet %} 2xl:!border-t-0 2xl:!bg-transparent 2xl:px-0{% endif %}">
4
- {% if cl.model_admin.list_filter_submit %}
5
- <button type="submit" class="bg-primary-600 block border border-transparent font-medium px-3 py-2 rounded text-white w-full">
6
- {% trans "Apply Filters" %}
7
- </button>
8
- {% endif %}
3
+ {% if cl.model_admin.list_filter_submit or cl.is_facets_optional or cl.has_active_filters %}
4
+ <div class="bg-white border-t border-base-200 p-3 py-2.5 dark:bg-base-800 dark:border-base-700{% if not cl.model_admin.list_filter_sheet %} 2xl:!border-t-0 2xl:!bg-transparent 2xl:px-0{% endif %}">
5
+ {% if cl.model_admin.list_filter_submit %}
6
+ <button type="submit" class="bg-primary-600 block border border-transparent font-medium px-3 py-2 rounded text-white w-full">
7
+ {% trans "Apply Filters" %}
8
+ </button>
9
+ {% endif %}
9
10
 
10
- {% if cl.is_facets_optional or cl.has_active_filters %}
11
- <span id="changelist-filter-extra-actions" class="flex flex-row gap-2 items-center mt-2">
12
- {% if cl.is_facets_optional %}
13
- {% if cl.add_facets %}
14
- <a href="{{ cl.remove_facet_link }}" class="hidelink border flex-grow font-medium px-3 py-2 rounded text-center transition-all w-full hover:bg-base-50 lg:w-auto dark:border-base-700 dark:hover:text-base-200">
15
- {% trans "Hide counts" %}
16
- </a>
17
- {% else %}
18
- <a href="{{ cl.add_facet_link }}" class="viewlink border flex-grow font-medium px-3 py-2 rounded text-center transition-all w-full lg:w-auto dark:border-base-700 dark:hover:text-base-200">
19
- {% trans "Show counts" %}
20
- </a>
11
+ {% if cl.is_facets_optional or cl.has_active_filters %}
12
+ <span id="changelist-filter-extra-actions" class="flex flex-row gap-2 items-center mt-2">
13
+ {% if cl.is_facets_optional %}
14
+ {% if cl.add_facets %}
15
+ <a href="{{ cl.remove_facet_link }}" class="hidelink border flex-grow font-medium px-3 py-2 rounded text-center transition-all w-full hover:bg-base-50 lg:w-auto dark:border-base-700 dark:hover:text-base-200">
16
+ {% trans "Hide counts" %}
17
+ </a>
18
+ {% else %}
19
+ <a href="{{ cl.add_facet_link }}" class="viewlink border flex-grow font-medium px-3 py-2 rounded text-center transition-all w-full lg:w-auto dark:border-base-700 dark:hover:text-base-200">
20
+ {% trans "Show counts" %}
21
+ </a>
22
+ {% endif %}
21
23
  {% endif %}
22
- {% endif %}
23
24
 
24
- {% if cl.has_active_filters %}
25
- <a href="{{ cl.clear_all_filters_qs }}" class="border flex-grow font-medium px-3 py-2 rounded text-center transition-all w-full lg:w-auto dark:border-base-700 dark:hover:text-base-200">
26
- {% trans "Clear all filters" %}
27
- </a>
28
- {% endif %}
29
- </span>
30
- {% endif %}
31
- </div>
25
+ {% if cl.has_active_filters %}
26
+ <a href="{{ cl.clear_all_filters_qs }}" class="border flex-grow font-medium px-3 py-2 rounded text-center transition-all w-full lg:w-auto dark:border-base-700 dark:hover:text-base-200">
27
+ {% trans "Clear all filters" %}
28
+ </a>
29
+ {% endif %}
30
+ </span>
31
+ {% endif %}
32
+ </div>
33
+ {% endif %}
@@ -12,14 +12,16 @@
12
12
  {% if field.is_readonly %}
13
13
  {% include "unfold/helpers/field_readonly_value.html" with tabular=1 %}
14
14
  {% else %}
15
- {{ field.field }}
15
+ <div class="flex flex-col">
16
+ {{ field.field }}
16
17
 
17
- {% if field.field.errors|length > 0 %}
18
- <div class="mt-1 text-red-600 text-sm dark:text-red-500">
19
- {% for error in field.field.errors %}
20
- {{ error }}
21
- {% endfor %}
22
- </div>
23
- {% endif %}
18
+ {% if field.field.errors|length > 0 %}
19
+ <div class="mt-1 text-red-600 text-sm dark:text-red-500">
20
+ {% for error in field.field.errors %}
21
+ {{ error }}
22
+ {% endfor %}
23
+ </div>
24
+ {% endif %}
25
+ </div>
24
26
  {% endif %}
25
27
  </div>
@@ -1,4 +1,6 @@
1
- {% if field.field.widget.input_type == "checkbox" %}
1
+ {% load unfold %}
2
+
3
+ {% if field.field.widget.input_type == "checkbox" and field.field.widget|class_name != "UnfoldAdminCheckboxSelectMultiple"%}
2
4
  <div class="{% if field.errors %}errors {% endif %}flex flex-col group mb-6 last:mb-4">
3
5
  <div class="flex flex-row gap-2 items-center">
4
6
  {{ field }}
@@ -11,7 +13,7 @@
11
13
  {% include "unfold/helpers/help_text.html" with help_text=field.help_text %}
12
14
  </div>
13
15
  {% else %}
14
- <div class="{% if field.errors %}errors {% endif %}flex flex-col group mb-6 last:mb-4">
16
+ <div class="{% if field.errors %}errors {% endif %}flex flex-col gap-2 group mb-6 last:mb-4">
15
17
  {% include "unfold/helpers/form_label.html" with field=field %}
16
18
 
17
19
  {{ field }}
@@ -1,4 +1,4 @@
1
- <label for="{{ field.id_for_label }}" class="block text-font-important-light dark:text-font-important-dark{% if field.field.widget.input_type == "checkbox" %}{% else %} font-semibold mb-2{% endif %}">
1
+ <label for="{{ field.id_for_label }}" class="block font-semibold text-font-important-light dark:text-font-important-dark">
2
2
  {{ field.label }}
3
3
 
4
4
  {% if field.field.required %}
@@ -15,8 +15,23 @@
15
15
  {{ title }}
16
16
 
17
17
  {% if not link %}
18
- <span class="material-symbols-outlined ml-auto rotate-90">
19
- chevron_right
18
+ <span class="border-l border-base-200 flex items-center select-none self-stretch ml-1 -mr-1 -my-2 pl-2
19
+ {% if action.variant.value == "primary" %}
20
+ border-primary-700 dark:border-primary-500
21
+ {% elif action.variant.value == "danger" %}
22
+ border-red-700 dark:border-red-500
23
+ {% elif action.variant.value == "success" %}
24
+ border-green-700 dark:border-green-500
25
+ {% elif action.variant.value == "info" %}
26
+ border-blue-700 dark:border-blue-500
27
+ {% elif action.variant.value == "warning" %}
28
+ border-orange-700 dark:border-orange-500
29
+ {% else %}
30
+ border-base-200 dark:border-base-700
31
+ {% endif %}">
32
+ <span class="material-symbols-outlined ml-auto rotate-90 transition-transform" x-bind:class="{'!-rotate-90': actionDropdownOpen }">
33
+ chevron_right
34
+ </span>
20
35
  </span>
21
36
  {% endif %}
22
37
  </a>
@@ -8,7 +8,7 @@
8
8
  </div>
9
9
  {% endif %}
10
10
 
11
- <div class="border border-base-200 flex items-center overflow-hidden rounded shadow-sm text-sm max-w-2xl dark:border-base-700">
11
+ <div class="{{ widget.file_wrapper_class }}">
12
12
  {% if widget.is_initial and not widget.required %}
13
13
  <div class="bg-base-50 border-r border-base-200 flex flex-none items-center self-stretch px-3 dark:bg-white/[.02] dark:border-base-700 dark:text-font-default-dark">
14
14
  <label for="{{ widget.checkbox_id }}" class="flex items-center">
@@ -1,7 +1,7 @@
1
1
  {% load i18n static %}
2
2
 
3
3
  <div class="flex flex-row">
4
- <div class="border border-base-200 flex items-center overflow-hidden rounded shadow-sm text-sm max-w-2xl w-full focus-within:ring focus-within:ring-primary-300 focus-within:border-primary-600 dark:border-base-700 dark:focus-within:border-primary-600 dark:focus-within:ring-primary-700 dark:focus-within:ring-opacity-50">
4
+ <div class="{{ widget.file_wrapper_class }}">
5
5
  {% if widget.is_initial and not widget.required %}
6
6
  <div class="bg-base-50 border-r border-base-200 flex flex-none items-center self-stretch px-3 dark:bg-base-900 dark:border-r-gray-700">
7
7
  <label for="{{ widget.checkbox_id }}" class="flex items-center">
@@ -5,7 +5,7 @@
5
5
  {% include "django/forms/widgets/input.html" %}
6
6
 
7
7
  {% if widget.wrap_label %}
8
- <span>
8
+ <span class="truncate">
9
9
  {{ widget.label }}
10
10
  </span>
11
11
  </label>
@@ -0,0 +1,9 @@
1
+ {% if form.form_html %}
2
+ {% if include_media %}{{ form.media }}{% endif %}
3
+ {% if form_show_errors %}
4
+ {% include "unfold_crispy/errors.html" %}
5
+ {% endif %}
6
+ {{ form.form_html }}
7
+ {% else %}
8
+ {% include "unfold_crispy/uni_form.html" %}
9
+ {% endif %}
@@ -0,0 +1,3 @@
1
+ {% if form.non_field_errors %}
2
+ {% include "unfold/helpers/messages/error.html" with errors=form.non_field_errors %}
3
+ {% endif %}
@@ -0,0 +1,23 @@
1
+ {% load crispy_forms_field unfold %}
2
+
3
+ {% if field.is_hidden %}
4
+ {{ field }}
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-9.5 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
+ {% if field.label and not field|is_checkbox and form_show_labels %}
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
+ {{ field.label }}{% if field.field.required %} <span class="asteriskField">*</span>{% endif %}
10
+ </label>
11
+ {% endif %}
12
+
13
+ {% if field|is_checkboxselectmultiple or field|is_radioselect %}
14
+ {% include 'unfold_crispy/layout/radio_checkbox_select.html' %}
15
+ {% elif field|is_checkbox %}
16
+ {% include 'unfold_crispy/layout/checkbox.html' %}
17
+ {% else %}
18
+ {% crispy_field field %}
19
+
20
+ {% include 'unfold_crispy/layout/help_text_and_errors.html' %}
21
+ {% endif %}
22
+ </{% if tag %}{{ tag }}{% else %}div{% endif %}>
23
+ {% endif %}
@@ -0,0 +1,7 @@
1
+ {% if inputs %}
2
+ <div class="border border-base-200 rounded flex flex-col gap-2 justify-end p-3 lg:flex-row dark:border-base-800 {{ field_class }}">
3
+ {% for input in inputs %}
4
+ {% include "unfold_crispy/layout/baseinput.html" %}
5
+ {% endfor %}
6
+ </div>
7
+ {% endif %}
@@ -0,0 +1 @@
1
+ {% for name, value in widget.attrs.items %}{% if value is not False %} {{ name }}{% if value is not True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %}{% endfor %}
@@ -0,0 +1,9 @@
1
+ <input type="{{ input.input_type }}"
2
+ name="{% if input.name|wordcount > 1 %}{{ input.name|slugify }}{% else %}{{ input.name }}{% endif %}"
3
+ value="{{ input.value }}"
4
+ {% if input.input_type != "hidden" %}
5
+ class="{{ input.field_classes }}"
6
+ id="{% if input.id %}{{ input.id }}{% else %}{{ input.input_type }}-id-{{ input.name|slugify }}{% endif %}"
7
+ {% endif %}
8
+ {{ input.flat_attrs }}
9
+ />
@@ -0,0 +1 @@
1
+ <button {{ button.flat_attrs }}>{{ button.content|safe }}</button>
@@ -0,0 +1,3 @@
1
+ <div {% if buttonholder.css_id %}id="{{ buttonholder.css_id }}"{% endif %} class="buttonHolder group-[.fieldset]:border-t group-[.fieldset]:border-base-200 group-[.fieldset]:flex group-[.fieldset]:flex-row group-[.fieldset]:justify-end group-[.fieldset]:-mx-3 group-[.fieldset]:pt-3 group-[.fieldset]:px-3{% if buttonholder.css_class %} {{ buttonholder.css_class }}{% endif %}">
2
+ {{ fields_output|safe }}
3
+ </div>
@@ -0,0 +1,19 @@
1
+ {% load crispy_forms_field unfold %}
2
+
3
+ {% if form_show_labels %}
4
+ <label for="{{ field.id_for_label }}" class="flex flex-row gap-3 items-center">
5
+ {% crispy_field field 'class' form_classes.switch %} <span>{{ field.label }}{% if field.field.required %} <span class="asteriskField">*</span>{% endif %}</span>
6
+ </label>
7
+
8
+ {% include 'unfold_crispy/layout/help_text_and_errors.html' %}
9
+ {% else %}
10
+ {% with field=field|add_css_class:form_classes.checkbox %}
11
+ {% if tag == "td" %}
12
+ <div class="flex items-center justify-center h-9.5">
13
+ {% crispy_field field %}
14
+ </div>
15
+ {% else %}
16
+ {% crispy_field field %}
17
+ {% endif %}
18
+ {% endwith %}
19
+ {% endif %}
@@ -0,0 +1,3 @@
1
+ <div {% if div.css_id %}id="{{ div.css_id }}"{% endif %} class="flex flex-col grow gap-5 {{ div.css_class|default:'' }}" {{ div.flat_attrs }}>
2
+ {{ fields|safe }}
3
+ </div>
@@ -0,0 +1,4 @@
1
+ <div {% if div.css_id %}id="{{ div.css_id }}"{% endif %}
2
+ {% if div.css_class %}class="{{ div.css_class }}"{% endif %} {{ div.flat_attrs }}>
3
+ {{ fields|safe }}
4
+ </div>
@@ -0,0 +1,7 @@
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 %}
7
+ {% endif %}
@@ -0,0 +1,9 @@
1
+ <fieldset {% if fieldset.css_id %}id="{{ fieldset.css_id }}"{% endif %} class="fieldset group flex flex-col gap-5 grow rounded border-base-200 shadow-sm aligned border p-3 relative dark:border-base-800 {% if fieldset.css_class %} {{ fieldset.css_class }}{% endif %}" {{ fieldset.flat_attrs }}>
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">
4
+ {{ legend|safe }}
5
+ </legend>
6
+ {% endif %}
7
+
8
+ {{ fields|safe }}
9
+ </fieldset>
@@ -0,0 +1,5 @@
1
+ {% if field.help_text %}
2
+ <div {% if field.id_for_label %}id="{{ field.id_for_label }}_helptext" {% endif %}class="leading-relaxed mt-2 text-xs">
3
+ {{ field.help_text|safe }}
4
+ </div>
5
+ {% endif %}
@@ -0,0 +1,3 @@
1
+ {% include 'unfold_crispy/layout/field_errors.html' %}
2
+
3
+ {% include 'unfold_crispy/layout/help_text.html' %}
@@ -0,0 +1,21 @@
1
+ {% load crispy_forms_filters l10n %}
2
+
3
+ <div class="flex flex-col gap-2 {% if field_class %}{{ field_class }}{% endif %}"{% if flat_attrs %} {{ flat_attrs }}{% endif %}>
4
+ {% for group, options, index in field|optgroups %}
5
+ {% if group %}
6
+ <strong>{{ group }}</strong>
7
+ {% endif %}
8
+
9
+ {% for option in options %}
10
+ <div>
11
+ <label for="{{ option.attrs.id }}" class="flex flex-row items-center gap-2">
12
+ <input type="{{ option.type }}" class="{% if field.errors %}errors{% endif %} {% if option.type == "radio" %}{{ form_classes.radio }}{% else %}{{ form_classes.checkbox }}{% endif %}" name="{{ field.html_name }}" value="{{ option.value|unlocalize }}" {% include "unfold_crispy/layout/attrs.html" with widget=option %}>
13
+
14
+ {{ option.label|unlocalize }}
15
+ </label>
16
+ </div>
17
+ {% endfor %}
18
+ {% endfor %}
19
+ </div>
20
+
21
+ {% include 'unfold_crispy/layout/help_text_and_errors.html' %}
@@ -0,0 +1,3 @@
1
+ <div {% if div.css_id %}id="{{ div.css_id }}"{% endif %} class="flex flex-col gap-5 grow lg:flex-row {{ div.css_class|default:'' }}" {{ div.flat_attrs }}>
2
+ {{ fields|safe }}
3
+ </div>
@@ -0,0 +1,100 @@
1
+ {% load crispy_forms_tags crispy_forms_utils crispy_forms_field i18n %}
2
+
3
+ {% specialspaceless %}
4
+ {% if formset_tag %}
5
+ <form {{ flat_attrs }} method="{{ form_method }}" {% if formset.is_multipart %} enctype="multipart/form-data"{% endif %}>
6
+ {% endif %}
7
+
8
+ {% if formset_method|lower == 'post' and not disable_csrf %}
9
+ {% csrf_token %}
10
+ {% endif %}
11
+
12
+ <div class="overflow-x-auto border border-base-200 rounded shadow-sm dark:border-base-800" {% if form_id %} id="{{ form_id }}"{% endif %}>
13
+ {{ formset.management_form|crispy }}
14
+
15
+ <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>
30
+
31
+ <tbody {% if formset_id %}id="{{ formset_id }}-rows"{% endif %}>
32
+ {% for form in formset %}
33
+ {% if form_show_errors and form.non_field_errors and not form.is_extra %}
34
+ <tr>
35
+ <td colspan="100%" class="border-t border-base-200 px-3 pt-3 dark:border-base-800">
36
+ {% include "unfold_crispy/errors.html" %}
37
+ </td>
38
+ </tr>
39
+ {% endif %}
40
+
41
+ <tr>
42
+ {% for field in form %}
43
+ {% include 'unfold_crispy/field.html' with tag="td" form_show_labels=False %}
44
+ {% endfor %}
45
+ </tr>
46
+ {% endfor %}
47
+ </tbody>
48
+
49
+ {% if form_add and formset_id %}
50
+ <tbody>
51
+ <tr class="empty-form">
52
+ {% for field in formset.empty_form %}
53
+ {% include 'unfold_crispy/field.html' with tag="td" form_show_labels=False %}
54
+ {% endfor %}
55
+ </tr>
56
+
57
+ <tr id="{{ formset_id }}-add-row-wrapper">
58
+ <td colspan="100%" class="border-t border-base-200 p-3 dark:border-base-800">
59
+ <div class="flex justify-end">
60
+ <button type="button" id="{{ formset_id }}-add-row-button" class="border border-base-200 font-medium px-3 py-2 rounded transition-all w-full hover:bg-base-50 lg:block lg:w-auto dark:border-base-700 dark:hover:text-base-200 dark:hover:bg-base-900">
61
+ {% trans "Add row" %}
62
+ </button>
63
+ </div>
64
+ </td>
65
+ </tr>
66
+ </tbody>
67
+ {% endif %}
68
+ </table>
69
+
70
+ {% include "unfold_crispy/inputs.html" %}
71
+ </div>
72
+
73
+ {% if formset_tag %}
74
+ </form>
75
+ {% endif %}
76
+
77
+ {% if form_add and formset_id %}
78
+ <script>
79
+ document.getElementById('{{ formset_id }}-add-row-button').addEventListener('click', function() {
80
+ const formTotalEl = document.querySelector(`#{{ formset_id }} input[name*="TOTAL_FORMS"]`)
81
+ const formCount = parseInt(formTotalEl.value);
82
+ const newForm = document.querySelector('#{{ formset_id }} .empty-form').cloneNode(true);
83
+
84
+ newForm.classList.remove('empty-form');
85
+ newForm.innerHTML = newForm.innerHTML.replaceAll(/__prefix__/g, formCount);
86
+
87
+ document.getElementById('{{ formset_id }}-rows').insertBefore(
88
+ newForm,
89
+ document.getElementById("#{{ formset_id }}-add-row-wrapper")
90
+ )
91
+
92
+ formTotalEl.value = formCount + 1;
93
+
94
+ newForm.dispatchEvent(new CustomEvent('formset:added', {
95
+ bubbles: true
96
+ }));
97
+ });
98
+ </script>
99
+ {% endif %}
100
+ {% endspecialspaceless %}
@@ -0,0 +1,11 @@
1
+ {% load crispy_forms_utils %}
2
+
3
+ {% specialspaceless %}
4
+ {% if include_media %}{{ form.media }}{% endif %}
5
+ {% if form_show_errors %}
6
+ {% include "unfold_crispy/errors.html" %}
7
+ {% endif %}
8
+ {% for field in form %}
9
+ {% include field_template %}
10
+ {% endfor %}
11
+ {% endspecialspaceless %}
@@ -0,0 +1,14 @@
1
+ {% load crispy_forms_utils %}
2
+
3
+ {% specialspaceless %}
4
+ {% if form_tag %}<form {{ flat_attrs }} method="{{ form_method }}" {% if form.is_multipart %} enctype="multipart/form-data"{% endif %}>{% endif %}
5
+ {% if form_method|lower == 'post' and not disable_csrf %}
6
+ {% csrf_token %}
7
+ {% endif %}
8
+
9
+ {% include "unfold_crispy/display_form.html" %}
10
+
11
+ {% include "unfold_crispy/inputs.html" %}
12
+
13
+ {% if form_tag %}</form>{% endif %}
14
+ {% endspecialspaceless %}
@@ -8,7 +8,7 @@ from django.contrib.admin.views.main import ChangeList
8
8
  from django.contrib.admin.widgets import RelatedFieldWidgetWrapper
9
9
  from django.db.models import Model
10
10
  from django.db.models.options import Options
11
- from django.forms import BoundField, Field
11
+ from django.forms import BoundField, CheckboxSelectMultiple, Field
12
12
  from django.http import HttpRequest
13
13
  from django.template import Context, Library, Node, RequestContext, TemplateSyntaxError
14
14
  from django.template.base import NodeList, Parser, Token, token_kwargs
@@ -18,7 +18,7 @@ from django.utils.safestring import SafeText, mark_safe
18
18
  from unfold.components import ComponentRegistry
19
19
  from unfold.dataclasses import UnfoldAction
20
20
  from unfold.enums import ActionVariant
21
- from unfold.widgets import UnfoldAdminSplitDateTimeWidget
21
+ from unfold.widgets import UnfoldAdminMoneyWidget, UnfoldAdminSplitDateTimeWidget
22
22
 
23
23
  register = Library()
24
24
 
@@ -438,41 +438,46 @@ def action_item_classes(context: Context, action: UnfoldAction) -> str:
438
438
  if variant == ActionVariant.PRIMARY:
439
439
  classes.extend(
440
440
  [
441
- "border-primary-600",
441
+ "border-primary-700",
442
442
  "bg-primary-600",
443
443
  "text-white",
444
+ "dark:border-primary-500",
444
445
  ]
445
446
  )
446
447
  elif variant == ActionVariant.DANGER:
447
448
  classes.extend(
448
449
  [
449
- "border-red-600",
450
+ "border-red-700",
450
451
  "bg-red-600",
451
452
  "text-white",
453
+ "dark:border-red-500",
452
454
  ]
453
455
  )
454
456
  elif variant == ActionVariant.SUCCESS:
455
457
  classes.extend(
456
458
  [
457
- "border-green-600",
459
+ "border-green-700",
458
460
  "bg-green-600",
459
461
  "text-white",
462
+ "dark:border-green-500",
460
463
  ]
461
464
  )
462
465
  elif variant == ActionVariant.INFO:
463
466
  classes.extend(
464
467
  [
465
- "border-blue-600",
468
+ "border-blue-700",
466
469
  "bg-blue-600",
467
470
  "text-white",
471
+ "dark:border-blue-500",
468
472
  ]
469
473
  )
470
474
  elif variant == ActionVariant.WARNING:
471
475
  classes.extend(
472
476
  [
473
- "border-orange-600",
477
+ "border-orange-700",
474
478
  "bg-orange-600",
475
479
  "text-white",
480
+ "dark:border-orange-500",
476
481
  ]
477
482
  )
478
483
  else:
@@ -480,6 +485,7 @@ def action_item_classes(context: Context, action: UnfoldAction) -> str:
480
485
  [
481
486
  "border-base-200",
482
487
  "hover:text-primary-600",
488
+ "dark:hover:text-primary-500",
483
489
  "dark:border-base-700",
484
490
  ]
485
491
  )
@@ -489,7 +495,7 @@ def action_item_classes(context: Context, action: UnfoldAction) -> str:
489
495
 
490
496
  @register.filter
491
497
  def changeform_data(adminform: AdminForm) -> str:
492
- fields = []
498
+ fields = {}
493
499
 
494
500
  for fieldset in adminform:
495
501
  for line in fieldset:
@@ -497,15 +503,19 @@ def changeform_data(adminform: AdminForm) -> str:
497
503
  if isinstance(field.field, dict):
498
504
  continue
499
505
 
500
- if isinstance(field.field.field.widget, UnfoldAdminSplitDateTimeWidget):
506
+ if isinstance(
507
+ field.field.field.widget, UnfoldAdminSplitDateTimeWidget
508
+ ) or isinstance(field.field.field.widget, UnfoldAdminMoneyWidget):
501
509
  for index, _widget in enumerate(field.field.field.widget.widgets):
502
- fields.append(
510
+ fields[
503
511
  f"{field.field.name}{field.field.field.widget.widgets_names[index]}"
504
- )
512
+ ] = None
513
+ elif isinstance(field.field.field.widget, CheckboxSelectMultiple):
514
+ fields[field.field.name] = []
505
515
  else:
506
- fields.append(field.field.name)
516
+ fields[field.field.name] = None
507
517
 
508
- return mark_safe(json.dumps(dict.fromkeys(fields, "")))
518
+ return mark_safe(json.dumps(fields))
509
519
 
510
520
 
511
521
  @register.filter(takes_context=True)
@@ -518,7 +528,9 @@ def changeform_condition(field: BoundField) -> BoundField:
518
528
  field.field.field.widget.widget.attrs["x-init"] = mark_safe(
519
529
  f"const $ = django.jQuery; $(function () {{ const select = $('#{field.field.auto_id}'); select.on('change', (ev) => {{ {field.field.name} = select.val(); }}); }});"
520
530
  )
521
- elif isinstance(field.field.field.widget, UnfoldAdminSplitDateTimeWidget):
531
+ elif isinstance(
532
+ field.field.field.widget, UnfoldAdminSplitDateTimeWidget
533
+ ) or isinstance(field.field.field.widget, UnfoldAdminMoneyWidget):
522
534
  for index, widget in enumerate(field.field.field.widget.widgets):
523
535
  field_name = (
524
536
  f"{field.field.name}{field.field.field.widget.widgets_names[index]}"