django-smartbase-admin 0.2.91__py3-none-any.whl → 0.2.93__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 (41) hide show
  1. django_smartbase_admin/actions/admin_action_list.py +0 -6
  2. django_smartbase_admin/admin/admin_base.py +1 -0
  3. django_smartbase_admin/admin/widgets.py +70 -0
  4. django_smartbase_admin/engine/admin_base_view.py +4 -1
  5. django_smartbase_admin/engine/filter_widgets.py +182 -1
  6. django_smartbase_admin/locale/sk/LC_MESSAGES/django.mo +0 -0
  7. django_smartbase_admin/locale/sk/LC_MESSAGES/django.po +241 -28
  8. django_smartbase_admin/services/views.py +2 -2
  9. django_smartbase_admin/static/sb_admin/build/webpack.common.js +9 -8
  10. django_smartbase_admin/static/sb_admin/dist/main.js +1 -1
  11. django_smartbase_admin/static/sb_admin/dist/main_style.css +1 -1
  12. django_smartbase_admin/static/sb_admin/dist/table.js +1 -1
  13. django_smartbase_admin/static/sb_admin/dist/tree_widget.js +1 -0
  14. django_smartbase_admin/static/sb_admin/dist/tree_widget_style.css +1 -0
  15. django_smartbase_admin/static/sb_admin/dist/tree_widget_style.js +0 -0
  16. django_smartbase_admin/static/sb_admin/fancytree/jquery.fancytree-all-deps.min.js +2 -0
  17. django_smartbase_admin/static/sb_admin/src/css/_base.css +4 -0
  18. django_smartbase_admin/static/sb_admin/src/css/_inlines.css +3 -3
  19. django_smartbase_admin/static/sb_admin/src/css/_tabulator.css +6 -0
  20. django_smartbase_admin/static/sb_admin/src/css/components/_toggle.css +2 -1
  21. django_smartbase_admin/static/sb_admin/src/css/tree_widget.css +405 -0
  22. django_smartbase_admin/static/sb_admin/src/js/datepicker.js +1 -0
  23. django_smartbase_admin/static/sb_admin/src/js/table.js +18 -2
  24. django_smartbase_admin/static/sb_admin/src/js/table_modules/filter_module.js +3 -1
  25. django_smartbase_admin/static/sb_admin/src/js/table_modules/selection_module.js +20 -2
  26. django_smartbase_admin/static/sb_admin/src/js/table_modules/table_params_module.js +6 -0
  27. django_smartbase_admin/static/sb_admin/src/js/tree_widget.js +376 -0
  28. django_smartbase_admin/templates/sb_admin/actions/tree_list.html +63 -0
  29. django_smartbase_admin/templates/sb_admin/components/columns.html +1 -1
  30. django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/tree_select_filter.html +2 -0
  31. django_smartbase_admin/templates/sb_admin/filter_widgets/tree_select_filter.html +16 -0
  32. django_smartbase_admin/templates/sb_admin/sb_admin_base_no_sidebar.html +7 -5
  33. django_smartbase_admin/templates/sb_admin/sb_admin_js_trans.html +1 -0
  34. django_smartbase_admin/templates/sb_admin/widgets/filer_file.html +1 -1
  35. django_smartbase_admin/templates/sb_admin/widgets/tree_base.html +54 -0
  36. django_smartbase_admin/templates/sb_admin/widgets/tree_select.html +24 -0
  37. django_smartbase_admin/templates/sb_admin/widgets/tree_select_inline.html +12 -0
  38. {django_smartbase_admin-0.2.91.dist-info → django_smartbase_admin-0.2.93.dist-info}/METADATA +1 -1
  39. {django_smartbase_admin-0.2.91.dist-info → django_smartbase_admin-0.2.93.dist-info}/RECORD +41 -29
  40. {django_smartbase_admin-0.2.91.dist-info → django_smartbase_admin-0.2.93.dist-info}/LICENSE.md +0 -0
  41. {django_smartbase_admin-0.2.91.dist-info → django_smartbase_admin-0.2.93.dist-info}/WHEEL +0 -0
@@ -141,11 +141,6 @@ class SBAdminListAction(SBAdminAction):
141
141
  if field.field in values
142
142
  ]
143
143
 
144
- def is_jquery_required(self, request):
145
- return (
146
- self.view.get_filters_version(request) == FilterVersions.FILTERS_VERSION_2
147
- )
148
-
149
144
  def get_template_data(self):
150
145
  context_data = self.view.get_context_data(self.threadsafe_request)
