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.
- django_smartbase_admin/actions/admin_action_list.py +0 -6
- django_smartbase_admin/admin/admin_base.py +1 -0
- django_smartbase_admin/admin/widgets.py +70 -0
- django_smartbase_admin/engine/admin_base_view.py +4 -1
- django_smartbase_admin/engine/filter_widgets.py +182 -1
- django_smartbase_admin/locale/sk/LC_MESSAGES/django.mo +0 -0
- django_smartbase_admin/locale/sk/LC_MESSAGES/django.po +241 -28
- django_smartbase_admin/services/views.py +2 -2
- django_smartbase_admin/static/sb_admin/build/webpack.common.js +9 -8
- django_smartbase_admin/static/sb_admin/dist/main.js +1 -1
- django_smartbase_admin/static/sb_admin/dist/main_style.css +1 -1
- django_smartbase_admin/static/sb_admin/dist/table.js +1 -1
- django_smartbase_admin/static/sb_admin/dist/tree_widget.js +1 -0
- django_smartbase_admin/static/sb_admin/dist/tree_widget_style.css +1 -0
- django_smartbase_admin/static/sb_admin/dist/tree_widget_style.js +0 -0
- django_smartbase_admin/static/sb_admin/fancytree/jquery.fancytree-all-deps.min.js +2 -0
- django_smartbase_admin/static/sb_admin/src/css/_base.css +4 -0
- django_smartbase_admin/static/sb_admin/src/css/_inlines.css +3 -3
- django_smartbase_admin/static/sb_admin/src/css/_tabulator.css +6 -0
- django_smartbase_admin/static/sb_admin/src/css/components/_toggle.css +2 -1
- django_smartbase_admin/static/sb_admin/src/css/tree_widget.css +405 -0
- django_smartbase_admin/static/sb_admin/src/js/datepicker.js +1 -0
- django_smartbase_admin/static/sb_admin/src/js/table.js +18 -2
- django_smartbase_admin/static/sb_admin/src/js/table_modules/filter_module.js +3 -1
- django_smartbase_admin/static/sb_admin/src/js/table_modules/selection_module.js +20 -2
- django_smartbase_admin/static/sb_admin/src/js/table_modules/table_params_module.js +6 -0
- django_smartbase_admin/static/sb_admin/src/js/tree_widget.js +376 -0
- django_smartbase_admin/templates/sb_admin/actions/tree_list.html +63 -0
- django_smartbase_admin/templates/sb_admin/components/columns.html +1 -1
- django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/tree_select_filter.html +2 -0
- django_smartbase_admin/templates/sb_admin/filter_widgets/tree_select_filter.html +16 -0
- django_smartbase_admin/templates/sb_admin/sb_admin_base_no_sidebar.html +7 -5
- django_smartbase_admin/templates/sb_admin/sb_admin_js_trans.html +1 -0
- django_smartbase_admin/templates/sb_admin/widgets/filer_file.html +1 -1
- django_smartbase_admin/templates/sb_admin/widgets/tree_base.html +54 -0
- django_smartbase_admin/templates/sb_admin/widgets/tree_select.html +24 -0
- django_smartbase_admin/templates/sb_admin/widgets/tree_select_inline.html +12 -0
- {django_smartbase_admin-0.2.91.dist-info → django_smartbase_admin-0.2.93.dist-info}/METADATA +1 -1
- {django_smartbase_admin-0.2.91.dist-info → django_smartbase_admin-0.2.93.dist-info}/RECORD +41 -29
- {django_smartbase_admin-0.2.91.dist-info → django_smartbase_admin-0.2.93.dist-info}/LICENSE.md +0 -0
- {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
|
-
|
|
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"
|
|
Binary file
|