django-unfold 0.29.1__py3-none-any.whl → 0.31.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 (74) hide show
  1. {django_unfold-0.29.1.dist-info → django_unfold-0.31.0.dist-info}/METADATA +63 -19
  2. {django_unfold-0.29.1.dist-info → django_unfold-0.31.0.dist-info}/RECORD +74 -73
  3. unfold/admin.py +32 -11
  4. unfold/contrib/filters/templates/unfold/filters/filters_numeric_slider.html +3 -3
  5. unfold/contrib/forms/static/unfold/forms/js/trix.js +2 -2
  6. unfold/contrib/forms/templates/unfold/forms/array.html +3 -1
  7. unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html +6 -6
  8. unfold/contrib/forms/templates/unfold/forms/wysiwyg.html +1 -1
  9. unfold/contrib/forms/widgets.py +22 -11
  10. unfold/contrib/guardian/templates/unfold/guardian/group_form.html +4 -4
  11. unfold/contrib/guardian/templates/unfold/guardian/user_form.html +4 -4
  12. unfold/contrib/import_export/templates/admin/import_export/change_form.html +1 -1
  13. unfold/contrib/import_export/templates/admin/import_export/import_errors.html +1 -1
  14. unfold/contrib/import_export/templates/admin/import_export/import_preview.html +3 -3
  15. unfold/contrib/import_export/templates/admin/import_export/import_validation.html +4 -4
  16. unfold/contrib/inlines/forms.py +1 -2
  17. unfold/contrib/simple_history/templates/simple_history/object_history_list.html +9 -9
  18. unfold/contrib/simple_history/templates/simple_history/submit_line.html +1 -1
  19. unfold/dataclasses.py +10 -1
  20. unfold/fields.py +2 -2
  21. unfold/forms.py +18 -3
  22. unfold/settings.py +1 -0
  23. unfold/sites.py +39 -15
  24. unfold/static/unfold/css/styles.css +1 -1
  25. unfold/static/unfold/js/alpine.anchor.js +1 -0
  26. unfold/static/unfold/js/alpine.js +2 -2
  27. unfold/static/unfold/js/alpine.persist.js +1 -1
  28. unfold/static/unfold/js/app.js +45 -3
  29. unfold/styles.css +15 -11
  30. unfold/templates/admin/actions.html +1 -1
  31. unfold/templates/admin/app_list.html +1 -1
  32. unfold/templates/admin/base.html +4 -4
  33. unfold/templates/admin/change_list.html +2 -2
  34. unfold/templates/admin/change_list_results.html +3 -3
  35. unfold/templates/admin/delete_confirmation.html +4 -4
  36. unfold/templates/admin/delete_selected_confirmation.html +4 -4
  37. unfold/templates/admin/edit_inline/stacked.html +2 -2
  38. unfold/templates/admin/edit_inline/tabular.html +4 -4
  39. unfold/templates/admin/filter.html +2 -2
  40. unfold/templates/admin/includes/fieldset.html +1 -1
  41. unfold/templates/admin/includes/object_delete_summary.html +1 -1
  42. unfold/templates/admin/login.html +8 -8
  43. unfold/templates/admin/object_history.html +4 -4
  44. unfold/templates/admin/search_form.html +1 -1
  45. unfold/templates/admin/submit_line.html +7 -5
  46. unfold/templates/auth/widgets/read_only_password_hash.html +1 -1
  47. unfold/templates/registration/logged_out.html +1 -1
  48. unfold/templates/unfold/change_list_filter.html +10 -2
  49. unfold/templates/unfold/helpers/account_links.html +2 -2
  50. unfold/templates/unfold/helpers/actions_row.html +4 -4
  51. unfold/templates/unfold/helpers/app_list.html +48 -38
  52. unfold/templates/unfold/helpers/app_list_default.html +4 -4
  53. unfold/templates/unfold/helpers/breadcrumb_item.html +1 -1
  54. unfold/templates/unfold/helpers/field_readonly_value.html +1 -1
  55. unfold/templates/unfold/helpers/fieldset_row.html +7 -7
  56. unfold/templates/unfold/helpers/fieldsets_tabs.html +2 -2
  57. unfold/templates/unfold/helpers/header.html +1 -1
  58. unfold/templates/unfold/helpers/help_text.html +1 -1
  59. unfold/templates/unfold/helpers/history.html +1 -1
  60. unfold/templates/unfold/helpers/label.html +1 -1
  61. unfold/templates/unfold/helpers/search.html +7 -4
  62. unfold/templates/unfold/helpers/search_results.html +2 -2
  63. unfold/templates/unfold/helpers/tab_action.html +1 -1
  64. unfold/templates/unfold/helpers/tab_list.html +27 -5
  65. unfold/templates/unfold/helpers/theme_switch.html +2 -2
  66. unfold/templates/unfold/layouts/skeleton.html +6 -1
  67. unfold/templates/unfold/widgets/clearable_file_input.html +14 -6
  68. unfold/templates/unfold/widgets/clearable_file_input_small.html +4 -4
  69. unfold/templates/unfold/widgets/split_datetime.html +2 -2
  70. unfold/templatetags/unfold.py +33 -12
  71. unfold/templatetags/unfold_list.py +16 -6
  72. unfold/widgets.py +11 -4
  73. {django_unfold-0.29.1.dist-info → django_unfold-0.31.0.dist-info}/LICENSE.md +0 -0
  74. {django_unfold-0.29.1.dist-info → django_unfold-0.31.0.dist-info}/WHEEL +0 -0
