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
@@ -15,7 +15,9 @@
15
15
 
16
16
  <template x-for="(item, index) in items" :key="item.key">
17
17
  <div class="flex flex-row">
18
- {% include template.template_name with widget=template %}
18
+ {% with widget=template %}
19
+ {% include template.template_name %}
20
+ {% endwith %}
19
21
 
20
22
  <a x-on:click="items.splice(index, 1)" class="bg-white border cursor-pointer flex items-center h-9.5 justify-center ml-2 rounded shadow-sm shrink-0 text-red-600 text-sm w-9.5 dark:bg-gray-900 dark:border-gray-700 dark:text-red-500">
21
23
  <span class="material-symbols-outlined text-sm">delete</span>
@@ -2,7 +2,7 @@
2
2
 
3
3
  <template id="trix-toolbar">
4
4
  <div class="bg-gray-50 flex flex-row flex-wrap border group-[.errors]:border-t-red-600 gap-4 group-[.errors]:border-x-red-600 px-3 py-2 relative rounded-t-md shadow-sm w-full dark:bg-white/[.02] dark:border-gray-700 dark:group-[.errors]:border-t-red-500 dark:group-[.errors]:border-x-red-500">
5
- <div data-trix-button-group="text-tools" class="bg-white border border-md flex flex-row rounded-md shadow-sm shrink-0 dark:bg-gray-900 dark:border-gray-700 dark:text-gray-400">
5
+ <div data-trix-button-group="text-tools" class="bg-white border border-md flex flex-row rounded-md shadow-sm shrink-0 dark:bg-gray-900 dark:border-gray-700 dark:text-gray-300">
6
6
  {% spaceless %}
7
7
  <button type="button" data-trix-attribute="p" data-trix-key="p" title="{% trans "Paragraph" %}" tabindex="-1" class="border-r cursor-pointer flex items-center h-8 justify-center transition-colors w-8 hover:text-primary-600 dark:border-gray-700">
8
8
  <span class="material-symbols-outlined">format_paragraph</span>
@@ -30,7 +30,7 @@
30
30
  {% endspaceless %}
31
31
  </div>
32
32
 
33
- <div data-trix-button-group="block-headings" class="bg-white border border-md flex flex-row rounded-md shadow-sm dark:bg-gray-900 dark:border-gray-700 dark:text-gray-400">
33
+ <div data-trix-button-group="block-headings" class="bg-white border border-md flex flex-row rounded-md shadow-sm dark:bg-gray-900 dark:border-gray-700 dark:text-gray-300">
34
34
  {% spaceless %}
35
35
  <button type="button" data-trix-attribute="heading1" title="{% trans "Heading" %} 1" tabindex="-1" class="border-r cursor-pointer flex items-center h-8 justify-center transition-colors w-8 hover:text-primary-600 dark:border-gray-700">
36
36
  <span class="material-symbols-outlined">format_h1</span>
@@ -50,7 +50,7 @@
50
50
  {% endspaceless %}
51
51
  </div>
52
52
 
53
- <div data-trix-button-group="block-tools" class="bg-white border border-md flex flex-row rounded-md shadow-sm dark:bg-gray-900 dark:border-gray-700 dark:text-gray-400">
53
+ <div data-trix-button-group="block-tools" class="bg-white border border-md flex flex-row rounded-md shadow-sm dark:bg-gray-900 dark:border-gray-700 dark:text-gray-300">
54
54
  {% spaceless %}
55
55
  <button type="button" data-trix-attribute="quote" title="{% trans "Quote" %}" tabindex="-1" class="border-r cursor-pointer flex items-center h-8 justify-center transition-colors w-8 hover:text-primary-600 dark:border-gray-700">
56
56
  <span class="material-symbols-outlined">format_quote</span>
@@ -79,7 +79,7 @@
79
79
  </div>
80
80
 
81
81
  <div class="lg:ml-auto">
82
- <div data-trix-button-group="history-tools" class="bg-white border border-md flex flex-row rounded-md shadow-sm dark:bg-gray-900 dark:border-gray-700 dark:text-gray-400">
82
+ <div data-trix-button-group="history-tools" class="bg-white border border-md flex flex-row rounded-md shadow-sm dark:bg-gray-900 dark:border-gray-700 dark:text-gray-300">
83
83
  <button type="button" data-trix-action="undo" data-trix-key="z" title="{% trans "Undo" %}" tabindex="-1" class="border-r cursor-pointer flex items-center h-8 justify-center transition-colors w-8 hover:text-primary-600 dark:border-gray-700">
