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
@@ -1,12 +1,20 @@
1
- from copy import deepcopy
2
-
3
1
  from django.contrib.auth import get_permission_codename
2
+ from django.contrib.auth.views import LoginView
4
3
  from django.db.models import Q
5
4
 
6
5
  from django_smartbase_admin.admin.site import sb_admin_site
7
6
  from django_smartbase_admin.engine.actions import SBAdminCustomAction
8
- from django_smartbase_admin.engine.const import GLOBAL_FILTER_DATA_KEY, FilterVersions
9
- from django_smartbase_admin.utils import to_list
7
+ from django_smartbase_admin.engine.const import (
8
+ GLOBAL_FILTER_DATA_KEY,
9
+ FilterVersions,
10
+ Action,
11
+ )
12
+ from django_smartbase_admin.models import (
13
+ ColorScheme,
14
+ SBAdminListViewConfiguration,
15
+ SBAdminUserConfiguration,
16
+ )
17
+ from django_smartbase_admin.utils import to_list, is_modal
10
18
 
11
19
 
12
20
  class SBAdminConfigurationBase(object):
@@ -19,6 +27,145 @@ class SBAdminConfigurationBase(object):
19
27
  def get_configuration_for_roles(self, user_roles):
20
28
  raise NotImplementedError
21
29
 
30
+ # User configuration hooks - override these methods to customize user identification
31
+ # (e.g., use email instead of user_id for OAuth/external auth scenarios)
32
+
33
+ @classmethod
34
+ def get_user_config(cls, request):
35
+ """
36
+ Get or create user configuration (e.g., color scheme preferences).
37
+
38
+ Override this method to customize user identification. Default uses user_id.
39
+
40
+ Example for email-based users::
41
+
42
+ @classmethod
43
+ def get_user_config(cls, request):
44
+ from myapp.models import MyUserConfig
45
+ email = getattr(request.user, "email", None)
46
+ if not email:
47
+ return MyUserConfig(email="anonymous", color_scheme=ColorScheme.AUTO.value)
48
+ config, _ = MyUserConfig.objects.get_or_create(
49
+ email=email,
50
+ defaults={"color_scheme": request.request_data.configuration.default_color_scheme},
51
+ )
52
+ return config
53
+ """
54
+ if not request.user or request.user.is_anonymous:
55
+ return None
56
+ user_config, _ = SBAdminUserConfiguration.objects.get_or_create(
57
+ defaults={
58
+ "color_scheme": request.request_data.configuration.default_color_scheme
59
+ },
60
+ user_id=request.user.id,
61
+ )
62
+ return user_config
63
+
64
+ @classmethod
65
+ def get_saved_views(cls, request, view_id):
66
+ """
67
+ Get saved views for the current user and view.
68
+
69
+ Override this method to customize user identification. Default uses user_id.
70
+ Returns a list of dicts with keys: id, name, url_params, view (view_id).
71
+
72
+ Example for email-based users::
73
+
74
+ @classmethod
75
+ def get_saved_views(cls, request, view_id):
76
+ from myapp.models import MySavedView
77
+ email = getattr(request.user, "email", None)
78
+ if not email:
79
+ return []
80
+ return list(
81
+ MySavedView.objects.filter(email=email, view_id=view_id)
82
+ .values("id", "name", "config", "view_id")
83
+ )
84
+ """
85
+ if not request.user or request.user.is_anonymous:
86
+ return []
87
+ return list(
88
+ SBAdminListViewConfiguration.objects.by_user_id(request.user.id)
89
+ .by_view_action_modifier(view=view_id)
90
+ .values()
91
+ )
92
+
93
+ @classmethod
94
+ def create_or_update_saved_view(
95
+ cls, request, view_id, config_id, config_name, url_params
96
+ ):
97
+ """
98
+ Create or update a saved view for the current user.
99
+
100
+ Override this method to customize user identification. Default uses user_id.
101
+ Returns the created/updated saved view object.
102
+
103
+ Example for email-based users::
104
+
105
+ @classmethod
106
+ def create_or_update_saved_view(cls, request, view_id, config_id, config_name, url_params):
107
+ from myapp.models import MySavedView
108
+ email = getattr(request.user, "email", None)
109
+ if not email:
110
+ return None
111
+ config_params = {}
112
+ if config_id:
113
+ config_params["id"] = config_id
114
+ if config_name:
115
+ config_params["name"] = config_name
116
+ saved_view, _ = MySavedView.objects.update_or_create(
117
+ email=email,
118
+ **config_params,
119
+ defaults={"config": {"url_params": url_params}, "view_id": view_id},
120
+ )
121
+ return saved_view
122
+ """
123
+ if not request.user or request.user.is_anonymous:
124
+ return None
125
+ config_params = {}
126
+ if config_id:
127
+ config_params["id"] = config_id
128
+ elif config_name:
129
+ config_params["name"] = config_name
130
+ if not config_params:
131
+ return None
132
+ saved_view, _ = SBAdminListViewConfiguration.objects.update_or_create(
133
+ user_id=request.user.id,
134
+ **config_params,
135
+ defaults={
136
+ "url_params": url_params,
137
+ "view": view_id,
138
+ "action": None,
139
+ "modifier": None,
140
+ },
141
+ )
142
+ return saved_view
143
+
144
+ @classmethod
145
+ def delete_saved_view(cls, request, view_id, config_id):
146
+ """
147
+ Delete a saved view for the current user.
148
+
149
+ Override this method to customize user identification. Default uses user_id.
150
+
151
+ Example for email-based users::
152
+
153
+ @classmethod
154
+ def delete_saved_view(cls, request, view_id, config_id):
155
+ from myapp.models import MySavedView
156
+ email = getattr(request.user, "email", None)
157
+ if not email or not config_id:
158
+ return
159
+ MySavedView.objects.filter(
160
+ email=email, id=config_id, view_id=view_id
161
+ ).delete()
162
+ """
163
+ if not request.user or request.user.is_anonymous or not config_id:
164
+ return
165
+ SBAdminListViewConfiguration.objects.by_user_id(request.user.id).by_id(
166
+ config_id
167
+ ).by_view_action_modifier(view=view_id).delete()
168
+
22
169
 