@@ -1,17 +1,39 @@
1
+ {% load i18n %}
2
+
1
3
  {% if not is_popup %}
2
- {% if tab_list or actions_list or actions_detail or actions_items or nav_global %}
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
- {% if tab_list %}
4
+ {% if tabs_list or inlines_list or actions_list or actions_detail or actions_items or nav_global %}
5
+ <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-300">
6
+ {% if inlines_list or tabs_list %}
5
7
  <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
- {% for item in tab_list %}
8
+ {% for item in tabs_list %}
7
9
  {% if item.has_permission %}
8
10
  <li class="border-b last:border-b-0 md:border-b-0 md:mr-8 dark:border-gray-800">
9
- <a href="{{ item.link }}" class="block px-3 py-2 {% if item.link|stringformat:"s" in request.path %} border-b md:border-primary-500 dark:md:border-primary-600 font-medium -mb-px text-primary-600 hover:text-primary-600 dark:text-primary-500{% else %} hover:text-gray-700 dark:hover:text-gray-200{% endif %} md:py-4 md:px-0 dark:border-gray-800">
11
+ <a href="{% if item.link_callback %}{{ item.link_callback }}{% else %}{{ item.link }}{% endif %}" class="block px-3 py-2 {% if item.active %} border-b md:border-primary-500 dark:md:border-primary-600 font-medium -mb-px text-primary-600 hover:text-primary-600 dark:text-primary-500{% else %} hover:text-gray-700 dark:hover:text-gray-200{% endif %} md:py-4 md:px-0 dark:border-gray-800">
10
12
  {{ item.title }}
11
13
  </a>
12
14
  </li>
13
15
  {% endif %}
14
16
  {% endfor %}
17
+
18
+ {% if inlines_list %}
19
+ <li class="border-b last:border-b-0 md:border-b-0 md:mr-8 dark:border-gray-800">
20
+ <a class="block cursor-pointer px-3 py-2 hover:text-gray-700 dark:hover:text-gray-200 md:py-4 md:px-0 dark:border-gray-800" x-on:click="activeTab = 'general'" x-bind:class="{'border-b md:border-primary-500 dark:md:border-primary-600 font-medium -mb-px text-primary-600 hover:text-primary-600 dark:text-primary-500': activeTab == 'general'}">
21
+ {% trans "General" %}
22
+ </a>
23
+ </li>
24
+
25
+ {% for inline in inlines_list %}
26
+ <li class="border-b last:border-b-0 md:border-b-0 md:mr-8 dark:border-gray-800">
27
+ <a class="block cursor-pointer px-3 py-2 hover:text-gray-700 dark:hover:text-gray-200 md:py-4 md:px-0 dark:border-gray-800" x-on:click="activeTab = '{{ inline.opts.verbose_name|slugify }}'" x-bind:class="{'border-b md:border-primary-500 dark:md:border-primary-600 font-medium -mb-px text-primary-600 hover:text-primary-600 dark:text-primary-500': activeTab == '{{ inline.opts.verbose_name|slugify }}'}">
28
+ {% if inline.formset.max_num == 1 %}
29
+ {{ inline.opts.verbose_name|capfirst }}
30
+ {% else %}
31
+ {{ inline.opts.verbose_name_plural|capfirst }}
32
+ {% endif %}
33
+ </a>
34
+ </li>
35
+ {% endfor %}
36
+ {% endif %}
15
37
  </ul>
