django-smartbase-admin 0.2.54__py3-none-any.whl → 1.0.42__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 (184) hide show
  1. django_smartbase_admin/actions/admin_action_list.py +79 -38
  2. django_smartbase_admin/actions/advanced_filters.py +24 -1
  3. django_smartbase_admin/admin/admin_base.py +402 -97
  4. django_smartbase_admin/admin/site.py +93 -35
  5. django_smartbase_admin/admin/widgets.py +636 -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 +49 -24
  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 +38 -14
  15. django_smartbase_admin/engine/filter_widgets.py +348 -24
  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/js/sbadmin_prepopulated_fields_init.js +25 -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 +61 -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 +31 -7
  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 +69 -11
  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 +306 -31
  102. django_smartbase_admin/static/sb_admin/src/js/multiselect.js +50 -41
  103. django_smartbase_admin/static/sb_admin/src/js/radio.js +31 -0
  104. django_smartbase_admin/static/sb_admin/src/js/range.js +3 -2
  105. django_smartbase_admin/static/sb_admin/src/js/table.js +34 -5
  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/detail_view_module.js +50 -1
  109. django_smartbase_admin/static/sb_admin/src/js/table_modules/filter_module.js +10 -3
  110. django_smartbase_admin/static/sb_admin/src/js/table_modules/header_tabs_module.js +11 -11
  111. django_smartbase_admin/static/sb_admin/src/js/table_modules/selection_module.js +28 -8
  112. django_smartbase_admin/static/sb_admin/src/js/table_modules/table_params_module.js +6 -0
  113. django_smartbase_admin/static/sb_admin/src/js/table_modules/views_module.js +6 -0
  114. django_smartbase_admin/static/sb_admin/src/js/tree_widget.js +406 -0
  115. django_smartbase_admin/static/sb_admin/src/js/utils.js +56 -21
  116. django_smartbase_admin/templates/sb_admin/actions/change_form.html +176 -116
  117. django_smartbase_admin/templates/sb_admin/actions/dashboard.html +2 -2
  118. django_smartbase_admin/templates/sb_admin/actions/list.html +79 -39
  119. django_smartbase_admin/templates/sb_admin/actions/partials/action_link.html +14 -0
  120. django_smartbase_admin/templates/sb_admin/actions/partials/tabulator_header_v2.html +2 -2
  121. django_smartbase_admin/templates/sb_admin/actions/tree_list.html +63 -0
  122. django_smartbase_admin/templates/sb_admin/authentification/login_base.html +5 -1
  123. django_smartbase_admin/templates/sb_admin/components/columns.html +1 -1
  124. django_smartbase_admin/templates/sb_admin/components/filters.html +1 -0
  125. django_smartbase_admin/templates/sb_admin/components/filters_v2.html +99 -85
  126. django_smartbase_admin/templates/sb_admin/dashboard/calendar_widget.html +69 -0
  127. django_smartbase_admin/templates/sb_admin/dashboard/chart_widget.html +21 -2
  128. django_smartbase_admin/templates/sb_admin/dashboard/list_widget.html +6 -0
  129. django_smartbase_admin/templates/sb_admin/dashboard/widget_base.html +1 -1
  130. django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/date_field.html +18 -8
  131. django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/multiple_choice_field.html +1 -1
  132. django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/tree_select_filter.html +2 -0
  133. django_smartbase_admin/templates/sb_admin/filter_widgets/boolean_field.html +1 -14
  134. django_smartbase_admin/templates/sb_admin/filter_widgets/date_field.html +18 -4
  135. django_smartbase_admin/templates/sb_admin/filter_widgets/multiple_choice_field.html +14 -0
  136. django_smartbase_admin/templates/sb_admin/filter_widgets/partials/clear.html +12 -6
  137. django_smartbase_admin/templates/sb_admin/filter_widgets/radio_choice_field.html +5 -3
  138. django_smartbase_admin/templates/sb_admin/filter_widgets/tree_select_filter.html +16 -0
  139. django_smartbase_admin/templates/sb_admin/includes/change_form_title.html +3 -1
  140. django_smartbase_admin/templates/sb_admin/includes/inline_fieldset.html +48 -39
  141. django_smartbase_admin/templates/sb_admin/includes/notifications.html +2 -1
  142. django_smartbase_admin/templates/sb_admin/includes/readonly_boolean_field.html +9 -0
  143. django_smartbase_admin/templates/sb_admin/includes/readonly_field.html +12 -0
  144. django_smartbase_admin/templates/sb_admin/includes/table_inline_delete_button.html +4 -5
  145. django_smartbase_admin/templates/sb_admin/inlines/stacked_inline.html +68 -40
  146. django_smartbase_admin/templates/sb_admin/inlines/table_inline.html +76 -34
  147. django_smartbase_admin/templates/sb_admin/integrations/filer/folder_list.html +18 -0
  148. django_smartbase_admin/templates/sb_admin/navigation.html +166 -158
  149. django_smartbase_admin/templates/sb_admin/partials/modal/modal_content.html +2 -6
  150. django_smartbase_admin/templates/sb_admin/sb_admin_base.html +49 -4
  151. django_smartbase_admin/templates/sb_admin/sb_admin_base_no_sidebar.html +27 -11
  152. django_smartbase_admin/templates/sb_admin/sb_admin_js_trans.html +3 -0
  153. django_smartbase_admin/templates/sb_admin/sprites/sb_admin.svg +1 -1
  154. django_smartbase_admin/templates/sb_admin/tailwind_whitelist.html +6 -3
  155. django_smartbase_admin/templates/sb_admin/widgets/array.html +0 -1
  156. django_smartbase_admin/templates/sb_admin/widgets/attributes.html +68 -0
  157. django_smartbase_admin/templates/sb_admin/widgets/autocomplete.html +13 -2
  158. django_smartbase_admin/templates/sb_admin/widgets/{checkbox_select.html → checkbox_dropdown.html} +2 -2
  159. django_smartbase_admin/templates/sb_admin/widgets/clearable_file_input.html +2 -2
  160. django_smartbase_admin/templates/sb_admin/widgets/color_field.html +30 -0
  161. django_smartbase_admin/templates/sb_admin/widgets/date.html +8 -1
  162. django_smartbase_admin/templates/sb_admin/widgets/filer_file.html +84 -0
  163. django_smartbase_admin/templates/sb_admin/widgets/includes/related_item_buttons.html +38 -0
  164. django_smartbase_admin/templates/sb_admin/widgets/multiwidget.html +1 -1
  165. django_smartbase_admin/templates/sb_admin/widgets/radio.html +3 -2
  166. django_smartbase_admin/templates/sb_admin/widgets/radio_dropdown.html +30 -0
  167. django_smartbase_admin/templates/sb_admin/widgets/read_only_password_hash.html +3 -0
  168. django_smartbase_admin/templates/sb_admin/widgets/time.html +8 -1
  169. django_smartbase_admin/templates/sb_admin/widgets/toggle.html +1 -1
  170. django_smartbase_admin/templates/sb_admin/widgets/tree_base.html +59 -0
  171. django_smartbase_admin/templates/sb_admin/widgets/tree_select.html +24 -0
  172. django_smartbase_admin/templates/sb_admin/widgets/tree_select_inline.html +12 -0
  173. django_smartbase_admin/templatetags/sb_admin_tags.py +115 -4
  174. django_smartbase_admin/utils.py +22 -3
  175. django_smartbase_admin/views/dashboard_view.py +6 -0
  176. django_smartbase_admin/views/global_filter_view.py +8 -2
  177. django_smartbase_admin/views/translations_view.py +12 -5
  178. django_smartbase_admin/views/user_config_view.py +52 -0
  179. django_smartbase_admin-1.0.42.dist-info/METADATA +166 -0
  180. {django_smartbase_admin-0.2.54.dist-info → django_smartbase_admin-1.0.42.dist-info}/RECORD +182 -118
  181. {django_smartbase_admin-0.2.54.dist-info → django_smartbase_admin-1.0.42.dist-info}/WHEEL +1 -1
  182. django_smartbase_admin/templates/sb_admin/integrations/sorting/change_list.html +0 -401
  183. django_smartbase_admin-0.2.54.dist-info/METADATA +0 -25
  184. {django_smartbase_admin-0.2.54.dist-info → django_smartbase_admin-1.0.42.dist-info}/LICENSE.md +0 -0
