django-smartbase-admin 1.0.8__py3-none-any.whl → 1.0.11__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 (33) hide show
  1. django_smartbase_admin/actions/admin_action_list.py +15 -15
  2. django_smartbase_admin/admin/admin_base.py +29 -7
  3. django_smartbase_admin/admin/widgets.py +31 -21
  4. django_smartbase_admin/engine/dashboard.py +15 -1
  5. django_smartbase_admin/engine/fake_inline.py +10 -0
  6. django_smartbase_admin/engine/field.py +13 -4
  7. django_smartbase_admin/engine/filter_widgets.py +3 -2
  8. django_smartbase_admin/static/sb_admin/dist/chart.js +1 -1
  9. django_smartbase_admin/static/sb_admin/dist/main.js +1 -1
  10. django_smartbase_admin/static/sb_admin/dist/main_style.css +1 -1
  11. django_smartbase_admin/static/sb_admin/images/file_types/file-doc.svg +11 -0
  12. django_smartbase_admin/static/sb_admin/images/file_types/file-xlsx.svg +11 -0
  13. django_smartbase_admin/static/sb_admin/src/js/autocomplete.js +32 -0
  14. django_smartbase_admin/static/sb_admin/src/js/chart.js +4 -3
  15. django_smartbase_admin/static/sb_admin/src/js/main.js +28 -4
  16. django_smartbase_admin/static/sb_admin/src/js/multiselect.js +2 -2
  17. django_smartbase_admin/templates/sb_admin/actions/change_form.html +90 -77
  18. django_smartbase_admin/templates/sb_admin/actions/dashboard.html +1 -1
  19. django_smartbase_admin/templates/sb_admin/dashboard/chart_widget.html +1 -0
  20. django_smartbase_admin/templates/sb_admin/includes/inline_fieldset.html +48 -36
  21. django_smartbase_admin/templates/sb_admin/inlines/table_inline.html +1 -0
  22. django_smartbase_admin/templates/sb_admin/sb_admin_base.html +35 -3
  23. django_smartbase_admin/templates/sb_admin/widgets/clearable_file_input.html +2 -2
  24. django_smartbase_admin/templates/sb_admin/widgets/date.html +1 -1
  25. django_smartbase_admin/templates/sb_admin/widgets/includes/related_item_buttons.html +25 -18
  26. django_smartbase_admin/templates/sb_admin/widgets/multiwidget.html +1 -1
  27. django_smartbase_admin/templates/sb_admin/widgets/time.html +1 -1
  28. django_smartbase_admin/templatetags/sb_admin_tags.py +40 -0
  29. django_smartbase_admin/views/dashboard_view.py +6 -0
  30. {django_smartbase_admin-1.0.8.dist-info → django_smartbase_admin-1.0.11.dist-info}/METADATA +3 -1
  31. {django_smartbase_admin-1.0.8.dist-info → django_smartbase_admin-1.0.11.dist-info}/RECORD +33 -31
  32. {django_smartbase_admin-1.0.8.dist-info → django_smartbase_admin-1.0.11.dist-info}/LICENSE.md +0 -0
  33. {django_smartbase_admin-1.0.8.dist-info → django_smartbase_admin-1.0.11.dist-info}/WHEEL +0 -0
@@ -1,7 +1,8 @@
1
+ from __future__ import annotations
1
2
  import json
2
3
  import math
4
+ from typing import Any, TYPE_CHECKING
3
5
 
4
- from django.core.paginator import Paginator
5
6
  from django.db.models import Q
6
7
  from django.utils import timezone
7
8
  from django.utils.html import escape
@@ -33,7 +34,6 @@ from django_smartbase_admin.engine.const import (
33
34
  CONFIG_NAME,
34
35
  TABLE_PARAMS_FULL_TEXT_SEARCH,
35
36
  TABLE_PARAMS_SELECTED_FILTER_TYPE,
36
- FilterVersions,
37
37
  ADVANCED_FILTER_DATA_NAME,
38
38
  IGNORE_LIST_SELECTION,
39
39
  )
@@ -44,6 +44,9 @@ QueryBuilderService = import_with_injection(
44
44
  "django_smartbase_admin.actions.advanced_filters", "QueryBuilderService"
45
45
  )
46
46
 
47
+ if TYPE_CHECKING:
48
+ from django_smartbase_admin.engine.field import SBAdminField
49
+
47
50
 
48
51
  class SBAdminAction(object):
49
52
  view = None