16
38
  {% endif %}
17
39
 
@@ -1,13 +1,13 @@
1
1
  {% load i18n %}
2
2
 
3
3
  <div class="relative" x-data="{ openTheme: false }">
4
- <a class="block cursor-pointer leading-none hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" x-on:click="openTheme = !openTheme">
4
+ <a class="block cursor-pointer leading-none hover:text-gray-700 dark:text-gray-300 dark:hover:text-gray-200" x-on:click="openTheme = !openTheme">
5
5
  <span class="material-symbols-outlined">
6
6
  <span x-text="adminTheme == 'dark' && 'dark_mode' || adminTheme == 'light' && 'light_mode' || 'computer'"></span>
7
7
  </span>
8
8
  </a>
9
9
 
10
- <nav class="absolute bg-white border flex flex-col leading-none overflow-hidden py-1 -right-2 rounded shadow-lg text-sm text-gray-500 top-7 w-40 z-50 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400" x-cloak x-show="openTheme" @click.outside="openTheme = false">
10
+ <nav class="absolute bg-white border flex flex-col leading-none overflow-hidden py-1 -right-2 rounded shadow-lg text-sm text-gray-500 top-7 w-40 z-50 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-300" x-cloak x-show="openTheme" @click.outside="openTheme = false">
11
11
  <a class="cursor-pointer flex flex-row leading-none mx-1 px-3 py-1.5 rounded hover:bg-gray-100 hover:text-gray-700 dark:hover:bg-gray-700 dark:hover:text-gray-200"
12
12
  x-on:click="adminTheme = 'dark'"
13
13
  x-bind:class="adminTheme == 'dark' && 'text-primary-600 dark:text-primary-500 dark:hover:!text-primary-500 hover:!text-primary-600'">
@@ -24,9 +24,14 @@
24
24
  <link href="{{ style }}" rel="stylesheet">
25
25
  {% endfor %}
26
26
 
27
+ {% for favicon in site_favicons %}
28
+ <link {% if favicon.rel %}rel="{{ favicon.rel }}"{% endif %} {% if favicon.href %}href="{{ favicon.href }}"{% endif %} {% if favicon.type %}type="{{ favicon.type }}"{% endif %} {% if favicon.sizes %}sizes="{{ favicon.sizes }}"{% endif %}>
29
+ {% endfor %}
30
+
27
31
  <link href="{% static 'unfold/css/styles.css' %}" rel="stylesheet">
28
32
  <link href="{% static 'unfold/css/simplebar.css' %}" rel="stylesheet">
29
33
 
34
+ <script src="{% static 'unfold/js/alpine.anchor.js' %}" defer></script>
30
35
  <script src="{% static 'unfold/js/alpine.persist.js' %}" defer></script>
31
36
  <script src="{% static 'unfold/js/alpine.js' %}" defer></script>
32
37
  <script src="{% static 'unfold/js/htmx.js' %}"></script>
@@ -63,7 +68,7 @@
63
68
  {% endif %}
64
69
  </head>
65
70
 
