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.
Files changed (179) hide show
  1. django_smartbase_admin/actions/admin_action_list.py +74 -38
  2. django_smartbase_admin/actions/advanced_filters.py +24 -1
  3. django_smartbase_admin/admin/admin_base.py +401 -96
  4. django_smartbase_admin/admin/site.py +93 -35
  5. django_smartbase_admin/admin/widgets.py +589 -26
  6. django_smartbase_admin/apps.py +2 -0
  7. django_smartbase_admin/engine/actions.py +34 -16
  8. django_smartbase_admin/engine/admin_base_view.py +252 -115
  9. django_smartbase_admin/engine/configuration.py +186 -4
  10. django_smartbase_admin/engine/const.py +6 -0
  11. django_smartbase_admin/engine/dashboard.py +44 -23
  12. django_smartbase_admin/engine/fake_inline.py +15 -11
  13. django_smartbase_admin/engine/field.py +42 -12
  14. django_smartbase_admin/engine/field_formatter.py +22 -8
  15. django_smartbase_admin/engine/filter_widgets.py +309 -20
  16. django_smartbase_admin/engine/menu_item.py +8 -5
  17. django_smartbase_admin/engine/modal_view.py +12 -7
  18. django_smartbase_admin/engine/request.py +2 -0
  19. django_smartbase_admin/integration/__init__.py +0 -0
  20. django_smartbase_admin/integration/django_cms.py +43 -0
  21. django_smartbase_admin/locale/sk/LC_MESSAGES/django.mo +0 -0
  22. django_smartbase_admin/locale/sk/LC_MESSAGES/django.po +268 -37
  23. django_smartbase_admin/migrations/0005_sbadminuserconfiguration.py +26 -0
  24. django_smartbase_admin/migrations/0006_alter_sbadminuserconfiguration_color_scheme.py +18 -0
  25. django_smartbase_admin/models.py +22 -0
  26. django_smartbase_admin/monkeypatch/admin_readonly_field_monkeypatch.py +96 -0
  27. django_smartbase_admin/monkeypatch/fake_inline_monkeypatch.py +1 -1
  28. django_smartbase_admin/querysets.py +3 -0
  29. django_smartbase_admin/services/configuration.py +30 -0
  30. django_smartbase_admin/services/thread_local.py +6 -19
  31. django_smartbase_admin/services/views.py +80 -13
  32. django_smartbase_admin/services/xlsx_export.py +6 -0
  33. django_smartbase_admin/static/sb_admin/build/tailwind.config.js +1 -0
  34. django_smartbase_admin/static/sb_admin/build/tailwind_config_partials/colors.js +4 -0
  35. django_smartbase_admin/static/sb_admin/build/tailwind_config_partials/spacing.js +1 -0
  36. django_smartbase_admin/static/sb_admin/build/webpack.common.js +11 -8
  37. django_smartbase_admin/static/sb_admin/css/ckeditor/ckeditor_content_dark.css +208 -0
  38. django_smartbase_admin/static/sb_admin/css/coloris/coloris.min.css +1 -0
  39. django_smartbase_admin/static/sb_admin/dist/calendar.js +1 -0
  40. django_smartbase_admin/static/sb_admin/dist/calendar_style.css +1 -0
  41. django_smartbase_admin/static/sb_admin/dist/calendar_style.js +0 -0
  42. django_smartbase_admin/static/sb_admin/dist/chart.js +1 -1
  43. django_smartbase_admin/static/sb_admin/dist/main.js +1 -1
  44. django_smartbase_admin/static/sb_admin/dist/main_style.css +1 -1
  45. django_smartbase_admin/static/sb_admin/dist/table.js +1 -1
  46. django_smartbase_admin/static/sb_admin/dist/tree_widget.js +1 -0
  47. django_smartbase_admin/static/sb_admin/dist/tree_widget_style.css +1 -0
  48. django_smartbase_admin/static/sb_admin/dist/tree_widget_style.js +0 -0
  49. django_smartbase_admin/static/sb_admin/fancytree/jquery.fancytree-all-deps.min.js +1 -0
  50. django_smartbase_admin/static/sb_admin/images/file_types/file-csv.svg +11 -0
  51. django_smartbase_admin/static/sb_admin/images/file_types/file-doc.svg +11 -0
  52. django_smartbase_admin/static/sb_admin/images/file_types/file-docx.svg +11 -0
  53. django_smartbase_admin/static/sb_admin/images/file_types/file-other.svg +13 -0
  54. django_smartbase_admin/static/sb_admin/images/file_types/file-pdf.svg +11 -0
  55. django_smartbase_admin/static/sb_admin/images/file_types/file-ppt.svg +11 -0
  56. django_smartbase_admin/static/sb_admin/images/file_types/file-xls.svg +11 -0
  57. django_smartbase_admin/static/sb_admin/images/file_types/file-xlsx.svg +11 -0
  58. django_smartbase_admin/static/sb_admin/images/file_types/file-zip.svg +18 -0
  59. django_smartbase_admin/static/sb_admin/images/flags/de-at.png +0 -0
  60. django_smartbase_admin/static/sb_admin/images/flags/de-ch.png +0 -0
  61. django_smartbase_admin/static/sb_admin/images/logo_light.svg +21 -0
  62. django_smartbase_admin/static/sb_admin/js/coloris/coloris.min.js +6 -0
  63. django_smartbase_admin/static/sb_admin/js/fullcalendar.min.js +14804 -0
  64. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Bolt-one.svg +3 -0
  65. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Calendar.svg +3 -0
  66. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Caution.svg +3 -0
  67. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Electric-drill.svg +3 -0
  68. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Fire-extinguisher.svg +3 -0
  69. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Gas.svg +3 -0
  70. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Lightning-fill.svg +3 -0
  71. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Moon.svg +3 -0
  72. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Phone-telephone.svg +3 -0
  73. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Printer.svg +3 -0
  74. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Pull.svg +3 -0
  75. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Sun-one.svg +3 -0
  76. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Time.svg +3 -0
  77. django_smartbase_admin/static/sb_admin/src/css/_base.css +5 -1
  78. django_smartbase_admin/static/sb_admin/src/css/_colors.css +257 -82
  79. django_smartbase_admin/static/sb_admin/src/css/_components.css +61 -13
  80. django_smartbase_admin/static/sb_admin/src/css/_datepicker.css +8 -1
  81. django_smartbase_admin/static/sb_admin/src/css/_filer.css +60 -0
  82. django_smartbase_admin/static/sb_admin/src/css/_inlines.css +51 -10
  83. django_smartbase_admin/static/sb_admin/src/css/_tabulator.css +8 -2
  84. django_smartbase_admin/static/sb_admin/src/css/calendar.css +162 -0
  85. django_smartbase_admin/static/sb_admin/src/css/components/_button.css +41 -1
  86. django_smartbase_admin/static/sb_admin/src/css/components/_dropdown.css +26 -8
  87. django_smartbase_admin/static/sb_admin/src/css/components/_input.css +62 -20
  88. django_smartbase_admin/static/sb_admin/src/css/components/_modal.css +1 -1
  89. django_smartbase_admin/static/sb_admin/src/css/components/_query-builder.css +21 -2
  90. django_smartbase_admin/static/sb_admin/src/css/components/_toggle.css +12 -1
  91. django_smartbase_admin/static/sb_admin/src/css/components/_tooltip.css +8 -22
  92. django_smartbase_admin/static/sb_admin/src/css/style.css +17 -0
  93. django_smartbase_admin/static/sb_admin/src/css/tree_widget.css +411 -0
  94. django_smartbase_admin/static/sb_admin/src/js/autocomplete.js +63 -5
  95. django_smartbase_admin/static/sb_admin/src/js/calendar.js +56 -0
  96. django_smartbase_admin/static/sb_admin/src/js/chart.js +8 -22
  97. django_smartbase_admin/static/sb_admin/src/js/choices.js +18 -8
  98. django_smartbase_admin/static/sb_admin/src/js/datepicker.js +97 -336
  99. django_smartbase_admin/static/sb_admin/src/js/datepicker_plugins.js +357 -0
  100. django_smartbase_admin/static/sb_admin/src/js/main.js +304 -31
  101. django_smartbase_admin/static/sb_admin/src/js/multiselect.js +50 -41
  102. django_smartbase_admin/static/sb_admin/src/js/range.js +3 -2
  103. django_smartbase_admin/static/sb_admin/src/js/table.js +34 -5
  104. django_smartbase_admin/static/sb_admin/src/js/table_modules/advanced_filter_module.js +43 -20
  105. django_smartbase_admin/static/sb_admin/src/js/table_modules/data_edit_module.js +8 -10
  106. django_smartbase_admin/static/sb_admin/src/js/table_modules/filter_module.js +3 -3
  107. django_smartbase_admin/static/sb_admin/src/js/table_modules/header_tabs_module.js +11 -11
  108. django_smartbase_admin/static/sb_admin/src/js/table_modules/selection_module.js +28 -8
  109. django_smartbase_admin/static/sb_admin/src/js/table_modules/table_params_module.js +6 -0
  110. django_smartbase_admin/static/sb_admin/src/js/table_modules/views_module.js +6 -0
  111. django_smartbase_admin/static/sb_admin/src/js/tree_widget.js +406 -0
  112. django_smartbase_admin/static/sb_admin/src/js/utils.js +56 -21
  113. django_smartbase_admin/templates/sb_admin/actions/change_form.html +169 -114
  114. django_smartbase_admin/templates/sb_admin/actions/dashboard.html +2 -2
  115. django_smartbase_admin/templates/sb_admin/actions/list.html +79 -39
  116. django_smartbase_admin/templates/sb_admin/actions/partials/action_link.html +14 -0
  117. django_smartbase_admin/templates/sb_admin/actions/partials/tabulator_header_v2.html +2 -2
  118. django_smartbase_admin/templates/sb_admin/actions/tree_list.html +63 -0
  119. django_smartbase_admin/templates/sb_admin/authentification/login_base.html +5 -1
  120. django_smartbase_admin/templates/sb_admin/components/columns.html +1 -1
  121. django_smartbase_admin/templates/sb_admin/components/filters_v2.html +99 -85
  122. django_smartbase_admin/templates/sb_admin/dashboard/calendar_widget.html +69 -0
  123. django_smartbase_admin/templates/sb_admin/dashboard/chart_widget.html +21 -2
  124. django_smartbase_admin/templates/sb_admin/dashboard/list_widget.html +6 -0
  125. django_smartbase_admin/templates/sb_admin/dashboard/widget_base.html +1 -1
  126. django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/date_field.html +18 -8
  127. django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/multiple_choice_field.html +1 -1
  128. django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/tree_select_filter.html +2 -0
  129. django_smartbase_admin/templates/sb_admin/filter_widgets/date_field.html +18 -4
  130. django_smartbase_admin/templates/sb_admin/filter_widgets/multiple_choice_field.html +14 -0
  131. django_smartbase_admin/templates/sb_admin/filter_widgets/partials/clear.html +10 -5
  132. django_smartbase_admin/templates/sb_admin/filter_widgets/radio_choice_field.html +2 -2
  133. django_smartbase_admin/templates/sb_admin/filter_widgets/tree_select_filter.html +16 -0
  134. django_smartbase_admin/templates/sb_admin/includes/change_form_title.html +3 -1
  135. django_smartbase_admin/templates/sb_admin/includes/inline_fieldset.html +48 -39
  136. django_smartbase_admin/templates/sb_admin/includes/notifications.html +2 -1
  137. django_smartbase_admin/templates/sb_admin/includes/readonly_boolean_field.html +9 -0
  138. django_smartbase_admin/templates/sb_admin/includes/readonly_field.html +12 -0
  139. django_smartbase_admin/templates/sb_admin/includes/table_inline_delete_button.html +4 -5
  140. django_smartbase_admin/templates/sb_admin/inlines/stacked_inline.html +68 -40
  141. django_smartbase_admin/templates/sb_admin/inlines/table_inline.html +76 -34
  142. django_smartbase_admin/templates/sb_admin/integrations/filer/folder_list.html +18 -0
  143. django_smartbase_admin/templates/sb_admin/navigation.html +166 -158
  144. django_smartbase_admin/templates/sb_admin/partials/modal/modal_content.html +2 -6
  145. django_smartbase_admin/templates/sb_admin/sb_admin_base.html +49 -4
  146. django_smartbase_admin/templates/sb_admin/sb_admin_base_no_sidebar.html +27 -11
  147. django_smartbase_admin/templates/sb_admin/sb_admin_js_trans.html +3 -0
  148. django_smartbase_admin/templates/sb_admin/sprites/sb_admin.svg +1 -1
  149. django_smartbase_admin/templates/sb_admin/tailwind_whitelist.html +6 -3
  150. django_smartbase_admin/templates/sb_admin/widgets/array.html +0 -1
  151. django_smartbase_admin/templates/sb_admin/widgets/attributes.html +68 -0
  152. django_smartbase_admin/templates/sb_admin/widgets/autocomplete.html +13 -2
  153. django_smartbase_admin/templates/sb_admin/widgets/{checkbox_select.html → checkbox_dropdown.html} +2 -2
  154. django_smartbase_admin/templates/sb_admin/widgets/clearable_file_input.html +2 -2
  155. django_smartbase_admin/templates/sb_admin/widgets/color_field.html +30 -0
  156. django_smartbase_admin/templates/sb_admin/widgets/date.html +8 -1
  157. django_smartbase_admin/templates/sb_admin/widgets/filer_file.html +84 -0
  158. django_smartbase_admin/templates/sb_admin/widgets/includes/related_item_buttons.html +38 -0
  159. django_smartbase_admin/templates/sb_admin/widgets/multiwidget.html +1 -1
  160. django_smartbase_admin/templates/sb_admin/widgets/radio.html +3 -2
  161. django_smartbase_admin/templates/sb_admin/widgets/radio_dropdown.html +30 -0
  162. django_smartbase_admin/templates/sb_admin/widgets/read_only_password_hash.html +3 -0
  163. django_smartbase_admin/templates/sb_admin/widgets/time.html +8 -1
  164. django_smartbase_admin/templates/sb_admin/widgets/toggle.html +1 -1
  165. django_smartbase_admin/templates/sb_admin/widgets/tree_base.html +59 -0
  166. django_smartbase_admin/templates/sb_admin/widgets/tree_select.html +24 -0
  167. django_smartbase_admin/templates/sb_admin/widgets/tree_select_inline.html +12 -0
  168. django_smartbase_admin/templatetags/sb_admin_tags.py +85 -4
  169. django_smartbase_admin/utils.py +22 -3
  170. django_smartbase_admin/views/dashboard_view.py +6 -0
  171. django_smartbase_admin/views/global_filter_view.py +8 -2
  172. django_smartbase_admin/views/translations_view.py +12 -5
  173. django_smartbase_admin/views/user_config_view.py +52 -0
  174. django_smartbase_admin-1.0.38.dist-info/METADATA +166 -0
  175. {django_smartbase_admin-0.2.54.dist-info → django_smartbase_admin-1.0.38.dist-info}/RECORD +177 -115
  176. {django_smartbase_admin-0.2.54.dist-info → django_smartbase_admin-1.0.38.dist-info}/WHEEL +1 -1
  177. django_smartbase_admin/templates/sb_admin/integrations/sorting/change_list.html +0 -401
  178. django_smartbase_admin-0.2.54.dist-info/METADATA +0 -25
  179. {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
- "label": shortcut["label"],
316
- "value": [
317
- now + timedelta(days=shortcut["value"][0]),
318
- now + timedelta(days=shortcut["value"][1]),
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 self.get_value_from_date_or_range(selected_shortcut_value)
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
- date_range = filter_value.split(cls.DATE_RANGE_SPLIT)
360
- if len(date_range) == 2:
361
- date_from = datetime.strptime(date_range[0], date_format)
362
- date_to = datetime.strptime(date_range[1], date_format)
363
- return [date_from, date_to]
364
- else:
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 f"{date_from}{cls.DATE_RANGE_SPLIT}{date_to}"
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
- return (
60
- self.url
61
- or (self.view.get_menu_view_url(request) if self.view else None)
62
- or ""
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
- "SBAdminReloadTableData",
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
- class Form(form_class):
31
- view = self.view
32
- threadsafe_request = self.request
31
+ fake_form_class = type(
32
+ form_class.__name__,
33
+ (form_class,),
34
+ {
35
+ "view": self.view,
36
+ },
37
+ )
33
38
 
34
- return Form
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 = SBAdminListAction(self.view, request)
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
+ }