23
170
  class Singleton(type):
24
171
  _instances = {}
@@ -37,6 +184,8 @@ class SBAdminRoleConfiguration(metaclass=Singleton):
37
184
  menu_items = None
38
185
  global_filter_form = None
39
186
  filters_version = FilterVersions.FILTERS_VERSION_1
187
+ default_color_scheme = ColorScheme.AUTO
188
+ login_view_class = LoginView
40
189
 
41
190
  def __init__(
42
191
  self,
@@ -45,6 +194,8 @@ class SBAdminRoleConfiguration(metaclass=Singleton):
45
194
  menu_items=None,
46
195
  global_filter_form=None,
47
196
  filters_version=None,
197
+ default_color_scheme=None,
198
+ login_view_class=None,
48
199
  ) -> None:
49
200
  super().__init__()
50
201
  self.default_view = default_view or self.default_view or []
@@ -54,6 +205,8 @@ class SBAdminRoleConfiguration(metaclass=Singleton):
54
205
  self.init_configuration_static()
55
206
  self.autocomplete_map = {}
56
207
  self.filters_version = filters_version or self.filters_version
208
+ self.default_color_scheme = default_color_scheme or self.default_color_scheme
209
+ self.login_view_class = login_view_class or self.login_view_class
57
210
 
58
211
  def init_registered_views(self):
59
212
  registered_views = []
@@ -84,6 +237,18 @@ class SBAdminRoleConfiguration(metaclass=Singleton):
84
237
  if hasattr(view, "get_id")
85
238
  }
86
239
  )
240
+ try:
241
+ from cms.plugin_pool import plugin_pool
242
+
243
+ for name, view in plugin_pool.plugins.items():
244
+ if hasattr(view, "get_id"):
245
+ view_instance = view(view.model, sb_admin_site)
246
+ self.view_map[view_instance.get_id()] = view_instance
247
+ view_instance.init_view_static(
248
+ self, view_instance.model, sb_admin_site
249
+ )
250
+ except ImportError:
251
+ pass
87
252
 
88
253
  def init_model_admin_view_map(self):