84
84
  <span class="material-symbols-outlined">undo</span>
85
85
  </button>
@@ -92,9 +92,9 @@
92
92
  <div data-trix-dialogs>
93
93
  <div class="absolute bg-white border-b -bottom-px left-0 px-4 py-2 right-0 shadow-sm translate-y-full dark:bg-gray-900 dark:border-gray-700" data-trix-dialog="href" data-trix-dialog-attribute="href">
94
94
  <div class="flex flex-row">
95
- <input type="url" name="href" class="border bg-white font-medium px-3 rounded-md shadow-sm text-gray-500 text-sm focus:ring focus:ring-primary-300 focus:border-primary-600 focus:outline-none group-[.errors]:border-red-600 group-[.errors]:focus:ring-red-200 dark:bg-gray-900 dark:border-gray-700 dark:text-gray-400 dark:focus:ring-primary-600/30 dark:group-[.errors]:border-red-500 dark:group-[.errors]:focus:ring-red-600/40" placeholder="{% trans "Enter an URL" %}" required data-trix-input>
95
+ <input type="url" name="href" class="border bg-white font-medium px-3 rounded-md shadow-sm text-gray-500 text-sm focus:ring focus:ring-primary-300 focus:border-primary-600 focus:outline-none group-[.errors]:border-red-600 group-[.errors]:focus:ring-red-200 dark:bg-gray-900 dark:border-gray-700 dark:text-gray-300 dark:focus:ring-primary-600/30 dark:group-[.errors]:border-red-500 dark:group-[.errors]:focus:ring-red-600/40" placeholder="{% trans "Enter an URL" %}" required data-trix-input>
96
96
 
97
- <div class="bg-white border border-md flex flex-row ml-4 rounded-md shadow-sm dark:bg-gray-900 dark:border-gray-700 dark:text-gray-400">
97
+ <div class="bg-white border border-md flex flex-row ml-4 rounded-md shadow-sm dark:bg-gray-900 dark:border-gray-700 dark:text-gray-300">
98
98
  <button type="button" data-trix-method="setAttribute" title="{% trans "Link" %}" class="border-r cursor-pointer flex items-center h-8 justify-center text-gray-400 transition-colors w-8 hover:text-primary-600 dark:border-gray-700">
99
99
  <span class="material-symbols-outlined">link</span>
100
100
  </button>
@@ -1,6 +1,6 @@
1
1
  {% include "unfold/forms/helpers/toolbar.html" %}
2
2
 
3
- <input type="hidden" name="{{ widget.name }}" id="wysiwyg-{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value }}"{% endif %}>
3
+ <input type="hidden" name="{{ widget.name }}" id="wysiwyg-{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value }}"{% endif %} {% include "django/forms/widgets/attrs.html" %}>
4
4
 
5
5
  <div class="max-w-4xl relative">
6
6
  <trix-editor input="wysiwyg-{{ widget.name }}" {% include "django/forms/widgets/attrs.html" %}></trix-editor>
@@ -4,7 +4,11 @@ from django.core.validators import EMPTY_VALUES
4
4
  from django.forms import MultiWidget, Widget
5
5
  from django.http import QueryDict
6
6
  from django.utils.datastructures import MultiValueDict
7
- from unfold.widgets import PROSE_CLASSES, UnfoldAdminTextInputWidget
7
+ from unfold.widgets import (
8
+ PROSE_CLASSES,
9
+ UnfoldAdminSelectWidget,
10
+ UnfoldAdminTextInputWidget,
11
+ )
8
12
 
