django-unfold 0.62.0__py3-none-any.whl → 0.64.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 (71) hide show
  1. {django_unfold-0.62.0.dist-info → django_unfold-0.64.0.dist-info}/METADATA +15 -2
  2. {django_unfold-0.62.0.dist-info → django_unfold-0.64.0.dist-info}/RECORD +71 -62
  3. unfold/admin.py +1 -1
  4. unfold/contrib/constance/__init__.py +0 -0
  5. unfold/contrib/constance/apps.py +6 -0
  6. unfold/contrib/constance/settings.py +32 -0
  7. unfold/contrib/constance/templates/admin/constance/change_list.html +52 -0
  8. unfold/contrib/constance/templates/admin/constance/includes/results_list.html +71 -0
  9. unfold/contrib/filters/templates/unfold/filters/filters_numeric_slider.html +1 -1
  10. unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage.html +0 -24
  11. unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_group.html +0 -26
  12. unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_user.html +0 -26
  13. unfold/contrib/import_export/templates/admin/import_export/export.html +0 -23
  14. unfold/contrib/import_export/templates/admin/import_export/import.html +0 -23
  15. unfold/contrib/simple_history/templates/simple_history/object_history_form.html +1 -33
  16. unfold/contrib/simple_history/templates/simple_history/object_history_list.html +0 -1
  17. unfold/dataclasses.py +8 -0
  18. unfold/fields.py +1 -1
  19. unfold/settings.py +28 -22
  20. unfold/sites.py +120 -43
  21. unfold/static/admin/js/inlines.js +42 -17
  22. unfold/static/unfold/css/styles.css +2 -2
  23. unfold/static/unfold/js/app.js +147 -9
  24. unfold/static/unfold/js/select2.init.js +4 -0
  25. unfold/styles.css +33 -33
  26. unfold/templates/admin/app_index.html +0 -18
  27. unfold/templates/admin/auth/user/change_password.html +1 -26
  28. unfold/templates/admin/base.html +0 -16
  29. unfold/templates/admin/change_form.html +7 -34
  30. unfold/templates/admin/change_list.html +0 -19
  31. unfold/templates/admin/change_list_results.html +2 -2
  32. unfold/templates/admin/delete_confirmation.html +0 -21
  33. unfold/templates/admin/delete_selected_confirmation.html +0 -22
  34. unfold/templates/admin/includes/fieldset.html +1 -1
  35. unfold/templates/admin/index.html +0 -2
  36. unfold/templates/admin/login.html +1 -1
  37. unfold/templates/admin/object_history.html +0 -24
  38. unfold/templates/registration/logged_out.html +12 -7
  39. unfold/templates/registration/password_change_done.html +0 -17
  40. unfold/templates/registration/password_change_form.html +0 -17
  41. unfold/templates/unfold/components/navigation.html +2 -2
  42. unfold/templates/unfold/components/table.html +55 -9
  43. unfold/templates/unfold/helpers/boolean.html +6 -6
  44. unfold/templates/unfold/helpers/command.html +53 -0
  45. unfold/templates/unfold/helpers/command_history.html +54 -0
  46. unfold/templates/unfold/helpers/command_results.html +50 -0
  47. unfold/templates/unfold/helpers/header_back_button.html +10 -2
  48. unfold/templates/unfold/helpers/header_title.html +11 -0
  49. unfold/templates/unfold/helpers/label.html +1 -1
  50. unfold/templates/unfold/helpers/navigation_header.html +2 -2
  51. unfold/templates/unfold/helpers/pagination_inline.html +28 -20
  52. unfold/templates/unfold/helpers/search.html +40 -22
  53. unfold/templates/unfold/helpers/search_results.html +2 -2
  54. unfold/templates/unfold/helpers/shortcut.html +1 -1
  55. unfold/templates/unfold/helpers/site_dropdown.html +1 -1
  56. unfold/templates/unfold/helpers/submit.html +1 -1
  57. unfold/templates/unfold/helpers/tab_items.html +15 -33
  58. unfold/templates/unfold/helpers/tab_list.html +1 -1
  59. unfold/templates/unfold/helpers/unauthenticated_header.html +2 -2
  60. unfold/templates/unfold/helpers/unauthenticated_title.html +1 -1
  61. unfold/templates/unfold/helpers/welcomemsg.html +6 -18
  62. unfold/templates/unfold/layouts/base_simple.html +0 -6
  63. unfold/templates/unfold/layouts/skeleton.html +4 -1
  64. unfold/templates/unfold/layouts/unauthenticated.html +0 -2
  65. unfold/templates/unfold/widgets/related_widget_wrapper.html +4 -4
  66. unfold/templatetags/unfold.py +141 -0
  67. unfold/utils.py +25 -10
  68. unfold/views.py +4 -1
  69. unfold/widgets.py +6 -1
  70. {django_unfold-0.62.0.dist-info → django_unfold-0.64.0.dist-info}/LICENSE.md +0 -0
  71. {django_unfold-0.62.0.dist-info → django_unfold-0.64.0.dist-info}/WHEEL +0 -0
