django-unfold 0.63.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 (55) hide show
  1. {django_unfold-0.63.0.dist-info → django_unfold-0.64.0.dist-info}/METADATA +1 -1
  2. {django_unfold-0.63.0.dist-info → django_unfold-0.64.0.dist-info}/RECORD +55 -51
  3. unfold/admin.py +1 -1
  4. unfold/contrib/constance/templates/admin/constance/change_list.html +0 -18
  5. unfold/contrib/filters/templates/unfold/filters/filters_numeric_slider.html +1 -1
  6. unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage.html +0 -24
  7. unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_group.html +0 -26
  8. unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_user.html +0 -26
  9. unfold/contrib/import_export/templates/admin/import_export/export.html +0 -23
  10. unfold/contrib/import_export/templates/admin/import_export/import.html +0 -23
  11. unfold/contrib/simple_history/templates/simple_history/object_history_form.html +1 -33
  12. unfold/contrib/simple_history/templates/simple_history/object_history_list.html +0 -1
  13. unfold/dataclasses.py +8 -0
  14. unfold/settings.py +28 -22
  15. unfold/sites.py +120 -43
  16. unfold/static/unfold/css/styles.css +1 -1
  17. unfold/static/unfold/js/app.js +147 -9
  18. unfold/styles.css +32 -32
  19. unfold/templates/admin/app_index.html +0 -18
  20. unfold/templates/admin/auth/user/change_password.html +1 -26
  21. unfold/templates/admin/base.html +0 -16
  22. unfold/templates/admin/change_form.html +0 -33
  23. unfold/templates/admin/change_list.html +0 -19
  24. unfold/templates/admin/change_list_results.html +2 -2
  25. unfold/templates/admin/delete_confirmation.html +0 -21
  26. unfold/templates/admin/delete_selected_confirmation.html +0 -22
  27. unfold/templates/admin/index.html +0 -2
  28. unfold/templates/admin/object_history.html +0 -24
  29. unfold/templates/registration/password_change_done.html +0 -17
  30. unfold/templates/registration/password_change_form.html +0 -17
  31. unfold/templates/unfold/components/navigation.html +2 -2
  32. unfold/templates/unfold/components/table.html +55 -9
  33. unfold/templates/unfold/helpers/boolean.html +6 -6
  34. unfold/templates/unfold/helpers/command.html +53 -0
  35. unfold/templates/unfold/helpers/command_history.html +54 -0
  36. unfold/templates/unfold/helpers/command_results.html +50 -0
  37. unfold/templates/unfold/helpers/header_back_button.html +10 -2
  38. unfold/templates/unfold/helpers/header_title.html +11 -0
  39. unfold/templates/unfold/helpers/label.html +1 -1
  40. unfold/templates/unfold/helpers/navigation_header.html +2 -2
  41. unfold/templates/unfold/helpers/search.html +40 -22
  42. unfold/templates/unfold/helpers/search_results.html +2 -2
  43. unfold/templates/unfold/helpers/shortcut.html +1 -1
  44. unfold/templates/unfold/helpers/site_dropdown.html +1 -1
  45. unfold/templates/unfold/helpers/tab_items.html +15 -33
  46. unfold/templates/unfold/helpers/tab_list.html +1 -1
  47. unfold/templates/unfold/helpers/welcomemsg.html +6 -18
  48. unfold/templates/unfold/layouts/base_simple.html +0 -6
  49. unfold/templates/unfold/layouts/skeleton.html +4 -1
  50. unfold/templates/unfold/layouts/unauthenticated.html +0 -2
  51. unfold/templatetags/unfold.py +141 -0
  52. unfold/utils.py +25 -10
  53. unfold/views.py +4 -1
  54. {django_unfold-0.63.0.dist-info → django_unfold-0.64.0.dist-info}/LICENSE.md +0 -0
  55. {django_unfold-0.63.0.dist-info → django_unfold-0.64.0.dist-info}/WHEEL +0 -0
@@ -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
  )