django-smartbase-admin 1.0.12__py3-none-any.whl → 1.0.14__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 (30) hide show
  1. django_smartbase_admin/admin/admin_base.py +19 -17
  2. django_smartbase_admin/admin/widgets.py +10 -9
  3. django_smartbase_admin/engine/admin_base_view.py +46 -22
  4. django_smartbase_admin/engine/configuration.py +25 -25
  5. django_smartbase_admin/engine/const.py +1 -0
  6. django_smartbase_admin/engine/menu_item.py +8 -5
  7. django_smartbase_admin/static/sb_admin/dist/main.js +1 -1
  8. django_smartbase_admin/static/sb_admin/dist/main_style.css +1 -1
  9. django_smartbase_admin/static/sb_admin/images/file_types/file-csv.svg +11 -0
  10. django_smartbase_admin/static/sb_admin/images/file_types/file-doc.svg +1 -1
  11. django_smartbase_admin/static/sb_admin/images/file_types/file-docx.svg +11 -0
  12. django_smartbase_admin/static/sb_admin/images/file_types/file-other.svg +13 -0
  13. django_smartbase_admin/static/sb_admin/images/file_types/file-pdf.svg +11 -0
  14. django_smartbase_admin/static/sb_admin/images/file_types/file-ppt.svg +11 -0
  15. django_smartbase_admin/static/sb_admin/images/file_types/file-xls.svg +11 -0
  16. django_smartbase_admin/static/sb_admin/images/file_types/file-xlsx.svg +1 -1
  17. django_smartbase_admin/static/sb_admin/images/file_types/file-zip.svg +18 -0
  18. django_smartbase_admin/static/sb_admin/src/css/components/_input.css +3 -0
  19. django_smartbase_admin/static/sb_admin/src/js/main.js +13 -1
  20. django_smartbase_admin/static/sb_admin/src/js/multiselect.js +4 -2
  21. django_smartbase_admin/templates/sb_admin/actions/change_form.html +25 -23
  22. django_smartbase_admin/templates/sb_admin/includes/inline_fieldset.html +11 -11
  23. django_smartbase_admin/templates/sb_admin/widgets/clearable_file_input.html +1 -1
  24. django_smartbase_admin/templatetags/sb_admin_tags.py +9 -11
  25. django_smartbase_admin/utils.py +4 -1
  26. django_smartbase_admin/views/translations_view.py +9 -3
  27. {django_smartbase_admin-1.0.12.dist-info → django_smartbase_admin-1.0.14.dist-info}/METADATA +1 -1
  28. {django_smartbase_admin-1.0.12.dist-info → django_smartbase_admin-1.0.14.dist-info}/RECORD +30 -23
  29. {django_smartbase_admin-1.0.12.dist-info → django_smartbase_admin-1.0.14.dist-info}/LICENSE.md +0 -0
  30. {django_smartbase_admin-1.0.12.dist-info → django_smartbase_admin-1.0.14.dist-info}/WHEEL +0 -0
@@ -36,6 +36,8 @@ from django.utils.text import capfirst
36
36
  from django.utils.translation import gettext_lazy as _
37
37
  from django_admin_inline_paginator.admin import TabularInlinePaginated
38
38
  from django_htmx.http import trigger_client_event
39
+ from parler.admin import TranslatableAdmin
40
+
39
41
  from django_smartbase_admin.engine.field import SBAdminField
40
42
  from filer.fields.file import FilerFileField
41
43
  from filer.fields.image import AdminImageFormField, FilerImageField