@@ -2,8 +2,8 @@
2
2
 
3
3
  <div class="absolute flex flex-row items-center justify-between left-0 m-4 right-0 top-0">
4
4
  {% if site_url %}
5
- <a href="{{ site_url }}" class="flex font-medium items-center text-sm text-primary-600 dark:text-primary-500">
6
- <span class="material-symbols-outlined mr-2">arrow_back</span> {% trans 'Return to site' %}
5
+ <a href="{{ site_url }}" class="flex font-medium gap-2 group items-center text-sm text-primary-600 dark:text-primary-500">
6
+ <span class="material-symbols-outlined left-0 relative text-sm transition-all group-hover:-left-1">arrow_back</span> {% trans 'Return to site' %}
7
7
  </a>
8
8
  {% endif %}
9
9
 
@@ -6,6 +6,6 @@
6
6
  <span class="block text-font-important-light dark:text-font-important-dark">{{ subtitle }} </span>
7
7
  {% endif %}
8
8
 
9
- <span class="block text-primary-600 text-xl dark:text-primary-500">{{ title }}</span>
9
+ <span class="block font-semibold text-primary-600 tracking-tight text-xl dark:text-primary-500">{{ title }}</span>
10
10
  </h1>
11
11
  </div>
@@ -1,4 +1,4 @@
1
- {% load unfold i18n %}
1
+ {% load unfold i18n admin_urls unfold %}
2
2
 
3
3
  <div class="flex flex-row grow font-semibold items-center min-w-0 mr-3">
4
4
  {% if is_nav_sidebar_enabled %}
@@ -17,25 +17,13 @@
17
17
 
18
18
  {% include "unfold/helpers/header_back_button.html" %}
19
19
 
20
- <h1 class="overflow-hidden text-ellipsis text-sm whitespace-nowrap xl:text-base text-font-important-light dark:text-font-important-dark">
21
- {% if content_title %}
22
- <span class="tracking-tight">
23
- {{ pretitle }}
20
+ <h1 class="overflow-hidden leading-5 text-ellipsis text-font-important-light whitespace-nowrap xl:text-base dark:text-font-important-dark">
21
+ {% header_title %}
24
22
 
25
- {{ content_title }}
23
+ {% if cl and cl.full_result_count != cl.result_count and cl.paginator|class_name != "InfinitePaginator" %}
24
+ <span class="font-medium ml-2 text-font-subtle-light text-sm dark:text-font-subtle-dark">
25
+ {% blocktranslate count counter=cl.result_count %}{{ counter }} result{% plural %}{{ counter }} results{% endblocktranslate %} (<a href="?{% if cl.is_popup %}_popup=1{% endif %}">{% if cl.show_full_result_count %}{% blocktranslate with full_result_count=cl.full_result_count %}{{ full_result_count }} total{% endblocktranslate %}{% else %}{% translate "Show all" %}{% endif %}</a>)
26
26
  </span>