89
254
  for model, admin_view in sb_admin_site._registry.items():
@@ -132,6 +297,8 @@ class SBAdminRoleConfiguration(metaclass=Singleton):
132
297
 
133
298
  def has_action_permission(self, request, request_data, view, model, obj, action):
134
299
  if model:
300
+ if action.action_id == Action.BULK_DELETE.value:
301
+ return view.has_delete_permission(request, obj)
135
302
  return self.has_permission(
136
303
  request, request_data, view, model, obj, "view"
137
304
  ) or self.has_permission(request, request_data, view, model, obj, "change")
@@ -166,6 +333,9 @@ class SBAdminRoleConfiguration(metaclass=Singleton):
166
333
  form_field, model=model, multiselect=multiselect
167
334
  )
168
335
 
336
+ def get_filter_widget(self, field, default_widget):
337
+ return default_widget
338
+
169
339
  def get_form_field_widget_class(
170
340
  self, view, request, form_field, db_field, default_widget_class
171
341
  ):
@@ -204,3 +374,15 @@ class SBAdminRoleConfiguration(metaclass=Singleton):
204
374
  if global_filter_mapped_filter_key:
205
375
  filter_query &= Q(**{f"{global_filter_mapped_filter_key}": field_value})
206
376
  return qs.filter(filter_query)
377
+
378
+ def process_global_filter_response(self, response, request):
379
+ return response
380
+
381
+ def autocomplete_show_related_buttons(
382
+ self,
383
+ related_model,
384
+ field_name,
385
+ current_view,
386
+ request,
387
+ ) -> bool:
388
+ return not is_modal(request)
@@ -31,6 +31,7 @@ DEFAULT_PAGE_SIZE = 20
31
31
  PAGE_SIZE_OPTIONS = [10, 20, 50, 100]
32
32
  AUTOCOMPLETE_PAGE_SIZE = 20
33
33
  XLSX_PAGE_CHUNK_SIZE = 50000
34
+ IGNORE_LIST_SELECTION = "__all__"
34
35
  NEW_OBJECT_ID = 0
35
36
  OBJECT_ID_PLACEHOLDER = -1
36
37
  ALL_MODEL_FIELDS = "__all__"
@@ -52,6 +53,9 @@ TABLE_PARAMS_PAGE_NAME = "page"
52
53
  TABLE_PARAMS_SORT_NAME = "sort"
53
54
  TABLE_PARAMS_FULL_TEXT_SEARCH = "sb_admin_full_search"
54
55
  TABLE_PARAMS_SELECTED_FILTER_TYPE = "sb_selected_filter_type"
56
+ TABLE_TAB_ADVANCED_FITLERS = "tab_advanced_filters"
57
+ TABLE_RELOAD_DATA_EVENT_NAME = "SBAdminReloadTableData"
58
+ TABLE_UPDATE_ROW_DATA_EVENT_NAME = "SBAdminUpdateRowData"
55
59
  FILTER_DATA_NAME = "filterData"
56
60
  ADVANCED_FILTER_DATA_NAME = "advancedFilterData"
57
61
  BASE_PARAMS_NAME = "params"
@@ -69,3 +73,5 @@ DETAIL_STRUCTURE_RIGHT_CLASS = "detail-structure-right"
69
73
  TRANSLATIONS_SELECTED_LANGUAGES = "translation_selected_languages"
70
74
  OVERRIDE_CONTENT_OF_NOTIFICATION = "override_notification_content"
71
75
  FIELDSET_HIDE_HEADER_CLASS = "hide-header"
76
+ ROW_CLASS_FIELD = "get_sbadmin_row_class"
77
+ SUPPORTED_FILE_TYPE_ICONS = ["doc", "docx", "csv", "xls", "xlsx", "pdf", "ppt", "zip"]
@@ -1,7 +1,5 @@
1
- import pickle
2
- from copy import copy, deepcopy
1
+ from copy import copy
3
2
  from datetime import timedelta
4
-
5
3
  from django.core.cache import cache
6
4
  from django.db import models
7
5
  from django.db.models import QuerySet
@@ -17,7 +15,6 @@ from django_smartbase_admin.engine.admin_view import SBAdminView
17
15
  from django_smartbase_admin.engine.const import OBJECT_ID_PLACEHOLDER