9
13
  WYSIWYG_CLASSES = [
10
14
  *PROSE_CLASSES,
@@ -20,26 +24,33 @@ WYSIWYG_CLASSES = [
20
24
  "w-full",
21
25
  "focus:outline-none",
22
26
  "dark:border-gray-700",
23
- "dark:text-gray-400",
27
+ "dark:text-gray-300",
24
28
  "dark:group-[.errors]:border-red-500",
25
29
  ]
26
30
 
27
31
 
28
32
  class ArrayWidget(MultiWidget):
29
33
  template_name = "unfold/forms/array.html"
30
- widget_class = UnfoldAdminTextInputWidget
31
34
 
32
35
  def __init__(self, *args: Any, **kwargs: Any) -> None:
33
- widgets = [self.widget_class]
36
+ if "choices" in kwargs:
37
+ self.choices = kwargs["choices"]
38
+
39
+ widgets = [self.get_widget_instance()]
34
40
  super().__init__(widgets)
35
41
 
42
+ def get_widget_instance(self) -> Any:
43
+ if hasattr(self, "choices"):
44
+ return UnfoldAdminSelectWidget(choices=self.choices)
45
+
46
+ return UnfoldAdminTextInputWidget()
47
+
36
48
  def get_context(self, name: str, value: str, attrs: Dict) -> Dict:
37
49
  self._resolve_widgets(value)
38
50
  context = super().get_context(name, value, attrs)
39
- template_widget = UnfoldAdminTextInputWidget()
40
- template_widget.name = name
41
-
42
- context.update({"template": template_widget})
51
+ context.update(
52
+ {"template": self.get_widget_instance().get_context(name, "", {})["widget"]}
53
+ )
43
54
  return context
44
55
 
45
56
  def value_from_datadict(
@@ -71,12 +82,12 @@ class ArrayWidget(MultiWidget):
71
82
  value = []
72
83
 
73
84
  elif isinstance(value, List):
74
- self.widgets = [self.widget_class for item in value]
85
+ self.widgets = [self.get_widget_instance() for item in value]
75
86
  else:
76
- self.widgets = [self.widget_class for item in value.split(",")]
87
+ self.widgets = [self.get_widget_instance() for item in value.split(",")]
77
88
 
78
89
  self.widgets_names = ["" for i in range(len(self.widgets))]
79
- self.widgets = [w() if isinstance(w, type) else w for w in self.widgets]
90
+ self.widgets = [w if isinstance(w, type) else w for w in self.widgets]
80
91
 
81
92
 
82
93
  class WysiwygWidget(Widget):
@@ -8,7 +8,7 @@
8
8
  </h2>
9
9
 
10
10
  {% if groups_perms.items %}
11
- <table id="group-permissions" class="border-gray-200 border-spacing-none border-separate mb-6 text-gray-700 w-full dark:text-gray-400 lg:border lg:rounded-md lg:shadow-sm lg:dark:border-gray-800">
11
+ <table id="group-permissions" class="border-gray-200 border-spacing-none border-separate mb-6 text-gray-700 w-full dark:text-gray-300 lg:border lg:rounded-md lg:shadow-sm lg:dark:border-gray-800">
12
12
  <thead class="hidden lg:table-header-group">
13
13
  <tr>
14
14
  <th class="align-middle font-medium px-3 py-2 text-left text-gray-400 text-sm">
@@ -30,14 +30,14 @@
30
30
  <tbody>
31
31
  {% for group, group_perms in groups_perms.items %}
32
32
  <tr class="block border mb-3 rounded-md shadow-sm lg:table-row lg:border-none lg:mb-0 lg:shadow-none dark:border-gray-800">
33
- <th class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-400 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans "User" %}">
33
+ <th class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-300 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans "User" %}">
34
34
  <span class="text-gray-700 dark:text-gray-200">
35
35
  {{ group }}
36
36
  </span>
37
37
  </th>
38
38
 
39
39
  {% for perm in model_perms %}
40
- <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-400 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{{ perm.name }}">
40
+ <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-300 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{{ perm.name }}">
41
41
  {% if perm.codename in group_perms %}
42
42
  {% include "unfold/helpers/boolean.html" with value=False %}
43
43
  {% else %}
@@ -46,7 +46,7 @@
46
46
  </td>
47
47
  {% endfor %}
48
48
 
49
- <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-right text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-400 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans "Action" %}">
49
+ <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-right text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-300 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans "Action" %}">
50
50
  <a href="group-manage/{{ group.id|safe }}/" class="hover:text-gray-700 dark:hover:text-white">
51
51
  {% trans "Edit" %}
52
52
  </a>
@@ -8,7 +8,7 @@
8
8
  </h2>
9
9
 
10
10
  {% if users_perms.items %}
11
- <table id="user-permissions" class="border-gray-200 border-spacing-none border-separate mb-6 text-gray-700 w-full dark:text-gray-400 lg:border lg:rounded-md lg:shadow-sm lg:dark:border-gray-800">
11
+ <table id="user-permissions" class="border-gray-200 border-spacing-none border-separate mb-6 text-gray-700 w-full dark:text-gray-300 lg:border lg:rounded-md lg:shadow-sm lg:dark:border-gray-800">
12
12
  <thead class="hidden lg:table-header-group">