66
- <body class="antialiased bg-white font-sans text-gray-500 dark:bg-gray-900 dark:text-gray-400 {% if is_popup %}popup {% endif %}{% block bodyclass %}{% endblock %}" data-admin-utc-offset="{% now "Z" %}" x-data="{ sidebarMobileOpen: false, sidebarDesktopOpen: {% if request.session.toggle_sidebar == False %}false{% else %}true{% endif %} }">
71
+ <body class="antialiased bg-white font-sans text-gray-500 dark:bg-gray-900 dark:text-gray-300 {% if is_popup %}popup {% endif %}{% block bodyclass %}{% endblock %}" data-admin-utc-offset="{% now "Z" %}" x-data="{ activeTab: 'general', sidebarMobileOpen: false, sidebarDesktopOpen: {% if request.session.toggle_sidebar == False %}false{% else %}true{% endif %} }">
67
72
  {% block base %}{% endblock %}
68
73
 
69
74
  <div id="modal-overlay" class="backdrop-blur-sm bg-opacity-80 bg-gray-900 bottom-0 fixed hidden left-0 mr-1 right-0 top-0 z-50"></div>
@@ -10,24 +10,32 @@
10
10
 
11
11
  <div class="border flex items-center overflow-hidden rounded-md shadow-sm text-sm max-w-2xl dark:border-gray-700">
12
12
  {% if widget.is_initial and not widget.required %}
13
- <div class="bg-gray-50 border-r flex flex-none items-center self-stretch px-3 dark:bg-white/[.02] dark:border-gray-700 dark:text-gray-400">
13
+ <div class="bg-gray-50 border-r flex flex-none items-center self-stretch px-3 dark:bg-white/[.02] dark:border-gray-700 dark:text-gray-300">
14
14
  <label for="{{ widget.checkbox_id }}" class="flex items-center">
15
15
  <input type="checkbox"{% if widget.class %} class="{{ widget.class }}"{% endif %} name="{{ widget.checkbox_name }}" id="{{ widget.checkbox_id }}" />
16
16
 
17
- <span class="ml-2 text-gray-500 dark:text-gray-400">
17
+ <span class="ml-2 text-gray-500 dark:text-gray-300">
18
18
  {{ widget.clear_checkbox_label }}
19
19
  </span>
20
20
  </label>
21
21
  </div>
22
22
  {% endif %}
23
23
 
24
- <input type="text" value="{% if widget.value %}{{ widget.value.url }}{% else %}{% trans 'Choose file to upload' %}{% endif %}" disabled class="bg-white flex-grow font-medium px-3 py-2 text-ellipsis dark:bg-gray-900 {% if widget.value %}text-gray-500 dark:text-gray-400{% else %}text-gray-300 dark:text-gray-400{% endif %}">
24
+ <input type="text" value="{% if widget.value %}{{ widget.value.url }}{% else %}{% trans 'Choose file to upload' %}{% endif %}" disabled class="bg-white flex-grow font-medium px-3 py-2 text-ellipsis dark:bg-gray-900 {% if widget.value %}text-gray-500 dark:text-gray-300{% else %}text-gray-300 dark:text-gray-300{% endif %}">
25
25
 
26
26
  <div class="flex flex-none items-center leading-none self-stretch">
27
- <input id="{{ widget.name }}" type="{{ widget.type }}" name="{{ widget.name }}" class="hidden" {% include "django/forms/widgets/attrs.html" %} />
27
+ <div class="hidden">
28
+ <input type="{{ widget.type }}" name="{{ widget.name }}" {% include "django/forms/widgets/attrs.html" %} />
29
+ </div>
30
+
31
+ {% if widget.is_initial %}
32
+ <a href="{{ widget.value.url }}" class="border-r cursor-pointer text-gray-400 px-3 hover:text-gray-700 dark:border-gray-700 dark:text-gray-500 dark:hover:text-gray-200" target="_blank">
33
+ <span class="material-symbols-outlined">download</span>
34
+ </a>
35
+ {% endif %}
28
36
 
29
- <label for="{{ widget.name }}" class="cursor-pointer text-gray-400 px-3 hover:text-gray-700 dark:text-gray-500 dark:hover:text-gray-200">
30
- <span class="material-symbols-outlined">file_upload</span>
37
+ <label for="{{ widget.attrs.id }}" class="cursor-pointer text-gray-400 px-3 hover:text-gray-700 dark:text-gray-500 dark:hover:text-gray-200">
38
+ <span class="material-symbols-outlined">upload</span>
31
39
  </label>