18
16
  from django_smartbase_admin.engine.field import SBAdminField
19
17
  from django_smartbase_admin.engine.filter_widgets import (
20
- ChoiceFilterWidget,
21
18
  DateFilterWidget,
22
19
  RadioChoiceFilterWidget,
23
20
  )
@@ -85,6 +82,7 @@ class SBAdminDashboardWidget(SBAdminView):
85
82
  def get_widget_context_data(self, request):
86
83
  return {
87
84
  "widget_id": self.get_id(),
85
+ "widget_name": self.name,
88
86
  "ajax_url": self.get_ajax_url,
89
87
  "filters": self.get_filters(),
90
88
  "settings": self.get_settings(),
@@ -95,6 +93,12 @@ class SBAdminDashboardWidget(SBAdminView):
95
93
  def get_sub_widgets(self):
96
94
  return self.sub_widgets
97
95
 
96
+ def get_sub_views(self, configuration):
97
+ for idx, sub_widget_view in enumerate(self.sub_widgets):
98
+ sub_widget_view.widget_id = f"{self.get_id()}_{idx}"
99
+ sub_widget_view.init_widget_static(configuration)
100
+ return self.sub_widgets
101
+
98
102
  def get_template_name(self):
99
103
  return self.template_name
100
104
 
@@ -196,6 +200,12 @@ class SBAdminChartAggregateSubWidget(object):
196
200
  def render(self, request):
197
201
  return render_to_string(self.template_name, self.get_context_data(request))
198
202
 
203
+ def init_widget_static(self, configuration):
204
+ pass
205
+
206
+ def init_view_dynamic(self, request, request_data=None, **kwargs):
207
+ pass
208
+
199
209
 
200
210
  class SBAdminDashboardChartWidget(SBAdminDashboardWidget):
201
211
  template_name = "sb_admin/dashboard/chart_widget.html"
@@ -542,25 +552,28 @@ class SBAdminDashboardChartWidgetByDate(SBAdminDashboardChartWidget):
542
552
  self.date_annotate_field
543
553
  )
544
554
  )
545
- date_range_compare = date_range
546
- if compare == self.CompareOptions.COMPARE_PREVIOUS:
547
- period_length = (date_range_compare[1] - date_range_compare[0]).days + 1
548
- date_range_compare[0] = date_range_compare[0] - timedelta(
549
- days=period_length
550
- )
551
- date_range_compare[1] = date_range_compare[1] - timedelta(
552
- days=period_length
553
- )
554
- if compare == self.CompareOptions.COMPARE_PREVIOUS_YOY:
555
- date_range_compare[0] = date_range_compare[0].replace(
556
- year=date_range_compare[0].year - 1
557
- )
558
- date_range_compare[1] = date_range_compare[1].replace(
559
- year=date_range_compare[1].year - 1
560
- )
561
- request_data_modified_date_filter.request_get[self.date_annotate_field] = (
562
- DateFilterWidget.get_value_from_date_or_range(date_range_compare)
563
- )
555
+ if date_range[0] and date_range[1]:
556
+ date_range_compare = date_range
557
+ if compare == self.CompareOptions.COMPARE_PREVIOUS:
558
+ period_length = (
559
+ date_range_compare[1] - date_range_compare[0]
560
+ ).days + 1
561
+ date_range_compare[0] = date_range_compare[0] - timedelta(
562
+ days=period_length
563
+ )
564
+ date_range_compare[1] = date_range_compare[1] - timedelta(
565
+ days=period_length
566
+ )
567
+ if compare == self.CompareOptions.COMPARE_PREVIOUS_YOY:
568
+ date_range_compare[0] = date_range_compare[0].replace(
569
+ year=date_range_compare[0].year - 1
570
+ )
571
+ date_range_compare[1] = date_range_compare[1].replace(
572
+ year=date_range_compare[1].year - 1
573
+ )
574
+ request_data_modified_date_filter.request_get[
575
+ self.date_annotate_field
576
+ ] = DateFilterWidget.get_value_from_date_or_range(date_range_compare)
564
577
  queryset_with_modified_date = self.get_data_queryset(request_copy)
