django-smartbase-admin 0.2.47__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 (188) hide show
  1. django_smartbase_admin/actions/admin_action_list.py +80 -51
  2. django_smartbase_admin/actions/advanced_filters.py +55 -20
  3. django_smartbase_admin/admin/admin_base.py +477 -89
  4. django_smartbase_admin/admin/site.py +104 -34
  5. django_smartbase_admin/admin/widgets.py +598 -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 +253 -115
  9. django_smartbase_admin/engine/configuration.py +186 -4
  10. django_smartbase_admin/engine/const.py +7 -0
  11. django_smartbase_admin/engine/dashboard.py +44 -23
  12. django_smartbase_admin/engine/fake_inline.py +44 -7
  13. django_smartbase_admin/engine/field.py +54 -10
  14. django_smartbase_admin/engine/field_formatter.py +32 -9
  15. django_smartbase_admin/engine/filter_widgets.py +356 -21
  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 +82 -27
  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/table.js.LICENSE.txt +9 -0
  47. django_smartbase_admin/static/sb_admin/dist/tree_widget.js +1 -0
  48. django_smartbase_admin/static/sb_admin/dist/tree_widget_style.css +1 -0
  49. django_smartbase_admin/static/sb_admin/dist/tree_widget_style.js +0 -0
  50. django_smartbase_admin/static/sb_admin/fancytree/jquery.fancytree-all-deps.min.js +1 -0
  51. django_smartbase_admin/static/sb_admin/images/file_types/file-csv.svg +11 -0
  52. django_smartbase_admin/static/sb_admin/images/file_types/file-doc.svg +11 -0
  53. django_smartbase_admin/static/sb_admin/images/file_types/file-docx.svg +11 -0
  54. django_smartbase_admin/static/sb_admin/images/file_types/file-other.svg +13 -0
  55. django_smartbase_admin/static/sb_admin/images/file_types/file-pdf.svg +11 -0
  56. django_smartbase_admin/static/sb_admin/images/file_types/file-ppt.svg +11 -0
  57. django_smartbase_admin/static/sb_admin/images/file_types/file-xls.svg +11 -0
  58. django_smartbase_admin/static/sb_admin/images/file_types/file-xlsx.svg +11 -0
  59. django_smartbase_admin/static/sb_admin/images/file_types/file-zip.svg +18 -0
  60. django_smartbase_admin/static/sb_admin/images/flags/de-at.png +0 -0
  61. django_smartbase_admin/static/sb_admin/images/flags/de-ch.png +0 -0
  62. django_smartbase_admin/static/sb_admin/images/logo_light.svg +21 -0
  63. django_smartbase_admin/static/sb_admin/js/coloris/coloris.min.js +6 -0
  64. django_smartbase_admin/static/sb_admin/js/fullcalendar.min.js +14804 -0
  65. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Bolt-one.svg +3 -0
  66. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Calendar.svg +3 -0
  67. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Caution.svg +3 -0
  68. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Electric-drill.svg +3 -0
  69. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Fire-extinguisher.svg +3 -0
  70. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Gas.svg +3 -0
  71. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Lightning-fill.svg +3 -0
  72. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Moon.svg +3 -0
  73. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Phone-telephone.svg +3 -0
  74. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Printer.svg +3 -0
  75. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Pull.svg +3 -0
  76. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Sun-one.svg +3 -0
  77. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Time.svg +3 -0
  78. django_smartbase_admin/static/sb_admin/src/css/_base.css +5 -1
  79. django_smartbase_admin/static/sb_admin/src/css/_colors.css +257 -82
  80. django_smartbase_admin/static/sb_admin/src/css/_components.css +66 -13
  81. django_smartbase_admin/static/sb_admin/src/css/_datepicker.css +8 -1
  82. django_smartbase_admin/static/sb_admin/src/css/_filer.css +60 -0
  83. django_smartbase_admin/static/sb_admin/src/css/_inlines.css +51 -10
  84. django_smartbase_admin/static/sb_admin/src/css/_tabulator.css +8 -2
  85. django_smartbase_admin/static/sb_admin/src/css/calendar.css +162 -0
  86. django_smartbase_admin/static/sb_admin/src/css/components/_button.css +41 -1
  87. django_smartbase_admin/static/sb_admin/src/css/components/_dropdown.css +26 -8
  88. django_smartbase_admin/static/sb_admin/src/css/components/_input.css +62 -20
  89. django_smartbase_admin/static/sb_admin/src/css/components/_modal.css +1 -1
  90. django_smartbase_admin/static/sb_admin/src/css/components/_query-builder.css +21 -2
  91. django_smartbase_admin/static/sb_admin/src/css/components/_toggle.css +12 -1
  92. django_smartbase_admin/static/sb_admin/src/css/components/_tooltip.css +8 -22
  93. django_smartbase_admin/static/sb_admin/src/css/style.css +17 -0
  94. django_smartbase_admin/static/sb_admin/src/css/tree_widget.css +411 -0
  95. django_smartbase_admin/static/sb_admin/src/js/autocomplete.js +63 -5
  96. django_smartbase_admin/static/sb_admin/src/js/calendar.js +56 -0
  97. django_smartbase_admin/static/sb_admin/src/js/chart.js +8 -22
  98. django_smartbase_admin/static/sb_admin/src/js/choices.js +18 -8
  99. django_smartbase_admin/static/sb_admin/src/js/datepicker.js +97 -336
  100. django_smartbase_admin/static/sb_admin/src/js/datepicker_plugins.js +357 -0
  101. django_smartbase_admin/static/sb_admin/src/js/main.js +307 -26
  102. django_smartbase_admin/static/sb_admin/src/js/multiselect.js +50 -41
  103. django_smartbase_admin/static/sb_admin/src/js/range.js +3 -2
  104. django_smartbase_admin/static/sb_admin/src/js/sb_ajax_params_tabulator_modifier.js +21 -0
  105. django_smartbase_admin/static/sb_admin/src/js/table.js +38 -13
  106. django_smartbase_admin/static/sb_admin/src/js/table_modules/advanced_filter_module.js +43 -20
  107. django_smartbase_admin/static/sb_admin/src/js/table_modules/data_edit_module.js +8 -10
  108. django_smartbase_admin/static/sb_admin/src/js/table_modules/filter_module.js +3 -3
  109. django_smartbase_admin/static/sb_admin/src/js/table_modules/header_tabs_module.js +11 -11
  110. django_smartbase_admin/static/sb_admin/src/js/table_modules/selection_module.js +28 -8
  111. django_smartbase_admin/static/sb_admin/src/js/table_modules/table_params_module.js +6 -0
  112. django_smartbase_admin/static/sb_admin/src/js/table_modules/views_module.js +19 -3
  113. django_smartbase_admin/static/sb_admin/src/js/tree_widget.js +406 -0
  114. django_smartbase_admin/static/sb_admin/src/js/utils.js +56 -21
  115. django_smartbase_admin/templates/sb_admin/actions/change_form.html +169 -117
  116. django_smartbase_admin/templates/sb_admin/actions/dashboard.html +2 -2
  117. django_smartbase_admin/templates/sb_admin/actions/delete_selected_confirmation.html +56 -32
  118. django_smartbase_admin/templates/sb_admin/actions/list.html +79 -42
  119. django_smartbase_admin/templates/sb_admin/actions/object_history.html +2 -2
  120. django_smartbase_admin/templates/sb_admin/actions/partials/action_link.html +14 -0
  121. django_smartbase_admin/templates/sb_admin/actions/partials/selected_rows_actions.html +2 -2
  122. django_smartbase_admin/templates/sb_admin/actions/partials/tabulator_header_v2.html +2 -2
  123. django_smartbase_admin/templates/sb_admin/actions/tree_list.html +63 -0
  124. django_smartbase_admin/templates/sb_admin/authentification/login_base.html +5 -1
  125. django_smartbase_admin/templates/sb_admin/components/columns.html +1 -1
  126. django_smartbase_admin/templates/sb_admin/components/filters_v2.html +99 -85
  127. django_smartbase_admin/templates/sb_admin/config/view.html +0 -1
  128. django_smartbase_admin/templates/sb_admin/dashboard/calendar_widget.html +69 -0
  129. django_smartbase_admin/templates/sb_admin/dashboard/chart_widget.html +21 -2
  130. django_smartbase_admin/templates/sb_admin/dashboard/list_widget.html +6 -0
  131. django_smartbase_admin/templates/sb_admin/dashboard/widget_base.html +1 -1
  132. django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/date_field.html +18 -8
  133. django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/multiple_choice_field.html +1 -1
  134. django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/tree_select_filter.html +2 -0
  135. django_smartbase_admin/templates/sb_admin/filter_widgets/date_field.html +18 -4
  136. django_smartbase_admin/templates/sb_admin/filter_widgets/multiple_choice_field.html +14 -0
  137. django_smartbase_admin/templates/sb_admin/filter_widgets/partials/clear.html +10 -5
  138. django_smartbase_admin/templates/sb_admin/filter_widgets/radio_choice_field.html +2 -2
  139. django_smartbase_admin/templates/sb_admin/filter_widgets/tree_select_filter.html +16 -0
  140. django_smartbase_admin/templates/sb_admin/includes/change_form_title.html +3 -1
  141. django_smartbase_admin/templates/sb_admin/includes/components.html +5 -1
  142. django_smartbase_admin/templates/sb_admin/includes/inline_fieldset.html +48 -39
  143. django_smartbase_admin/templates/sb_admin/includes/notifications.html +2 -1
  144. django_smartbase_admin/templates/sb_admin/includes/readonly_boolean_field.html +9 -0
  145. django_smartbase_admin/templates/sb_admin/includes/readonly_field.html +12 -0
  146. django_smartbase_admin/templates/sb_admin/includes/table_inline_delete_button.html +4 -5
  147. django_smartbase_admin/templates/sb_admin/inlines/stacked_inline.html +68 -40
  148. django_smartbase_admin/templates/sb_admin/inlines/table_inline.html +78 -36
  149. django_smartbase_admin/templates/sb_admin/integrations/filer/folder_list.html +18 -0
  150. django_smartbase_admin/templates/sb_admin/navigation.html +166 -158
  151. django_smartbase_admin/templates/sb_admin/partials/modal/modal_content.html +2 -6
  152. django_smartbase_admin/templates/sb_admin/sb_admin_base.html +49 -4
  153. django_smartbase_admin/templates/sb_admin/sb_admin_base_no_sidebar.html +35 -11
  154. django_smartbase_admin/templates/sb_admin/sb_admin_js_trans.html +3 -0
  155. django_smartbase_admin/templates/sb_admin/sprites/sb_admin.svg +1 -1
  156. django_smartbase_admin/templates/sb_admin/tailwind_whitelist.html +6 -3
  157. django_smartbase_admin/templates/sb_admin/widgets/array.html +0 -1
  158. django_smartbase_admin/templates/sb_admin/widgets/attributes.html +68 -0
  159. django_smartbase_admin/templates/sb_admin/widgets/autocomplete.html +13 -2
  160. django_smartbase_admin/templates/sb_admin/widgets/{checkbox_select.html → checkbox_dropdown.html} +2 -2
  161. django_smartbase_admin/templates/sb_admin/widgets/checkbox_group.html +15 -0
  162. django_smartbase_admin/templates/sb_admin/widgets/clearable_file_input.html +2 -2
  163. django_smartbase_admin/templates/sb_admin/widgets/color_field.html +30 -0
  164. django_smartbase_admin/templates/sb_admin/widgets/date.html +8 -1
  165. django_smartbase_admin/templates/sb_admin/widgets/filer_file.html +84 -0
  166. django_smartbase_admin/templates/sb_admin/widgets/html_read_only.html +1 -0
  167. django_smartbase_admin/templates/sb_admin/widgets/includes/related_item_buttons.html +38 -0
  168. django_smartbase_admin/templates/sb_admin/widgets/multiwidget.html +1 -1
  169. django_smartbase_admin/templates/sb_admin/widgets/radio.html +3 -2
  170. django_smartbase_admin/templates/sb_admin/widgets/radio_dropdown.html +30 -0
  171. django_smartbase_admin/templates/sb_admin/widgets/read_only_password_hash.html +3 -0
  172. django_smartbase_admin/templates/sb_admin/widgets/time.html +8 -1
  173. django_smartbase_admin/templates/sb_admin/widgets/toggle.html +1 -1
  174. django_smartbase_admin/templates/sb_admin/widgets/tree_base.html +59 -0
  175. django_smartbase_admin/templates/sb_admin/widgets/tree_select.html +24 -0
  176. django_smartbase_admin/templates/sb_admin/widgets/tree_select_inline.html +12 -0
  177. django_smartbase_admin/templatetags/sb_admin_tags.py +163 -4
  178. django_smartbase_admin/utils.py +22 -3
  179. django_smartbase_admin/views/dashboard_view.py +6 -0
  180. django_smartbase_admin/views/global_filter_view.py +8 -2
  181. django_smartbase_admin/views/translations_view.py +12 -5
  182. django_smartbase_admin/views/user_config_view.py +52 -0
  183. django_smartbase_admin-1.0.38.dist-info/METADATA +166 -0
  184. {django_smartbase_admin-0.2.47.dist-info → django_smartbase_admin-1.0.38.dist-info}/RECORD +186 -121
  185. {django_smartbase_admin-0.2.47.dist-info → django_smartbase_admin-1.0.38.dist-info}/WHEEL +1 -1
  186. django_smartbase_admin/templates/sb_admin/integrations/sorting/change_list.html +0 -401
  187. django_smartbase_admin-0.2.47.dist-info/METADATA +0 -25
  188. {django_smartbase_admin-0.2.47.dist-info → django_smartbase_admin-1.0.38.dist-info}/LICENSE.md +0 -0