13
13
  <tr>
14
14
  <th class="align-middle font-medium px-3 py-2 text-left text-gray-400 text-sm">
@@ -30,14 +30,14 @@
30
30
  <tbody>
31
31
  {% for user, user_perms in users_perms.items %}
32
32
  <tr class="block border mb-3 rounded-md shadow-sm lg:table-row lg:border-none lg:mb-0 lg:shadow-none dark:border-gray-800">
33
- <th class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-400 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans "User" %}">
33
+ <th class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-300 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans "User" %}">
34
34
  <span class="text-gray-700 dark:text-gray-200">
35
35
  {{ user }}
36
36
  </span>
37
37
  </th>
38
38
 
39
39
  {% for perm in model_perms %}
40
- <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-400 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{{ perm.name }}">
40
+ <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-300 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{{ perm.name }}">
41
41
  {% if perm.codename in group_perms %}
42
42
  {% include "unfold/helpers/boolean.html" with value=False %}
43
43
  {% else %}
@@ -46,7 +46,7 @@
46
46
  </td>
47
47
  {% endfor %}
48
48
 
49
- <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-right text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-400 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans "Action" %}">
49
+ <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-right text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-300 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans "Action" %}">
50
50
  <a href="user-manage/{{ user.id|safe }}/" class="hover:text-gray-700 dark:hover:text-white">
51
51
  {% trans "Edit" %}
52
52
  </a>
@@ -5,6 +5,6 @@
5
5
  {{ block.super }}
6
6
 
7
7
  {% if show_change_form_export %}
8
- <input type="submit" value="{% translate 'Export' %}" name="_export-item" class="bg-white text-gray-500 border cursor-pointer flex font-medium items-center px-3 py-2 mr-3 rounded-md shadow-sm text-sm dark:bg-gray-900 dark:border dark:border-gray-700 dark:text-gray-400">
8
+ <input type="submit" value="{% translate 'Export' %}" name="_export-item" class="bg-white text-gray-500 border cursor-pointer flex font-medium items-center px-3 py-2 mr-3 rounded-md shadow-sm text-sm dark:bg-gray-900 dark:border dark:border-gray-700 dark:text-gray-300">
9
9
  {% endif %}
10
10
  {% endblock %}
@@ -26,7 +26,7 @@
26
26
  </div>
27
27
  </div>
28
28
 
29
- <div class="block border leading-relaxed rounded p-4 text-sm traceback dark:border-gray-800 dark:text-gray-400 " x-show="open">
29
+ <div class="block border leading-relaxed rounded p-4 text-sm traceback dark:border-gray-800 dark:text-gray-300 " x-show="open">
30
30
  {{ error.traceback|linebreaks }}
31
31
  </div>
32
32
  </li>
@@ -1,7 +1,7 @@
1
1
  {% load admin_urls i18n import_export_tags unfold %}
2
2
 
3
3
  {% block preview %}
4
- <table class="border-gray-200 border-spacing-none border-separate text-gray-700 w-full dark:text-gray-400 lg:border lg:rounded-md lg:shadow-sm lg:dark:border-gray-800">
4
+ <table class="border-gray-200 border-spacing-none border-separate text-gray-700 w-full dark:text-gray-300 lg:border lg:rounded-md lg:shadow-sm lg:dark:border-gray-800">
5
5
  <thead class="hidden lg:table-header-group">
6
6
  <tr>
7
7
  <th class="align-middle capitalize font-medium px-3 py-2 text-left text-gray-400 text-sm"></th>
@@ -17,7 +17,7 @@
17
17
  <tbody>
18
18
  {% for row in result.valid_rows %}
19
19
  <tr class="{{ row.import_type }} {% cycle '' 'bg-gray-50 dark:bg-white/[.02]' %} block border mb-3 rounded-md shadow-sm lg:table-row lg:border-none lg:mb-0 lg:shadow-none dark:border-gray-800">
20
- <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:block before:capitalize before:content-[attr(data-label)] before:mr-auto before:text-gray-500 dark:before:text-gray-400 lg:before:hidden lg:py-3 lg:table-cell dark:border-gray-800">
20
+ <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:block before:capitalize before:content-[attr(data-label)] before:mr-auto before:text-gray-500 dark:before:text-gray-300 lg:before:hidden lg:py-3 lg:table-cell dark:border-gray-800">
21
21
  {% if row.import_type == 'new' %}