@@ -1,5 +1,10 @@
1
1
  import json
2
+ import logging
2
3
  import urllib.parse
4
+ from collections.abc import Iterable
5
+ from functools import partial
6
+ from typing import Any
7
+ from urllib.parse import urlparse
3
8
 
4
9
  from ckeditor.fields import RichTextFormField
5
10
  from ckeditor_uploader.fields import RichTextUploadingFormField
@@ -9,24 +14,32 @@ from django.contrib.admin.options import get_content_type_for_model
9
14
  from django.contrib.admin.utils import unquote
10
15
  from django.contrib.admin.widgets import AdminTextareaWidget
11
16
  from django.contrib.auth.forms import UsernameField, ReadOnlyPasswordHashWidget
12
- from django.contrib.postgres.forms import SimpleArrayField
17
+ from django.contrib.contenttypes.forms import BaseGenericInlineFormSet
18
+ from django.contrib.contenttypes.models import ContentType
13
19
  from django.core.exceptions import (
14
20
  FieldDoesNotExist,
15
21
  ImproperlyConfigured,
16
22
  PermissionDenied,
17
23
  )
18
24
  from django.db import models
25
+ from django.db.models import QuerySet, Q, Model
19
26
  from django.forms import HiddenInput
20
- from django.forms.models import ModelFormMetaclass
27
+ from django.forms.models import (
28
+ ModelFormMetaclass,
29
+ modelform_factory,
30
+ )
31
+ from django.http import HttpResponse
21
32
  from django.template.loader import render_to_string
22
33
  from django.template.response import TemplateResponse
23
- from django.urls import reverse
24
- from django.utils.safestring import mark_safe
34
+ from django.urls import reverse, NoReverseMatch, resolve
35
+ from django.utils.safestring import mark_safe, SafeString
25
36
  from django.utils.text import capfirst
26
37
  from django.utils.translation import gettext_lazy as _
27
- from django.utils.translation import override as translation_override
28
38
  from django_admin_inline_paginator.admin import TabularInlinePaginated