@@ -1,8 +1,18 @@
1
+ from enum import Enum
2
+
1
3
  from django.template.defaultfilters import date, time
4
+ from django.utils.safestring import mark_safe
2
5
  from django.utils.translation import gettext_lazy as _
3
6
  from django.utils import timezone
4
7
 
5
8
 
9
+ class BadgeType(Enum):
10
+ SUCCESS = "positive"
11
+ NOTICE = "notice"
12
+ WARNING = "warning"
13
+ ERROR = "negative"
14
+
15
+
6
16
  def datetime_formatter(object_id, value):
7
17
  if value is None:
8
18
  return None
@@ -12,27 +22,38 @@ def datetime_formatter(object_id, value):
12
22
 
13
23
  def datetime_formatter_with_format(date_format=None, time_format=None):
14
24
  def inner_formatter(object_id, value):
25
+ if value is None:
26
+ return None
15
27
  value = timezone.localtime(value)
16
- return f"{date(value, date_format)} {time(value, time_format)}"
28
+ return_value = ""
29
+ if date_format:
30
+ return_value += date(value, date_format)
31
+ if time_format:
32
+ if return_value:
33
+ return_value += " "
34
+ return_value += time(value, time_format)
35
+ return return_value
17
36
 
18
37
  return inner_formatter