@@ -289,7 +292,7 @@ class SBAdminListAction(SBAdminAction):
289
292
  **self.get_annotates(visible_fields)
290
293
  )
291
294
 
292
- def get_visible_column_fields(self):
295
+ def get_visible_column_fields(self) -> list[SBAdminField]:
293
296
  columns_data_dict = self.columns_data.get(COLUMNS_DATA_COLUMNS_NAME, {})
294
297
  return [
295
298
  field
@@ -407,14 +410,13 @@ class SBAdminListAction(SBAdminAction):
407
410
  "last_row": total_count,
408
411
  }
409
412
 
410
- def process_final_data(self, final_data):
413
+ def process_final_data(self, final_data: list[dict[str, Any]]) -> None:
411
414
  visible_columns = self.get_visible_column_fields()
412
- fields_with_methods_to_call_by_field_key = {
413
- field.field: field
414
- for field in visible_columns
415
- if field.view_method or field.python_formatter
415
+ field_key_field_map: dict[str, SBAdminField] = {
416
+ field.field: field for field in visible_columns
416
417
  }
417
418
  for row in final_data:
419
+ obj_id = row.get(self.get_pk_field().name, None)
418
420
  additional_data = {}
419
421
  if self.view.sbadmin_list_display_data:
420
422
  additional_data = {
@@ -422,13 +424,11 @@ class SBAdminListAction(SBAdminAction):
422
424
  for data in self.view.sbadmin_list_display_data
423
425
  }
424
426
  for field_key, value in row.items():
425
- if field_key in fields_with_methods_to_call_by_field_key:
426
- field = fields_with_methods_to_call_by_field_key[field_key]
427
- object_id = row.get(self.get_pk_field().name, None)
428
- if field.view_method:
429
- value = field.view_method(object_id, value, **additional_data)
430
- if field.python_formatter:
431
- value = field.python_formatter(object_id, value)
427
+ if field_key in field_key_field_map:
428
+ field = field_key_field_map[field_key]
429
+ value = self.view.process_field_data(
430
+ self.threadsafe_request, field, obj_id, value, additional_data
431
+ )
432
432
  if isinstance(value, str) and not isinstance(value, SafeString):
433
433
  value = escape(value)
434
434
  row[field_key] = value
@@ -21,7 +21,7 @@ from django.core.exceptions import (
21
21
  PermissionDenied,
22
22
  )
23
23
  from django.db import models
24
- from django.db.models import QuerySet
24
+ from django.db.models import QuerySet, Q
25
25
  from django.forms import HiddenInput
26
26
  from django.forms.models import (
27
27
  ModelFormMetaclass,
@@ -36,6 +36,7 @@ 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 django_smartbase_admin.engine.field import SBAdminField
39
40
  from filer.fields.file import FilerFileField
40
41
  from filer.fields.image import AdminImageFormField, FilerImageField
41
42
  from nested_admin.formsets import NestedInlineFormSet
@@ -129,6 +130,7 @@ from django_smartbase_admin.engine.const import (
129
130
  OBJECT_ID_PLACEHOLDER,
130
131
  TRANSLATIONS_SELECTED_LANGUAGES,
131
132
  ROW_CLASS_FIELD,
133
+ Action,
132
134
  )
133
135
  from django_smartbase_admin.services.translations import SBAdminTranslationsService
134
136
  from django_smartbase_admin.services.views import SBAdminViewService
@@ -702,9 +704,6 @@ class SBAdmin(
702
704
  menu_label = None
703
705
  sbadmin_is_generic_model = False
704
706
 
705
- def __init__(self, model, admin_site):
706
- super().__init__(model, admin_site)
707
-
708
707
  def save_formset(self, request, form, formset, change):
709
708
  if not change and hasattr(formset, "inline_instance"):
710
709
  # update inline_instance parent_instance on formset when creating new object
@@ -819,6 +818,9 @@ class SBAdmin(
819
818
  def get_new_url(self, request) -> str:
820
819
  return reverse(f"sb_admin:{self.get_id()}_add")
821
820
 
821
+ def get_additional_filter_for_previous_next_context(self, request, object_id) -> Q:
822
+ return Q()
823
+
822
824
  def get_previous_next_context(self, request, object_id) -> dict | dict[str, Any]:
823
825
  if not self.sbadmin_previous_next_buttons_enabled or not object_id:
824
826
  return {}
@@ -834,14 +836,17 @@ class SBAdmin(
834
836
  list_action = self.sbadmin_list_action_class(
835
837
  self, request, all_params=all_params
836
838
  )
839
+ additional_filter = self.get_additional_filter_for_previous_next_context(
840
+ request, object_id
841
+ )
837
842
  all_ids = list(
838
- list_action.build_final_data_count_queryset()
843
+ list_action.build_final_data_count_queryset(additional_filter)
839
844
  .order_by(*list_action.get_order_by_from_request())
840
845
  .values_list("id", flat=True)
841
846
  )
842
847
  index = all_ids.index(int(object_id))
843
- previous_id = None if index == 0 else all_ids[index - 1]
844
- next_id = None if index == len(all_ids) - 1 else all_ids[index + 1]
848
+ previous_id = all_ids[-1] if index == 0 else all_ids[index - 1]
849
+ next_id = all_ids[0] if index == len(all_ids) - 1 else all_ids[index + 1]
845
850
  return {
846
851
  "previous_url": (
847
852
  f"{self.get_detail_url(previous_id)}?_changelist_filters={changelist_filters}"
@@ -985,6 +990,23 @@ class SBAdmin(
985
990
  self.set_generic_relation_from_parent(request, obj)
986
991
  super().save_model(request, obj, form, change)
987
992
 
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
+
988
1010
 
989
1011
  class SBAdminInline(
990
1012
  SBAdminInlineAndAdminCommon, SBAdminBaseQuerysetMixin, SBAdminBaseView
@@ -31,7 +31,9 @@ from django_smartbase_admin.engine.filter_widgets import (
31
31
  SBAdminTreeWidgetMixin,
32
32
  )
33
33
  from django_smartbase_admin.services.thread_local import SBAdminThreadLocalService
34
- from django_smartbase_admin.templatetags.sb_admin_tags import SBAdminJSONEncoder
34
+ from django_smartbase_admin.templatetags.sb_admin_tags import (
35
+ SBAdminJSONEncoder,
36
+ )
35
37
  from django_smartbase_admin.utils import is_modal
36
38
 
37
39
  logger = logging.getLogger(__name__)
@@ -60,25 +62,29 @@ class SBAdminBaseWidget(ContextMixin):
60
62
  def get_context(self, name, value, attrs):
61
63
  context = super().get_context(name, value, attrs)
62
64
  context["widget"]["form_field"] = self.form_field
63
- opts = (
64
- self.form_field.view.opts
65
- if self.form_field
66
- and hasattr(self.form_field, "view")
67
- and hasattr(self.form_field.view, "opts")
68
- else None
69
- )
70
- modal_prefix = ""
71
- try:
72
- modal_prefix = (
73
- "modal_" if is_modal(SBAdminThreadLocalService.get_request()) else ""
74
- )
75
- except:
76
- pass
65
+ opts = None
66
+
67
+ if self.form_field:
68
+ view = getattr(self.form_field, "view", None)
69
+ if view:
70
+ if hasattr(view, "opts"):
71
+ opts = view.opts
72
+ elif hasattr(view, "view") and hasattr(view.view, "opts"):
73
+ opts = view.view.opts
74
+
77
75
  if opts:
76
+ modal_prefix = ""
77
+ try:
78
+ modal_prefix = (
79
+ "modal_"
80
+ if is_modal(SBAdminThreadLocalService.get_request())
81
+ else ""
82
+ )
83
+ except:
84
+ pass
78
85
  context["widget"]["attrs"][
79
86
  "id"
80
87
  ] = f"{modal_prefix}{opts.app_label}_{opts.model_name}_{context['widget']['attrs']['id']}"
81
-
82
88
  return context
83
89
 
84
90
 
@@ -366,6 +372,7 @@ class SBAdminAutocompleteWidget(
366
372
  self.input_id = (
367
373
  context["widget"]["attrs"]["id"] or f'id_{context["widget"]["name"]}'
368
374
  )
375
+
369
376
  context["widget"]["type"] = "hidden"
370
377
  context["widget"]["attrs"]["id"] = self.input_id
371
378
  context["widget"]["attrs"]["class"] = "js-autocomplete-detail"
@@ -404,11 +411,14 @@ class SBAdminAutocompleteWidget(
404
411
  context["widget"]["value"] = json.dumps(selected_options)
405
412
  context["widget"]["value_list"] = selected_options
406
413
 
407
- if threadsafe_request.request_data.configuration.autocomplete_show_related_buttons(
408
- self.model,
409
- field_name=self.field_name,
410
- current_view=self.view,
411
- request=threadsafe_request,
414
+ if (
415
+ threadsafe_request.request_data.configuration.autocomplete_show_related_buttons(
416
+ self.model,
417
+ field_name=self.field_name,
418
+ current_view=self.view,
419
+ request=threadsafe_request,
420
+ )
421
+ and not self.is_multiselect()
412
422
  ):
413
423
  self.add_related_buttons_urls(parsed_value, context)
414
424
 
@@ -17,7 +17,8 @@ from django_smartbase_admin.engine.const import OBJECT_ID_PLACEHOLDER
17
17
  from django_smartbase_admin.engine.field import SBAdminField
18
18
  from django_smartbase_admin.engine.filter_widgets import (
19
19
  DateFilterWidget,
20
- RadioChoiceFilterWidget, )
20
+ RadioChoiceFilterWidget,
21
+ )
21
22
  from django_smartbase_admin.services.views import SBAdminViewService
22
23
  from django_smartbase_admin.utils import to_list
23
24
 
@@ -82,6 +83,7 @@ class SBAdminDashboardWidget(SBAdminView):
82
83
  def get_widget_context_data(self, request):
83
84
  return {
84
85
  "widget_id": self.get_id(),
86
+ "widget_name": self.name,
85
87
  "ajax_url": self.get_ajax_url,
86
88
  "filters": self.get_filters(),
87
89
  "settings": self.get_settings(),
@@ -92,6 +94,12 @@ class SBAdminDashboardWidget(SBAdminView):
92
94
  def get_sub_widgets(self):
93
95
  return self.sub_widgets
94
96
 
97
+ def get_sub_views(self, configuration):
98
+ for idx, sub_widget_view in enumerate(self.sub_widgets):
99
+ sub_widget_view.widget_id = f"{self.get_id()}_{idx}"
100
+ sub_widget_view.init_widget_static(configuration)
101
+ return self.sub_widgets
102
+
95
103
  def get_template_name(self):
96
104
  return self.template_name
97
105
 
@@ -193,6 +201,12 @@ class SBAdminChartAggregateSubWidget(object):
193
201
  def render(self, request):
194
202
  return render_to_string(self.template_name, self.get_context_data(request))
195
203
 
204
+ def init_widget_static(self, configuration):
205
+ pass
206
+
207
+ def init_view_dynamic(self, request, request_data=None, **kwargs):
208
+ pass
209
+
196
210
 
197
211
  class SBAdminDashboardChartWidget(SBAdminDashboardWidget):
198
212
  template_name = "sb_admin/dashboard/chart_widget.html"
@@ -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
@@ -1,3 +1,6 @@
1
+ from collections.abc import Callable
2
+ from typing import Any
3
+
1
4
  from django.core.exceptions import FieldDoesNotExist, FieldError, ImproperlyConfigured
2
5
  from django.db.models import (
3
6
  Count,
@@ -52,17 +55,23 @@ class TabulatorFieldOptions(JSONSerializableMixin):
52
55
 
53
56
 
54
57
  class XLSXFieldOptions(JSONSerializableMixin):
55
- title = None
56
- field = None
57
- 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
58
62
 
59
63
  def __init__(
60
- 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,
61
69
  ) -> None:
62
70
  super().__init__()
63
71
  self.title = title
64
72
  self.field = field
65
73
  self.formatter = formatter
74
+ self.python_formatter = python_formatter
66
75
 
67
76
 
68
77
  class SBAdminField(JSONSerializableMixin):
@@ -19,7 +19,8 @@ from django_smartbase_admin.engine.const import (
19
19
  AUTOCOMPLETE_PAGE_SIZE,
20
20
  Action,
21
21
  AUTOCOMPLETE_PAGE_NUM,
22
- AUTOCOMPLETE_FORWARD_NAME, SELECT_ALL_KEYWORD,
22
+ AUTOCOMPLETE_FORWARD_NAME,
23
+ SELECT_ALL_KEYWORD,
23
24
  )
24
25
  from django_smartbase_admin.services.translations import SBAdminTranslationsService
25
26
  from django_smartbase_admin.services.views import SBAdminViewService
@@ -232,7 +233,7 @@ class MultipleChoiceFilterWidget(AutocompleteParseMixin, ChoiceFilterWidget):
232
233
  default_label=None,
233
234
  enable_select_all=False,
234
235
  select_all_keyword=SELECT_ALL_KEYWORD,
235
- select_all_label=_('All'),
236
+ select_all_label=_("All"),
236
237
  **kwargs,
237
238
  ) -> None:
238
239
  super().__init__(