32
40
  </div>
33
41
  </div>
@@ -7,19 +7,19 @@
7
7
  <label for="{{ widget.checkbox_id }}" class="flex items-center">
8
8
  <input type="checkbox"{% if widget.class %} class="{{ widget.class }}"{% endif %} name="{{ widget.checkbox_name }}" id="{{ widget.checkbox_id }}" />
9
9
 
10
- <span class="ml-2 text-gray-500 dark:text-gray-400">
10
+ <span class="ml-2 text-gray-500 dark:text-gray-300">
11
11
  {{ widget.clear_checkbox_label }}
12
12
  </span>
13
13
  </label>
14
14
  </div>
15
15
  {% endif %}
16
16
 
17
- <input type="text" value="{% if widget.value %}{{ widget.value.url }}{% else %}{% trans 'Choose file to upload' %}{% endif %}" disabled class="bg-white flex-grow font-medium px-3 py-2 text-ellipsis dark:bg-gray-900 {% if widget.value %}text-gray-500 dark:text-gray-400{% else %}text-gray-300 dark:text-gray-400{% endif %}">
17
+ <input type="text" value="{% if widget.value %}{{ widget.value.url }}{% else %}{% trans 'Choose file to upload' %}{% endif %}" disabled class="bg-white flex-grow font-medium px-3 py-2 text-ellipsis dark:bg-gray-900 {% if widget.value %}text-gray-500 dark:text-gray-300{% else %}text-gray-300 dark:text-gray-300{% endif %}">
18
18
 
19
19
  <div class="flex flex-none items-center leading-none self-stretch">
20
- <input id="{{ widget.name }}" type="{{ widget.type }}" name="{{ widget.name }}" class="{{ widget.file_input_class }}" {% include "django/forms/widgets/attrs.html" %} />
20
+ <input type="{{ widget.type }}" name="{{ widget.name }}" class="{{ widget.file_input_class }}" {% include "django/forms/widgets/attrs.html" %} />
21
21
 
22
- <label for="{{ widget.name }}" class="cursor-pointer text-gray-400 px-3 hover:text-gray-700 dark:text-gray-500 dark:hover:text-gray-200">
22
+ <label for="{{ widget.attrs.id }}" class="cursor-pointer text-gray-400 px-3 hover:text-gray-700 dark:text-gray-500 dark:hover:text-gray-200">
23
23
  <span class="material-symbols-outlined">file_upload</span>
24
24
  </label>
25
25
  </div>
@@ -1,6 +1,6 @@
1
1
  <div class="datetime flex flex-col gap-2 max-w-2xl lg:flex-row lg:group-[.field-row]:flex-row lg:group-[.field-row]:items-center lg:group-[.field-tabular]:flex-row lg:group-[.field-tabular]:items-center">
2
2
  <div class="basis-1/2 flex flex-col lg:group-[.field-row]:flex-row group-[.field-row]:gap-2 lg:group-[.field-tabular]:flex-row group-[.field-tabular]:gap-2">
3
- <div class="font-medium mb-2 text-gray-500 text-sm dark:text-gray-400 group-[.field-row]:mb-0 group-[.field-row]:flex group-[.field-row]:items-center group-[.field-tabular]:mb-0 group-[.field-tabular]:flex group-[.field-tabular]:items-center">
3
+ <div class="font-medium mb-2 text-gray-500 text-sm dark:text-gray-300 group-[.field-row]:mb-0 group-[.field-row]:flex group-[.field-row]:items-center group-[.field-tabular]:mb-0 group-[.field-tabular]:flex group-[.field-tabular]:items-center">
4
4
  {{ date_label }}
5
5
  </div>
6
6
 
@@ -12,7 +12,7 @@
12
12
  </div>
13
13
 