151
146
  constants = {
@@ -194,7 +189,6 @@ class SBAdminListAction(SBAdminAction):
194
189
  "tabulator_definition": tabulator_definition,
195
190
  "id_column_name": id_column_name,
196
191
  "filters": self.get_filters(),
197
- "is_jquery_required": self.is_jquery_required(self.threadsafe_request),
198
192
  "advanced_filters_data": QueryBuilderService.get_advanced_filters_context_data(
199
193
  self
200
194
  ),
@@ -651,6 +651,7 @@ class SBAdmin(
651
651
  NestedModelAdmin,
652
652
  ):
653
653
  change_list_template = "sb_admin/actions/list.html"
654
+ reorder_list_template = "sb_admin/actions/list.html"
654
655
  change_form_template = "sb_admin/actions/change_form.html"
655
656
  delete_selected_confirmation_template = (
656
657
  "sb_admin/actions/delete_selected_confirmation.html"
@@ -10,6 +10,7 @@ from django.contrib.admin.widgets import (
10
10
  ForeignKeyRawIdWidget,
11
11
  )
12
12
  from django.contrib.auth.forms import ReadOnlyPasswordHashWidget
13
+ from django.core.exceptions import ValidationError
13
14
  from django.template.loader import render_to_string
14
15
  from django.urls import reverse
15
16
  from django.utils.formats import get_format
@@ -23,6 +24,7 @@ from filer.models import File
23
24
 
24
25
  from django_smartbase_admin.engine.filter_widgets import (
25
26
  AutocompleteFilterWidget,
27
+ SBAdminTreeWidgetMixin,
26
28
  )
27
29
  from django_smartbase_admin.services.thread_local import SBAdminThreadLocalService
28
30
  from django_smartbase_admin.templatetags.sb_admin_tags import SBAdminJSONEncoder
@@ -542,3 +544,71 @@ class SBAdminColorWidget(SBAdminTextInputWidget):
542
544
  js = [
543
545
  "sb_admin/js/coloris/coloris.min.js",
544
546
  ]
547
+
548
+
549
+ class SBAdminTreeWidget(SBAdminTreeWidgetMixin, SBAdminAutocompleteWidget):
550
+ template_name = "sb_admin/widgets/tree_select.html"
551
+
552
+ def get_context(self, name, value, attrs):
553
+ context = super().get_context(name, value, attrs)
554
+ context["widget"]["raw_value"] = value
555
+ context["widget"]["relationship_pick_mode"] = self.relationship_pick_mode
556
+ context["widget"]["value_dict"] = {
557
+ item["value"]: item["label"]
558
+ for item in context["widget"].get("value_list", [])
559
+ }
560
+ context["widget"]["additional_columns"] = self.additional_columns
561
+ context["widget"]["tree_strings"] = self.tree_strings
562
+
563
+ return context
564
+
565
+ @classmethod
566
+ def get_descendants_from_tree_data(cls, tree_data, parent_id):
567
+ parent_item = cls.find_parent_in_tree_data(tree_data, parent_id)
568
+ descendants = cls.get_descendats_from_item(parent_item)
569
+ return descendants
570
+
571
+ @classmethod
572
+ def get_descendats_from_item(cls, item):
573
+ descendants = []
574
+ if not item:
575
+ return descendants
576
+ for child in item.get("children", []):
577
+ descendants.append(child)
578
+ descendants.extend(cls.get_descendats_from_item(child))
579
+ return descendants
580
+
581
+ @classmethod
582
+ def find_parent_in_tree_data(cls, tree_data, parent_id):
583
+ str_parent_id = str(parent_id)
584
+ for item in tree_data:
585
+ if item["key"] == str_parent_id:
586
+ return item
587
+ parent = cls.find_parent_in_tree_data(
588
+ item.get("children", []), str_parent_id
589
+ )
590
+ if parent:
591
+ return parent
592
+ return None
593
+
594
+ def value_from_datadict(self, data, files, name):
595
+ input_value = data.get(name)
596
+ threadsafe_request = SBAdminThreadLocalService.get_request()
597
+ parsed_value = self.parse_value_from_input(threadsafe_request, input_value)
598
+ obj = self.form.instance
599
+ if (
600
+ obj
601
+ and parsed_value
602
+ and self.relationship_pick_mode == self.RELATIONSHIP_PICK_MODE_PARENT
603
+ ):
604
+ if obj.id == parsed_value:
605
+ raise ValidationError(_("Cannot set parent to itself"))
606
+ qs = self.get_queryset(threadsafe_request).order_by(*self.order_by)
607
+ tree_data = self.format_tree_data(threadsafe_request, qs)
608
+ children = self.get_descendants_from_tree_data(tree_data, obj.id)
609
+ children_ids = []
610
+ for child in children:
611
+ children_ids.append(child.get("key"))
612
+ if input_value in children_ids:
613
+ raise ValidationError(_("Cannot set parent to it's own child"))
614
+ return parsed_value
@@ -282,6 +282,7 @@ class SBAdminBaseListView(SBAdminBaseView):
282
282
  url=self.get_menu_view_url(request),
283
283
  ),
284
284
  ],