@@ -821,6 +823,9 @@ class SBAdmin(
821
823
  def get_additional_filter_for_previous_next_context(self, request, object_id) -> Q:
822
824
  return Q()
823
825
 
826
+ def get_change_view_context(self, request, object_id) -> dict | dict[str, Any]:
827
+ return {"show_back_button": True}
828
+
824
829
  def get_previous_next_context(self, request, object_id) -> dict | dict[str, Any]:
825
830
  if not self.sbadmin_previous_next_buttons_enabled or not object_id:
826
831
  return {}
@@ -871,6 +876,7 @@ class SBAdmin(
871
876
 
872
877
  def change_view(self, request, object_id, form_url="", extra_context=None):
873
878
  extra_context = extra_context or {}
879
+ extra_context.update(self.get_change_view_context(request, object_id))
874
880
  extra_context.update(self.get_global_context(request, object_id))
875
881
  extra_context.update(self.get_fieldsets_context(request, object_id))
876
882
  extra_context.update(self.get_tabs_context(request, object_id))
@@ -990,23 +996,6 @@ class SBAdmin(
990
996
  self.set_generic_relation_from_parent(request, obj)
991
997
  super().save_model(request, obj, form, change)
992
998
 
993
- def process_field_data(
994
- self,
995
- request,
996
- field: SBAdminField,
997
- obj_id: Any,
998
- value: Any,
999
- additional_data: dict[str, Any],
1000
- ) -> Any:
1001
- is_xlsx_export = request.request_data.action == Action.XLSX_EXPORT.value
1002
- if field.view_method:
1003
- value = field.view_method(obj_id, value, **additional_data)
1004
- if is_xlsx_export and getattr(field.xlsx_options, "python_formatter", None):
1005
- value = field.xlsx_options.python_formatter(obj_id, value)
1006
- elif field.python_formatter:
1007
- value = field.python_formatter(obj_id, value)
1008
- return value
1009
-
1010
999
 
1011
1000
  class SBAdminInline(
1012
1001
  SBAdminInlineAndAdminCommon, SBAdminBaseQuerysetMixin, SBAdminBaseView
@@ -1168,3 +1157,16 @@ class SBAdminStackedInline(SBAdminInline, NestedStackedInline):
1168
1157
  class SBAdminGenericStackedInline(SBAdminInline, NestedGenericStackedInline):
1169
1158
  template = "sb_admin/inlines/stacked_inline.html"
1170
1159
  fieldset_template = "sb_admin/includes/inline_fieldset.html"
1160
+
1161
+
1162
+ class SBTranslatableAdmin(SBAdmin, TranslatableAdmin):
1163
+ def get_readonly_fields(self, request, obj=...):
1164
+ readonly_fields = super().get_readonly_fields(request, obj)
1165
+ if "sbadmin_translation_status" not in readonly_fields:
1166
+ readonly_fields += ("sbadmin_translation_status",)
1167
+ return readonly_fields
1168
+
1169
+ def get_fieldsets(self, request, obj=...):
1170
+ fieldsets = super().get_fieldsets(request, obj)
1171
+ fieldsets.append(SBAdminTranslationsService.get_translation_fieldset())
1172
+ return fieldsets
@@ -82,9 +82,10 @@ class SBAdminBaseWidget(ContextMixin):
82
82
  )
83
83
  except:
84
84
  pass
85
- context["widget"]["attrs"][
86
- "id"
87
- ] = f"{modal_prefix}{opts.app_label}_{opts.model_name}_{context['widget']['attrs']['id']}"
85
+ widget_id = f"{modal_prefix}{opts.app_label}_{opts.model_name}_{context['widget']['attrs']['id']}"
86
+ context["widget"]["attrs"]["id"] = widget_id
87
+ # needed for BoundField.id_for_label to work correctly
88
+ self.attrs["id"] = widget_id
88
89
  return context
89
90
 
90
91
 
@@ -420,25 +421,25 @@ class SBAdminAutocompleteWidget(
420
421
  )
421
422
  and not self.is_multiselect()
422
423
  ):
423
- self.add_related_buttons_urls(parsed_value, context)
424
+ self.add_related_buttons_urls(parsed_value, threadsafe_request, context)
424
425
 
425
426
  return context
426
427
 
427
- def add_related_buttons_urls(self, parsed_value, context):
428
+ def add_related_buttons_urls(self, parsed_value, request, context):
428
429
  related_model = self.model
429
430
  app_label = related_model._meta.app_label
430
431
  model_name = related_model._meta.model_name
431
432
 
432
433
  try:
433
- if parsed_value:
434
+ if parsed_value and self.has_view_or_change_permission(request, self.model):
434
435
  change_url = reverse(
435
436
  "sb_admin:{}_{}_change".format(app_label, model_name),
436
437
  args=(parsed_value,),
437
438
  )
438
439
  context["widget"]["attrs"]["related_edit_url"] = change_url
439
-
440
- add_url = reverse("sb_admin:{}_{}_add".format(app_label, model_name))
441
- context["widget"]["attrs"]["related_add_url"] = add_url
440
+ if self.has_add_permission(request, self.model):
441
+ add_url = reverse("sb_admin:{}_{}_add".format(app_label, model_name))
442
+ context["widget"]["attrs"]["related_add_url"] = add_url
442
443
  except NoReverseMatch:
443
444
  pass
444
445
 
@@ -2,7 +2,7 @@ import json
2
2
  import urllib.parse
3
3
  from collections import defaultdict
4
4
  from collections.abc import Iterable
5
- from typing import Any
5
+ from typing import Any, TYPE_CHECKING
6
6
 
7
7
  from django.contrib import messages
8
8
  from django.contrib.admin.actions import delete_selected
@@ -11,6 +11,7 @@ from django.db.models import F
11
11
  from django.http import HttpResponse, Http404, JsonResponse
12
12
  from django.shortcuts import redirect
13
13
  from django.template.response import TemplateResponse
14
+ from django.templatetags.static import static
14
15
  from django.urls import reverse
15
16
  from django.utils.translation import gettext_lazy as _
16
17
 
@@ -35,6 +36,7 @@ from django_smartbase_admin.engine.const import (
35
36
  TABLE_UPDATE_ROW_DATA_EVENT_NAME,
36
37
  SELECT_ALL_KEYWORD,
37
38
  IGNORE_LIST_SELECTION,
39
+ SUPPORTED_FILE_TYPE_ICONS,
38
40
  )
39
41
  from django_smartbase_admin.services.views import SBAdminViewService
40
42
  from django_smartbase_admin.services.xlsx_export import (
@@ -44,6 +46,9 @@ from django_smartbase_admin.services.xlsx_export import (
44
46
  )
45
47
  from django_smartbase_admin.utils import is_htmx_request, render_notifications, is_modal
46
48
 
49
+ if TYPE_CHECKING:
50
+ from django_smartbase_admin.engine.field import SBAdminField
51
+
47
52
  SBADMIN_IS_MODAL_VAR = "sbadmin_is_modal"
48
53
  SBADMIN_PARENT_INSTANCE_FIELD_NAME_VAR = "sbadmin_parent_instance_field"
49
54
  SBADMIN_PARENT_INSTANCE_PK_VAR = "sbadmin_parent_instance_pk"
@@ -103,7 +108,7 @@ class SBAdminBaseView(object):
103
108
  return inner_view
104
109
 
105
110
  def process_actions(
106
- self, request, actions: list[SBAdminCustomAction]
111
+ self, request, actions: list[SBAdminCustomAction]
107
112
  ) -> list[SBAdminCustomAction]:
108
113
  processed_actions = self.process_actions_permissions(request, actions)
109
114
  for processed_action in processed_actions:
@@ -120,7 +125,7 @@ class SBAdminBaseView(object):
120
125
  return processed_actions
121
126
 
122
127
  def process_actions_permissions(
123
- self, request, actions: list[SBAdminCustomAction]
128
+ self, request, actions: list[SBAdminCustomAction]
124
129
  ) -> list[SBAdminCustomAction]:
125
130
  result = []
126
131
  for action in actions:
@@ -199,12 +204,12 @@ class SBAdminBaseView(object):
199
204
  }
200
205
 
201
206
  def get_sbadmin_detail_actions(
202
- self, request, object_id: int | str | None = None
207
+ self, request, object_id: int | str | None = None
203
208
  ) -> Iterable[SBAdminCustomAction] | None:
204
209
  return self.sbadmin_detail_actions
205
210
 
206
211
  def get_global_context(
207
- self, request, object_id: int | str | None = None
212
+ self, request, object_id: int | str | None = None
208
213
  ) -> dict[str, Any]:
209
214
  return {
210
215
  "view_id": self.get_id(),
@@ -216,7 +221,7 @@ class SBAdminBaseView(object):
216
221
  "detail_actions": self.get_sbadmin_detail_actions(request, object_id),
217
222
  SBADMIN_IS_MODAL_VAR: is_modal(request),
218
223
  SBADMIN_RELOAD_ON_SAVE_VAR: SBADMIN_RELOAD_ON_SAVE_VAR in request.GET
219
- or SBADMIN_RELOAD_ON_SAVE_VAR in request.POST,
224
+ or SBADMIN_RELOAD_ON_SAVE_VAR in request.POST,
220
225
  "const": json.dumps(
221
226
  {
222
227
  "MULTISELECT_FILTER_MAX_CHOICES_SHOWN": MULTISELECT_FILTER_MAX_CHOICES_SHOWN,
@@ -225,6 +230,8 @@ class SBAdminBaseView(object):
225
230
  "TABLE_RELOAD_DATA_EVENT_NAME": TABLE_RELOAD_DATA_EVENT_NAME,
226
231
  "TABLE_UPDATE_ROW_DATA_EVENT_NAME": TABLE_UPDATE_ROW_DATA_EVENT_NAME,
227
232
  "SELECT_ALL_KEYWORD": SELECT_ALL_KEYWORD,
233
+ "SUPPORTED_FILE_TYPE_ICONS": SUPPORTED_FILE_TYPE_ICONS,
234
+ "STATIC_BASE_PATH": static("sb_admin"),
228
235
  }
229
236
  ),
230
237
  }
@@ -232,6 +239,23 @@ class SBAdminBaseView(object):
232
239
  def get_model_path(self) -> str:
233
240
  return SBAdminViewService.get_model_path(self.model)
234
241
 
242
+ def process_field_data(
243
+ self,
244
+ request,
245
+ field: "SBAdminField",
246
+ obj_id: Any,
247
+ value: Any,
248
+ additional_data: dict[str, Any],
249
+ ) -> Any:
250
+ is_xlsx_export = request.request_data.action == Action.XLSX_EXPORT.value
251
+ if field.view_method:
252
+ value = field.view_method(obj_id, value, **additional_data)
253
+ if is_xlsx_export and getattr(field.xlsx_options, "python_formatter", None):
254
+ value = field.xlsx_options.python_formatter(obj_id, value)
255
+ elif field.python_formatter:
256
+ value = field.python_formatter(obj_id, value)
257
+ return value
258
+
235
259
 
236
260
  class SBAdminBaseQuerysetMixin(object):
237
261
  def get_queryset(self, request=None):
@@ -298,8 +322,8 @@ class SBAdminBaseListView(SBAdminBaseView):
298
322
 
299
323
  def is_reorder_active(self, request) -> bool:
300
324
  return (
301
- self.is_reorder_available(request)
302
- and getattr(request, "reorder_active", False) == True
325
+ self.is_reorder_available(request)
326
+ and getattr(request, "reorder_active", False) == True
303
327
  )
304
328
 
305
329
  def is_reorder_available(self, request) -> str | None:
@@ -334,7 +358,7 @@ class SBAdminBaseListView(SBAdminBaseView):
334
358
  qs.filter(**{f"{pk_field}__in": item_ids}).update(
335
359
  **{
336
360
  self.sbadmin_list_reorder_field: F(self.sbadmin_list_reorder_field)
337
- + int(diff)
361
+ + int(diff)
338
362
  }
339
363
  )
340
364
  return JsonResponse({"message": request.POST})
@@ -514,7 +538,7 @@ class SBAdminBaseListView(SBAdminBaseView):
514
538
  return self.sbadmin_list_selection_actions
515
539
 
516
540
  def get_sbadmin_list_selection_actions_grouped(
517
- self, request
541
+ self, request
518
542
  ) -> dict[str, list[SBAdminCustomAction]]:
519
543
  result = {}
520
544
  list_selection_actions = self.process_actions(
@@ -546,8 +570,8 @@ class SBAdminBaseListView(SBAdminBaseView):
546
570
  def action_bulk_delete(self, request, modifier):
547
571
  action = self.sbadmin_list_action_class(self, request)
548
572
  if (
549
- request.request_data.request_method == "POST"
550
- and request.headers.get("X-TabulatorRequest", None) == "true"
573
+ request.request_data.request_method == "POST"
574
+ and request.headers.get("X-TabulatorRequest", None) == "true"
551
575
  ):
552
576
  return redirect(
553
577
  self.get_action_url("action_bulk_delete")
@@ -625,13 +649,13 @@ class SBAdminBaseListView(SBAdminBaseView):
625
649
  return redirect_to
626
650
 
627
651
  def action_list(
628
- self,
629
- request,
630
- page_size=None,
631
- tabulator_definition=None,
632
- extra_context=None,
633
- list_actions=None,
634
- template=None,
652
+ self,
653
+ request,
654
+ page_size=None,
655
+ tabulator_definition=None,
656
+ extra_context=None,
657
+ list_actions=None,
658
+ template=None,
635
659
  ):
636
660
  action = self.sbadmin_list_action_class(
637
661
  self,
@@ -684,8 +708,8 @@ class SBAdminBaseListView(SBAdminBaseView):
684
708
  getattr(field, "filter_field", field): ""
685
709
  for field in list_fields
686
710
  if field in list_filter
687
- or getattr(field, "name", None) in list_filter
688
- or getattr(field, "filter_field", None) in list_filter
711
+ or getattr(field, "name", None) in list_filter
712
+ or getattr(field, "filter_field", None) in list_filter
689
713
  }
690
714
  url_params = None
691
715
  if base_filter:
@@ -760,7 +784,7 @@ class SBAdminBaseListView(SBAdminBaseView):
760
784
 
761
785
  def get_filters_version(self, request) -> FilterVersions:
762
786
  return (
763
- self.filters_version or request.request_data.configuration.filters_version
787
+ self.filters_version or request.request_data.configuration.filters_version
764
788
  )
765
789
 
766
790
  def get_filters_template_name(self, request) -> str:
@@ -41,12 +41,12 @@ class SBAdminRoleConfiguration(metaclass=Singleton):
41
41
  filters_version = FilterVersions.FILTERS_VERSION_1
42
42
 
43
43
  def __init__(
44
- self,
45
- default_view=None,
46
- registered_views=None,
47
- menu_items=None,
48
- global_filter_form=None,
49
- filters_version=None,
44
+ self,
45
+ default_view=None,
46
+ registered_views=None,
47
+ menu_items=None,
48
+ global_filter_form=None,
49
+ filters_version=None,
50
50
  ) -> None:
51
51
  super().__init__()
52
52
  self.default_view = default_view or self.default_view or []
@@ -134,13 +134,13 @@ class SBAdminRoleConfiguration(metaclass=Singleton):
134
134
  self.autocomplete_map[view.get_id()] = view
135
135
 
136
136
  def restrict_queryset(
137
- self,
138
- qs,
139
- model,
140
- request,
141
- request_data,
142
- global_filter=True,
143
- global_filter_data_map=None,
137
+ self,
138
+ qs,
139
+ model,
140
+ request,
141
+ request_data,
142
+ global_filter=True,
143
+ global_filter_data_map=None,
144
144
  ):
145
145
  return qs
146
146
 
@@ -154,7 +154,7 @@ class SBAdminRoleConfiguration(metaclass=Singleton):
154
154
  return request.user.is_staff
155
155
 
156
156
  def has_permission(
157
- self, request, request_data, view, model=None, obj=None, permission=None
157
+ self, request, request_data, view, model=None, obj=None, permission=None
158
158
  ):
159
159
  if isinstance(permission, SBAdminCustomAction):
160
160
  return self.has_action_permission(
@@ -174,7 +174,7 @@ class SBAdminRoleConfiguration(metaclass=Singleton):
174
174
  return request.user.is_staff
175
175
 
176
176
  def get_autocomplete_widget(
177
- self, view, request, form_field, db_field, model, multiselect=False
177
+ self, view, request, form_field, db_field, model, multiselect=False
178
178
  ):
179
179
  from django_smartbase_admin.admin.widgets import SBAdminAutocompleteWidget
180
180
 
@@ -186,12 +186,12 @@ class SBAdminRoleConfiguration(metaclass=Singleton):
186
186
  return default_widget
187
187
 
188
188
  def get_form_field_widget_class(
189
- self, view, request, form_field, db_field, default_widget_class
189
+ self, view, request, form_field, db_field, default_widget_class
190
190
  ):
191
191
  return default_widget_class
192
192
 
193
193
  def apply_global_filter_to_queryset(
194
- self, qs, request, request_data, global_filter_data_map
194
+ self, qs, request, request_data, global_filter_data_map
195
195
  ):
196
196
  global_filter_data_map = global_filter_data_map or {}
197
197
  global_filter_data_map = {
@@ -211,9 +211,9 @@ class SBAdminRoleConfiguration(metaclass=Singleton):
211
211
  except:
212
212
  pass
213
213
  if (
214
- include_all_values_for_empty_fields
215
- and field.name in include_all_values_for_empty_fields
216
- and not field_value
214
+ include_all_values_for_empty_fields
215
+ and field.name in include_all_values_for_empty_fields
216
+ and not field_value
217
217
  ):
218
218
  continue
219
219
  field_value = to_list(field_value)
@@ -228,10 +228,10 @@ class SBAdminRoleConfiguration(metaclass=Singleton):
228
228
  return response
229
229
 
230
230
  def autocomplete_show_related_buttons(
231
- self,
232
- related_model,
233
- field_name,
234
- current_view,
235
- request,
231
+ self,
232
+ related_model,
233
+ field_name,
234
+ current_view,
235
+ request,
236
236
  ) -> bool:
237
237
  return not is_modal(request)
@@ -74,3 +74,4 @@ TRANSLATIONS_SELECTED_LANGUAGES = "translation_selected_languages"
74
74
  OVERRIDE_CONTENT_OF_NOTIFICATION = "override_notification_content"
75
75
  FIELDSET_HIDE_HEADER_CLASS = "hide-header"
76
76
  ROW_CLASS_FIELD = "get_sbadmin_row_class"
77
+ SUPPORTED_FILE_TYPE_ICONS = ["doc", "docx", "csv", "xls", "xlsx", "pdf", "ppt", "zip"]
@@ -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)