19
38
 
20
39
 
21
40
  def boolean_formatter(object_id, value):
22
41
  if value:
23
- return f'<span class="badge badge-simple badge-positive">{_("Yes")}</span>'
24
- return f'<span class="badge badge-simple badge-neutral">{_("No")}</span>'
42
+ return mark_safe(
43
+ f'<span class="badge badge-simple badge-positive">{_("Yes")}</span>'
44
+ )
45
+ return mark_safe(f'<span class="badge badge-simple badge-neutral">{_("No")}</span>')
25
46
 
26
47
 
27
- def format_array(value_list, separator=""):
48
+ def format_array(value_list, separator="", badge_type: BadgeType = BadgeType.NOTICE):
28
49
  result = ""
29
50
  if not value_list:
30
51
  return result
31
52
  for value in value_list:
32
53
  if not value:
33
54
  continue
34
- result += f'<span class="badge badge-simple badge-notice mr-4">{value}</span>{separator}'
35
- return result
55
+ result += f'<span class="badge badge-simple badge-{badge_type.value} mr-4">{value}</span>{separator}'
56
+ return mark_safe(result)
36
57
 
37
58
 
38
59
  def array_badge_formatter(object_id, value_list):
@@ -40,12 +61,14 @@ def array_badge_formatter(object_id, value_list):
40
61
 