22
22
  {% trans "New" %}
23
23
  {% elif row.import_type == 'skip' %}
@@ -30,7 +30,7 @@
30
30
  </td>
31
31
 
32
32
  {% for field in row.diff %}
33
- <td data-label="{{ result.diff_headers|index:forloop.counter0 }}" class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:block before:capitalize before:content-[attr(data-label)] before:mr-auto before:text-gray-500 dark:before:text-gray-400 lg:before:hidden lg:py-3 lg:table-cell dark:border-gray-800">
33
+ <td data-label="{{ result.diff_headers|index:forloop.counter0 }}" class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:block before:capitalize before:content-[attr(data-label)] before:mr-auto before:text-gray-500 dark:before:text-gray-300 lg:before:hidden lg:py-3 lg:table-cell dark:border-gray-800">
34
34
  {{ field }}
35
35
  </td>
36
36
  {% endfor %}
@@ -15,7 +15,7 @@
15
15
  </div>
16
16
  </div>
17
17
 
18
- <table class="border-gray-200 border-spacing-none border-separate text-gray-700 w-full dark:text-gray-400 lg:border lg:rounded-md lg:shadow-sm lg:dark:border-gray-800">
18
+ <table class="border-gray-200 border-spacing-none border-separate text-gray-700 w-full dark:text-gray-300 lg:border lg:rounded-md lg:shadow-sm lg:dark:border-gray-800">
19
19
  <thead class="hidden lg:table-header-group">
20
20
  <tr>
21
21
  <th class="align-middle capitalize font-medium px-3 py-2 text-left text-gray-400 text-sm">
@@ -37,11 +37,11 @@
37
37
  <tbody>
38
38
  {% for row in result.invalid_rows %}
39
39
  <tr class="{% cycle '' 'bg-gray-50 dark:bg-white/[.02]' %} block border mb-3 rounded-md shadow-sm lg:table-row lg:border-none lg:mb-0 lg:shadow-none dark:border-gray-800">
40
- <td data-label="{% trans "Row" %}" class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:block before:capitalize before:content-[attr(data-label)] before:mr-auto before:text-gray-500 dark:before:text-gray-400 lg:before:hidden lg:py-3 lg:table-cell dark:border-gray-800">
40
+ <td data-label="{% trans "Row" %}" class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:block before:capitalize before:content-[attr(data-label)] before:mr-auto before:text-gray-500 dark:before:text-gray-300 lg:before:hidden lg:py-3 lg:table-cell dark:border-gray-800">
41
41
  {{ row.number }}
42
42
  </td>
43
43
 
44
- <td data-label="{% trans "Errors" %}" class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:block before:capitalize before:content-[attr(data-label)] before:mr-auto before:text-gray-500 dark:before:text-gray-400 lg:before:hidden lg:py-3 lg:table-cell dark:border-gray-800">
44
+ <td data-label="{% trans "Errors" %}" class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:block before:capitalize before:content-[attr(data-label)] before:mr-auto before:text-gray-500 dark:before:text-gray-300 lg:before:hidden lg:py-3 lg:table-cell dark:border-gray-800">
45
45
  <div>
46
46
  <span class="bg-red-600 font-semibold ml-2 px-1 rounded-sm text-xs text-white">{{ row.error_count }}</span>
47
47
  </div>
@@ -77,7 +77,7 @@
77
77
  </td>
78
78
 
79
79
  {% for field in row.values %}
80
- <td data-label="{{ result.diff_headers|index:forloop.counter0 }}" class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:block before:capitalize before:content-[attr(data-label)] before:mr-auto before:text-gray-500 dark:before:text-gray-400 lg:before:hidden lg:py-3 lg:table-cell dark:border-gray-800">
80
+ <td data-label="{{ result.diff_headers|index:forloop.counter0 }}" class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:block before:capitalize before:content-[attr(data-label)] before:mr-auto before:text-gray-500 dark:before:text-gray-300 lg:before:hidden lg:py-3 lg:table-cell dark:border-gray-800">
81
81
  {{ field }}
82
82
  </td>
83
83
  {% endfor %}
@@ -12,9 +12,8 @@ class NonrelatedInlineModelFormSet(BaseModelFormSet):
12
12
  **kwargs: Any,
13
13
  ) -> None:
14
14
  self.instance = instance