29
- from filer.fields.image import AdminImageFormField
39
+ from django_htmx.http import trigger_client_event
40
+ from filer.fields.file import FilerFileField
41
+ from filer.fields.image import AdminImageFormField, FilerImageField
42
+ from nested_admin.formsets import NestedInlineFormSet
30
43
  from nested_admin.nested import (
31
44
  NestedModelAdmin,
32
45
  NestedTabularInline,
@@ -35,12 +48,14 @@ from nested_admin.nested import (
35
48
  NestedGenericStackedInline,
36
49
  )
37
50
 
38
- from django_smartbase_admin.actions.admin_action_list import SBAdminListAction
39
51
  from django_smartbase_admin.engine.actions import SBAdminCustomAction
40
- from django_smartbase_admin.utils import FormFieldsetMixin
52
+ from django_smartbase_admin.services.thread_local import SBAdminThreadLocalService
53
+ from django_smartbase_admin.utils import FormFieldsetMixin, is_modal
41
54
 
42
55
  parler_enabled = None
43
56
  try:
57
+ from parler.admin import TranslatableAdmin
58
+
44
59
  from parler.forms import (
45
60
  TranslatableModelForm,
46
61
  TranslatableModelFormMetaclass,
@@ -54,6 +69,30 @@ try:
54
69
  except ImportError:
55
70
  pass
56
71
 
72
+ postrgres_enabled = None
73
+ try:
74
+ from django.contrib.postgres.forms import SimpleArrayField
75
+
76
+ postrgres_enabled = True
77
+ except ImportError:
78
+ pass
79
+
80
+ django_cms_attributes = None
81
+ try:
82
+ from djangocms_attributes_field.fields import AttributesFormField
83
+
84
+ django_cms_attributes = True
85
+ except ImportError:
86
+ pass
87
+
88
+ color_field_enabled = None
89
+ try:
90
+ from colorfield.fields import ColorField
91
+
92
+ color_field_enabled = True
93
+ except ImportError:
94
+ pass
95
+
57
96
  from django_smartbase_admin.admin.widgets import (
58
97
  SBAdminTextInputWidget,
59
98
  SBAdminTextareaWidget,
@@ -74,19 +113,32 @@ from django_smartbase_admin.admin.widgets import (
74
113
  SBAdminPasswordInputWidget,
75
114
  SBAdminReadOnlyPasswordHashWidget,
76
115
  SBAdminHiddenWidget,
116
+ SBAdminCKEditorUploadingWidget,
117
+ SBAdminAttributesWidget,
118
+ SBAdminMultipleChoiceInlineWidget,
119
+ SBAdminColorWidget,
120
+ SBAdminFilerFileWidget,
77
121
  )
78
122
  from django_smartbase_admin.engine.admin_base_view import (
79
123
  SBAdminBaseListView,
80
124
  SBAdminBaseView,
81
125
  SBAdminBaseQuerysetMixin,
126
+ SBADMIN_IS_MODAL_VAR,
127
+ SBADMIN_PARENT_INSTANCE_PK_VAR,
128
+ SBADMIN_PARENT_INSTANCE_LABEL_VAR,
129
+ SBADMIN_PARENT_INSTANCE_FIELD_NAME_VAR,
130
+ SBADMIN_RELOAD_ON_SAVE_VAR,
82
131
  )
83
132
  from django_smartbase_admin.engine.const import (
84
133
  OBJECT_ID_PLACEHOLDER,
85
134
  TRANSLATIONS_SELECTED_LANGUAGES,
135
+ ROW_CLASS_FIELD,
86
136
  )
87
137
  from django_smartbase_admin.services.translations import SBAdminTranslationsService
88
138
  from django_smartbase_admin.services.views import SBAdminViewService
89
139
 
140
+ logger = logging.getLogger(__name__)
141
+
90
142
 
91
143
  class SBAdminFormFieldWidgetsMixin:
92
144
  formfield_widgets = {
@@ -109,15 +161,26 @@ class SBAdminFormFieldWidgetsMixin:
109
161
  forms.BooleanField: SBAdminToggleWidget,
110
162
  forms.SlugField: SBAdminTextInputWidget,
111
163
  RichTextFormField: SBAdminCKEditorWidget,
112
- RichTextUploadingFormField: SBAdminCKEditorWidget,
164
+ RichTextUploadingFormField: SBAdminCKEditorUploadingWidget,
113
165
  forms.ChoiceField: SBAdminSelectWidget,
114
166
  forms.TypedChoiceField: SBAdminSelectWidget,
167
+ forms.MultipleChoiceField: SBAdminMultipleChoiceInlineWidget,
168
+ forms.TypedMultipleChoiceField: SBAdminMultipleChoiceInlineWidget,
115
169
  forms.NullBooleanField: SBAdminNullBooleanSelectWidget,
116
- SimpleArrayField: SBAdminArrayWidget,
117
170
  AdminImageFormField: SBAdminImageWidget,
118
171
  ReadOnlyPasswordHashWidget: SBAdminReadOnlyPasswordHashWidget,
119
172
  forms.HiddenInput: SBAdminHiddenWidget,
120
173
  }
174
+ db_field_widgets = {
175
+ FilerImageField: SBAdminFilerFileWidget,
176
+ FilerFileField: SBAdminFilerFileWidget,
177
+ }
178
+ if postrgres_enabled:
179
+ formfield_widgets[SimpleArrayField] = SBAdminArrayWidget
180
+ if django_cms_attributes:
181
+ formfield_widgets[AttributesFormField] = SBAdminAttributesWidget
182
+ if color_field_enabled:
183
+ db_field_widgets[ColorField] = SBAdminColorWidget
121
184
 
122
185
  django_widget_to_widget = {
123
186
  forms.PasswordInput: SBAdminPasswordInputWidget,
@@ -125,7 +188,9 @@ class SBAdminFormFieldWidgetsMixin:
125
188
  }
126
189
 
127
190
  def get_form_field_widget_class(self, form_field, db_field, request):
128
- default_widget_class = self.formfield_widgets.get(form_field.__class__)
191
+ default_widget_class = self.db_field_widgets.get(
192
+ db_field.__class__, self.formfield_widgets.get(form_field.__class__)
193
+ )
129
194
  if not hasattr(request, "request_data"):
130
195
  # in case of login the view is not wrapped and we have no request_data present
131
196
  return default_widget_class
@@ -161,7 +226,19 @@ class SBAdminFormFieldWidgetsMixin:
161
226
  widget_attrs.pop(
162
227
  "class", None
163
228
  ) # remove origin classes to prevent override our custom widget class
164
- form_field.widget = widget(form_field=form_field, attrs=widget_attrs)
229
+ kwargs = {}
230
+ if isinstance(form_field, RichTextFormField):
231
+ kwargs["config_name"] = getattr(
232
+ form_field.widget, "config_name", None
233
+ ) or getattr(db_field, "config_name", "default")
234
+
235
+ kwargs["external_plugin_resources"] = getattr(
236
+ form_field.widget, "external_plugin_resources", None
237
+ ) or getattr(db_field, "external_plugin_resources", [])
238
+ kwargs["extra_plugins"] = getattr(
239
+ form_field.widget, "extra_plugins", None
240
+ ) or getattr(db_field, "extra_plugins", [])
241
+ form_field.widget = widget(form_field=form_field, attrs=widget_attrs, **kwargs)
165
242
  return form_field
166
243
 
167
244
  def formfield_for_foreignkey(self, db_field, request, **kwargs):
@@ -233,13 +310,21 @@ class SBAdminFormFieldWidgetsMixin:
233
310
 
234
311
 
235
312
  class SBAdminBaseFormInit(SBAdminFormFieldWidgetsMixin, FormFieldsetMixin):
236
- threadsafe_request = None
237
313
  view = None
238
314
 
239
315
  def __init__(self, *args, **kwargs):
240
316
  self.view = kwargs.pop("view", self.view)
241
- self.threadsafe_request = kwargs.pop("request", self.threadsafe_request)
317
+ threadsafe_request = kwargs.pop(
318
+ "request", SBAdminThreadLocalService.get_request()
319
+ )
242
320
  super().__init__(*args, **kwargs)
321
+ self.init_widgets_dynamic(threadsafe_request)
322
+ for field in self.declared_fields:
323
+ form_field = self.fields.get(field)
324
+ if form_field:
325
+ self.assign_widget_to_form_field(form_field, request=threadsafe_request)
326
+
327
+ def init_widgets_dynamic(self, request):
243
328
  for field in self.fields:
244
329
  if not hasattr(self.fields[field].widget, "init_widget_dynamic"):
245
330
  continue
@@ -248,19 +333,11 @@ class SBAdminBaseFormInit(SBAdminFormFieldWidgetsMixin, FormFieldsetMixin):
248
333
  self.fields[field],
249
334
  field,
250
335
  self.view,
251
- self.threadsafe_request,
336
+ request,
252
337
  )
253
- for field in self.declared_fields:
254
- form_field = self.fields.get(field)
255
- if form_field:
256
- self.assign_widget_to_form_field(
257
- form_field, request=self.threadsafe_request
258
- )
259
338
 
260
339
 
261
- class SBAdminBaseForm(
262
- SBAdminBaseFormInit, forms.ModelForm, SBAdminFormFieldWidgetsMixin
263
- ):
340
+ class SBAdminBaseForm(SBAdminBaseFormInit, forms.ModelForm):
264
341
  pass
265
342
 
266
343
 
@@ -403,6 +480,7 @@ if parler_enabled:
403
480
 
404
481
  class SBAdminInlineAndAdminCommon(SBAdminFormFieldWidgetsMixin):
405
482
  sbadmin_fake_inlines = None
483
+ all_base_fields_form = None
406
484
 
407
485
  def init_view_static(self, configuration, model, admin_site):
408
486
  configuration.view_map[self.get_id()] = self
@@ -413,17 +491,14 @@ class SBAdminInlineAndAdminCommon(SBAdminFormFieldWidgetsMixin):
413
491
  for inline_view in inlines:
414
492
  if issubclass(inline_view, SBAdminInline):
415
493
  inline_view_instance = inline_view(model, admin_site)
416
- configuration.view_map[inline_view_instance.get_id()] = (
417
- inline_view_instance
418
- )
419
494
  inline_view_instance.init_view_static(
420
495
  configuration, inline_view_instance.model, admin_site
421
496
  )
422
497
 
423
- def get_sbadmin_fake_inlines(self, request, obj):
498
+ def get_sbadmin_fake_inlines(self, request, obj) -> Iterable:
424
499
  return self.sbadmin_fake_inlines or []
425
500
 
426
- def get_inline_instances(self, request, obj=None):
501
+ def get_inline_instances(self, request, obj=None) -> list:
427
502
  inline_classes = self.get_inlines(request, obj)
428
503
  inline_classes = [*inline_classes] or []
429
504
  inline_classes.extend(self.get_sbadmin_fake_inlines(request, obj))
@@ -442,7 +517,7 @@ class SBAdminInlineAndAdminCommon(SBAdminFormFieldWidgetsMixin):
442
517
  inlines.append(inline)
443
518
  return inlines
444
519
 
445
- def init_view_dynamic(self, request, request_data=None, **kwargs):
520
+ def init_view_dynamic(self, request, request_data=None, **kwargs) -> None:
446
521
  if SBAdminTranslationsService.is_translated_model(self.model):
447
522
  has_default_form = (
448
523
  self.form == TranslatableModelForm or self.form == forms.ModelForm
@@ -454,23 +529,26 @@ class SBAdminInlineAndAdminCommon(SBAdminFormFieldWidgetsMixin):
454
529
  f"Admin '{self}' form class '{self.form}' needs to extend SBTranslatableModelForm in case of translatable model."
455
530
  )
456
531
  super().init_view_dynamic(request, request_data, **kwargs)
457
- self.initialize_form_class(self.form)
532
+ self.initialize_form_class(self.form, request)
458
533
 
459
- def initialize_form_class(self, form):
534
+ def initialize_form_class(self, form, request) -> None:
460
535
  if form:
461
536
  form.view = self
462
537
 
463
- def initialize_form_class_threadsafe(self, form, request):
464
- self.initialize_form_class(form)
465
- if form:
466
- form.threadsafe_request = request
538
+ def initialize_all_base_fields_form(self, request) -> None:
539
+ params = {
540
+ "form": self.form,
541
+ "fields": "__all__",
542
+ "formfield_callback": partial(self.formfield_for_dbfield, request=request),
543
+ }
544
+ self.all_base_fields_form = modelform_factory(self.model, **params)
467
545
 
468
546
 
469
547
  class SBAdminThirdParty(SBAdminInlineAndAdminCommon, SBAdminBaseView):
470
- def get_menu_view_url(self, request):
548
+ def get_menu_view_url(self, request) -> str:
471
549
  return reverse(f"sb_admin:{self.get_id()}_changelist")
472
550
 
473
- def get_id(self):
551
+ def get_id(self) -> str:
474
552
  return self.get_model_path()
475
553
 
476
554
  def change_view(self, request, object_id, form_url="", extra_context=None):
@@ -483,10 +561,14 @@ class SBAdminThirdParty(SBAdminInlineAndAdminCommon, SBAdminBaseView):
483
561
  extra_context.update(self.get_global_context(request))
484
562
  return super().changelist_view(request, extra_context)
485
563
 
486
- def get_action_url(self, action, modifier="template"):
564
+ def get_action_url(self, action, modifier="template") -> str:
487
565
  return reverse(
488
- f"sb_admin:{self.get_id()}_action",
489
- kwargs={"action": action, "modifier": modifier},
566
+ "sb_admin:sb_admin_base",
567
+ kwargs={
568
+ "view": self.get_id(),
569
+ "action": action,
570
+ "modifier": modifier,
571
+ },
490
572
  )
491
573
 
492
574
 
@@ -498,7 +580,7 @@ class SBAdminTranslationStatusMixin:
498
580
  main_language_code,
499
581
  current_lang_code,
500
582
  translations_edit_url,
501
- ):
583
+ ) -> dict[str, Any]:
502
584
  language_code = language[0]