41
62
 
42
63
  def newline_separated_array_badge_formatter(object_id, value_list):
43
- return format_array(value_list, separator="<br>")
64
+ return mark_safe(f'<div>{format_array(value_list, separator="<br>")}</div>')
44
65
 
45
66
 
46
67
  def rich_text_formatter(object_id, value):
47
- return f'<div style="max-width: 500px; white-space: normal;">{value}</div>'
68
+ return mark_safe(
69
+ f'<div style="max-width: 500px; white-space: normal;">{value}</div>'
70
+ )
48
71
 
49
72
 
50
73
  def link_formatter(object_id, value):
51
- return f'<a href="{value}">{value}</a>'
74
+ return mark_safe(f'<a href="{value}">{value}</a>')
@@ -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.db.models import Q, fields, FilteredRelation
5
+ from django.contrib.postgres.fields import ArrayField
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
@@ -56,6 +74,7 @@ class SBAdminFilterWidget(JSONSerializableMixin):
56
74
  default_value = None
57
75
  default_label = None
58
76
  filter_query_lambda = None
77
+ exclude_null_operators = False
59
78
 
60
79
  def __init__(
61
80
  self,
@@ -63,6 +82,7 @@ class SBAdminFilterWidget(JSONSerializableMixin):
63
82
  default_value=None,
64
83
  default_label=None,
65
84
  filter_query_lambda=None,
85
+ exclude_null_operators=None,
66
86
  **kwargs,
67
87
  ) -> None:
68
88
  super().__init__()
@@ -70,6 +90,9 @@ class SBAdminFilterWidget(JSONSerializableMixin):
70
90
  self.default_value = self.default_value or default_value
71
91
  self.default_label = self.default_label or default_label
72
92
  self.filter_query_lambda = filter_query_lambda or self.filter_query_lambda
93
+ self.exclude_null_operators = (
94
+ exclude_null_operators or self.exclude_null_operators
95
+ )
73
96
 
74
97
  def init_filter_widget_static(self, field, view, configuration):
75
98
  self.field = field
@@ -206,6 +229,8 @@ class ChoiceFilterWidget(SBAdminFilterWidget):
206
229
  return found_label[0] if found_label else default_value
207
230
 
208
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]})
209
234
  return Q(**{self.field.filter_field: filter_value})
210
235
 
211
236
 
@@ -215,8 +240,38 @@ class RadioChoiceFilterWidget(ChoiceFilterWidget):
215
240
 
216
241
  class MultipleChoiceFilterWidget(AutocompleteParseMixin, ChoiceFilterWidget):
217
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
218
268
 
219
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
220
275
  return Q(**{f"{self.field.filter_field}__in": filter_value})
221
276
 
222
277
  def get_advanced_filter_operators(self):
@@ -279,6 +334,7 @@ class DateFilterWidget(SBAdminFilterWidget):
279
334
  "label": _("Last 12 months"),
280
335
  },
281
336
  ]
337
+ shortcuts_dict = {AllOperators.IN_THE_LAST.value: shortcuts}
282
338
  default_value_shortcut_index = None
283
339
 
284
340
  def __init__(
@@ -301,33 +357,43 @@ class DateFilterWidget(SBAdminFilterWidget):
301
357
  def get_advanced_filter_operators(self):
302
358
  return DATE_ATTRIBUTES
303
359
 
360
+ def process_shortcut(self, shortcut, now):
361
+ return shortcut
362
+
304
363
  def get_shortcuts(self):
305
364
  now = timezone.now()
306
365
  shortcuts = []
307
366
  for shortcut in self.shortcuts:
308
- shortcuts.append(
309
- {
310
- "label": shortcut["label"],
311
- "value": [
312
- now + timedelta(days=shortcut["value"][0]),
313
- now + timedelta(days=shortcut["value"][1]),
314
- ],
315
- }
316
- )
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))
317
377
  return shortcuts