27
-
28
- {% if cl and cl.full_result_count != cl.result_count and cl.paginator|class_name != "InfinitePaginator" %}
29
- <span class="font-medium ml-2 text-font-subtle-light text-sm dark:text-font-subtle-dark">
30
- {% blocktranslate count counter=cl.result_count %}{{ counter }} result{% plural %}{{ counter }} results{% endblocktranslate %} (<a href="?{% if cl.is_popup %}_popup=1{% endif %}">{% if cl.show_full_result_count %}{% blocktranslate with full_result_count=cl.full_result_count %}{{ full_result_count }} total{% endblocktranslate %}{% else %}{% translate "Show all" %}{% endif %}</a>)
31
- </span>
32
- {% endif %}
33
-
34
- {% if content_subtitle %}
35
- {{ content_subtitle }}
36
- {% endif %}
37
- {% else %}
38
- {% translate 'Welcome,' %} <strong>{% firstof user.get_short_name user.get_username %}</strong>.
39
27
  {% endif %}
40
28
  </h1>
41
29
  </div>
@@ -15,12 +15,6 @@
15
15
  {% include "unfold/helpers/header.html" %}
16
16
  {% endblock %}
17
17
 
18
- {% if not is_popup %}
19
- {% spaceless %}
20
- {% block breadcrumbs %}{% endblock %}
21
- {% endspaceless %}
22
- {% endif %}
23
-
24
18
  <div class="px-4">
25
19
  <div id="content" class="container mx-auto {% block coltype %}colM{% endblock %}">
26
20
  {% block content %}
@@ -11,6 +11,7 @@
11
11
  {% capture as nav_global_side silent %}{% block nav-global-side %}{% endblock %}{% endcapture %}
12
12
  {% capture as actions_items silent %}{% block actions-items %}{% endblock %}{% endcapture %}
13
13
  {% capture as extra_userlinks silent %}{% block extra_userlinks %}{% endblock %}{% endcapture %}
14
+ {% capture as show_back_button silent %}{% block show_back_button %}{{ show_back_button }}{% endblock %}{% endcapture %}
14
15
 
15
16
  <!DOCTYPE html>
16
17
  <html lang="{{ LANGUAGE_CODE|default:"en-us" }}" dir="{{ LANGUAGE_BIDI|yesno:"rtl,ltr,auto" }}" {% if theme %}class="{{ theme }}"{% endif %} x-data="{ adminTheme: {% if theme %}'{{ theme }}'{% else %}$persist('auto').as('adminTheme'){% endif %} }" x-bind:class="{'dark': adminTheme === 'dark' || (adminTheme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches)}" x-cloak>
@@ -78,7 +79,9 @@
78
79
  {% block base %}{% endblock %}
79
80
 
80
81
  <div id="modal-overlay" class="backdrop-blur-xs bg-base-900/80 bottom-0 fixed hidden left-0 mr-1 right-0 top-0 z-50"></div>
82
+
83
+ {% include "unfold/helpers/command.html" %}
84
+
81
85
  <script src="{% static 'unfold/js/simplebar/simplebar.js' %}"></script>
82
86
  </body>
83
-
84
87
  </html>
@@ -13,8 +13,6 @@
13
13
 
14
14
  {% block content_title %}{% endblock %}
15
15
 
16
- {% block breadcrumbs %}{% endblock %}
17
-
18
16
  {% block title %}
19
17
  {{ title }} | {{ site_title }}
20
18
  {% endblock %}
@@ -9,7 +9,7 @@
9
9
  {% if not is_hidden %}
10
10
  {% if can_add_related or can_change_related or can_delete_related or can_view_related%}
11
11
  {% if can_change_related %}
12
- <a class="related-widget-wrapper-link change-related bg-white border border-base-200 cursor-pointer flex items-center h-[38px] justify-center ml-2 rounded-default shadow-xs shrink-0 text-base-400 text-sm w-[38px] hover:text-base-700 dark:bg-base-900 dark:border-base-700 dark:text-base-500 dark:hover:text-base-200"
12
+ <a class="related-widget-wrapper-link change-related bg-white border border-base-200 cursor-pointer flex items-center h-[38px] justify-center ml-2 rounded-default shadow-xs shrink-0 text-base-400 text-sm w-[38px] hover:text-base-700 dark:bg-base-900 dark:border-base-700 dark:text-base-500 dark:hover:text-base-200 focus:outline-2 focus:-outline-offset-2 focus:outline-primary-600"
13
13
  id="change_id_{{ name }}"