285
+ template=self.reorder_list_template,
285
286
  )
286
287
 
287
288
  def is_reorder_active(self, request) -> bool:
@@ -618,6 +619,7 @@ class SBAdminBaseListView(SBAdminBaseView):
618
619
  tabulator_definition=None,
619
620
  extra_context=None,
620
621
  list_actions=None,
622
+ template=None,
621
623
  ):
622
624
  action = self.sbadmin_list_action_class(
623
625
  self,
@@ -640,7 +642,8 @@ class SBAdminBaseListView(SBAdminBaseView):
640
642
 
641
643
  return TemplateResponse(
642
644
  request,
643
- self.change_list_template
645
+ template
646
+ or self.change_list_template
644
647
  or [
645
648
  "admin/%s/%s/change_list.html"
646
649
  % (self.model._meta.app_label, self.model._meta.model_name),
@@ -5,7 +5,7 @@ from django.core.exceptions import ImproperlyConfigured
5
5
  from django.db.models import Q, fields, FilteredRelation, Count
6
6
  from django.http import JsonResponse
7
7
  from django.utils import timezone
8
- from django.utils.translation import gettext_lazy as _
8
+ from django.utils.translation import gettext_lazy as _, pgettext_lazy
9
9
 
10
10
  from django_smartbase_admin.actions.advanced_filters import (
11
11
  AllOperators,
@@ -701,3 +701,184 @@ class FromValuesAutocompleteWidget(AutocompleteFilterWidget):
701
701
 
702
702
  def get_label(self, request, item):
703
703
  return item.get(self.field.name)
704
+
705
+
706
+ class SBAdminTreeWidgetMixin:
707
+ order_by = None
708
+ inline = False
709
+ RELATIONSHIP_PICK_MODE_NONE = None
710
+ RELATIONSHIP_PICK_MODE_PARENT = "parent"
711
+ relationship_pick_mode = RELATIONSHIP_PICK_MODE_NONE
712
+ additional_columns = None
713
+ tree_strings = {
714
+ "loading": pgettext_lazy("Tree widget", "Loading..."),
715
+ "loadError": pgettext_lazy("Tree widget", "Load error!"),
716
+ "moreData": pgettext_lazy("Tree widget", "More..."),
717
+ "noData": pgettext_lazy("Tree widget", "No data."),
718
+ }
719
+ model = None
720
+ path_field = "path"
721
+
722
+ def __init__(
723
+ self,
724
+ order_by=None,
725
+ relationship_pick_mode=None,
726
+ inline=None,
727
+ additional_columns=None,
728
+ tree_strings=None,
729
+ *args,
730
+ **kwargs,
731
+ ):
732
+ self.inline = inline if inline is not None else self.inline
733
+ self.order_by = order_by if order_by is not None else self.order_by
734
+ self.relationship_pick_mode = relationship_pick_mode
735
+ self.additional_columns = (
736
+ additional_columns
737
+ if additional_columns is not None
738
+ else self.additional_columns
739
+ )
740
+ self.tree_strings = (
741
+ tree_strings if tree_strings is not None else self.tree_strings
742
+ )
743
+ if self.inline:
744
+ self.template_name = "sb_admin/widgets/tree_select_inline.html"
745
+ super().__init__(*args, **kwargs)
746
+
747
+ def action_autocomplete(self, request, modifier):
748
+ result = self.format_tree_data(request, self.get_queryset(request))
749
+ return JsonResponse(data=result, safe=False)
750
+
751
+ def format_tree_data(self, request, queryset):
752
+ self_id = None
753
+ if self.relationship_pick_mode == self.RELATIONSHIP_PICK_MODE_PARENT:
754
+ # disable selecting self and children if selecting parent
755
+ self_id = self.form.instance.id if self.form.instance else None
756
+ return self.get_tree_data(request, queryset, self_id=self_id)
757
+
758
+ @classmethod
759
+ def get_tree_base_values(cls):
760
+ return ["id", cls.path_field]
761
+
762
+ @classmethod
763
+ def get_tree_key(cls, request, item):
764
+ return item.get(cls.path_field)
765
+
766
+ @classmethod
767
+ def get_tree_title(cls, request, item):
768
+ raise NotImplementedError
769
+
770
+ @classmethod
771
+ def get_value(cls, request, item):
772
+ return getattr(item, cls.path_field)
773
+
774
+ @classmethod
775
+ def get_label(cls, request, item):
776
+ raise NotImplementedError
777
+
778
+ @classmethod
779
+ def tree_process_global_data(cls, request, queryset, **kwargs):
780
+ return {}
781
+
782
+ @classmethod
783
+ def get_additional_data(cls, request, item, tree_process_global_data):
784
+ return {}
785
+
786
+ @classmethod
787
+ def get_tree_data(cls, request, queryset, values=None, self_id=None, **kwargs):
788
+ tree_values = cls.get_tree_base_values()
789
+ tree_values.extend(values if values else [])
790
+
791
+ queryset = queryset.order_by(*cls.order_by)
792
+ queryset = queryset.annotate(
793
+ **SBAdminViewService.get_annotates(cls.model, tree_values, [])
794
+ )
795
+ flat_data = []
796
+ tree_data, lnk = [], {}
797
+ tree_process_global_data = cls.tree_process_global_data(
798
+ request, queryset, **kwargs
799
+ )
800
+
801
+ data = list(queryset.values(*tree_values))
802
+ for item in data:
803
+ path = item.get("path")
804
+ depth = int(len(path) / cls.model.steplen)
805
+ item_id = cls.get_tree_key(request, item)
806
+ item_label = cls.get_tree_title(request, item)
807
+ newobj = {
808
+ "title": item_label,
809
+ "key": str(item_id),
810
+ "data": {"id": item.get("id")},
811
+ }
812
+ if item_id == self_id:
813
+ # disable selecting self and children if selecting parent
814
+ newobj["checkbox"] = False
815
+
816
+ additional_data = cls.get_additional_data(
817
+ request, item, tree_process_global_data
818
+ )
819
+ newobj.update(additional_data)
820
+
821
+ if depth == 1:
822
+ tree_data.append(newobj)
823
+ flat_data.append(newobj)
824
+ else:
825
+ parentpath = cls.model._get_basepath(path, depth - 1)
826
+ parentobj = lnk[parentpath]
827
+ if "children" not in parentobj:
828
+ parentobj["children"] = []
829
+ if parentobj.get("checkbox") is False:
830
+ # disable selecting self and children if selecting parent
831
+ newobj["checkbox"] = False
832
+ parentobj["children"].append(newobj)
833
+ flat_data.append(newobj)
834
+ lnk[path] = newobj
835
+ return tree_data
836
+
837
+ # tree_widget_data: [{"key":"path", "children": [{...}]}]
838
+ @classmethod
839
+ def process_treebeard_tree(
840
+ cls,
841
+ tree_widget_data,
842
+ treebeard_objs_by_path,
843
+ depth=1,
844
+ parent_path="",
845
+ path_base="",
846
+ ):
847
+ if not path_base:
848
+ path_base = ((cls.model.steplen - 1) * "0") + "1"
849
+ previous = None
850
+ objs_to_update = []
851
+ for tree_widget_node in tree_widget_data:
852
+ treebeard_obj = treebeard_objs_by_path.get(tree_widget_node["key"])
853
+ old_depth = treebeard_obj.depth
854
+ old_path = getattr(treebeard_obj, cls.path_field)
855
+ old_numchild = treebeard_obj.numchild
856
+ treebeard_obj.depth = depth
857
+ if not previous:
858
+ previous = treebeard_obj
859
+ setattr(treebeard_obj, cls.path_field, parent_path + path_base)
860
+ else:
861
+ setattr(treebeard_obj, cls.path_field, previous._inc_path())
862
+ previous = treebeard_obj
863
+ children = tree_widget_node.get("children", [])
864
+ treebeard_obj.numchild = len(children)
865
+ if (
866
+ treebeard_obj.depth != old_depth
867
+ or getattr(treebeard_obj, cls.path_field) != old_path
868
+ or treebeard_obj.numchild != old_numchild
869
+ ):
870
+ objs_to_update.append(treebeard_obj)
871
+ objs_to_update.extend(
872
+ cls.process_treebeard_tree(
873
+ children,
874
+ treebeard_objs_by_path,
875
+ depth + 1,
876
+ getattr(treebeard_obj, cls.path_field),
877
+ path_base,
878
+ )
879
+ )
880
+ return objs_to_update
881
+
882
+
883
+ class SBAdminTreeFilterWidget(SBAdminTreeWidgetMixin, AutocompleteFilterWidget):
884
+ template_name = "sb_admin/filter_widgets/tree_select_filter.html"