318
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
+
319
384
  def get_default_value(self):
320
385
  if self.default_value_shortcut_index is not None:
321
386
  selected_shortcut_value = self.get_shortcuts()[
322
387
  self.default_value_shortcut_index
323
388
  ]["value"]
324
- 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
+ )
325
392
  return super().get_default_value()
326
393
 
327
394
  def get_data(self):
328
395
  return json.dumps(
329
396
  {
330
- "shortcuts": self.get_shortcuts(),
331
397
  "flatpickrOptions": {
332
398
  "locale": {
333
399
  "rangeSeparator": self.DATE_RANGE_SPLIT,
@@ -337,6 +403,19 @@ class DateFilterWidget(SBAdminFilterWidget):
337
403
  cls=SBAdminJSONEncoder,
338
404
  )
339
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
+
340
419
  @classmethod
341
420
  def is_used_for_model_field_type(cls, model_field):
342
421
  return isinstance(model_field, fields.DateField)
@@ -351,12 +430,28 @@ class DateFilterWidget(SBAdminFilterWidget):
351
430
  if filter_value is None:
352
431
  return [None, None]
353
432
  date_format = cls.DATE_FORMAT
354
- date_range = filter_value.split(cls.DATE_RANGE_SPLIT)
355
- if len(date_range) == 2:
356
- date_from = datetime.strptime(date_range[0], date_format)
357
- date_to = datetime.strptime(date_range[1], date_format)
358
- return [date_from, date_to]
359
- 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]
360
455
  date_value = datetime.strptime(filter_value, date_format)
361
456
  return [date_value, date_value]
362
457
 
@@ -364,9 +459,11 @@ class DateFilterWidget(SBAdminFilterWidget):
364
459
  def get_value_from_date_or_range(cls, date_or_range):
365
460
  if not isinstance(date_or_range, list):
366
461
  return datetime.strftime(date_or_range, cls.DATE_FORMAT)
462
+ if type(date_or_range[0]) is int:
463
+ return date_or_range
367
464
  date_from = datetime.strftime(date_or_range[0], cls.DATE_FORMAT)
368
465
  date_to = datetime.strftime(date_or_range[1], cls.DATE_FORMAT)
369
- return f"{date_from}{cls.DATE_RANGE_SPLIT}{date_to}"
466
+ return [date_from, date_to]
370
467
 
371
468
  def parse_value_from_input(self, request, filter_value):
372
469
  return self.get_range_from_value(filter_value)
@@ -398,6 +495,7 @@ class AutocompleteFilterWidget(
398
495
  allow_add = False
399
496
  hide_clear_button = False
400
497
  search_query_lambda = None
498
+ create_value_field = None
401
499
 
402
500
  def get_field_name(self):
403
501
  return self.field.name
@@ -420,13 +518,16 @@ class AutocompleteFilterWidget(
420
518
  allow_add=None,
421
519
  hide_clear_button=None,
422
520
  search_query_lambda=None,
521
+ create_value_field=None,
423
522
  **kwargs,
424
523
  ) -> None:
425
524
  super().__init__(template_name, default_value, **kwargs)
426
525
  self.model = model or self.model
427
526
  self.value_field = value_field or self.value_field
428
527
  self.filter_query_lambda = filter_query_lambda or self.filter_query_lambda
528
+ # filters queryset to search in
429
529
  self.filter_search_lambda = filter_search_lambda or self.filter_search_lambda
530
+ # defines fields to search on
430
531
  self.search_query_lambda = search_query_lambda or self.search_query_lambda
431
532
  self.label_lambda = label_lambda or self.label_lambda
432
533
  self.value_lambda = value_lambda or self.value_lambda
@@ -434,6 +535,7 @@ class AutocompleteFilterWidget(
434
535
  self.multiselect = self.multiselect if self.multiselect is not None else True
435
536
  self.forward = forward or self.forward
436
537
  self.allow_add = allow_add or self.allow_add
538
+ self.create_value_field = create_value_field or self.create_value_field
437
539
  self.hide_clear_button = (
438
540
  hide_clear_button
439
541
  if hide_clear_button is not None
@@ -542,8 +644,9 @@ class AutocompleteFilterWidget(
542
644
  def get_value_field(self):
543
645
  return self.value_field or self.model._meta.pk.name
544
646
 
545
- def filter_search_queryset(self, request, qs, search_term, forward_data):
647
+ def filter_search_queryset(self, request, qs, search_term="", forward_data=None):
546
648
  if self.filter_search_lambda:
649
+ forward_data = forward_data or {}
547
650
  qs = qs.filter(
548
651
  self.filter_search_lambda(request, search_term, forward_data)
549
652
  )
@@ -555,9 +658,16 @@ class AutocompleteFilterWidget(
555
658
  page_num = int(post_data.get(AUTOCOMPLETE_PAGE_NUM, 1))
556
659
  from_item = (page_num - 1) * AUTOCOMPLETE_PAGE_SIZE
557
660
  to_item = (page_num) * AUTOCOMPLETE_PAGE_SIZE
661
+
662
+ # filter queryset
663
+ # base restricted queryset
558
664
  qs = self.get_queryset(request)
665
+ # filters queryset to search in, uses filter_search_lambda
559
666
  qs = self.filter_search_queryset(request, qs, search_term, forward_data)
667
+
668
+ # search in queryset
560
669
  if self.search_query_lambda:
670
+ # defines fields to search on
561
671
  qs = self.search_query_lambda(
562
672
  request,
563
673
  qs,
@@ -566,6 +676,7 @@ class AutocompleteFilterWidget(
566
676
  SBAdminTranslationsService.get_main_lang_code(),
567
677
  )
568
678
  else:
679
+ # defines default fields to search on - all char fields
569
680
  qs = self.get_default_search_query(
570
681
  request,
571
682
  qs,
@@ -593,6 +704,8 @@ class AutocompleteFilterWidget(
593
704
  def get_label(self, request, item):
594
705
  if self.label_lambda:
595
706
  return self.label_lambda(request, item)
707
+ if isinstance(item, list):
708
+ return ", ".join(map(str, item))
596
709
  return str(item)
597
710
 
598
711
  def get_context(self, name, value, attrs):
@@ -611,3 +724,225 @@ class AutocompleteFilterWidget(
611
724
  AllOperators.IS_NULL,
612
725
  AllOperators.IS_NOT_NULL,
613
726
  ]
727
+
728
+
729
+ class FromValuesAutocompleteWidget(AutocompleteFilterWidget):
730
+ def init_filter_widget_static(self, field, view, configuration):
731
+ self.model = field.view.model
732
+ super().init_filter_widget_static(field, view, configuration)
733
+
734
+ def get_queryset(self, request=None):
735
+ qs = super().get_queryset(request)
736
+ qs = qs.annotate(**self.field.get_field_annotates(values=[]))
737
+ return qs
738
+
739
+ def search(self, request, post_data):
740
+ search_term = post_data.get(AUTOCOMPLETE_SEARCH_NAME)
741
+ forward_data = json.loads(post_data.get(AUTOCOMPLETE_FORWARD_NAME, "{}"))
742
+ page_num = int(post_data.get(AUTOCOMPLETE_PAGE_NUM, 1))
743
+ from_item = (page_num - 1) * AUTOCOMPLETE_PAGE_SIZE
744
+ to_item = (page_num) * AUTOCOMPLETE_PAGE_SIZE
745
+ qs = self.get_queryset(request)
746
+ qs = self.filter_search_queryset(request, qs, search_term, forward_data)
747
+ qs = (
748
+ qs.filter(**{f"{self.field.name}__icontains": search_term})
749
+ .values(self.field.name)
750
+ .annotate(remove_duplicates_count=Count(self.field.name))
751
+ .order_by(self.field.name)[from_item:to_item]
752
+ )
753
+ result = []
754
+ for item in qs:
755
+ result.append(
756
+ {
757
+ "value": self.get_value(request, item),
758
+ "label": self.get_label(request, item),
759
+ }
760
+ )
761
+ return result
762
+
763
+ def get_value(self, request, item):
764
+ return item.get(self.field.name)
765
+
766
+ def get_label(self, request, item):
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)