django-smartbase-admin 0.2.54__py3-none-any.whl → 1.0.42__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 +79 -38
- django_smartbase_admin/actions/advanced_filters.py +24 -1
- django_smartbase_admin/admin/admin_base.py +402 -97
- django_smartbase_admin/admin/site.py +93 -35
- django_smartbase_admin/admin/widgets.py +636 -26
- django_smartbase_admin/apps.py +2 -0
- django_smartbase_admin/engine/actions.py +34 -16
- django_smartbase_admin/engine/admin_base_view.py +252 -115
- django_smartbase_admin/engine/configuration.py +186 -4
- django_smartbase_admin/engine/const.py +6 -0
- django_smartbase_admin/engine/dashboard.py +49 -24
- django_smartbase_admin/engine/fake_inline.py +15 -11
- django_smartbase_admin/engine/field.py +42 -12
- django_smartbase_admin/engine/field_formatter.py +38 -14
- django_smartbase_admin/engine/filter_widgets.py +348 -24
- django_smartbase_admin/engine/menu_item.py +8 -5
- django_smartbase_admin/engine/modal_view.py +12 -7
- django_smartbase_admin/engine/request.py +2 -0
- django_smartbase_admin/integration/__init__.py +0 -0
- django_smartbase_admin/integration/django_cms.py +43 -0
- django_smartbase_admin/locale/sk/LC_MESSAGES/django.mo +0 -0
- django_smartbase_admin/locale/sk/LC_MESSAGES/django.po +268 -37
- django_smartbase_admin/migrations/0005_sbadminuserconfiguration.py +26 -0
- django_smartbase_admin/migrations/0006_alter_sbadminuserconfiguration_color_scheme.py +18 -0
- django_smartbase_admin/models.py +22 -0
- django_smartbase_admin/monkeypatch/admin_readonly_field_monkeypatch.py +96 -0
- django_smartbase_admin/monkeypatch/fake_inline_monkeypatch.py +1 -1
- django_smartbase_admin/querysets.py +3 -0
- django_smartbase_admin/services/configuration.py +30 -0
- django_smartbase_admin/services/thread_local.py +6 -19
- django_smartbase_admin/services/views.py +80 -13
- django_smartbase_admin/services/xlsx_export.py +6 -0
- django_smartbase_admin/static/sb_admin/build/tailwind.config.js +1 -0
- django_smartbase_admin/static/sb_admin/build/tailwind_config_partials/colors.js +4 -0
- django_smartbase_admin/static/sb_admin/build/tailwind_config_partials/spacing.js +1 -0
- django_smartbase_admin/static/sb_admin/build/webpack.common.js +11 -8
- django_smartbase_admin/static/sb_admin/css/ckeditor/ckeditor_content_dark.css +208 -0
- django_smartbase_admin/static/sb_admin/css/coloris/coloris.min.css +1 -0
- django_smartbase_admin/static/sb_admin/dist/calendar.js +1 -0
- django_smartbase_admin/static/sb_admin/dist/calendar_style.css +1 -0
- django_smartbase_admin/static/sb_admin/dist/calendar_style.js +0 -0
- django_smartbase_admin/static/sb_admin/dist/chart.js +1 -1
- 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 +1 -0
- django_smartbase_admin/static/sb_admin/images/file_types/file-csv.svg +11 -0
- django_smartbase_admin/static/sb_admin/images/file_types/file-doc.svg +11 -0
- django_smartbase_admin/static/sb_admin/images/file_types/file-docx.svg +11 -0
- django_smartbase_admin/static/sb_admin/images/file_types/file-other.svg +13 -0
- django_smartbase_admin/static/sb_admin/images/file_types/file-pdf.svg +11 -0
- django_smartbase_admin/static/sb_admin/images/file_types/file-ppt.svg +11 -0
- django_smartbase_admin/static/sb_admin/images/file_types/file-xls.svg +11 -0
- django_smartbase_admin/static/sb_admin/images/file_types/file-xlsx.svg +11 -0
- django_smartbase_admin/static/sb_admin/images/file_types/file-zip.svg +18 -0
- django_smartbase_admin/static/sb_admin/images/flags/de-at.png +0 -0
- django_smartbase_admin/static/sb_admin/images/flags/de-ch.png +0 -0
- django_smartbase_admin/static/sb_admin/images/logo_light.svg +21 -0
- django_smartbase_admin/static/sb_admin/js/coloris/coloris.min.js +6 -0
- django_smartbase_admin/static/sb_admin/js/fullcalendar.min.js +14804 -0
- django_smartbase_admin/static/sb_admin/js/sbadmin_prepopulated_fields_init.js +25 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Bolt-one.svg +3 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Calendar.svg +3 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Caution.svg +3 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Electric-drill.svg +3 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Fire-extinguisher.svg +3 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Gas.svg +3 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Lightning-fill.svg +3 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Moon.svg +3 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Phone-telephone.svg +3 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Printer.svg +3 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Pull.svg +3 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Sun-one.svg +3 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Time.svg +3 -0
- django_smartbase_admin/static/sb_admin/src/css/_base.css +5 -1
- django_smartbase_admin/static/sb_admin/src/css/_colors.css +257 -82
- django_smartbase_admin/static/sb_admin/src/css/_components.css +61 -13
- django_smartbase_admin/static/sb_admin/src/css/_datepicker.css +8 -1
- django_smartbase_admin/static/sb_admin/src/css/_filer.css +60 -0
- django_smartbase_admin/static/sb_admin/src/css/_inlines.css +51 -10
- django_smartbase_admin/static/sb_admin/src/css/_tabulator.css +8 -2
- django_smartbase_admin/static/sb_admin/src/css/calendar.css +162 -0
- django_smartbase_admin/static/sb_admin/src/css/components/_button.css +41 -1
- django_smartbase_admin/static/sb_admin/src/css/components/_dropdown.css +31 -7
- django_smartbase_admin/static/sb_admin/src/css/components/_input.css +62 -20
- django_smartbase_admin/static/sb_admin/src/css/components/_modal.css +1 -1
- django_smartbase_admin/static/sb_admin/src/css/components/_query-builder.css +21 -2
- django_smartbase_admin/static/sb_admin/src/css/components/_toggle.css +12 -1
- django_smartbase_admin/static/sb_admin/src/css/components/_tooltip.css +8 -22
- django_smartbase_admin/static/sb_admin/src/css/style.css +17 -0
- django_smartbase_admin/static/sb_admin/src/css/tree_widget.css +411 -0
- django_smartbase_admin/static/sb_admin/src/js/autocomplete.js +69 -11
- django_smartbase_admin/static/sb_admin/src/js/calendar.js +56 -0
- django_smartbase_admin/static/sb_admin/src/js/chart.js +8 -22
- django_smartbase_admin/static/sb_admin/src/js/choices.js +18 -8
- django_smartbase_admin/static/sb_admin/src/js/datepicker.js +97 -336
- django_smartbase_admin/static/sb_admin/src/js/datepicker_plugins.js +357 -0
- django_smartbase_admin/static/sb_admin/src/js/main.js +306 -31
- django_smartbase_admin/static/sb_admin/src/js/multiselect.js +50 -41
- django_smartbase_admin/static/sb_admin/src/js/radio.js +31 -0
- django_smartbase_admin/static/sb_admin/src/js/range.js +3 -2
- django_smartbase_admin/static/sb_admin/src/js/table.js +34 -5
- django_smartbase_admin/static/sb_admin/src/js/table_modules/advanced_filter_module.js +43 -20
- django_smartbase_admin/static/sb_admin/src/js/table_modules/data_edit_module.js +8 -10
- django_smartbase_admin/static/sb_admin/src/js/table_modules/detail_view_module.js +50 -1
- django_smartbase_admin/static/sb_admin/src/js/table_modules/filter_module.js +10 -3
- django_smartbase_admin/static/sb_admin/src/js/table_modules/header_tabs_module.js +11 -11
- django_smartbase_admin/static/sb_admin/src/js/table_modules/selection_module.js +28 -8
- django_smartbase_admin/static/sb_admin/src/js/table_modules/table_params_module.js +6 -0
- django_smartbase_admin/static/sb_admin/src/js/table_modules/views_module.js +6 -0
- django_smartbase_admin/static/sb_admin/src/js/tree_widget.js +406 -0
- django_smartbase_admin/static/sb_admin/src/js/utils.js +56 -21
- django_smartbase_admin/templates/sb_admin/actions/change_form.html +176 -116
- django_smartbase_admin/templates/sb_admin/actions/dashboard.html +2 -2
- django_smartbase_admin/templates/sb_admin/actions/list.html +79 -39
- django_smartbase_admin/templates/sb_admin/actions/partials/action_link.html +14 -0
- django_smartbase_admin/templates/sb_admin/actions/partials/tabulator_header_v2.html +2 -2
- django_smartbase_admin/templates/sb_admin/actions/tree_list.html +63 -0
- django_smartbase_admin/templates/sb_admin/authentification/login_base.html +5 -1
- django_smartbase_admin/templates/sb_admin/components/columns.html +1 -1
- django_smartbase_admin/templates/sb_admin/components/filters.html +1 -0
- django_smartbase_admin/templates/sb_admin/components/filters_v2.html +99 -85
- django_smartbase_admin/templates/sb_admin/dashboard/calendar_widget.html +69 -0
- django_smartbase_admin/templates/sb_admin/dashboard/chart_widget.html +21 -2
- django_smartbase_admin/templates/sb_admin/dashboard/list_widget.html +6 -0
- django_smartbase_admin/templates/sb_admin/dashboard/widget_base.html +1 -1
- django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/date_field.html +18 -8
- django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/multiple_choice_field.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/boolean_field.html +1 -14
- django_smartbase_admin/templates/sb_admin/filter_widgets/date_field.html +18 -4
- django_smartbase_admin/templates/sb_admin/filter_widgets/multiple_choice_field.html +14 -0
- django_smartbase_admin/templates/sb_admin/filter_widgets/partials/clear.html +12 -6
- django_smartbase_admin/templates/sb_admin/filter_widgets/radio_choice_field.html +5 -3
- django_smartbase_admin/templates/sb_admin/filter_widgets/tree_select_filter.html +16 -0
- django_smartbase_admin/templates/sb_admin/includes/change_form_title.html +3 -1
- django_smartbase_admin/templates/sb_admin/includes/inline_fieldset.html +48 -39
- django_smartbase_admin/templates/sb_admin/includes/notifications.html +2 -1
- django_smartbase_admin/templates/sb_admin/includes/readonly_boolean_field.html +9 -0
- django_smartbase_admin/templates/sb_admin/includes/readonly_field.html +12 -0
- django_smartbase_admin/templates/sb_admin/includes/table_inline_delete_button.html +4 -5
- django_smartbase_admin/templates/sb_admin/inlines/stacked_inline.html +68 -40
- django_smartbase_admin/templates/sb_admin/inlines/table_inline.html +76 -34
- django_smartbase_admin/templates/sb_admin/integrations/filer/folder_list.html +18 -0
- django_smartbase_admin/templates/sb_admin/navigation.html +166 -158
- django_smartbase_admin/templates/sb_admin/partials/modal/modal_content.html +2 -6
- django_smartbase_admin/templates/sb_admin/sb_admin_base.html +49 -4
- django_smartbase_admin/templates/sb_admin/sb_admin_base_no_sidebar.html +27 -11
- django_smartbase_admin/templates/sb_admin/sb_admin_js_trans.html +3 -0
- django_smartbase_admin/templates/sb_admin/sprites/sb_admin.svg +1 -1
- django_smartbase_admin/templates/sb_admin/tailwind_whitelist.html +6 -3
- django_smartbase_admin/templates/sb_admin/widgets/array.html +0 -1
- django_smartbase_admin/templates/sb_admin/widgets/attributes.html +68 -0
- django_smartbase_admin/templates/sb_admin/widgets/autocomplete.html +13 -2
- django_smartbase_admin/templates/sb_admin/widgets/{checkbox_select.html → checkbox_dropdown.html} +2 -2
- django_smartbase_admin/templates/sb_admin/widgets/clearable_file_input.html +2 -2
- django_smartbase_admin/templates/sb_admin/widgets/color_field.html +30 -0
- django_smartbase_admin/templates/sb_admin/widgets/date.html +8 -1
- django_smartbase_admin/templates/sb_admin/widgets/filer_file.html +84 -0
- django_smartbase_admin/templates/sb_admin/widgets/includes/related_item_buttons.html +38 -0
- django_smartbase_admin/templates/sb_admin/widgets/multiwidget.html +1 -1
- django_smartbase_admin/templates/sb_admin/widgets/radio.html +3 -2
- django_smartbase_admin/templates/sb_admin/widgets/radio_dropdown.html +30 -0
- django_smartbase_admin/templates/sb_admin/widgets/read_only_password_hash.html +3 -0
- django_smartbase_admin/templates/sb_admin/widgets/time.html +8 -1
- django_smartbase_admin/templates/sb_admin/widgets/toggle.html +1 -1
- django_smartbase_admin/templates/sb_admin/widgets/tree_base.html +59 -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/templatetags/sb_admin_tags.py +115 -4
- django_smartbase_admin/utils.py +22 -3
- django_smartbase_admin/views/dashboard_view.py +6 -0
- django_smartbase_admin/views/global_filter_view.py +8 -2
- django_smartbase_admin/views/translations_view.py +12 -5
- django_smartbase_admin/views/user_config_view.py +52 -0
- django_smartbase_admin-1.0.42.dist-info/METADATA +166 -0
- {django_smartbase_admin-0.2.54.dist-info → django_smartbase_admin-1.0.42.dist-info}/RECORD +182 -118
- {django_smartbase_admin-0.2.54.dist-info → django_smartbase_admin-1.0.42.dist-info}/WHEEL +1 -1
- django_smartbase_admin/templates/sb_admin/integrations/sorting/change_list.html +0 -401
- django_smartbase_admin-0.2.54.dist-info/METADATA +0 -25
- {django_smartbase_admin-0.2.54.dist-info → django_smartbase_admin-1.0.42.dist-info}/LICENSE.md +0 -0
|
@@ -1,6 +1,17 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
1
3
|
from django.template.defaultfilters import date, time
|
|
2
|
-
from django.utils.translation import gettext_lazy as _
|
|
3
4
|
from django.utils import timezone
|
|
5
|
+
from django.utils.html import format_html, format_html_join
|
|
6
|
+
from django.utils.safestring import mark_safe
|
|
7
|
+
from django.utils.translation import gettext_lazy as _
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BadgeType(Enum):
|
|
11
|
+
SUCCESS = "positive"
|
|
12
|
+
NOTICE = "notice"
|
|
13
|
+
WARNING = "warning"
|
|
14
|
+
ERROR = "negative"
|
|
4
15
|
|
|
5
16
|
|
|
6
17
|
def datetime_formatter(object_id, value):
|
|
@@ -29,19 +40,26 @@ def datetime_formatter_with_format(date_format=None, time_format=None):
|
|
|
29
40
|
|
|
30
41
|
def boolean_formatter(object_id, value):
|
|
31
42
|
if value:
|
|
32
|
-
return
|
|
33
|
-
|
|
43
|
+
return format_html(
|
|
44
|
+
'<span class="badge badge-simple badge-positive">{}</span>', _("Yes")
|
|
45
|
+
)
|
|
46
|
+
return format_html(
|
|
47
|
+
'<span class="badge badge-simple badge-neutral">{}</span>', _("No")
|
|
48
|
+
)
|
|
34
49
|
|
|
35
50
|
|
|
36
|
-
def format_array(value_list, separator=""):
|
|
37
|
-
result = ""
|
|
51
|
+
def format_array(value_list, separator="", badge_type: BadgeType = BadgeType.NOTICE):
|
|
38
52
|
if not value_list:
|
|
39
|
-
return
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return
|
|
53
|
+
return ""
|
|
54
|
+
|
|
55
|
+
# `separator` is intended to be an internal constant (e.g. "" or "<br>").
|
|
56
|
+
# We mark it safe so HTML separators render as HTML rather than being escaped.
|
|
57
|
+
sep = mark_safe(separator) if separator else ""
|
|
58
|
+
return format_html_join(
|
|
59
|
+
sep,
|
|
60
|
+
'<span class="badge badge-simple badge-{} mr-4">{}</span>',
|
|
61
|
+
((badge_type.value, value) for value in value_list if value),
|
|
62
|
+
)
|
|
45
63
|
|
|
46
64
|
|
|
47
65
|
def array_badge_formatter(object_id, value_list):
|
|
@@ -49,12 +67,18 @@ def array_badge_formatter(object_id, value_list):
|
|
|
49
67
|
|
|
50
68
|
|
|
51
69
|
def newline_separated_array_badge_formatter(object_id, value_list):
|
|
52
|
-
return format_array(value_list, separator="<br>")
|
|
70
|
+
return format_html("<div>{}</div>", format_array(value_list, separator="<br>"))
|
|
53
71
|
|
|
54
72
|
|
|
55
73
|
def rich_text_formatter(object_id, value):
|
|
56
|
-
|
|
74
|
+
# Intentionally renders HTML (e.g. from a rich text editor field).
|
|
75
|
+
return format_html(
|
|
76
|
+
'<div style="max-width: 500px; white-space: normal;">{}</div>',
|
|
77
|
+
mark_safe(value) if value else "",
|
|
78
|
+
)
|
|
57
79
|
|
|
58
80
|
|
|
59
81
|
def link_formatter(object_id, value):
|
|
60
|
-
|
|
82
|
+
if not value:
|
|
83
|
+
return ""
|
|
84
|
+
return format_html('<a href="{0}">{0}</a>', value)
|
|
@@ -2,10 +2,11 @@ import json
|
|
|
2
2
|
from datetime import datetime, timedelta
|
|
3
3
|
|
|
4
4
|
from django.core.exceptions import ImproperlyConfigured
|
|
5
|
+
from django.contrib.postgres.fields import ArrayField
|
|
5
6
|
from django.db.models import Q, fields, FilteredRelation, Count
|
|
6
7
|
from django.http import JsonResponse
|
|
7
8
|
from django.utils import timezone
|
|
8
|
-
from django.utils.translation import gettext_lazy as _
|
|
9
|
+
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
|
9
10
|
|
|
10
11
|
from django_smartbase_admin.actions.advanced_filters import (
|
|
11
12
|
AllOperators,
|
|
@@ -20,6 +21,7 @@ from django_smartbase_admin.engine.const import (
|
|
|
20
21
|
Action,
|
|
21
22
|
AUTOCOMPLETE_PAGE_NUM,
|
|
22
23
|
AUTOCOMPLETE_FORWARD_NAME,
|
|
24
|
+
SELECT_ALL_KEYWORD,
|
|
23
25
|
)
|
|
24
26
|
from django_smartbase_admin.services.translations import SBAdminTranslationsService
|
|
25
27
|
from django_smartbase_admin.services.views import SBAdminViewService
|
|
@@ -44,6 +46,22 @@ class AutocompleteParseMixin:
|
|
|
44
46
|
value = input_value
|
|
45
47
|
return value
|
|
46
48
|
|
|
49
|
+
def parse_is_create_from_input(self, request, input_value):
|
|
50
|
+
try:
|
|
51
|
+
input_value = json.loads(input_value)
|
|
52
|
+
except:
|
|
53
|
+
pass
|
|
54
|
+
if isinstance(input_value, list):
|
|
55
|
+
value = []
|
|
56
|
+
for data in input_value:
|
|
57
|
+
if type(data) is dict:
|
|
58
|
+
value.append(data.get("create", False))
|
|
59
|
+
else:
|
|
60
|
+
value.append(False)
|
|
61
|
+
else:
|
|
62
|
+
value = False
|
|
63
|
+
return value
|
|
64
|
+
|
|
47
65
|
|
|
48
66
|
class SBAdminFilterWidget(JSONSerializableMixin):
|
|
49
67
|
template_name = None
|
|
@@ -57,6 +75,11 @@ class SBAdminFilterWidget(JSONSerializableMixin):
|
|
|
57
75
|
default_label = None
|
|
58
76
|
filter_query_lambda = None
|
|
59
77
|
exclude_null_operators = False
|
|
78
|
+
# If True, the filter dropdown closes after the filter value changes (frontend behavior).
|
|
79
|
+
# Useful for single-step filters; set to False for widgets where users typically make multiple
|
|
80
|
+
# changes before closing the dropdown.
|
|
81
|
+
close_dropdown_on_change = False
|
|
82
|
+
allow_clear = True
|
|
60
83
|
|
|
61
84
|
def __init__(
|
|
62
85
|
self,
|
|
@@ -65,6 +88,8 @@ class SBAdminFilterWidget(JSONSerializableMixin):
|
|
|
65
88
|
default_label=None,
|
|
66
89
|
filter_query_lambda=None,
|
|
67
90
|
exclude_null_operators=None,
|
|
91
|
+
close_dropdown_on_change=None,
|
|
92
|
+
allow_clear=None,
|
|
68
93
|
**kwargs,
|
|
69
94
|
) -> None:
|
|
70
95
|
super().__init__()
|
|
@@ -75,6 +100,10 @@ class SBAdminFilterWidget(JSONSerializableMixin):
|
|
|
75
100
|
self.exclude_null_operators = (
|
|
76
101
|
exclude_null_operators or self.exclude_null_operators
|
|
77
102
|
)
|
|
103
|
+
if close_dropdown_on_change is not None:
|
|
104
|
+
self.close_dropdown_on_change = close_dropdown_on_change
|
|
105
|
+
if allow_clear is not None:
|
|
106
|
+
self.allow_clear = allow_clear
|
|
78
107
|
|
|
79
108
|
def init_filter_widget_static(self, field, view, configuration):
|
|
80
109
|
self.field = field
|
|
@@ -106,7 +135,9 @@ class SBAdminFilterWidget(JSONSerializableMixin):
|
|
|
106
135
|
return original_query
|
|
107
136
|
|
|
108
137
|
def to_json(self):
|
|
109
|
-
return {
|
|
138
|
+
return {
|
|
139
|
+
"input_id": self.input_id,
|
|
140
|
+
}
|
|
110
141
|
|
|
111
142
|
def get_default_value(self):
|
|
112
143
|
return self.default_value
|
|
@@ -144,6 +175,7 @@ class SBAdminFilterWidget(JSONSerializableMixin):
|
|
|
144
175
|
|
|
145
176
|
class StringFilterWidget(SBAdminFilterWidget):
|
|
146
177
|
template_name = "sb_admin/filter_widgets/string_field.html"
|
|
178
|
+
close_dropdown_on_change = True
|
|
147
179
|
|
|
148
180
|
def get_advanced_filter_operators(self):
|
|
149
181
|
return STRING_ATTRIBUTES
|
|
@@ -160,6 +192,29 @@ class StringFilterWidget(SBAdminFilterWidget):
|
|
|
160
192
|
|
|
161
193
|
class BooleanFilterWidget(SBAdminFilterWidget):
|
|
162
194
|
template_name = "sb_admin/filter_widgets/boolean_field.html"
|
|
195
|
+
choices = None
|
|
196
|
+
close_dropdown_on_change = True
|
|
197
|
+
|
|
198
|
+
def __init__(
|
|
199
|
+
self,
|
|
200
|
+
template_name=None,
|
|
201
|
+
default_value=None,
|
|
202
|
+
default_label=None,
|
|
203
|
+
filter_query_lambda=None,
|
|
204
|
+
exclude_null_operators=None,
|
|
205
|
+
close_dropdown_on_change=None,
|
|
206
|
+
**kwargs,
|
|
207
|
+
) -> None:
|
|
208
|
+
super().__init__(
|
|
209
|
+
template_name,
|
|
210
|
+
default_value,
|
|
211
|
+
default_label,
|
|
212
|
+
filter_query_lambda,
|
|
213
|
+
exclude_null_operators,
|
|
214
|
+
close_dropdown_on_change,
|
|
215
|
+
**kwargs,
|
|
216
|
+
)
|
|
217
|
+
self.choices = ((True, _("Yes")), (False, _("No")))
|
|
163
218
|
|
|
164
219
|
def parse_value_from_input(self, request, filter_value):
|
|
165
220
|
input_value = super().parse_value_from_input(request, filter_value)
|
|
@@ -183,6 +238,7 @@ class BooleanFilterWidget(SBAdminFilterWidget):
|
|
|
183
238
|
class ChoiceFilterWidget(SBAdminFilterWidget):
|
|
184
239
|
template_name = "sb_admin/filter_widgets/choice_field.html"
|
|
185
240
|
choices = None
|
|
241
|
+
close_dropdown_on_change = True
|
|
186
242
|
|
|
187
243
|
def __init__(
|
|
188
244
|
self,
|
|
@@ -211,17 +267,51 @@ class ChoiceFilterWidget(SBAdminFilterWidget):
|
|
|
211
267
|
return found_label[0] if found_label else default_value
|
|
212
268
|
|
|
213
269
|
def get_base_filter_query_for_parsed_value(self, request, filter_value):
|
|
270
|
+
if isinstance(self.model_field, ArrayField):
|
|
271
|
+
return Q(**{f"{self.field.filter_field}__contains": [filter_value]})
|
|
214
272
|
return Q(**{self.field.filter_field: filter_value})
|
|
215
273
|
|
|
216
274
|
|
|
217
275
|
class RadioChoiceFilterWidget(ChoiceFilterWidget):
|
|
218
276
|
template_name = "sb_admin/filter_widgets/radio_choice_field.html"
|
|
277
|
+
close_dropdown_on_change = True
|
|
219
278
|
|
|
220
279
|
|
|
221
280
|
class MultipleChoiceFilterWidget(AutocompleteParseMixin, ChoiceFilterWidget):
|
|
222
281
|
template_name = "sb_admin/filter_widgets/multiple_choice_field.html"
|
|
282
|
+
enable_select_all = False
|
|
283
|
+
select_all_keyword = None
|
|
284
|
+
select_all_label = None
|
|
285
|
+
close_dropdown_on_change = False
|
|
286
|
+
|
|
287
|
+
def __init__(
|
|
288
|
+
self,
|
|
289
|
+
choices,
|
|
290
|
+
template_name=None,
|
|
291
|
+
default_value=None,
|
|
292
|
+
default_label=None,
|
|
293
|
+
enable_select_all=False,
|
|
294
|
+
select_all_keyword=SELECT_ALL_KEYWORD,
|
|
295
|
+
select_all_label=_("All"),
|
|
296
|
+
**kwargs,
|
|
297
|
+
) -> None:
|
|
298
|
+
super().__init__(
|
|
299
|
+
choices=choices,
|
|
300
|
+
template_name=template_name,
|
|
301
|
+
default_value=default_value,
|
|
302
|
+
default_label=default_label,
|
|
303
|
+
**kwargs,
|
|
304
|
+
)
|
|
305
|
+
self.enable_select_all = enable_select_all
|
|
306
|
+
self.select_all_keyword = select_all_keyword
|
|
307
|
+
self.select_all_label = select_all_label
|
|
223
308
|
|
|
224
309
|
def get_base_filter_query_for_parsed_value(self, request, filter_value):
|
|
310
|
+
if isinstance(self.model_field, ArrayField):
|
|
311
|
+
q_objects = Q()
|
|
312
|
+
for value in filter_value:
|
|
313
|
+
q_objects |= Q(**{f"{self.field.filter_field}__contains": [value]})
|
|
314
|
+
return q_objects
|
|
225
315
|
return Q(**{f"{self.field.filter_field}__in": filter_value})
|
|
226
316
|
|
|
227
317
|
def get_advanced_filter_operators(self):
|
|
@@ -284,6 +374,7 @@ class DateFilterWidget(SBAdminFilterWidget):
|
|
|
284
374
|
"label": _("Last 12 months"),
|
|
285
375
|
},
|
|
286
376
|
]
|
|
377
|
+
shortcuts_dict = {AllOperators.IN_THE_LAST.value: shortcuts}
|
|
287
378
|
default_value_shortcut_index = None
|
|
288
379
|
|
|
289
380
|
def __init__(
|
|
@@ -306,33 +397,43 @@ class DateFilterWidget(SBAdminFilterWidget):
|
|
|
306
397
|
def get_advanced_filter_operators(self):
|
|
307
398
|
return DATE_ATTRIBUTES
|
|
308
399
|
|
|
400
|
+
def process_shortcut(self, shortcut, now):
|
|
401
|
+
return shortcut
|
|
402
|
+
|
|
309
403
|
def get_shortcuts(self):
|
|
310
404
|
now = timezone.now()
|
|
311
405
|
shortcuts = []
|
|
312
406
|
for shortcut in self.shortcuts:
|
|
313
|
-
shortcuts.append(
|
|
314
|
-
{
|
|
315
|
-
"label": shortcut["label"],
|
|
316
|
-
"value": [
|
|
317
|
-
now + timedelta(days=shortcut["value"][0]),
|
|
318
|
-
now + timedelta(days=shortcut["value"][1]),
|
|
319
|
-
],
|
|
320
|
-
}
|
|
321
|
-
)
|
|
407
|
+
shortcuts.append(self.process_shortcut(shortcut, now))
|
|
322
408
|
return shortcuts
|
|
323
409
|
|
|
410
|
+
def get_shortcuts_dict(self):
|
|
411
|
+
now = timezone.now()
|
|
412
|
+
shortcuts = {}
|
|
413
|
+
for key, shortcuts_group in self.shortcuts_dict.items():
|
|
414
|
+
shortcuts[key] = []
|
|
415
|
+
for shortcut in shortcuts_group:
|
|
416
|
+
shortcuts[key].append(self.process_shortcut(shortcut, now))
|
|
417
|
+
return shortcuts
|
|
418
|
+
|
|
419
|
+
def get_default_label(self):
|
|
420
|
+
if self.default_value_shortcut_index is not None:
|
|
421
|
+
return self.get_shortcuts()[self.default_value_shortcut_index]["label"]
|
|
422
|
+
return super().get_default_value()
|
|
423
|
+
|
|
324
424
|
def get_default_value(self):
|
|
325
425
|
if self.default_value_shortcut_index is not None:
|
|
326
426
|
selected_shortcut_value = self.get_shortcuts()[
|
|
327
427
|
self.default_value_shortcut_index
|
|
328
428
|
]["value"]
|
|
329
|
-
return
|
|
429
|
+
return SBAdminViewService.json_dumps_for_url(
|
|
430
|
+
self.get_value_from_date_or_range(selected_shortcut_value)
|
|
431
|
+
)
|
|
330
432
|
return super().get_default_value()
|
|
331
433
|
|
|
332
434
|
def get_data(self):
|
|
333
435
|
return json.dumps(
|
|
334
436
|
{
|
|
335
|
-
"shortcuts": self.get_shortcuts(),
|
|
336
437
|
"flatpickrOptions": {
|
|
337
438
|
"locale": {
|
|
338
439
|
"rangeSeparator": self.DATE_RANGE_SPLIT,
|
|
@@ -342,6 +443,19 @@ class DateFilterWidget(SBAdminFilterWidget):
|
|
|
342
443
|
cls=SBAdminJSONEncoder,
|
|
343
444
|
)
|
|
344
445
|
|
|
446
|
+
def get_shortcuts_data(self):
|
|
447
|
+
return json.dumps(
|
|
448
|
+
self.get_shortcuts(),
|
|
449
|
+
cls=SBAdminJSONEncoder,
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
def get_shortcuts_dict_data(self):
|
|
453
|
+
# used for advanced filters with different calendar operators "in the last", "in the next", etc.
|
|
454
|
+
return json.dumps(
|
|
455
|
+
self.get_shortcuts_dict(),
|
|
456
|
+
cls=SBAdminJSONEncoder,
|
|
457
|
+
)
|
|
458
|
+
|
|
345
459
|
@classmethod
|
|
346
460
|
def is_used_for_model_field_type(cls, model_field):
|
|
347
461
|
return isinstance(model_field, fields.DateField)
|
|
@@ -356,12 +470,28 @@ class DateFilterWidget(SBAdminFilterWidget):
|
|
|
356
470
|
if filter_value is None:
|
|
357
471
|
return [None, None]
|
|
358
472
|
date_format = cls.DATE_FORMAT
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
473
|
+
if type(filter_value) is list:
|
|
474
|
+
if type(filter_value[0]) is int:
|
|
475
|
+
return [
|
|
476
|
+
timezone.now() + timedelta(days=filter_value[0]),
|
|
477
|
+
timezone.now() + timedelta(days=filter_value[1]),
|
|
478
|
+
]
|
|
479
|
+
return [
|
|
480
|
+
datetime.strptime(filter_value[0], date_format),
|
|
481
|
+
datetime.strptime(filter_value[1], date_format),
|
|
482
|
+
]
|
|
483
|
+
try:
|
|
484
|
+
days_range = json.loads(filter_value)
|
|
485
|
+
return [
|
|
486
|
+
timezone.now() + timedelta(days=days_range[0]),
|
|
487
|
+
timezone.now() + timedelta(days=days_range[1]),
|
|
488
|
+
]
|
|
489
|
+
except json.decoder.JSONDecodeError:
|
|
490
|
+
date_range = filter_value.split(cls.DATE_RANGE_SPLIT)
|
|
491
|
+
if len(date_range) == 2:
|
|
492
|
+
date_from = datetime.strptime(date_range[0], date_format)
|
|
493
|
+
date_to = datetime.strptime(date_range[1], date_format)
|
|
494
|
+
return [date_from, date_to]
|
|
365
495
|
date_value = datetime.strptime(filter_value, date_format)
|
|
366
496
|
return [date_value, date_value]
|
|
367
497
|
|
|
@@ -369,9 +499,11 @@ class DateFilterWidget(SBAdminFilterWidget):
|
|
|
369
499
|
def get_value_from_date_or_range(cls, date_or_range):
|
|
370
500
|
if not isinstance(date_or_range, list):
|
|
371
501
|
return datetime.strftime(date_or_range, cls.DATE_FORMAT)
|
|
502
|
+
if type(date_or_range[0]) is int:
|
|
503
|
+
return date_or_range
|
|
372
504
|
date_from = datetime.strftime(date_or_range[0], cls.DATE_FORMAT)
|
|
373
505
|
date_to = datetime.strftime(date_or_range[1], cls.DATE_FORMAT)
|
|
374
|
-
return
|
|
506
|
+
return [date_from, date_to]
|
|
375
507
|
|
|
376
508
|
def parse_value_from_input(self, request, filter_value):
|
|
377
509
|
return self.get_range_from_value(filter_value)
|
|
@@ -400,9 +532,9 @@ class AutocompleteFilterWidget(
|
|
|
400
532
|
forward = None
|
|
401
533
|
label_lambda = None
|
|
402
534
|
value_lambda = None
|
|
403
|
-
allow_add = False
|
|
404
535
|
hide_clear_button = False
|
|
405
536
|
search_query_lambda = None
|
|
537
|
+
create_value_field = None
|
|
406
538
|
|
|
407
539
|
def get_field_name(self):
|
|
408
540
|
return self.field.name
|
|
@@ -422,7 +554,6 @@ class AutocompleteFilterWidget(
|
|
|
422
554
|
value_lambda=None,
|
|
423
555
|
multiselect=None,
|
|
424
556
|
forward=None,
|
|
425
|
-
allow_add=None,
|
|
426
557
|
hide_clear_button=None,
|
|
427
558
|
search_query_lambda=None,
|
|
428
559
|
**kwargs,
|
|
@@ -431,14 +562,15 @@ class AutocompleteFilterWidget(
|
|
|
431
562
|
self.model = model or self.model
|
|
432
563
|
self.value_field = value_field or self.value_field
|
|
433
564
|
self.filter_query_lambda = filter_query_lambda or self.filter_query_lambda
|
|
565
|
+
# filters queryset to search in
|
|
434
566
|
self.filter_search_lambda = filter_search_lambda or self.filter_search_lambda
|
|
567
|
+
# defines fields to search on
|
|
435
568
|
self.search_query_lambda = search_query_lambda or self.search_query_lambda
|
|
436
569
|
self.label_lambda = label_lambda or self.label_lambda
|
|
437
570
|
self.value_lambda = value_lambda or self.value_lambda
|
|
438
571
|
self.multiselect = multiselect if multiselect is not None else self.multiselect
|
|
439
572
|
self.multiselect = self.multiselect if self.multiselect is not None else True
|
|
440
573
|
self.forward = forward or self.forward
|
|
441
|
-
self.allow_add = allow_add or self.allow_add
|
|
442
574
|
self.hide_clear_button = (
|
|
443
575
|
hide_clear_button
|
|
444
576
|
if hide_clear_button is not None
|
|
@@ -547,8 +679,9 @@ class AutocompleteFilterWidget(
|
|
|
547
679
|
def get_value_field(self):
|
|
548
680
|
return self.value_field or self.model._meta.pk.name
|
|
549
681
|
|
|
550
|
-
def filter_search_queryset(self, request, qs, search_term, forward_data):
|
|
682
|
+
def filter_search_queryset(self, request, qs, search_term="", forward_data=None):
|
|
551
683
|
if self.filter_search_lambda:
|
|
684
|
+
forward_data = forward_data or {}
|
|
552
685
|
qs = qs.filter(
|
|
553
686
|
self.filter_search_lambda(request, search_term, forward_data)
|
|
554
687
|
)
|
|
@@ -560,9 +693,16 @@ class AutocompleteFilterWidget(
|
|
|
560
693
|
page_num = int(post_data.get(AUTOCOMPLETE_PAGE_NUM, 1))
|
|
561
694
|
from_item = (page_num - 1) * AUTOCOMPLETE_PAGE_SIZE
|
|
562
695
|
to_item = (page_num) * AUTOCOMPLETE_PAGE_SIZE
|
|
696
|
+
|
|
697
|
+
# filter queryset
|
|
698
|
+
# base restricted queryset
|
|
563
699
|
qs = self.get_queryset(request)
|
|
700
|
+
# filters queryset to search in, uses filter_search_lambda
|
|
564
701
|
qs = self.filter_search_queryset(request, qs, search_term, forward_data)
|
|
702
|
+
|
|
703
|
+
# search in queryset
|
|
565
704
|
if self.search_query_lambda:
|
|
705
|
+
# defines fields to search on
|
|
566
706
|
qs = self.search_query_lambda(
|
|
567
707
|
request,
|
|
568
708
|
qs,
|
|
@@ -571,6 +711,7 @@ class AutocompleteFilterWidget(
|
|
|
571
711
|
SBAdminTranslationsService.get_main_lang_code(),
|
|
572
712
|
)
|
|
573
713
|
else:
|
|
714
|
+
# defines default fields to search on - all char fields
|
|
574
715
|
qs = self.get_default_search_query(
|
|
575
716
|
request,
|
|
576
717
|
qs,
|
|
@@ -598,6 +739,8 @@ class AutocompleteFilterWidget(
|
|
|
598
739
|
def get_label(self, request, item):
|
|
599
740
|
if self.label_lambda:
|
|
600
741
|
return self.label_lambda(request, item)
|
|
742
|
+
if isinstance(item, list):
|
|
743
|
+
return ", ".join(map(str, item))
|
|
601
744
|
return str(item)
|
|
602
745
|
|
|
603
746
|
def get_context(self, name, value, attrs):
|
|
@@ -657,3 +800,184 @@ class FromValuesAutocompleteWidget(AutocompleteFilterWidget):
|
|
|
657
800
|
|
|
658
801
|
def get_label(self, request, item):
|
|
659
802
|
return item.get(self.field.name)
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
class SBAdminTreeWidgetMixin:
|
|
806
|
+
order_by = None
|
|
807
|
+
inline = False
|
|
808
|
+
RELATIONSHIP_PICK_MODE_NONE = None
|
|
809
|
+
RELATIONSHIP_PICK_MODE_PARENT = "parent"
|
|
810
|
+
relationship_pick_mode = RELATIONSHIP_PICK_MODE_NONE
|
|
811
|
+
additional_columns = None
|
|
812
|
+
tree_strings = {
|
|
813
|
+
"loading": pgettext_lazy("Tree widget", "Loading..."),
|
|
814
|
+
"loadError": pgettext_lazy("Tree widget", "Load error!"),
|
|
815
|
+
"moreData": pgettext_lazy("Tree widget", "More..."),
|
|
816
|
+
"noData": pgettext_lazy("Tree widget", "No data."),
|
|
817
|
+
}
|
|
818
|
+
model = None
|
|
819
|
+
path_field = "path"
|
|
820
|
+
|
|
821
|
+
def __init__(
|
|
822
|
+
self,
|
|
823
|
+
order_by=None,
|
|
824
|
+
relationship_pick_mode=None,
|
|
825
|
+
inline=None,
|
|
826
|
+
additional_columns=None,
|
|
827
|
+
tree_strings=None,
|
|
828
|
+
*args,
|
|
829
|
+
**kwargs,
|
|
830
|
+
):
|
|
831
|
+
self.inline = inline if inline is not None else self.inline
|
|
832
|
+
self.order_by = order_by if order_by is not None else self.order_by
|
|
833
|
+
self.relationship_pick_mode = relationship_pick_mode
|
|
834
|
+
self.additional_columns = (
|
|
835
|
+
additional_columns
|
|
836
|
+
if additional_columns is not None
|
|
837
|
+
else self.additional_columns
|
|
838
|
+
)
|
|
839
|
+
self.tree_strings = (
|
|
840
|
+
tree_strings if tree_strings is not None else self.tree_strings
|
|
841
|
+
)
|
|
842
|
+
if self.inline:
|
|
843
|
+
self.template_name = "sb_admin/widgets/tree_select_inline.html"
|
|
844
|
+
super().__init__(*args, **kwargs)
|
|
845
|
+
|
|
846
|
+
def action_autocomplete(self, request, modifier):
|
|
847
|
+
result = self.format_tree_data(request, self.get_queryset(request))
|
|
848
|
+
return JsonResponse(data=result, safe=False)
|
|
849
|
+
|
|
850
|
+
def format_tree_data(self, request, queryset):
|
|
851
|
+
self_id = None
|
|
852
|
+
if self.relationship_pick_mode == self.RELATIONSHIP_PICK_MODE_PARENT:
|
|
853
|
+
# disable selecting self and children if selecting parent
|
|
854
|
+
self_id = self.form.instance.id if self.form.instance else None
|
|
855
|
+
return self.get_tree_data(request, queryset, self_id=self_id)
|
|
856
|
+
|
|
857
|
+
@classmethod
|
|
858
|
+
def get_tree_base_values(cls):
|
|
859
|
+
return ["id", cls.path_field]
|
|
860
|
+
|
|
861
|
+
@classmethod
|
|
862
|
+
def get_tree_key(cls, request, item):
|
|
863
|
+
return item.get(cls.path_field)
|
|
864
|
+
|
|
865
|
+
@classmethod
|
|
866
|
+
def get_tree_title(cls, request, item):
|
|
867
|
+
raise NotImplementedError
|
|
868
|
+
|
|
869
|
+
@classmethod
|
|
870
|
+
def get_value(cls, request, item):
|
|
871
|
+
return getattr(item, cls.path_field)
|
|
872
|
+
|
|
873
|
+
@classmethod
|
|
874
|
+
def get_label(cls, request, item):
|
|
875
|
+
raise NotImplementedError
|
|
876
|
+
|
|
877
|
+
@classmethod
|
|
878
|
+
def tree_process_global_data(cls, request, queryset, **kwargs):
|
|
879
|
+
return {}
|
|
880
|
+
|
|
881
|
+
@classmethod
|
|
882
|
+
def get_additional_data(cls, request, item, tree_process_global_data):
|
|
883
|
+
return {}
|
|
884
|
+
|
|
885
|
+
@classmethod
|
|
886
|
+
def get_tree_data(cls, request, queryset, values=None, self_id=None, **kwargs):
|
|
887
|
+
tree_values = cls.get_tree_base_values()
|
|
888
|
+
tree_values.extend(values if values else [])
|
|
889
|
+
|
|
890
|
+
queryset = queryset.order_by(*cls.order_by)
|
|
891
|
+
queryset = queryset.annotate(
|
|
892
|
+
**SBAdminViewService.get_annotates(cls.model, tree_values, [])
|
|
893
|
+
)
|
|
894
|
+
flat_data = []
|
|
895
|
+
tree_data, lnk = [], {}
|
|
896
|
+
tree_process_global_data = cls.tree_process_global_data(
|
|
897
|
+
request, queryset, **kwargs
|
|
898
|
+
)
|
|
899
|
+
|
|
900
|
+
data = list(queryset.values(*tree_values))
|
|
901
|
+
for item in data:
|
|
902
|
+
path = item.get("path")
|
|
903
|
+
depth = int(len(path) / cls.model.steplen)
|
|
904
|
+
item_id = cls.get_tree_key(request, item)
|
|
905
|
+
item_label = cls.get_tree_title(request, item)
|
|
906
|
+
newobj = {
|
|
907
|
+
"title": item_label,
|
|
908
|
+
"key": str(item_id),
|
|
909
|
+
"data": {"id": item.get("id")},
|
|
910
|
+
}
|
|
911
|
+
if item_id == self_id:
|
|
912
|
+
# disable selecting self and children if selecting parent
|
|
913
|
+
newobj["checkbox"] = False
|
|
914
|
+
|
|
915
|
+
additional_data = cls.get_additional_data(
|
|
916
|
+
request, item, tree_process_global_data
|
|
917
|
+
)
|
|
918
|
+
newobj.update(additional_data)
|
|
919
|
+
|
|
920
|
+
if depth == 1:
|
|
921
|
+
tree_data.append(newobj)
|
|
922
|
+
flat_data.append(newobj)
|
|
923
|
+
else:
|
|
924
|
+
parentpath = cls.model._get_basepath(path, depth - 1)
|
|
925
|
+
parentobj = lnk[parentpath]
|
|
926
|
+
if "children" not in parentobj:
|
|
927
|
+
parentobj["children"] = []
|
|
928
|
+
if parentobj.get("checkbox") is False:
|
|
929
|
+
# disable selecting self and children if selecting parent
|
|
930
|
+
newobj["checkbox"] = False
|
|
931
|
+
parentobj["children"].append(newobj)
|
|
932
|
+
flat_data.append(newobj)
|
|
933
|
+
lnk[path] = newobj
|
|
934
|
+
return tree_data
|
|
935
|
+
|
|
936
|
+
# tree_widget_data: [{"key":"path", "children": [{...}]}]
|
|
937
|
+
@classmethod
|
|
938
|
+
def process_treebeard_tree(
|
|
939
|
+
cls,
|
|
940
|
+
tree_widget_data,
|
|
941
|
+
treebeard_objs_by_path,
|
|
942
|
+
depth=1,
|
|
943
|
+
parent_path="",
|
|
944
|
+
path_base="",
|
|
945
|
+
):
|
|
946
|
+
if not path_base:
|
|
947
|
+
path_base = ((cls.model.steplen - 1) * "0") + "1"
|
|
948
|
+
previous = None
|
|
949
|
+
objs_to_update = []
|
|
950
|
+
for tree_widget_node in tree_widget_data:
|
|
951
|
+
treebeard_obj = treebeard_objs_by_path.get(tree_widget_node["key"])
|
|
952
|
+
old_depth = treebeard_obj.depth
|
|
953
|
+
old_path = getattr(treebeard_obj, cls.path_field)
|
|
954
|
+
old_numchild = treebeard_obj.numchild
|
|
955
|
+
treebeard_obj.depth = depth
|
|
956
|
+
if not previous:
|
|
957
|
+
previous = treebeard_obj
|
|
958
|
+
setattr(treebeard_obj, cls.path_field, parent_path + path_base)
|
|
959
|
+
else:
|
|
960
|
+
setattr(treebeard_obj, cls.path_field, previous._inc_path())
|
|
961
|
+
previous = treebeard_obj
|
|
962
|
+
children = tree_widget_node.get("children", [])
|
|
963
|
+
treebeard_obj.numchild = len(children)
|
|
964
|
+
if (
|
|
965
|
+
treebeard_obj.depth != old_depth
|
|
966
|
+
or getattr(treebeard_obj, cls.path_field) != old_path
|
|
967
|
+
or treebeard_obj.numchild != old_numchild
|
|
968
|
+
):
|
|
969
|
+
objs_to_update.append(treebeard_obj)
|
|
970
|
+
objs_to_update.extend(
|
|
971
|
+
cls.process_treebeard_tree(
|
|
972
|
+
children,
|
|
973
|
+
treebeard_objs_by_path,
|
|
974
|
+
depth + 1,
|
|
975
|
+
getattr(treebeard_obj, cls.path_field),
|
|
976
|
+
path_base,
|
|
977
|
+
)
|
|
978
|
+
)
|
|
979
|
+
return objs_to_update
|
|
980
|
+
|
|
981
|
+
|
|
982
|
+
class SBAdminTreeFilterWidget(SBAdminTreeWidgetMixin, AutocompleteFilterWidget):
|
|
983
|
+
template_name = "sb_admin/filter_widgets/tree_select_filter.html"
|
|
@@ -56,11 +56,14 @@ class SBAdminMenuItem(object):
|
|
|
56
56
|
return self.label or self.view.get_menu_label()
|
|
57
57
|
|
|
58
58
|
def get_url(self, request):
|
|
59
|
-
|
|
60
|
-
self.url
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
if callable(self.url):
|
|
60
|
+
return self.url(request)
|
|
61
|
+
elif self.url:
|
|
62
|
+
return self.url
|
|
63
|
+
elif self.view:
|
|
64
|
+
return self.view.get_menu_view_url(request)
|
|
65
|
+
else:
|
|
66
|
+
return ""
|
|
64
67
|
|
|
65
68
|
def get_icon(self):
|
|
66
69
|
return self.icon or getattr(self.view, "icon", None)
|