565
578
 
566
579
  sub_widget_data = {}
@@ -666,5 +679,13 @@ class SBAdminDashboardListWidget(SBAdminBaseListView, SBAdminDashboardWidget):
666
679
  "viewsModule",
667
680
  "tableParamsModule",
668
681
  "detailViewModule",
682
+ "filterModule",
669
683
  ]
670
684
  return tabulator_definition
685
+
686
+
687
+ class SbAdminCalendarWidget(SBAdminDashboardWidget):
688
+ template_name = "sb_admin/dashboard/calendar_widget.html"
689
+
690
+ def action_get_data(self, request, modifier):
691
+ return JsonResponse(data=self.get_cached_data(request), safe=False)
@@ -3,7 +3,9 @@ from django.db import models
3
3
  from django.db.models import F
4
4
  from django.forms import BaseInlineFormSet
5
5
 
6
+ from django_smartbase_admin.services.thread_local import SBAdminThreadLocalService
6
7
  from django_smartbase_admin.services.views import SBAdminViewService
8
+ from django_smartbase_admin.utils import is_modal
7
9
 
8
10
 
9
11
  class FakeQueryset(models.QuerySet):
@@ -33,6 +35,14 @@ class SBAdminFakeInlineFormset(BaseInlineFormSet):
33
35
  original_model = None
34
36
  inline_instance = None
35
37
 
38
+ @classmethod
39
+ def get_default_prefix(cls):
40
+ prefix = super().get_default_prefix()
41
+ modal_prefix = (
42
+ "modal_" if is_modal(SBAdminThreadLocalService.get_request()) else ""
43
+ )
44
+ return f"{modal_prefix}{prefix}"
45
+
36
46
  def save_new(self, form, commit=True):
