django-smartbase-admin 0.2.54__py3-none-any.whl → 1.0.38__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 +74 -38
- django_smartbase_admin/actions/advanced_filters.py +24 -1
- django_smartbase_admin/admin/admin_base.py +401 -96
- django_smartbase_admin/admin/site.py +93 -35
- django_smartbase_admin/admin/widgets.py +589 -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 +44 -23
- django_smartbase_admin/engine/fake_inline.py +15 -11
- django_smartbase_admin/engine/field.py +42 -12
- django_smartbase_admin/engine/field_formatter.py +22 -8
- django_smartbase_admin/engine/filter_widgets.py +309 -20
- 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/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 +26 -8
- 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 +63 -5
- 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 +304 -31
- django_smartbase_admin/static/sb_admin/src/js/multiselect.js +50 -41
- 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/filter_module.js +3 -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 +169 -114
- 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_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/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 +10 -5
- django_smartbase_admin/templates/sb_admin/filter_widgets/radio_choice_field.html +2 -2
- 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 +85 -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.38.dist-info/METADATA +166 -0
- {django_smartbase_admin-0.2.54.dist-info → django_smartbase_admin-1.0.38.dist-info}/RECORD +177 -115
- {django_smartbase_admin-0.2.54.dist-info → django_smartbase_admin-1.0.38.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.38.dist-info}/LICENSE.md +0 -0
|
@@ -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
|
|
@@ -211,6 +229,8 @@ class ChoiceFilterWidget(SBAdminFilterWidget):
|
|
|
211
229
|
return found_label[0] if found_label else default_value
|
|
212
230
|
|
|
213
231
|
def get_base_filter_query_for_parsed_value(self, request, filter_value):
|
|
232
|
+
if isinstance(self.model_field, ArrayField):
|
|
233
|
+
return Q(**{f"{self.field.filter_field}__contains": [filter_value]})
|
|
214
234
|
return Q(**{self.field.filter_field: filter_value})
|
|
215
235
|
|
|
216
236
|
|
|
@@ -220,8 +240,38 @@ class RadioChoiceFilterWidget(ChoiceFilterWidget):
|
|
|
220
240
|
|
|
221
241
|
class MultipleChoiceFilterWidget(AutocompleteParseMixin, ChoiceFilterWidget):
|
|
222
242
|
template_name = "sb_admin/filter_widgets/multiple_choice_field.html"
|
|
243
|
+
enable_select_all = False
|
|
244
|
+
select_all_keyword = None
|
|
245
|
+
select_all_label = None
|
|
246
|
+
|
|
247
|
+
def __init__(
|
|
248
|
+
self,
|
|
249
|
+
choices,
|
|
250
|
+
template_name=None,
|
|
251
|
+
default_value=None,
|
|
252
|
+
default_label=None,
|
|
253
|
+
enable_select_all=False,
|
|
254
|
+
select_all_keyword=SELECT_ALL_KEYWORD,
|
|
255
|
+
select_all_label=_("All"),
|
|
256
|
+
**kwargs,
|
|
257
|
+
) -> None:
|
|
258
|
+
super().__init__(
|
|
259
|
+
choices=choices,
|
|
260
|
+
template_name=template_name,
|
|
261
|
+
default_value=default_value,
|
|
262
|
+
default_label=default_label,
|
|
263
|
+
**kwargs,
|
|
264
|
+
)
|
|
265
|
+
self.enable_select_all = enable_select_all
|
|
266
|
+
self.select_all_keyword = select_all_keyword
|
|
267
|
+
self.select_all_label = select_all_label
|
|
223
268
|
|
|
224
269
|
def get_base_filter_query_for_parsed_value(self, request, filter_value):
|
|
270
|
+
if isinstance(self.model_field, ArrayField):
|
|
271
|
+
q_objects = Q()
|
|
272
|
+
for value in filter_value:
|
|
273
|
+
q_objects |= Q(**{f"{self.field.filter_field}__contains": [value]})
|
|
274
|
+
return q_objects
|
|
225
275
|
return Q(**{f"{self.field.filter_field}__in": filter_value})
|
|
226
276
|
|
|
227
277
|
def get_advanced_filter_operators(self):
|
|
@@ -284,6 +334,7 @@ class DateFilterWidget(SBAdminFilterWidget):
|
|
|
284
334
|
"label": _("Last 12 months"),
|
|
285
335
|
},
|
|
286
336
|
]
|
|
337
|
+
shortcuts_dict = {AllOperators.IN_THE_LAST.value: shortcuts}
|
|
287
338
|
default_value_shortcut_index = None
|
|
288
339
|
|
|
289
340
|
def __init__(
|
|
@@ -306,33 +357,43 @@ class DateFilterWidget(SBAdminFilterWidget):
|
|
|
306
357
|
def get_advanced_filter_operators(self):
|
|
307
358
|
return DATE_ATTRIBUTES
|
|
308
359
|
|
|
360
|
+
def process_shortcut(self, shortcut, now):
|
|
361
|
+
return shortcut
|
|
362
|
+
|
|
309
363
|
def get_shortcuts(self):
|
|
310
364
|
now = timezone.now()
|
|
311
365
|
shortcuts = []
|
|
312
366
|
for shortcut in self.shortcuts:
|
|
313
|
-
shortcuts.append(
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
367
|
+
shortcuts.append(self.process_shortcut(shortcut, now))
|
|
368
|
+
return shortcuts
|
|
369
|
+
|
|
370
|
+
def get_shortcuts_dict(self):
|
|
371
|
+
now = timezone.now()
|
|
372
|
+
shortcuts = {}
|
|
373
|
+
for key, shortcuts_group in self.shortcuts_dict.items():
|
|
374
|
+
shortcuts[key] = []
|
|
375
|
+
for shortcut in shortcuts_group:
|
|
376
|
+
shortcuts[key].append(self.process_shortcut(shortcut, now))
|
|
322
377
|
return shortcuts
|
|
323
378
|
|
|
379
|
+
def get_default_label(self):
|
|
380
|
+
if self.default_value_shortcut_index is not None:
|
|
381
|
+
return self.get_shortcuts()[self.default_value_shortcut_index]["label"]
|
|
382
|
+
return super().get_default_value()
|
|
383
|
+
|
|
324
384
|
def get_default_value(self):
|
|
325
385
|
if self.default_value_shortcut_index is not None:
|
|
326
386
|
selected_shortcut_value = self.get_shortcuts()[
|
|
327
387
|
self.default_value_shortcut_index
|
|
328
388
|
]["value"]
|
|
329
|
-
return
|
|
389
|
+
return SBAdminViewService.json_dumps_for_url(
|
|
390
|
+
self.get_value_from_date_or_range(selected_shortcut_value)
|
|
391
|
+
)
|
|
330
392
|
return super().get_default_value()
|
|
331
393
|
|
|
332
394
|
def get_data(self):
|
|
333
395
|
return json.dumps(
|
|
334
396
|
{
|
|
335
|
-
"shortcuts": self.get_shortcuts(),
|
|
336
397
|
"flatpickrOptions": {
|
|
337
398
|
"locale": {
|
|
338
399
|
"rangeSeparator": self.DATE_RANGE_SPLIT,
|
|
@@ -342,6 +403,19 @@ class DateFilterWidget(SBAdminFilterWidget):
|
|
|
342
403
|
cls=SBAdminJSONEncoder,
|
|
343
404
|
)
|
|
344
405
|
|
|
406
|
+
def get_shortcuts_data(self):
|
|
407
|
+
return json.dumps(
|
|
408
|
+
self.get_shortcuts(),
|
|
409
|
+
cls=SBAdminJSONEncoder,
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
def get_shortcuts_dict_data(self):
|
|
413
|
+
# used for advanced filters with different calendar operators "in the last", "in the next", etc.
|
|
414
|
+
return json.dumps(
|
|
415
|
+
self.get_shortcuts_dict(),
|
|
416
|
+
cls=SBAdminJSONEncoder,
|
|
417
|
+
)
|
|
418
|
+
|
|
345
419
|
@classmethod
|
|
346
420
|
def is_used_for_model_field_type(cls, model_field):
|
|
347
421
|
return isinstance(model_field, fields.DateField)
|
|
@@ -356,12 +430,28 @@ class DateFilterWidget(SBAdminFilterWidget):
|
|
|
356
430
|
if filter_value is None:
|
|
357
431
|
return [None, None]
|
|
358
432
|
date_format = cls.DATE_FORMAT
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
433
|
+
if type(filter_value) is list:
|
|
434
|
+
if type(filter_value[0]) is int:
|
|
435
|
+
return [
|
|
436
|
+
timezone.now() + timedelta(days=filter_value[0]),
|
|
437
|
+
timezone.now() + timedelta(days=filter_value[1]),
|
|
438
|
+
]
|
|
439
|
+
return [
|
|
440
|
+
datetime.strptime(filter_value[0], date_format),
|
|
441
|
+
datetime.strptime(filter_value[1], date_format),
|
|
442
|
+
]
|
|
443
|
+
try:
|
|
444
|
+
days_range = json.loads(filter_value)
|
|
445
|
+
return [
|
|
446
|
+
timezone.now() + timedelta(days=days_range[0]),
|
|
447
|
+
timezone.now() + timedelta(days=days_range[1]),
|
|
448
|
+
]
|
|
449
|
+
except json.decoder.JSONDecodeError:
|
|
450
|
+
date_range = filter_value.split(cls.DATE_RANGE_SPLIT)
|
|
451
|
+
if len(date_range) == 2:
|
|
452
|
+
date_from = datetime.strptime(date_range[0], date_format)
|
|
453
|
+
date_to = datetime.strptime(date_range[1], date_format)
|
|
454
|
+
return [date_from, date_to]
|
|
365
455
|
date_value = datetime.strptime(filter_value, date_format)
|
|
366
456
|
return [date_value, date_value]
|
|
367
457
|
|
|
@@ -369,9 +459,11 @@ class DateFilterWidget(SBAdminFilterWidget):
|
|
|
369
459
|
def get_value_from_date_or_range(cls, date_or_range):
|
|
370
460
|
if not isinstance(date_or_range, list):
|
|
371
461
|
return datetime.strftime(date_or_range, cls.DATE_FORMAT)
|
|
462
|
+
if type(date_or_range[0]) is int:
|
|
463
|
+
return date_or_range
|
|
372
464
|
date_from = datetime.strftime(date_or_range[0], cls.DATE_FORMAT)
|
|
373
465
|
date_to = datetime.strftime(date_or_range[1], cls.DATE_FORMAT)
|
|
374
|
-
return
|
|
466
|
+
return [date_from, date_to]
|
|
375
467
|
|
|
376
468
|
def parse_value_from_input(self, request, filter_value):
|
|
377
469
|
return self.get_range_from_value(filter_value)
|
|
@@ -403,6 +495,7 @@ class AutocompleteFilterWidget(
|
|
|
403
495
|
allow_add = False
|
|
404
496
|
hide_clear_button = False
|
|
405
497
|
search_query_lambda = None
|
|
498
|
+
create_value_field = None
|
|
406
499
|
|
|
407
500
|
def get_field_name(self):
|
|
408
501
|
return self.field.name
|
|
@@ -425,13 +518,16 @@ class AutocompleteFilterWidget(
|
|
|
425
518
|
allow_add=None,
|
|
426
519
|
hide_clear_button=None,
|
|
427
520
|
search_query_lambda=None,
|
|
521
|
+
create_value_field=None,
|
|
428
522
|
**kwargs,
|
|
429
523
|
) -> None:
|
|
430
524
|
super().__init__(template_name, default_value, **kwargs)
|
|
431
525
|
self.model = model or self.model
|
|
432
526
|
self.value_field = value_field or self.value_field
|
|
433
527
|
self.filter_query_lambda = filter_query_lambda or self.filter_query_lambda
|
|
528
|
+
# filters queryset to search in
|
|
434
529
|
self.filter_search_lambda = filter_search_lambda or self.filter_search_lambda
|
|
530
|
+
# defines fields to search on
|
|
435
531
|
self.search_query_lambda = search_query_lambda or self.search_query_lambda
|
|
436
532
|
self.label_lambda = label_lambda or self.label_lambda
|
|
437
533
|
self.value_lambda = value_lambda or self.value_lambda
|
|
@@ -439,6 +535,7 @@ class AutocompleteFilterWidget(
|
|
|
439
535
|
self.multiselect = self.multiselect if self.multiselect is not None else True
|
|
440
536
|
self.forward = forward or self.forward
|
|
441
537
|
self.allow_add = allow_add or self.allow_add
|
|
538
|
+
self.create_value_field = create_value_field or self.create_value_field
|
|
442
539
|
self.hide_clear_button = (
|
|
443
540
|
hide_clear_button
|
|
444
541
|
if hide_clear_button is not None
|
|
@@ -547,8 +644,9 @@ class AutocompleteFilterWidget(
|
|
|
547
644
|
def get_value_field(self):
|
|
548
645
|
return self.value_field or self.model._meta.pk.name
|
|
549
646
|
|
|
550
|
-
def filter_search_queryset(self, request, qs, search_term, forward_data):
|
|
647
|
+
def filter_search_queryset(self, request, qs, search_term="", forward_data=None):
|
|
551
648
|
if self.filter_search_lambda:
|
|
649
|
+
forward_data = forward_data or {}
|
|
552
650
|
qs = qs.filter(
|
|
553
651
|
self.filter_search_lambda(request, search_term, forward_data)
|
|
554
652
|
)
|
|
@@ -560,9 +658,16 @@ class AutocompleteFilterWidget(
|
|
|
560
658
|
page_num = int(post_data.get(AUTOCOMPLETE_PAGE_NUM, 1))
|
|
561
659
|
from_item = (page_num - 1) * AUTOCOMPLETE_PAGE_SIZE
|
|
562
660
|
to_item = (page_num) * AUTOCOMPLETE_PAGE_SIZE
|
|
661
|
+
|
|
662
|
+
# filter queryset
|
|
663
|
+
# base restricted queryset
|
|
563
664
|
qs = self.get_queryset(request)
|
|
665
|
+
# filters queryset to search in, uses filter_search_lambda
|
|
564
666
|
qs = self.filter_search_queryset(request, qs, search_term, forward_data)
|
|
667
|
+
|
|
668
|
+
# search in queryset
|
|
565
669
|
if self.search_query_lambda:
|
|
670
|
+
# defines fields to search on
|
|
566
671
|
qs = self.search_query_lambda(
|
|
567
672
|
request,
|
|
568
673
|
qs,
|
|
@@ -571,6 +676,7 @@ class AutocompleteFilterWidget(
|
|
|
571
676
|
SBAdminTranslationsService.get_main_lang_code(),
|
|
572
677
|
)
|
|
573
678
|
else:
|
|
679
|
+
# defines default fields to search on - all char fields
|
|
574
680
|
qs = self.get_default_search_query(
|
|
575
681
|
request,
|
|
576
682
|
qs,
|
|
@@ -598,6 +704,8 @@ class AutocompleteFilterWidget(
|
|
|
598
704
|
def get_label(self, request, item):
|
|
599
705
|
if self.label_lambda:
|
|
600
706
|
return self.label_lambda(request, item)
|
|
707
|
+
if isinstance(item, list):
|
|
708
|
+
return ", ".join(map(str, item))
|
|
601
709
|
return str(item)
|
|
602
710
|
|
|
603
711
|
def get_context(self, name, value, attrs):
|
|
@@ -657,3 +765,184 @@ class FromValuesAutocompleteWidget(AutocompleteFilterWidget):
|
|
|
657
765
|
|
|
658
766
|
def get_label(self, request, item):
|
|
659
767
|
return item.get(self.field.name)
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
class SBAdminTreeWidgetMixin:
|
|
771
|
+
order_by = None
|
|
772
|
+
inline = False
|
|
773
|
+
RELATIONSHIP_PICK_MODE_NONE = None
|
|
774
|
+
RELATIONSHIP_PICK_MODE_PARENT = "parent"
|
|
775
|
+
relationship_pick_mode = RELATIONSHIP_PICK_MODE_NONE
|
|
776
|
+
additional_columns = None
|
|
777
|
+
tree_strings = {
|
|
778
|
+
"loading": pgettext_lazy("Tree widget", "Loading..."),
|
|
779
|
+
"loadError": pgettext_lazy("Tree widget", "Load error!"),
|
|
780
|
+
"moreData": pgettext_lazy("Tree widget", "More..."),
|
|
781
|
+
"noData": pgettext_lazy("Tree widget", "No data."),
|
|
782
|
+
}
|
|
783
|
+
model = None
|
|
784
|
+
path_field = "path"
|
|
785
|
+
|
|
786
|
+
def __init__(
|
|
787
|
+
self,
|
|
788
|
+
order_by=None,
|
|
789
|
+
relationship_pick_mode=None,
|
|
790
|
+
inline=None,
|
|
791
|
+
additional_columns=None,
|
|
792
|
+
tree_strings=None,
|
|
793
|
+
*args,
|
|
794
|
+
**kwargs,
|
|
795
|
+
):
|
|
796
|
+
self.inline = inline if inline is not None else self.inline
|
|
797
|
+
self.order_by = order_by if order_by is not None else self.order_by
|
|
798
|
+
self.relationship_pick_mode = relationship_pick_mode
|
|
799
|
+
self.additional_columns = (
|
|
800
|
+
additional_columns
|
|
801
|
+
if additional_columns is not None
|
|
802
|
+
else self.additional_columns
|
|
803
|
+
)
|
|
804
|
+
self.tree_strings = (
|
|
805
|
+
tree_strings if tree_strings is not None else self.tree_strings
|
|
806
|
+
)
|
|
807
|
+
if self.inline:
|
|
808
|
+
self.template_name = "sb_admin/widgets/tree_select_inline.html"
|
|
809
|
+
super().__init__(*args, **kwargs)
|
|
810
|
+
|
|
811
|
+
def action_autocomplete(self, request, modifier):
|
|
812
|
+
result = self.format_tree_data(request, self.get_queryset(request))
|
|
813
|
+
return JsonResponse(data=result, safe=False)
|
|
814
|
+
|
|
815
|
+
def format_tree_data(self, request, queryset):
|
|
816
|
+
self_id = None
|
|
817
|
+
if self.relationship_pick_mode == self.RELATIONSHIP_PICK_MODE_PARENT:
|
|
818
|
+
# disable selecting self and children if selecting parent
|
|
819
|
+
self_id = self.form.instance.id if self.form.instance else None
|
|
820
|
+
return self.get_tree_data(request, queryset, self_id=self_id)
|
|
821
|
+
|
|
822
|
+
@classmethod
|
|
823
|
+
def get_tree_base_values(cls):
|
|
824
|
+
return ["id", cls.path_field]
|
|
825
|
+
|
|
826
|
+
@classmethod
|
|
827
|
+
def get_tree_key(cls, request, item):
|
|
828
|
+
return item.get(cls.path_field)
|
|
829
|
+
|
|
830
|
+
@classmethod
|
|
831
|
+
def get_tree_title(cls, request, item):
|
|
832
|
+
raise NotImplementedError
|
|
833
|
+
|
|
834
|
+
@classmethod
|
|
835
|
+
def get_value(cls, request, item):
|
|
836
|
+
return getattr(item, cls.path_field)
|
|
837
|
+
|
|
838
|
+
@classmethod
|
|
839
|
+
def get_label(cls, request, item):
|
|
840
|
+
raise NotImplementedError
|
|
841
|
+
|
|
842
|
+
@classmethod
|
|
843
|
+
def tree_process_global_data(cls, request, queryset, **kwargs):
|
|
844
|
+
return {}
|
|
845
|
+
|
|
846
|
+
@classmethod
|
|
847
|
+
def get_additional_data(cls, request, item, tree_process_global_data):
|
|
848
|
+
return {}
|
|
849
|
+
|
|
850
|
+
@classmethod
|
|
851
|
+
def get_tree_data(cls, request, queryset, values=None, self_id=None, **kwargs):
|
|
852
|
+
tree_values = cls.get_tree_base_values()
|
|
853
|
+
tree_values.extend(values if values else [])
|
|
854
|
+
|
|
855
|
+
queryset = queryset.order_by(*cls.order_by)
|
|
856
|
+
queryset = queryset.annotate(
|
|
857
|
+
**SBAdminViewService.get_annotates(cls.model, tree_values, [])
|
|
858
|
+
)
|
|
859
|
+
flat_data = []
|
|
860
|
+
tree_data, lnk = [], {}
|
|
861
|
+
tree_process_global_data = cls.tree_process_global_data(
|
|
862
|
+
request, queryset, **kwargs
|
|
863
|
+
)
|
|
864
|
+
|
|
865
|
+
data = list(queryset.values(*tree_values))
|
|
866
|
+
for item in data:
|
|
867
|
+
path = item.get("path")
|
|
868
|
+
depth = int(len(path) / cls.model.steplen)
|
|
869
|
+
item_id = cls.get_tree_key(request, item)
|
|
870
|
+
item_label = cls.get_tree_title(request, item)
|
|
871
|
+
newobj = {
|
|
872
|
+
"title": item_label,
|
|
873
|
+
"key": str(item_id),
|
|
874
|
+
"data": {"id": item.get("id")},
|
|
875
|
+
}
|
|
876
|
+
if item_id == self_id:
|
|
877
|
+
# disable selecting self and children if selecting parent
|
|
878
|
+
newobj["checkbox"] = False
|
|
879
|
+
|
|
880
|
+
additional_data = cls.get_additional_data(
|
|
881
|
+
request, item, tree_process_global_data
|
|
882
|
+
)
|
|
883
|
+
newobj.update(additional_data)
|
|
884
|
+
|
|
885
|
+
if depth == 1:
|
|
886
|
+
tree_data.append(newobj)
|
|
887
|
+
flat_data.append(newobj)
|
|
888
|
+
else:
|
|
889
|
+
parentpath = cls.model._get_basepath(path, depth - 1)
|
|
890
|
+
parentobj = lnk[parentpath]
|
|
891
|
+
if "children" not in parentobj:
|
|
892
|
+
parentobj["children"] = []
|
|
893
|
+
if parentobj.get("checkbox") is False:
|
|
894
|
+
# disable selecting self and children if selecting parent
|
|
895
|
+
newobj["checkbox"] = False
|
|
896
|
+
parentobj["children"].append(newobj)
|
|
897
|
+
flat_data.append(newobj)
|
|
898
|
+
lnk[path] = newobj
|
|
899
|
+
return tree_data
|
|
900
|
+
|
|
901
|
+
# tree_widget_data: [{"key":"path", "children": [{...}]}]
|
|
902
|
+
@classmethod
|
|
903
|
+
def process_treebeard_tree(
|
|
904
|
+
cls,
|
|
905
|
+
tree_widget_data,
|
|
906
|
+
treebeard_objs_by_path,
|
|
907
|
+
depth=1,
|
|
908
|
+
parent_path="",
|
|
909
|
+
path_base="",
|
|
910
|
+
):
|
|
911
|
+
if not path_base:
|
|
912
|
+
path_base = ((cls.model.steplen - 1) * "0") + "1"
|
|
913
|
+
previous = None
|
|
914
|
+
objs_to_update = []
|
|
915
|
+
for tree_widget_node in tree_widget_data:
|
|
916
|
+
treebeard_obj = treebeard_objs_by_path.get(tree_widget_node["key"])
|
|
917
|
+
old_depth = treebeard_obj.depth
|
|
918
|
+
old_path = getattr(treebeard_obj, cls.path_field)
|
|
919
|
+
old_numchild = treebeard_obj.numchild
|
|
920
|
+
treebeard_obj.depth = depth
|
|
921
|
+
if not previous:
|
|
922
|
+
previous = treebeard_obj
|
|
923
|
+
setattr(treebeard_obj, cls.path_field, parent_path + path_base)
|
|
924
|
+
else:
|
|
925
|
+
setattr(treebeard_obj, cls.path_field, previous._inc_path())
|
|
926
|
+
previous = treebeard_obj
|
|
927
|
+
children = tree_widget_node.get("children", [])
|
|
928
|
+
treebeard_obj.numchild = len(children)
|
|
929
|
+
if (
|
|
930
|
+
treebeard_obj.depth != old_depth
|
|
931
|
+
or getattr(treebeard_obj, cls.path_field) != old_path
|
|
932
|
+
or treebeard_obj.numchild != old_numchild
|
|
933
|
+
):
|
|
934
|
+
objs_to_update.append(treebeard_obj)
|
|
935
|
+
objs_to_update.extend(
|
|
936
|
+
cls.process_treebeard_tree(
|
|
937
|
+
children,
|
|
938
|
+
treebeard_objs_by_path,
|
|
939
|
+
depth + 1,
|
|
940
|
+
getattr(treebeard_obj, cls.path_field),
|
|
941
|
+
path_base,
|
|
942
|
+
)
|
|
943
|
+
)
|
|
944
|
+
return objs_to_update
|
|
945
|
+
|
|
946
|
+
|
|
947
|
+
class SBAdminTreeFilterWidget(SBAdminTreeWidgetMixin, AutocompleteFilterWidget):
|
|
948
|
+
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)
|
|
@@ -2,6 +2,7 @@ from django.http import HttpResponse
|
|
|
2
2
|
from django.views.generic import FormView
|
|
3
3
|
from django_htmx.http import trigger_client_event
|
|
4
4
|
from django_smartbase_admin.actions.admin_action_list import SBAdminListAction
|
|
5
|
+
from django_smartbase_admin.engine.const import TABLE_RELOAD_DATA_EVENT_NAME
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class ActionModalView(FormView):
|
|
@@ -19,7 +20,7 @@ class ActionModalView(FormView):
|
|
|
19
20
|
trigger_client_event(response, "hideModal", {})
|
|
20
21
|
trigger_client_event(
|
|
21
22
|
response,
|
|
22
|
-
|
|
23
|
+
TABLE_RELOAD_DATA_EVENT_NAME,
|
|
23
24
|
{},
|
|
24
25
|
)
|
|
25
26
|
return response
|
|
@@ -27,11 +28,15 @@ class ActionModalView(FormView):
|
|
|
27
28
|
def get_form_class(self):
|
|
28
29
|
form_class = super().get_form_class()
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
fake_form_class = type(
|
|
32
|
+
form_class.__name__,
|
|
33
|
+
(form_class,),
|
|
34
|
+
{
|
|
35
|
+
"view": self.view,
|
|
36
|
+
},
|
|
37
|
+
)
|
|
33
38
|
|
|
34
|
-
return
|
|
39
|
+
return fake_form_class
|
|
35
40
|
|
|
36
41
|
def post(self, request, *args, **kwargs):
|
|
37
42
|
form = self.get_form()
|
|
@@ -59,9 +64,9 @@ class ListActionModalView(ActionModalView):
|
|
|
59
64
|
return response
|
|
60
65
|
|
|
61
66
|
def get_selection_queryset(self, request, form):
|
|
62
|
-
list_action =
|
|
67
|
+
list_action = self.view.sbadmin_list_action_class(self.view, request)
|
|
63
68
|
return list_action.get_data_queryset().filter(
|
|
64
|
-
list_action.get_selection_queryset()
|
|
69
|
+
list_action.get_selection_queryset(), list_action.get_filter_from_request()
|
|
65
70
|
)
|
|
66
71
|
|
|
67
72
|
def process_form_valid_list_selection_queryset(
|
|
@@ -20,6 +20,7 @@ class SBAdminViewRequestData(object):
|
|
|
20
20
|
configuration = None
|
|
21
21
|
selected_view = None
|
|
22
22
|
session = None
|
|
23
|
+
additional_data = None
|
|
23
24
|
|
|
24
25
|
def __init__(
|
|
25
26
|
self,
|
|
@@ -47,6 +48,7 @@ class SBAdminViewRequestData(object):
|
|
|
47
48
|
self.request_method = request_method
|
|
48
49
|
self.global_filter = global_filter or {}
|
|
49
50
|
self.session = session or {}
|
|
51
|
+
self.additional_data = {}
|
|
50
52
|
|
|
51
53
|
def refresh_selected_view(self, request):
|
|
52
54
|
self.configuration = SBAdminConfigurationService.get_configuration(self)
|
|
File without changes
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from django_smartbase_admin.admin.site import sb_admin_site
|
|
4
|
+
|
|
5
|
+
from django_smartbase_admin.admin.admin_base import SBAdmin
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DjangoCMSPluginSBAdmin(SBAdmin):
|
|
9
|
+
initialised = False
|
|
10
|
+
list_display = []
|
|
11
|
+
|
|
12
|
+
def get_inline_instances(self, request, obj=None):
|
|
13
|
+
inline_instances = super().get_inline_instances(request, obj)
|
|
14
|
+
if not self.initialised:
|
|
15
|
+
for inline_instance in inline_instances:
|
|
16
|
+
inline_instance.init_view_static(
|
|
17
|
+
request.request_data.configuration,
|
|
18
|
+
inline_instance.model,
|
|
19
|
+
sb_admin_site,
|
|
20
|
+
)
|
|
21
|
+
self.initialised = True
|
|
22
|
+
return inline_instances
|
|
23
|
+
|
|
24
|
+
def get_sbadmin_fieldsets(self, request, object_id=None):
|
|
25
|
+
obj = self.model.objects.get(pk=object_id) if object_id else None
|
|
26
|
+
fieldsets = self.sbadmin_fieldsets or [
|
|
27
|
+
(
|
|
28
|
+
None,
|
|
29
|
+
{"fields": self.get_fields(request, obj)},
|
|
30
|
+
)
|
|
31
|
+
]
|
|
32
|
+
self.sbadmin_fieldsets = fieldsets
|
|
33
|
+
return fieldsets
|
|
34
|
+
|
|
35
|
+
def response_add(self, request, obj, **kwargs):
|
|
36
|
+
# response_add from CMSPluginBase
|
|
37
|
+
self.object_successfully_changed = True
|
|
38
|
+
return self.render_close_frame(request, obj)
|
|
39
|
+
|
|
40
|
+
def get_change_view_context(self, request, object_id) -> dict | dict[str, Any]:
|
|
41
|
+
return {
|
|
42
|
+
"show_back_button": True,
|
|
43
|
+
}
|
|
Binary file
|