15
- self.queryset = self.provided_queryset
16
-
17
15
  super().__init__(**kwargs)
16
+ self.queryset = self.provided_queryset
18
17
 
19
18
  @classmethod
20
19
  def get_default_prefix(cls: BaseModelFormSet) -> str:
@@ -3,7 +3,7 @@
3
3
  {% load admin_urls %}
4
4
  {% load getattribute from getattributes %}
5
5
 
6
- <table id="change-history" class="border-gray-200 border-spacing-none border-separate mb-6 text-gray-700 w-full dark:text-gray-400 lg:border lg:rounded-md lg:shadow-sm lg:dark:border-gray-800">
6
+ <table id="change-history" class="border-gray-200 border-spacing-none border-separate mb-6 text-gray-700 w-full dark:text-gray-300 lg:border lg:rounded-md lg:shadow-sm lg:dark:border-gray-800">
7
7
  <thead class="hidden lg:table-header-group">
8
8
  <tr>
9
9
  <th class="align-middle font-medium px-3 py-2 text-left text-gray-400 text-sm">
@@ -41,27 +41,27 @@
41
41
  <tbody>
42
42
  {% for record in historical_records %}
43
43
  <tr class="block border mb-3 rounded-md shadow-sm lg:table-row lg:border-none lg:mb-0 lg:shadow-none dark:border-gray-800">
44
- <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-400 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans 'Object' %}">
44
+ <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-300 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans 'Object' %}">
45
45
  <a href="{% url opts|admin_urlname:'simple_history' object.pk record.pk %}">
46
46
  {{ record.history_object }}
47
47
  </a>
48
48
  </td>
49
49
 
50
50
  {% for column in history_list_display %}
51
- <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-400 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans column %}">
51
+ <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-300 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans column %}">
52
52
  {{ record|getattribute:column }}
53
53
  </th>
54
54
  {% endfor %}
55
55
 
56
- <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-400 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans 'Date/time' %}">
56
+ <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-300 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans 'Date/time' %}">
57
57
  {{ record.history_date }}
58
58
  </td>
59
59
 
60
- <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-400 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans 'Comment' %}">
60
+ <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-300 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans 'Comment' %}">
61
61
  {{ record.get_history_type_display }}
62
62
  </td>
63
63
 
64
- <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-400 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans 'Changed by' %}">
64
+ <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-300 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans 'Changed by' %}">
65
65
  {% if record.history_user %}
66
66
  {% url admin_user_view record.history_user_id as admin_user_url %}
67
67
 
@@ -75,11 +75,11 @@
75
75
  {% endif %}
76
76
  </td>
77
77
 
78
- <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-400 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans 'Change reason' %}">
78
+ <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-300 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans 'Change reason' %}">
79
79
  {{ record.history_change_reason }}
80
80
  </td>
81
81
 
82
- <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-400 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans 'Changes' %}">
82
+ <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left text-sm before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto before:text-gray-500 first:border-t-0 dark:before:text-gray-300 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{% trans 'Changes' %}">
83
83
  {% block history_delta_changes %}
84
84
  {% if record.history_delta_changes %}
85
85
  <ul>
@@ -97,7 +97,7 @@
97
97
  {% endif %}
98
98
 
99
99
  {% if change.old and change.new %}
100
- <span class="align-text-top material-symbols-outlined md-18 text-gray-300 group-hover:text-gray-400 dark:text-gray-600">arrow_right_alt</span>
100
+ <span class="align-text-top material-symbols-outlined md-18 text-gray-300 group-hover:text-gray-300 dark:text-gray-600">arrow_right_alt</span>
101
101
  {% endif %}
102
102
 
103
103
  {% if change.new %}
@@ -16,7 +16,7 @@
16
16
  </button>
17
17
  {% endif %}
18
18
 
19
- <a href="{{ history_url }}" class="border font-medium mr-auto px-3 py-2 rounded-md text-sm text-gray-500 text-center transition-all w-full hover:bg-gray-50 lg:block lg:w-auto dark:border-gray-700 dark:text-gray-400 dark:hover:text-gray-200 dark:hover:bg-gray-900">
19
+ <a href="{{ history_url }}" class="border font-medium mr-auto px-3 py-2 rounded-md text-sm text-gray-500 text-center transition-all w-full hover:bg-gray-50 lg:block lg:w-auto dark:border-gray-700 dark:text-gray-300 dark:hover:text-gray-200 dark:hover:bg-gray-900">
20
20
  {% trans 'Close' %}