14
14
  <div class="basis-1/2 flex flex-col lg:ml-auto md:mt-0 lg:group-[.field-row]:flex-row group-[.field-row]:gap-2 lg:group-[.field-tabular]:flex-row group-[.field-tabular]:gap-2">
15
- <div class="font-medium mb-2 text-gray-500 text-sm dark:text-gray-400 group-[.field-row]:mb-0 group-[.field-row]:flex group-[.field-row]:items-center group-[.field-tabular]:mb-0 group-[.field-tabular]:flex group-[.field-tabular]:items-center">
15
+ <div class="font-medium mb-2 text-gray-500 text-sm dark:text-gray-300 group-[.field-row]:mb-0 group-[.field-row]:flex group-[.field-row]:items-center group-[.field-tabular]:mb-0 group-[.field-tabular]:flex group-[.field-tabular]:items-center">
16
16
  {{ time_label }}
17
17
  </div>
18
18
 
@@ -12,28 +12,49 @@ register = Library()
12
12
 
13
13
 
14
14
  @register.simple_tag(name="tab_list", takes_context=True)
15
- def tab_list(context, opts) -> str:
16
- tabs = None
15
+ def tab_list(context, page, opts) -> str:
16
+ tabs_list = []
17
+ inlines_list = []
18
+
19
+ data = {
20
+ "nav_global": context.get("nav_global"),
21
+ "actions_detail": context.get("actions_detail"),
22
+ "actions_list": context.get("actions_list"),
23
+ "actions_items": context.get("actions_items"),
24
+ "is_popup": context.get("is_popup"),
25
+ }
17
26
 
18
27
  for tab in context.get("tab_list", []):
19
28
  if str(opts) in tab["models"]:
20
- tabs = tab["items"]
29
+ tabs_list = tab["items"]
21
30
  break
22
31
 
32
+ if page == "changelist":
33
+ data["tabs_list"] = tabs_list
34
+
35
+ for inline in context.get("inline_admin_formsets", []):
36
+ if hasattr(inline.opts, "tab"):
37
+ inlines_list.append(inline)
38
+
39
+ if page == "changeform" and len(inlines_list) > 0:
40
+ data["inlines_list"] = inlines_list
41
+
23
42
  return render_to_string(
24
43
  "unfold/helpers/tab_list.html",
25
- request=context.request,
26
- context={
27
- "tab_list": tabs,
28
- "nav_global": context.get("nav_global"),
29
- "actions_detail": context.get("actions_detail"),
30
- "actions_list": context.get("actions_list"),
31
- "actions_items": context.get("actions_items"),
32
- "is_popup": context.get("is_popup"),
33
- },
44
+ request=context["request"],
45
+ context=data,
34
46
  )
35
47
 
36
48
 
49
+ @register.simple_tag(name="has_nav_item_active")
50
+ def has_nav_item_active(items: list) -> bool:
51
+ for item in items:
52
+ if "active" in item and item["active"]:
53
+ return True
54
+
55
+ return False
56
+
57
+
37
58
  @register.filter
38
59
  def class_name(value: Any) -> str:
39
60
  return value.__class__.__name__
@@ -63,8 +63,8 @@ ROW_CLASSES = [
63
63
  "before:mr-auto",
64
64
  "before:text-gray-500",
65
65
  "first:border-t-0",
66
- "dark:before:text-gray-400",
67
- "dark:text-gray-400",
66
+ "dark:before:text-gray-300",
67
+ "dark:text-gray-300",
68
68
  "lg:before:hidden",
69
69
  "lg:first:border-t",
70
70
  "lg:py-3",
@@ -90,7 +90,7 @@ CHECKBOX_CLASSES = [
90
90
  "lg:border-t",
91
91
  "lg:border-gray-200",
92
92
  "lg:table-cell",
93
- "dark:before:text-gray-400",
93
+ "dark:before:text-gray-300",
94
94
  "dark:lg:border-gray-800",
95
95
  ]
96
96
 
@@ -254,7 +254,7 @@ def items_for_result(cl: ChangeList, result: HttpRequest, form) -> SafeText:
254
254
  f, (models.DateField, models.TimeField, models.ForeignKey)
255
255
  ):