14
14
  data-popup="yes"
15
15
  data-href-template="{{ change_related_template_url }}?{{ url_params }}"
@@ -19,7 +19,7 @@
19
19
  {% endif %}
20
20
 
21
21
  {% if can_add_related %}
22
- <a class="related-widget-wrapper-link add-related bg-white border border-base-200 cursor-pointer flex items-center h-[38px] justify-center ml-2 rounded-default shadow-xs shrink-0 text-base-400 text-sm w-[38px] hover:text-base-700 dark:bg-base-900 dark:border-base-700 dark:text-base-500 dark:hover:text-base-200"
22
+ <a class="related-widget-wrapper-link add-related bg-white border border-base-200 cursor-pointer flex items-center h-[38px] justify-center ml-2 rounded-default shadow-xs shrink-0 text-base-400 text-sm w-[38px] hover:text-base-700 dark:bg-base-900 dark:border-base-700 dark:text-base-500 dark:hover:text-base-200 focus:outline-2 focus:-outline-offset-2 focus:outline-primary-600"
23
23
  data-popup="yes"
24
24
  id="add_id_{{ name }}"
25
25
  href="{{ add_related_url }}?{{ url_params }}"
@@ -29,7 +29,7 @@
29
29
  {% endif %}
30
30
 
31
31
  {% if can_view_related %}
32
- <a class="related-widget-wrapper-link view-related bg-white border border-base-200 cursor-pointer flex items-center h-[38px] justify-center ml-2 rounded-default shadow-xs shrink-0 text-base-400 text-sm w-[38px] hover:text-base-700 dark:bg-base-900 dark:border-base-700 dark:text-base-500 dark:hover:text-base-200"
32
+ <a class="related-widget-wrapper-link view-related bg-white border border-base-200 cursor-pointer flex items-center h-[38px] justify-center ml-2 rounded-default shadow-xs shrink-0 text-base-400 text-sm w-[38px] hover:text-base-700 dark:bg-base-900 dark:border-base-700 dark:text-base-500 dark:hover:text-base-200 focus:outline-2 focus:-outline-offset-2 focus:outline-primary-600"
33
33
  id="view_id_{{ name }}"
34
34
  data-href-template="{{ change_related_template_url }}?{{ view_related_url_params }}"
35
35
  title="{% blocktranslate %}View selected {{ model }}{% endblocktranslate %}">
@@ -38,7 +38,7 @@
38
38
  {% endif %}
39
39
 
40
40
  {% if can_delete_related %}
41
- <a class="related-widget-wrapper-link delete-related bg-white border border-base-200 cursor-pointer flex items-center h-[38px] justify-center ml-2 rounded-default shadow-xs shrink-0 text-red-600 text-sm w-[38px] dark:bg-base-900 dark:border-base-700 dark:text-red-500"
41
+ <a class="related-widget-wrapper-link delete-related bg-white border border-base-200 cursor-pointer flex items-center h-[38px] justify-center ml-2 rounded-default shadow-xs shrink-0 text-red-600 text-sm w-[38px] dark:bg-base-900 dark:border-base-700 dark:text-red-500 focus:outline-2 focus:-outline-offset-2 focus:outline-red-500"
42
42
  id="delete_id_{{ name }}"
43
43
  data-href-template="{{ delete_related_template_url }}?{{ url_params }}"
44
44
  data-popup="yes"
@@ -14,7 +14,9 @@ from django.http import HttpRequest, QueryDict
14
14
  from django.template import Context, Library, Node, RequestContext, TemplateSyntaxError
15
15
  from django.template.base import NodeList, Parser, Token, token_kwargs
16
16
  from django.template.loader import render_to_string
17
+ from django.urls import reverse_lazy
17
18
  from django.utils.safestring import SafeText, mark_safe