503
585
  language_title = language[1]
504
586
  this_lang_count = languages_count.get(language_code, 0)
@@ -534,11 +616,11 @@ class SBAdminTranslationStatusMixin:
534
616
  }
535
617
 
536
618
  @classmethod
537
- def get_empty_state(cls):
619
+ def get_empty_state(cls) -> SafeString:
538
620
  return mark_safe("<div class='is-empty'></div>")
539
621
 
540
622
  @admin.display(description="")
541
- def sbadmin_translation_status(self, obj):
623
+ def sbadmin_translation_status(self, obj) -> SafeString:
542
624
  if not SBAdminTranslationsService.is_i18n_enabled():
543
625
  return self.get_empty_state()
544
626
 
@@ -582,6 +664,29 @@ class SBAdminTranslationStatusMixin:
582
664
  return mark_safe(result)
583
665
 
584
666
 
667
+ class SBAdminInlineFormSetMixin:
668
+ @classmethod
669
+ def get_default_prefix(cls):
670
+ view = getattr(cls.form, "view", None)
671
+ if view and view.parent_model and view.opts:
672
+ parent_opts = view.parent_model._meta
673
+ opts = view.opts
674
+ modal_prefix = (
675
+ "modal_" if is_modal(SBAdminThreadLocalService.get_request()) else ""
676
+ )
677
+ return f"{modal_prefix}{parent_opts.app_label}_{parent_opts.model_name}_{opts.app_label}-{opts.model_name}"
678
+
679
+ return super().get_default_prefix()
680
+
681
+
682
+ class SBAdminGenericInlineFormSet(SBAdminInlineFormSetMixin, BaseGenericInlineFormSet):
683
+ pass
684
+
685
+
686
+ class SBAdminNestedInlineFormSet(SBAdminInlineFormSetMixin, NestedInlineFormSet):
687
+ pass
688
+
689
+
585
690
  class SBAdmin(
586
691
  SBAdminInlineAndAdminCommon,
587
692
  SBAdminBaseQuerysetMixin,
@@ -590,8 +695,8 @@ class SBAdmin(
590
695
  NestedModelAdmin,
591
696
  ):
592
697
  change_list_template = "sb_admin/actions/list.html"
698
+ reorder_list_template = "sb_admin/actions/list.html"
593
699
  change_form_template = "sb_admin/actions/change_form.html"
594
- delete_confirmation_template = "sb_admin/actions/delete_confirmation.html"
595
700
  delete_selected_confirmation_template = (
596
701
  "sb_admin/actions/delete_selected_confirmation.html"
597
702
  )
@@ -602,19 +707,29 @@ class SBAdmin(
602
707
  sbadmin_tabs = None
603
708
  request_data = None
604
709
  menu_label = None
710
+ sbadmin_is_generic_model = False
711
+
712
+ def save_formset(self, request, form, formset, change):
713
+ if not change and hasattr(formset, "inline_instance"):
714
+ # update inline_instance parent_instance on formset when creating new object
715
+ formset.inline_instance.parent_instance = form.instance
716
+ super().save_formset(request, form, formset, change)
605
717
 
606
- def get_sbadmin_list_filter(self, request):
718
+ def get_sbadmin_list_filter(self, request) -> Iterable:
607
719
  return self.sbadmin_list_filter or self.get_list_filter(request)
608
720
 
609
721
  def get_form(self, request, obj=None, **kwargs):
722
+ self.initialize_all_base_fields_form(request)
610
723
  form = super().get_form(request, obj, **kwargs)
611
- self.initialize_form_class_threadsafe(form, request)
724
+ self.initialize_form_class(form, request)
612
725
  return form
613
726
 
614
- def get_id(self):
727
+ def get_id(self) -> str:
615
728
  return self.get_model_path()
616
729
 
617
- def get_sbadmin_fieldsets(self, request, object_id=None):
730
+ def get_sbadmin_fieldsets(
731
+ self, request, object_id=None
732
+ ) -> list[tuple[str | None, dict[str, Any]]]:
618
733
  fieldsets = self.sbadmin_fieldsets or self.fieldsets
619
734
  if fieldsets:
620
735
  return fieldsets
@@ -623,9 +738,12 @@ class SBAdmin(
623
738
  )
624
739
 
625
740
  def register_autocomplete_views(self, request):
741
+ super().register_autocomplete_views(request)
626
742
  self.get_form(request)()
627
743
 
628
- def get_fieldsets(self, request, obj):
744
+ def get_fieldsets(
745
+ self, request, obj=None
746
+ ) -> list[tuple[str | None, dict[str, Any]]]:
629
747
  fieldsets = []
630
748
  object_id = obj.id if obj else None
631
749
  for fieldset in self.get_sbadmin_fieldsets(request, object_id):
@@ -640,7 +758,9 @@ class SBAdmin(
640
758
  fieldsets.append(fieldset_django)
641
759
  return fieldsets
642
760
 
643
- def get_fieldsets_context(self, request, object_id):
761
+ def get_fieldsets_context(
762
+ self, request, object_id
763
+ ) -> dict[str, dict[str | None, dict[str, Any]]]:
644
764
  fielsets_context = {}
645
765
  for fieldset in self.get_sbadmin_fieldsets(request, object_id):
646
766
  actions = fieldset[1].get("actions", [])
@@ -654,53 +774,69 @@ class SBAdmin(
654
774
  fielsets_context[fieldset[0]] = fieldset[1]
655
775
  return {"fieldsets_context": fielsets_context}
656
776
 
657
- def get_sbadmin_tabs(self, request, object_id):
777
+ def get_sbadmin_tabs(self, request, object_id) -> Iterable:
658
778
  return self.sbadmin_tabs
659
779
 
660
- def get_tabs_context(self, request, object_id):
780
+ def get_tabs_context(self, request, object_id) -> dict[str, Iterable]:
661
781
  return {"tabs_context": self.get_sbadmin_tabs(request, object_id)}
662
782
 
663
- def get_context_data(self, request):
783
+ def get_context_data(self, request) -> dict[str, Any]:
664
784
  return {
665
785
  "base_change_list_template": self.change_list_template,
666
786
  }
667
787
 
668
- def get_menu_view_url(self, request):
788
+ def get_menu_view_url(self, request) -> str:
669
789
  all_config = self.get_all_config(request)
670
790
  url_suffix = ""
671
791
  if all_config and all_config.get("all_params_changed", False):
672
- url_params_dict = all_config.get("url_params")
792
+ url_params_dict = SBAdminViewService.process_url_params(
793
+ view_id=self.get_id(),
794
+ url_params=all_config.get("url_params"),
795
+ filter_version=self.get_filters_version(request),
796
+ )
673
797
  if url_params_dict:
674
798
  url_suffix = f"?{SBAdminViewService.build_list_url(self.get_id(), url_params_dict)}"
675
799
 
676
800
  return f'{reverse(f"sb_admin:{self.get_id()}_changelist")}{url_suffix}'
677
801
 
678
- def get_menu_label(self):
802
+ def get_menu_label(self) -> str:
679
803
  return self.menu_label or self.model._meta.verbose_name_plural
680
804
 
681
- def get_action_url(self, action, modifier="template"):
805
+ def get_action_url(self, action, modifier="template") -> str:
682
806
  if not hasattr(self, action):
683
807
  raise ImproperlyConfigured(f"Action {action} does not exist on {self}")
684
808
  return reverse(
685
- f"sb_admin:{self.get_id()}_action",
809
+ "sb_admin:sb_admin_base",
686
810
  kwargs={
811
+ "view": self.get_id(),
687
812
  "action": action,
688
- "modifier": (
689
- urllib.parse.quote(str(modifier), safe="") if modifier else None
690
- ),
813
+ "modifier": modifier,
691
814
  },
692
815
  )
693
816
 
694
- def get_detail_url(self, object_id=None):
817
+ def get_detail_url(self, object_id=None) -> str:
695
818
  return reverse(
696
819
  f"sb_admin:{self.get_id()}_change",
697
820
  kwargs={"object_id": object_id or OBJECT_ID_PLACEHOLDER},
698
821
  )
699
822
 
700
- def get_new_url(self):
823
+ def get_new_url(self, request) -> str:
701
824
  return reverse(f"sb_admin:{self.get_id()}_add")
702
825
 
703
- def get_previous_next_context(self, request, object_id):
826
+ def get_additional_filter_for_previous_next_context(self, request, object_id) -> Q:
827
+ return Q()
828
+
829
+ def get_change_view_context(self, request, object_id) -> dict | dict[str, Any]:
830
+ return {
831
+ "show_back_button": True,
832
+ "back_url": reverse(
833
+ "sb_admin:{}_{}_changelist".format(
834
+ self.opts.app_label, self.opts.model_name
835
+ )
836
+ ),
837
+ }
838
+
839
+ def get_previous_next_context(self, request, object_id) -> dict | dict[str, Any]:
704
840
  if not self.sbadmin_previous_next_buttons_enabled or not object_id:
705
841
  return {}
706
842
  changelist_filters = request.GET.get("_changelist_filters", "")
@@ -712,15 +848,20 @@ class SBAdmin(
712
848
  )
713
849
  except:
714
850
  all_params = {}
715
- list_action = SBAdminListAction(self, request, all_params=all_params)
851
+ list_action = self.sbadmin_list_action_class(
852
+ self, request, all_params=all_params
853
+ )
854
+ additional_filter = self.get_additional_filter_for_previous_next_context(
855
+ request, object_id
856
+ )
716
857
  all_ids = list(
717
- list_action.build_final_data_count_queryset()
858
+ list_action.build_final_data_count_queryset(additional_filter)
718
859
  .order_by(*list_action.get_order_by_from_request())
719
860
  .values_list("id", flat=True)
720
861
  )
721
862
  index = all_ids.index(int(object_id))
722
- previous_id = None if index == 0 else all_ids[index - 1]
723
- next_id = None if index == len(all_ids) - 1 else all_ids[index + 1]
863
+ previous_id = all_ids[-1] if index == 0 else all_ids[index - 1]
864
+ next_id = all_ids[0] if index == len(all_ids) - 1 else all_ids[index + 1]
724
865
  return {
725
866
  "previous_url": (
726
867
  f"{self.get_detail_url(previous_id)}?_changelist_filters={changelist_filters}"
@@ -736,8 +877,16 @@ class SBAdmin(
736
877
  ),
737
878
  }
738
879
 
880
+ def add_view(self, request, form_url="", extra_context=None):
881
+ extra_context = extra_context or {}
882
+ extra_context.update(self.get_global_context(request, None))
883
+ extra_context.update(self.get_fieldsets_context(request, None))
884
+ extra_context.update(self.get_tabs_context(request, None))
885
+ return self.changeform_view(request, None, form_url, extra_context)
886
+
739
887
  def change_view(self, request, object_id, form_url="", extra_context=None):
740
888
  extra_context = extra_context or {}
889
+ extra_context.update(self.get_change_view_context(request, object_id))
741
890
  extra_context.update(self.get_global_context(request, object_id))
742
891
  extra_context.update(self.get_fieldsets_context(request, object_id))
743
892
  extra_context.update(self.get_tabs_context(request, object_id))
@@ -747,16 +896,6 @@ class SBAdmin(
747
896
  def changelist_view(self, request, extra_context=None):
748
897
  return self.action_list(request, extra_context=extra_context)
749
898
 
750
- def _get_changed_field_labels_from_form(form, changed_data):
751
- changed_field_labels = []
752
- for field_name in changed_data:
753
- try:
754
- verbose_field_name = form.fields[field_name].label or field_name
755
- except KeyError:
756
- verbose_field_name = field_name
757
- changed_field_labels.append(str(verbose_field_name))
758
- return changed_field_labels
759
-
760
899
  def history_view(self, request, object_id, extra_context=None):
761
900
  try:
762
901
  "The 'history' admin view for this model."
@@ -821,6 +960,52 @@ class SBAdmin(
821
960
  except Exception as e:
822
961
  return super().history_view(request, object_id, extra_context)
823
962
 
963
+ @classmethod
964
+ def get_modal_save_response(cls, request, obj):
965
+ response = HttpResponse()
966
+ trigger_client_event(
967
+ response,
968
+ "sbadmin:modal-change-form-response",
969
+ {
970
+ "field": request.POST.get("sb_admin_source_field"),
971
+ "id": obj.pk,
972
+ "label": str(obj),
973
+ "reload": request.POST.get(SBADMIN_RELOAD_ON_SAVE_VAR) == "1",
974
+ },
975
+ )
976
+ trigger_client_event(response, "hideModal", {"elt": "sb-admin-modal"})
977
+ return response
978
+
979
+ def response_add(self, request, obj, post_url_continue=None):
980
+ if is_modal(request):
981
+ return self.get_modal_save_response(request, obj)
982
+ return super().response_add(request, obj, post_url_continue)
983
+
984
+ def response_change(self, request, obj):
985
+ if is_modal(request):
986
+ return self.get_modal_save_response(request, obj)
987
+ return super().response_change(request, obj)
988
+
989
+ @classmethod
990
+ def set_generic_relation_from_parent(cls, request, obj):
991
+ parent_model_path = request.POST.get(SBADMIN_PARENT_INSTANCE_FIELD_NAME_VAR)
992
+ parent_pk = request.POST.get(SBADMIN_PARENT_INSTANCE_PK_VAR)
993
+
994
+ if parent_model_path and parent_pk:
995
+ prefix, app_label, model_name, field, parent_model = (
996
+ parent_model_path.split("_", 5)
997
+ )
998
+ content_type = ContentType.objects.get(
999
+ app_label=app_label, model=parent_model
1000
+ )
1001
+ obj.content_type = content_type
1002
+ obj.object_id = int(parent_pk)
1003
+
1004
+ def save_model(self, request, obj, form, change):
1005
+ if self.sbadmin_is_generic_model and SBADMIN_IS_MODAL_VAR in request.POST:
1006
+ self.set_generic_relation_from_parent(request, obj)
1007
+ super().save_model(request, obj, form, change)
1008
+
824
1009
 
825
1010
  class SBAdminInline(
826
1011
  SBAdminInlineAndAdminCommon, SBAdminBaseQuerysetMixin, SBAdminBaseView
@@ -831,21 +1016,43 @@ class SBAdminInline(
831
1016
  sbadmin_inline_list_actions = None
832
1017
  extra = 0
833
1018
  ordering = None
834
-
835
- def get_ordering(self, request):
1019
+ all_base_fields_form = None
1020
+ sb_admin_add_modal = False
1021
+
1022
+ def get_instance_label(self, request, obj: Model | None = None) -> str | None:
1023
+ if obj:
1024
+ return str(obj)
1025
+ return None
1026
+
1027
+ def get_readonly_fields(self, request, obj=None):
1028
+ readonly_fields = super().get_readonly_fields(request, obj)
1029
+ if ROW_CLASS_FIELD not in readonly_fields:
1030
+ readonly_fields += (ROW_CLASS_FIELD,)
1031
+ return readonly_fields
1032
+
1033
+ def get_fields(self, request, obj=None):
1034
+ fields = super().get_fields(request, obj)
1035
+ if ROW_CLASS_FIELD not in fields:
1036
+ fields += (ROW_CLASS_FIELD,)
1037
+ return fields
1038
+
1039
+ def get_sbadmin_row_class(self, obj):
1040
+ return ""
1041
+
1042
+ def get_ordering(self, request) -> tuple[str]:
836
1043
  """
837
1044
  Hook for specifying field ordering.
838
1045
  """
839
1046
  return self.ordering or ("-id",)
840
1047
 
841
- def get_queryset(self, request=None):
1048
+ def get_queryset(self, request=None) -> QuerySet:
842
1049
  qs = super().get_queryset(request)
843
1050
  return qs.order_by(*self.get_ordering(request))
844
1051
 
845
- def get_sbadmin_inline_list_actions(self):
1052
+ def get_sbadmin_inline_list_actions(self, request) -> list:
846
1053
  return [*(self.sbadmin_inline_list_actions or [])]
847
1054
 
848
- def get_action_url(self, action, modifier="template"):
1055
+ def get_action_url(self, action, modifier="template") -> str:
849
1056
  return reverse(
850
1057
  "sb_admin:sb_admin_base",
851
1058
  kwargs={
@@ -855,17 +1062,72 @@ class SBAdminInline(
855
1062
  },
856
1063
  )
857
1064
 
858
- def register_autocomplete_views(self, request):
1065
+ def register_autocomplete_views(self, request) -> None:
859
1066
  super().register_autocomplete_views(request)
860
- form_class = self.get_formset(request, self.model()).form
861
- self.initialize_form_class_threadsafe(form_class, request)
1067
+ form_class = self.get_formset(request, None).form
1068
+ self.initialize_form_class(form_class, request)
862
1069
  form_class()
863
1070
 
864
- @property
865
- def get_context_data(self):
866
- return {"inline_list_actions": self.get_sbadmin_inline_list_actions()}
1071
+ def get_parent_instance_from_request(self):
1072
+ # Try to get parent instance from request referrer
1073
+ request = (
1074
+ getattr(self, "threadsafe_request", None)
1075
+ or SBAdminThreadLocalService.get_request()
1076
+ )
1077
+ allowed = SBAdminViewService.has_permission(
1078
+ request=request, model=self.parent_model, permission="view"
1079
+ )
1080
+ if not allowed:
1081
+ return None
1082
+
1083
+ referer = request.META.get("HTTP_REFERER")
1084
+ if not referer:
1085
+ return None
1086
+ resolved = resolve(urlparse(referer).path)
1087
+ # Try common kwargs for object ID
1088
+ object_id = resolved.kwargs.get("object_id")
1089
+ if not object_id:
1090
+ return None
1091
+ base_qs = SBAdminViewService.get_restricted_queryset(
1092
+ self.parent_model, request, request.request_data
1093
+ )
1094
+ return base_qs.get(pk=object_id)
1095
+
1096
+ def get_context_data(self, request) -> dict[str, Any]:
1097
+ is_sortable_active: bool = self.sortable_field_name and (
1098
+ self.has_add_permission(request) or self.has_change_permission(request)
1099
+ )
1100
+ add_url = None
1101
+ try:
1102
+ if self.sb_admin_add_modal and self.has_add_permission(request):
1103
+ add_url = reverse(
1104
+ "sb_admin:{}_{}_add".format(
1105
+ self.opts.app_label, self.opts.model_name
1106
+ )
1107
+ )
1108
+ except NoReverseMatch:
1109
+ logger.warning(
1110
+ "To use Add in modal, You have to specify SBAdmin view for %s model",
1111
+ self.opts.model_name,
1112
+ )
1113
+ context_data = {
1114
+ "inline_list_actions": self.get_sbadmin_inline_list_actions(request),
1115
+ "is_sortable_active": is_sortable_active,
1116
+ "add_url": add_url,
1117
+ }
1118
+ if self.parent_instance:
1119
+ context_data["parent_data"] = {
1120
+ SBADMIN_PARENT_INSTANCE_PK_VAR: self.parent_instance.pk,
1121
+ SBADMIN_PARENT_INSTANCE_LABEL_VAR: str(self.parent_instance),
1122
+ SBADMIN_PARENT_INSTANCE_FIELD_NAME_VAR: "{}_{}_id_{}".format(
1123
+ self.model._meta.app_label,
1124
+ self.model._meta.model_name,
1125
+ self.parent_model._meta.model_name,
1126
+ ),
1127
+ }
1128
+ return context_data
867
1129
 
868
- def init_sortable_field(self):
1130
+ def init_sortable_field(self) -> None:
869
1131
  if not self.sortable_field_name:
870
1132
  for field_name in self.sbadmin_sortable_field_options:
871
1133
  is_sortable_field_present = False
@@ -881,15 +1143,15 @@ class SBAdminInline(
881
1143
  self.init_sortable_field()
882
1144
  super().__init__(parent_model, admin_site)
883
1145
 
884
- def init_view_dynamic(self, request, request_data=None, **kwargs):
1146
+ def init_view_dynamic(self, request, request_data=None, **kwargs) -> None:
885
1147
  return super().init_view_dynamic(request, request_data, **kwargs)
886
1148
 
887
- def get_id(self):
1149
+ def get_id(self) -> str:
888
1150
  return (
889
1151
  f"{self.__class__.__name__}_{SBAdminViewService.get_model_path(self.model)}"
890
1152
  )
891
1153
 
892
- def init_inline_dynamic(self, request, obj=None):
1154
+ def init_inline_dynamic(self, request, obj=None) -> None:
893
1155
  self.threadsafe_request = request
894
1156
  self.parent_instance = obj
895
1157
 
@@ -900,18 +1162,21 @@ class SBAdminInline(
900
1162
  return formfield
901
1163
 
902
1164
  def get_formset(self, request, obj=None, **kwargs):
1165
+ self.initialize_all_base_fields_form(request)
903
1166
  formset = super().get_formset(request, obj, **kwargs)
904
1167
  form_class = formset.form
905
- self.initialize_form_class_threadsafe(form_class, request)
1168
+ self.initialize_form_class(form_class, request)
906
1169
  return formset
907
1170
 
908
1171
 
909
1172
  class SBAdminTableInline(SBAdminInline, NestedTabularInline):
910
1173
  template = "sb_admin/inlines/table_inline.html"
1174
+ formset = SBAdminNestedInlineFormSet
911
1175
 
912
1176
 
913
1177
  class SBAdminGenericTableInline(SBAdminInline, NestedGenericTabularInline):
914
1178
  template = "sb_admin/inlines/table_inline.html"
1179
+ formset = SBAdminGenericInlineFormSet
915
1180
 
916
1181
 
917
1182
  class SBAdminTableInlinePaginated(SBAdminTableInline, TabularInlinePaginated):
@@ -924,11 +1189,51 @@ class SBAdminGenericTableInlinePaginated(SBAdminGenericTableInline):
924
1189
  per_page = 50
925
1190
 
926
1191
 
927
- class SBAdminStackedInline(SBAdminInline, NestedStackedInline):
1192
+ class SBAdminStackedInlineBase(SBAdminInline):
1193
+ default_collapsed = False
1194
+
1195
+ def get_sbadmin_default_collapsed(self, request):
1196
+ return self.default_collapsed
1197
+
1198
+ def get_context_data(self, request) -> dict[str, Any]:
1199
+ context_data = super().get_context_data(request)
1200
+ context_data["default_collapsed"] = self.get_sbadmin_default_collapsed(request)
1201
+ return context_data
1202
+
1203
+ def get_sbadmin_inline_list_actions(self, request) -> list:
1204
+ actions = super().get_sbadmin_inline_list_actions(request)
1205
+ actions.append(
1206
+ SBAdminCustomAction(
1207
+ title="Collapse",
1208
+ css_class=f"collapse-all-stacked-inlines {'collapsed' if self.get_sbadmin_default_collapsed(request) else ''}",
1209
+ url=request.get_full_path(),
1210
+ )
1211
+ )
1212
+ return actions
1213
+
1214
+
1215
+ class SBAdminStackedInline(SBAdminStackedInlineBase, NestedStackedInline):
928
1216
  template = "sb_admin/inlines/stacked_inline.html"
929
1217
  fieldset_template = "sb_admin/includes/inline_fieldset.html"
1218
+ formset = SBAdminNestedInlineFormSet
930
1219
 
931
1220
 
932
- class SBAdminGenericStackedInline(SBAdminInline, NestedGenericStackedInline):
1221
+ class SBAdminGenericStackedInline(SBAdminStackedInlineBase, NestedGenericStackedInline):
933
1222
  template = "sb_admin/inlines/stacked_inline.html"
934
1223
  fieldset_template = "sb_admin/includes/inline_fieldset.html"
1224
+ formset = SBAdminGenericInlineFormSet
1225
+
1226
+
1227
+ if parler_enabled:
1228
+
1229
+ class SBTranslatableAdmin(SBAdmin, TranslatableAdmin):
1230
+ def get_readonly_fields(self, request, obj=...):
1231
+ readonly_fields = super().get_readonly_fields(request, obj)
1232
+ if "sbadmin_translation_status" not in readonly_fields:
1233
+ readonly_fields += ("sbadmin_translation_status",)
1234
+ return readonly_fields
1235
+
1236
+ def get_fieldsets(self, request, obj=...):
1237
+ fieldsets = super().get_fieldsets(request, obj)
1238
+ fieldsets.append(SBAdminTranslationsService.get_translation_fieldset())
1239
+ return fieldsets