256
256
  row_classes.append("nowrap")
257
- row_class = mark_safe(f' class="{" ".join(row_classes)}"')
257
+
258
258
  # If list_display_links not defined, add the link tag to the first field
259
259
 
260
260
  if link_in_col(first, field_name, cl):
@@ -287,7 +287,7 @@ def items_for_result(cl: ChangeList, result: HttpRequest, form) -> SafeText:
287
287
  else "",
288
288
  result_repr,
289
289
  )
290
-
290
+ row_class = mark_safe(f' class="{" ".join(row_classes)}"')
291
291
  yield format_html(
292
292
  '<{}{} data-label="{}">{}</{}>',
293
293
  table_tag,
@@ -309,7 +309,17 @@ def items_for_result(cl: ChangeList, result: HttpRequest, form) -> SafeText:
309
309
  )
310
310
  ):
311
311
  bf = form[field_name]
312
- result_repr = mark_safe(str(bf.errors) + str(bf))
312
+ result_repr = mark_safe(
313
+ str(bf)
314
+ + render_to_string(
315
+ "unfold/helpers/form_errors.html", {"errors": bf.errors}
316
+ )
317
+ )
318
+
319
+ if bf.errors:
320
+ row_classes += ["group", "errors"]
321
+
322
+ row_class = mark_safe(f' class="{" ".join(row_classes)}"')
313
323
 
314
324
  if field_index != 0:
315
325
  yield format_html(
unfold/widgets.py CHANGED
@@ -23,6 +23,7 @@ from django.forms import (
23
23
  MultiWidget,
24
24
  NullBooleanSelect,
25
25
  NumberInput,
26
+ PasswordInput,
26
27
  Select,
27
28
  )
28
29
  from django.utils.translation import gettext_lazy as _
@@ -62,7 +63,7 @@ BASE_CLASSES = [
62
63
  "group-[.errors]:focus:ring-red-200",
63
64
  "dark:bg-gray-900",
64
65
  "dark:border-gray-700",
65
- "dark:text-gray-400",
66
+ "dark:text-gray-300",
66
67
  "dark:focus:border-primary-600",
67
68
  "dark:focus:ring-primary-700",
68
69
  "dark:focus:ring-opacity-50",
@@ -90,12 +91,10 @@ TEXTAREA_CLASSES = [
90
91
  "max-w-4xl",
91
92
  "appearance-none",
92
93
  "expandable",
93
- "overflow-hidden",
94
94
  "transition",
95
95
  "transition-height",
96
96
  "duration-75",
97
97
  "ease-in-out",
98
- "resize-none",
99
98
  ]
100
99
 
101
100
  TEXTAREA_EXPANDABLE_CLASSES = [
@@ -116,6 +115,7 @@ SELECT_CLASSES = [
116
115
 
117
116
  PROSE_CLASSES = [
118
117
  "font-normal",
118
+ "whitespace-normal",
119
119
  "prose-sm",
120
120
  "prose-blockquote:border-l-4",
121
121
  "prose-blockquote:not-italic",
@@ -131,7 +131,7 @@ PROSE_CLASSES = [
131
131
  "prose-strong:text-gray-700",
132
132
  "dark:prose-pre:bg-gray-800",
133
133
  "dark:prose-blockquote:border-gray-700",
134
- "dark:prose-blockquote:text-gray-400",
134
+ "dark:prose-blockquote:text-gray-300",
135
135
  "dark:prose-headings:text-gray-200",
136
136
  "dark:prose-strong:text-gray-200",
137
137
  ]
@@ -567,3 +567,10 @@ class UnfoldForeignKeyRawIdWidget(ForeignKeyRawIdWidget):
567
567
  **(attrs or {}),
568
568
  }
569
569
  super().__init__(rel, admin_site, attrs, using)
570
+
571
+
572
+ class UnfoldAdminPasswordInput(PasswordInput):
573
+ def __init__(self, attrs=None, render_value=False):
574
+ super().__init__(
575
+ {"class": " ".join(INPUT_CLASSES), **(attrs or {})}, render_value
576
+ )