21
21
  </a>
22
22
  </div>
unfold/dataclasses.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass
2
- from typing import Dict, Optional
2
+ from typing import Callable, Dict, Optional, Union
3
3
 
4
4
  from .typing import ActionFunction
5
5
 
@@ -11,3 +11,12 @@ class UnfoldAction:
11
11
  description: str
12
12
  path: str
13
13
  attrs: Optional[Dict] = None
14
+ object_id: Optional[Union[int, str]] = None
15
+
16
+
17
+ @dataclass
18
+ class Favicon:
19
+ href: Union[str, Callable]
20
+ rel: Optional[str] = None
21
+ type: Optional[str] = None
22
+ sizes: Optional[str] = None
unfold/fields.py CHANGED
@@ -95,7 +95,7 @@ class UnfoldAdminReadonlyField(helpers.AdminReadonlyField):
95
95
  current_app=self.model_admin.admin_site.name,
96
96
  )
97
97
  return format_html(
98
- '<a href="{}" class="text-primary-600 underline whitespace-nowrap">{}</a>',
98
+ '<a href="{}" class="text-primary-600 underline dark:text-primary-500">{}</a>',
99
99
  url,
100
100
  remote_obj,
101
101
  )
@@ -148,7 +148,7 @@ class UnfoldAdminReadonlyField(helpers.AdminReadonlyField):
148
148
  return conditional_escape(result_repr)
149
149
  elif isinstance(f, models.URLField):
150
150
  return format_html(
151
- '<a href="{}" class="text-primary-600 underline whitespace-nowrap">{}</a>',
151
+ '<a href="{}" class="text-primary-600 underline whitespace-nowrap dark:text-primary-500">{}</a>',
152
152
  value,
153
153
  value,
154
154
  )
unfold/forms.py CHANGED
@@ -17,7 +17,13 @@ from django.http import HttpRequest
17
17
  from django.utils.safestring import mark_safe
18
18
  from django.utils.translation import gettext_lazy as _
19
19
 
20
- from .widgets import BASE_INPUT_CLASSES, INPUT_CLASSES, SELECT_CLASSES
20
+ from .widgets import (
21
+ BASE_INPUT_CLASSES,
22
+ INPUT_CLASSES,
23
+ SELECT_CLASSES,
24
+ UnfoldAdminPasswordInput,
25
+ UnfoldAdminRadioSelectWidget,
26
+ )
21
27
 
22
28
 
23
29
  class UnfoldReadOnlyPasswordHashWidget(ReadOnlyPasswordHashWidget):
@@ -65,8 +71,17 @@ class UserCreationForm(BaseUserCreationForm):
65
71
  ) -> None:
66
72
  super().__init__(request, *args, **kwargs)
67
73
 
68
- self.fields["password1"].widget.attrs["class"] = " ".join(INPUT_CLASSES)
69
- self.fields["password2"].widget.attrs["class"] = " ".join(INPUT_CLASSES)
74
+ self.fields["password1"].widget = UnfoldAdminPasswordInput(
75
+ attrs={"autocomplete": "new-password"}
76
+ )
77
+ self.fields["password2"].widget = UnfoldAdminPasswordInput(
78
+ attrs={"autocomplete": "new-password"}
79
+ )
80
+
81
+ if "usable_password" in self.fields:
82
+ self.fields["usable_password"].widget = UnfoldAdminRadioSelectWidget(
83
+ choices=self.fields["usable_password"].choices,
84
+ )
70
85
 
71
86
 
72
87
  class UserChangeForm(BaseUserChangeForm):