19
+ from django.utils.translation import gettext_lazy as _
18
20
 
19
21
  from unfold.components import ComponentRegistry
20
22
  from unfold.dataclasses import UnfoldAction
@@ -603,3 +605,142 @@ def querystring_params(
603
605
  result[query_key] = query_value
604
606
 
605
607
  return result.urlencode()
608
+
609
+
610
+ @register.simple_tag(takes_context=True)
611
+ def header_title(context: RequestContext) -> str:
612
+ parts = []
613
+ opts = context.get("opts")
614
+
615
+ if opts:
616
+ parts.append(
617
+ {
618
+ "link": reverse_lazy("admin:app_list", args=[opts.app_label]),
619
+ "title": opts.app_config.verbose_name,
620
+ }
621
+ )
622
+
623
+ if (original := context.get("original")) and not isinstance(original, str):
624
+ parts.append(
625
+ {
626
+ "link": reverse_lazy(
627
+ f"admin:{original._meta.app_label}_{original._meta.model_name}_changelist"
628
+ ),
629
+ "title": original._meta.verbose_name_plural,
630
+ }
631
+ )
632
+
633
+ parts.append(
634
+ {
635
+ "link": reverse_lazy(
636
+ f"admin:{original._meta.app_label}_{original._meta.model_name}_change",
637
+ args=[original.pk],
638
+ ),
639
+ "title": original,
640
+ }
641
+ )
642
+ elif object := context.get("object"):
643
+ parts.append(
644
+ {
645
+ "link": reverse_lazy(
646
+ f"admin:{object._meta.app_label}_{object._meta.model_name}_changelist"
647
+ ),
648
+ "title": object._meta.verbose_name_plural,
649
+ }
650
+ )
651
+
652
+ parts.append(
653
+ {
654
+ "link": reverse_lazy(
655
+ f"admin:{object._meta.app_label}_{object._meta.model_name}_change",
656
+ args=[object.pk],
657
+ ),
658
+ "title": object,
659
+ }
660
+ )
661
+ else:
662
+ parts.append(
663
+ {
664
+ "link": reverse_lazy(
665
+ f"admin:{opts.app_label}_{opts.model_name}_changelist"
666
+ ),
667
+ "title": opts.verbose_name_plural,
668
+ }
669
+ )
670
+ elif object := context.get("object"):
671
+ parts.append(
672
+ {
673
+ "link": reverse_lazy("admin:app_list", args=[object._meta.app_label]),
674
+ "title": object._meta.app_label,
675
+ }
676
+ )
677
+
678
+ parts.append(
679
+ {
680
+ "link": reverse_lazy(
681
+ f"admin:{object._meta.app_label}_{object._meta.model_name}_changelist",
682
+ ),
683
+ "title": object._meta.verbose_name_plural,
684
+ }
685
+ )
686
+
687
+ parts.append(
688
+ {
689
+ "link": reverse_lazy(
690
+ f"admin:{object._meta.app_label}_{object._meta.model_name}_change",
691
+ args=[object.pk],
692
+ ),
693
+ "title": object,
694
+ }
695
+ )
696
+ elif (model_admin := context.get("model_admin")) and hasattr(model_admin, "model"):
697
+ parts.append(
698
+ {
699
+ "link": reverse_lazy(
700
+ "admin:app_list", args=[model_admin.model._meta.app_label]
701
+ ),
702
+ "title": model_admin.model._meta.app_label,
703
+ }
704
+ )
705
+
706
+ parts.append(
707
+ {
708
+ "link": reverse_lazy(
709
+ f"admin:{model_admin.model._meta.app_label}_{model_admin.model._meta.model_name}_changelist",
710
+ ),
711
+ "title": model_admin.model._meta.verbose_name_plural,
712
+ }
713
+ )
714
+
715
+ if not opts and (content_title := context.get("content_title")):
716
+ parts.append(
717
+ {
718
+ "title": content_title,
719
+ }
720
+ )
721
+
722
+ if len(parts) == 0:
723
+ username = (
724
+ context.request.user.get_short_name() or context.request.user.get_username()
725
+ )
726
+ parts.append({"title": f"{_('Welcome')} {username}"})
727
+
728
+ return render_to_string(
729
+ "unfold/helpers/header_title.html",
730
+ request=context.request,
731
+ context={
732
+ "parts": parts,
733
+ },
734
+ )
735
+
736
+
737
+ @register.filter
738
+ def admin_object_app_url(object: Model, arg: str) -> str:
739
+ return f"admin:{object._meta.app_label}_{object._meta.model_name}_{arg}"
740
+
741
+
742
+ @register.filter
743
+ def has_nested_tables(table: dict) -> bool:
744
+ return any(
745
+ isinstance(row, dict) and "table" in row for row in table.get("rows", [])
746
+ )
unfold/utils.py CHANGED
@@ -140,16 +140,6 @@ def display_for_field(value: Any, field: Any, empty_value_display: str) -> str:
140
140
  return display_for_value(value, empty_value_display)
141
141
 
142
142
 
143
- def hex_to_rgb(hex_color: str) -> list[int]:
144
- hex_color = hex_color.lstrip("#")
145
-
146
- r = int(hex_color[0:2], 16)
147
- g = int(hex_color[2:4], 16)
148
- b = int(hex_color[4:6], 16)
149
-
150
- return (r, g, b)
151
-
152
-
153
143
  def prettify_json(data: Any, encoder: Any) -> Optional[str]:
154
144
  try:
155
145
  from pygments import highlight
@@ -189,3 +179,28 @@ def parse_datetime_str(value: str) -> Optional[datetime.datetime]:
189
179
  return datetime.datetime.strptime(value, format)
190
180
  except (ValueError, TypeError):
191
181
  continue
182
+
183
+
184
+ def hex_to_rgb(hex_color: str) -> list[int]:
185
+ hex_color = hex_color.lstrip("#")
186
+
187
+ r = int(hex_color[0:2], 16)
188
+ g = int(hex_color[2:4], 16)
189
+ b = int(hex_color[4:6], 16)
190
+
191
+ return (r, g, b)
192
+
193
+
194
+ def hex_to_values(value: str) -> str:
195
+ return ", ".join(str(item) for item in hex_to_rgb(value))
196
+
197
+
198
+ def convert_color(value: str) -> str:
199
+ if value[0] == "#":
200
+ return f"rgb({hex_to_values(value)})"
201
+ elif value.startswith("rgb") or value.startswith("oklch"):
202
+ return value
203
+ elif isinstance(value, str) and all(part.isdigit() for part in value.split()):
204
+ return f"rgb({', '.join(value.split(' '))})"
205
+
206
+ return value
unfold/views.py CHANGED
@@ -43,5 +43,8 @@ class UnfoldModelAdminViewMixin(PermissionRequiredMixin):
43
43
  return super().get_context_data(
44
44
  **kwargs,
45
45
  **self.model_admin.admin_site.each_context(self.request),
46
- **{"title": self.title},
46
+ **{
47
+ "title": self.title,
48
+ "model_admin": self.model_admin,
49
+ },
47
50
  )
unfold/widgets.py CHANGED
@@ -244,13 +244,17 @@ RADIO_CLASSES = [
244
244
  SWITCH_CLASSES = [
245
245
  "appearance-none",
246
246
  "bg-base-300",
247
+ "block",
247
248
  "cursor-pointer",
248
249
  "h-5",
249
250
  "relative",
250
251
  "rounded-full",
251
- "transition-all",
252
+ "transition-colors",
252
253
  "w-8",
253
254
  "min-w-8",
255
+ "focus:outline-2",
256
+ "focus:outline-offset-2",
257
+ "focus:outline-primary-600",
254
258
  "after:absolute",
255
259
  "after:bg-white",
256
260
  "after:content-['']",
@@ -258,6 +262,7 @@ SWITCH_CLASSES = [
258
262
  "after:h-3",
259
263
  "after:rounded-full",
260
264
  "after:shadow-xs",
265
+ "after:transition-all",
261
266
  "after:left-1",
262
267
  "after:top-1",
263
268
  "after:w-3",