37
47
  return self.inline_instance.save_new_fake_inline_instance(
38
48
  form, self.inline_instance.parent_instance, commit
@@ -82,23 +92,17 @@ class SBAdminFakeInlineMixin:
82
92
  formset.inline_instance = self
83
93
  return formset
84
94
 
85
- def has_add_permission(self, request, obj=None):
86
- # don't allow adding new fake inline instances if parent instance is not saved yet
87
- if not self.parent_instance.id:
88
- return False
89
- return super().has_add_permission(request, obj)
90
-
91
95
  def filter_fake_inline_identifier_by_parent_instance(
92
96
  self, inline_queryset, parent_instance
93
97
  ):
94
98
  # filter queryset by parent istance, in queryset there is annotated 'identifier' by get_fake_inline_identifier_annotate
95
99
  # example usage:
96
- # qs = inline_queryset.filter(path_to_parent_instance__id: parent_instance.id)
100
+ # qs = inline_queryset.filter(path_to_parent_instance__id: parent_instance.pk)
97
101
  # return qs
98
102
  if not self.path_to_parent_instance_id:
99
103
  raise NotImplementedError
100
104
  qs = inline_queryset.filter(
101
- **{self.path_to_parent_instance_id: parent_instance.id}
105
+ **{self.path_to_parent_instance_id: parent_instance.pk}
102
106
  )
103
107
  return qs
104
108
 
@@ -107,9 +111,9 @@ class SBAdminFakeInlineMixin:
107
111
  if not self.path_to_parent_instance_id:
108
112
  raise NotImplementedError
109
113
  fake_inline_object = form.save(commit=False)
110
- if parent_instance.id:
114
+ if parent_instance.pk:
111
115
  setattr(
112
- fake_inline_object, self.path_to_parent_instance_id, parent_instance.id
116
+ fake_inline_object, self.path_to_parent_instance_id, parent_instance.pk
113
117
  )
114
118
  fake_inline_object.save()
115
119
  return fake_inline_object
@@ -120,7 +124,7 @@ class SBAdminFakeInlineMixin:
120
124
  # result of this method is used in fake_inline_queryset.annotate(SBAdminFakeInlineMixin.fk_name='result')
121
125
  # example usage:
122
126
  # return F('some_model__relationship_to_parent')
123
- return F("id")
127
+ return F("pk")
124
128
 
125
129
  def get_queryset(self, request):
126
130
  model = self.original_model
@@ -1,5 +1,16 @@
1
+ from collections.abc import Callable
2
+ from typing import Any
3
+
1
4
  from django.core.exceptions import FieldDoesNotExist, FieldError, ImproperlyConfigured
2
- from django.db.models import Count, Value, CharField, F, DateTimeField, BooleanField
5
+ from django.db.models import (
6
+ Count,
7
+ Value,
8
+ CharField,
9
+ F,
10
+ DateTimeField,
11
+ BooleanField,
12
+ FilteredRelation,
13
+ )
3
14
  from django.db.models.functions import Concat
4
15
 
5
16
  from django_smartbase_admin.engine.const import ANNOTATE_KEY, Formatter
@@ -23,6 +34,7 @@ class TabulatorFieldOptions(JSONSerializableMixin):
23
34
  formatterParams = None
24
35
  frozen = False
25
36
  title = None
37
+ editorParams = None
26
38
 
27
39
  def __init__(
28
40
  self,
@@ -31,6 +43,7 @@ class TabulatorFieldOptions(JSONSerializableMixin):
31
43
  formatterParams=None,
32
44
  frozen=None,
33
45
  title=None,
46
+ editorParams=None,
34
47
  ) -> None:
35
48
  super().__init__()
36
49
  self.headerFilter = headerFilter
@@ -38,20 +51,27 @@ class TabulatorFieldOptions(JSONSerializableMixin):
38
51
  self.formatterParams = formatterParams
39
52
  self.frozen = frozen
40
53
  self.title = title
54
+ self.editorParams = editorParams
41
55
 
42
56
 
43
57
  class XLSXFieldOptions(JSONSerializableMixin):
44
- title = None
45
- field = None
46
- formatter = None
58
+ title: str | None = None
59
+ field: str | None = None
60
+ formatter: Formatter | None = None
61
+ python_formatter: Callable[[int, Any], Any] | None = None
47
62
 
48
63
  def __init__(
49
- self, title: str = None, field: str = None, formatter: Formatter = None
64
+ self,
65
+ title: str | None = None,
66
+ field: str | None = None,
67
+ formatter: Formatter | None = None,
68
+ python_formatter: Callable[[int, Any], Any] | None = None,
50
69
  ) -> None:
51
70
  super().__init__()
52
71
  self.title = title
53
72
  self.field = field
54
73
  self.formatter = formatter
74
+ self.python_formatter = python_formatter
55
75
 
56
76
 
57
77
  class SBAdminField(JSONSerializableMixin):
@@ -80,6 +100,7 @@ class SBAdminField(JSONSerializableMixin):
80
100
  python_formatter = None
81
101
  tabulator_options = None
82
102
  xlsx_options = None
103
+ initialized = False
83
104
 
84
105
  def __init__(
85
106
  self,
@@ -96,7 +117,7 @@ class SBAdminField(JSONSerializableMixin):
96
117
  list_visible=None,
97
118
  list_collapsed=None,
98
119
  auto_created=None,
99
- formatter: Formatter = None,
120
+ formatter: Formatter = Formatter.HTML.value,
100
121
  tabulator_editor=None,
101
122
  python_formatter=None,
102
123
  tabulator_options: "TabulatorFieldOptions" = None,
@@ -118,6 +139,7 @@ class SBAdminField(JSONSerializableMixin):
118
139
  if (list_visible is not None)
119
140
  else (self.list_visible if self.list_visible is not None else True)
120
141
  )
142
+ self.list_visible_arg = list_visible
121
143
  self.list_collapsed = list_collapsed or self.list_collapsed or False
122
144
  self.auto_created = auto_created or self.auto_created or False
123
145
  self.formatter = formatter
@@ -139,6 +161,7 @@ class SBAdminField(JSONSerializableMixin):
139
161
  )
140
162
  if not filter_widget:
141
163
  filter_widget = StringFilterWidget()
164
+ filter_widget = configuration.get_filter_widget(self, filter_widget)
142
165
  if filter_widget:
143
166
  filter_widget.init_filter_widget_static(self, self.view, configuration)
144
167
  self.filter_widget = filter_widget