unfold/settings.py CHANGED
@@ -7,6 +7,7 @@ CONFIG_DEFAULTS = {
7
7
  "SITE_ICON": None,
8
8
  "SITE_SYMBOL": None,
9
9
  "SITE_LOGO": None,
10
+ "SITE_FAVICONS": [],
10
11
  "SHOW_HISTORY": True,
11
12
  "SHOW_VIEW_ON_SITE": True,
12
13
  "COLORS": {
unfold/sites.py CHANGED
@@ -10,6 +10,7 @@ from django.urls import URLPattern, path, reverse, reverse_lazy
10
10
  from django.utils.functional import lazy
11
11
  from django.utils.module_loading import import_string
12
12
 
13
+ from .dataclasses import Favicon
13
14
  from .settings import get_config
14
15
  from .utils import hex_to_rgb
15
16
  from .widgets import CHECKBOX_CLASSES, INPUT_CLASSES
@@ -66,6 +67,9 @@ class UnfoldAdminSite(AdminSite):
66
67
  "site_symbol": self._get_value(
67
68
  get_config(self.settings_name)["SITE_SYMBOL"], request
68
69
  ),
70
+ "site_favicons": self._process_favicons(
71
+ request, get_config(self.settings_name)["SITE_FAVICONS"]
72
+ ),
69
73
  "show_history": get_config(self.settings_name)["SHOW_HISTORY"],
70
74
  "show_view_on_site": get_config(self.settings_name)[
71
75
  "SHOW_VIEW_ON_SITE"
@@ -220,23 +224,14 @@ class UnfoldAdminSite(AdminSite):
220
224
  navigation = get_config(self.settings_name)["SIDEBAR"].get("navigation", [])
221
225
  results = []
222
226
 
223
- def _get_is_active(link: str) -> bool:
224
- if not isinstance(link, str):
225
- link = str(link)
226
-
227
- if link in request.path and link != reverse_lazy(f"{self.name}:index"):
228
- return True
229
- elif link == request.path == reverse_lazy(f"{self.name}:index"):
230
- return True
231
-
232
- return False
233
-
234
227
  for group in navigation:
235
228
  allowed_items = []
236
229
 
237
230
  for item in group["items"]:
238
231
  item["active"] = False
239
- item["active"] = _get_is_active(item["link"])
232
+ item["active"] = self._get_is_active(
233
+ request, item.get("link_callback") or item["link"]
234
+ )
240
235
 
241
236
  for tab in get_config(self.settings_name)["TABS"]:
242
237
  has_primary_link = False
@@ -247,7 +242,9 @@ class UnfoldAdminSite(AdminSite):
247
242
  has_primary_link = True
248
243
  continue
249
244
 
250
- if _get_is_active(tab_item["link"]):
245
+ if self._get_is_active(
246
+ request, tab_item.get("link_callback") or tab_item["link"]
247
+ ):
251
248
  has_tab_link_active = True
252
249
  break
253
250
 
@@ -255,7 +252,7 @@ class UnfoldAdminSite(AdminSite):
255
252
  item["active"] = True
256
253
 
257
254
  if isinstance(item["link"], Callable):
258
- item["link"] = item["link"](request)
255
+ item["link_callback"] = lazy(item["link"])(request)
259
256
 
260
257
  # Permission callback
261
258
  item["has_permission"] = self._call_permission_callback(
@@ -290,8 +287,11 @@ class UnfoldAdminSite(AdminSite):
290
287
  )
291
288
 
292
289
  if isinstance(item["link"], Callable):
293
- item["link"] = item["link"](request)
290
+ item["link_callback"] = lazy(item["link"])(request)
294
291
 
292
+ item["active"] = self._get_is_active(
293
+ request, item.get("link_callback") or item["link"]
294
+ )
295
295
  allowed_items.append(item)
296
296
 
297
297
  tab["items"] = allowed_items
@@ -354,6 +354,19 @@ class UnfoldAdminSite(AdminSite):
354
354
 
355
355
  return target
356
356
 
357
+ def _process_favicons(
358
+ self, request: HttpRequest, favicons: List[Dict]
359
+ ) -> List[Favicon]:
360
+ return [
361
+ Favicon(
362
+ href=self._get_value(item["href"], request),
363
+ rel=item.get("rel"),
364
+ sizes=item.get("sizes"),
365
+ type=item.get("type"),
366
+ )
367
+ for item in favicons
368
+ ]
369
+
357
370
  def _process_colors(
358
371
  self, colors: Dict[str, Dict[str, str]]
359
372
  ) -> Dict[str, Dict[str, str]]:
@@ -365,3 +378,14 @@ class UnfoldAdminSite(AdminSite):
365
378
  colors[name][weight] = " ".join(str(item) for item in hex_to_rgb(value))
366
379
 
367
380
  return colors
381
+
382
+ def _get_is_active(self, request: HttpRequest, link: str) -> bool:
383
+ if not isinstance(link, str):
384
+ link = str(link)
385
+
386
+ if link in request.path and link != reverse_lazy(f"{self.name}:index"):
387
+ return True
388
+ elif link == request.path == reverse_lazy(f"{self.name}:index"):
389
+ return True
390
+
391
+ return False