django-unfold 0.29.1__py3-none-any.whl → 0.31.0__py3-none-any.whl

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