@@ -179,7 +202,6 @@ class SBAdminField(JSONSerializableMixin):
179
202
  if self.model_field and not self.annotate:
180
203
  self.annotate = F(field_name)
181
204
  self.filter_field = self.filter_field or field_name
182
- self.formatter = "html"
183
205
  if self.view.model and not self.model_field:
184
206
  self.model_field = self.get_model_field_from_model(self.name)
185
207
  if (
@@ -199,7 +221,9 @@ class SBAdminField(JSONSerializableMixin):
199
221
  if self.model_field:
200
222
  self.editable = self.model_field.editable
201
223
  if self.model_field.is_relation:
202
- self.list_visible = False
224
+ self.list_visible = (
225
+ False if self.list_visible_arg is None else self.list_visible_arg
226
+ )
203
227
  if self.model_field.auto_created:
204
228
  self.detail_visible = False
205
229
  self.title = self.title or getattr(
@@ -217,10 +241,9 @@ class SBAdminField(JSONSerializableMixin):
217
241
  self.python_formatter = datetime_formatter
218
242
  if isinstance(self.model_field, BooleanField):
219
243
  self.python_formatter = boolean_formatter
220
- if self.python_formatter and not self.formatter:
221
- self.formatter = "html"
222
244
  self.filter_field = self.filter_field or self.field
223
245
  self.init_filter_for_field(configuration)
246
+ self.initialized = True
224
247
 
225
248
  def serialize_tabulator(self):
226
249
  data = {
@@ -252,6 +275,7 @@ class SBAdminField(JSONSerializableMixin):
252
275
 
253
276
  def get_field_annotates(self, values):
254
277
  field_annotates = {}
278
+ supporting_annotates = {}
255
279
  if self.annotate:
256
280
  field_annotates[self.field] = self.annotate
257
281
  if self.annotate_function:
@@ -261,5 +285,11 @@ class SBAdminField(JSONSerializableMixin):
261
285
  else:
262
286
  field_annotates[self.field] = Value(None, output_field=CharField())
263
287
  if self.supporting_annotates:
264
- field_annotates.update(self.supporting_annotates)
265
- return field_annotates
288
+ for key, value in self.supporting_annotates.items():
289
+ # workaround for a django bug
290
+ # https://code.djangoproject.com/ticket/36442#ticket
291
+ if isinstance(value, FilteredRelation):
292
+ supporting_annotates[key] = value.clone()
293
+ else:
294
+ supporting_annotates[key] = value
295
+ return {**supporting_annotates, **field_annotates}
@@ -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
@@ -29,19 +39,21 @@ def datetime_formatter_with_format(date_format=None, time_format=None):
29
39
 
30
40
  def boolean_formatter(object_id, value):
31
41
  if value:
32
- return f'<span class="badge badge-simple badge-positive">{_("Yes")}</span>'
33
- 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>')
34
46
 
35
47
 
36
- def format_array(value_list, separator=""):
48
+ def format_array(value_list, separator="", badge_type: BadgeType = BadgeType.NOTICE):
37
49
  result = ""
38
50
  if not value_list:
39
51
  return result
40
52
  for value in value_list:
41
53
  if not value:
42
54
  continue
43
- result += f'<span class="badge badge-simple badge-notice mr-4">{value}</span>{separator}'
44
- return result
55
+ result += f'<span class="badge badge-simple badge-{badge_type.value} mr-4">{value}</span>{separator}'
56
+ return mark_safe(result)
45
57
 
46
58
 
47
59
  def array_badge_formatter(object_id, value_list):
@@ -49,12 +61,14 @@ def array_badge_formatter(object_id, value_list):
49
61
 
50
62
 
51
63
  def newline_separated_array_badge_formatter(object_id, value_list):
52
- return format_array(value_list, separator="<br>")
64
+ return mark_safe(f'<div>{format_array(value_list, separator="<br>")}</div>')
53
65
 
54
66
 
55
67
  def rich_text_formatter(object_id, value):
56
- 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
+ )
57
71
 
58
72
 
59
73
  def link_formatter(object_id, value):
60
- return f'<a href="{value}">{value}</a>'
74
+ return mark_safe(f'<a href="{value}">{value}</a>')