django-smartbase-admin 0.2.47__py3-none-any.whl → 1.0.38__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_smartbase_admin/actions/admin_action_list.py +80 -51
- django_smartbase_admin/actions/advanced_filters.py +55 -20
- django_smartbase_admin/admin/admin_base.py +477 -89
- django_smartbase_admin/admin/site.py +104 -34
- django_smartbase_admin/admin/widgets.py +598 -26
- django_smartbase_admin/apps.py +2 -0
- django_smartbase_admin/engine/actions.py +34 -16
- django_smartbase_admin/engine/admin_base_view.py +253 -115
- django_smartbase_admin/engine/configuration.py +186 -4
- django_smartbase_admin/engine/const.py +7 -0
- django_smartbase_admin/engine/dashboard.py +44 -23
- django_smartbase_admin/engine/fake_inline.py +44 -7
- django_smartbase_admin/engine/field.py +54 -10
- django_smartbase_admin/engine/field_formatter.py +32 -9
- django_smartbase_admin/engine/filter_widgets.py +356 -21
- django_smartbase_admin/engine/menu_item.py +8 -5
- django_smartbase_admin/engine/modal_view.py +12 -7
- django_smartbase_admin/engine/request.py +2 -0
- django_smartbase_admin/integration/__init__.py +0 -0
- django_smartbase_admin/integration/django_cms.py +43 -0
- django_smartbase_admin/locale/sk/LC_MESSAGES/django.mo +0 -0
- django_smartbase_admin/locale/sk/LC_MESSAGES/django.po +268 -37
- django_smartbase_admin/migrations/0005_sbadminuserconfiguration.py +26 -0
- django_smartbase_admin/migrations/0006_alter_sbadminuserconfiguration_color_scheme.py +18 -0
- django_smartbase_admin/models.py +22 -0
- django_smartbase_admin/monkeypatch/admin_readonly_field_monkeypatch.py +96 -0
- django_smartbase_admin/monkeypatch/fake_inline_monkeypatch.py +1 -1
- django_smartbase_admin/querysets.py +3 -0
- django_smartbase_admin/services/configuration.py +30 -0
- django_smartbase_admin/services/thread_local.py +6 -19
- django_smartbase_admin/services/views.py +82 -27
- django_smartbase_admin/services/xlsx_export.py +6 -0
- django_smartbase_admin/static/sb_admin/build/tailwind.config.js +1 -0
- django_smartbase_admin/static/sb_admin/build/tailwind_config_partials/colors.js +4 -0
- django_smartbase_admin/static/sb_admin/build/tailwind_config_partials/spacing.js +1 -0
- django_smartbase_admin/static/sb_admin/build/webpack.common.js +11 -8
- django_smartbase_admin/static/sb_admin/css/ckeditor/ckeditor_content_dark.css +208 -0
- django_smartbase_admin/static/sb_admin/css/coloris/coloris.min.css +1 -0
- django_smartbase_admin/static/sb_admin/dist/calendar.js +1 -0
- django_smartbase_admin/static/sb_admin/dist/calendar_style.css +1 -0
- django_smartbase_admin/static/sb_admin/dist/calendar_style.js +0 -0
- django_smartbase_admin/static/sb_admin/dist/chart.js +1 -1
- django_smartbase_admin/static/sb_admin/dist/main.js +1 -1
- django_smartbase_admin/static/sb_admin/dist/main_style.css +1 -1
- django_smartbase_admin/static/sb_admin/dist/table.js +1 -1
- django_smartbase_admin/static/sb_admin/dist/table.js.LICENSE.txt +9 -0
- django_smartbase_admin/static/sb_admin/dist/tree_widget.js +1 -0
- django_smartbase_admin/static/sb_admin/dist/tree_widget_style.css +1 -0
- django_smartbase_admin/static/sb_admin/dist/tree_widget_style.js +0 -0
- django_smartbase_admin/static/sb_admin/fancytree/jquery.fancytree-all-deps.min.js +1 -0
- django_smartbase_admin/static/sb_admin/images/file_types/file-csv.svg +11 -0
- django_smartbase_admin/static/sb_admin/images/file_types/file-doc.svg +11 -0
- django_smartbase_admin/static/sb_admin/images/file_types/file-docx.svg +11 -0
- django_smartbase_admin/static/sb_admin/images/file_types/file-other.svg +13 -0
- django_smartbase_admin/static/sb_admin/images/file_types/file-pdf.svg +11 -0
- django_smartbase_admin/static/sb_admin/images/file_types/file-ppt.svg +11 -0
- django_smartbase_admin/static/sb_admin/images/file_types/file-xls.svg +11 -0
- django_smartbase_admin/static/sb_admin/images/file_types/file-xlsx.svg +11 -0
- django_smartbase_admin/static/sb_admin/images/file_types/file-zip.svg +18 -0
- django_smartbase_admin/static/sb_admin/images/flags/de-at.png +0 -0
- django_smartbase_admin/static/sb_admin/images/flags/de-ch.png +0 -0
- django_smartbase_admin/static/sb_admin/images/logo_light.svg +21 -0
- django_smartbase_admin/static/sb_admin/js/coloris/coloris.min.js +6 -0
- django_smartbase_admin/static/sb_admin/js/fullcalendar.min.js +14804 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Bolt-one.svg +3 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Calendar.svg +3 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Caution.svg +3 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Electric-drill.svg +3 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Fire-extinguisher.svg +3 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Gas.svg +3 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Lightning-fill.svg +3 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Moon.svg +3 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Phone-telephone.svg +3 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Printer.svg +3 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Pull.svg +3 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Sun-one.svg +3 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Time.svg +3 -0
- django_smartbase_admin/static/sb_admin/src/css/_base.css +5 -1
- django_smartbase_admin/static/sb_admin/src/css/_colors.css +257 -82
- django_smartbase_admin/static/sb_admin/src/css/_components.css +66 -13
- django_smartbase_admin/static/sb_admin/src/css/_datepicker.css +8 -1
- django_smartbase_admin/static/sb_admin/src/css/_filer.css +60 -0
- django_smartbase_admin/static/sb_admin/src/css/_inlines.css +51 -10
- django_smartbase_admin/static/sb_admin/src/css/_tabulator.css +8 -2
- django_smartbase_admin/static/sb_admin/src/css/calendar.css +162 -0
- django_smartbase_admin/static/sb_admin/src/css/components/_button.css +41 -1
- django_smartbase_admin/static/sb_admin/src/css/components/_dropdown.css +26 -8
- django_smartbase_admin/static/sb_admin/src/css/components/_input.css +62 -20
- django_smartbase_admin/static/sb_admin/src/css/components/_modal.css +1 -1
- django_smartbase_admin/static/sb_admin/src/css/components/_query-builder.css +21 -2
- django_smartbase_admin/static/sb_admin/src/css/components/_toggle.css +12 -1
- django_smartbase_admin/static/sb_admin/src/css/components/_tooltip.css +8 -22
- django_smartbase_admin/static/sb_admin/src/css/style.css +17 -0
- django_smartbase_admin/static/sb_admin/src/css/tree_widget.css +411 -0
- django_smartbase_admin/static/sb_admin/src/js/autocomplete.js +63 -5
- django_smartbase_admin/static/sb_admin/src/js/calendar.js +56 -0
- django_smartbase_admin/static/sb_admin/src/js/chart.js +8 -22
- django_smartbase_admin/static/sb_admin/src/js/choices.js +18 -8
- django_smartbase_admin/static/sb_admin/src/js/datepicker.js +97 -336
- django_smartbase_admin/static/sb_admin/src/js/datepicker_plugins.js +357 -0
- django_smartbase_admin/static/sb_admin/src/js/main.js +307 -26
- django_smartbase_admin/static/sb_admin/src/js/multiselect.js +50 -41
- django_smartbase_admin/static/sb_admin/src/js/range.js +3 -2
- django_smartbase_admin/static/sb_admin/src/js/sb_ajax_params_tabulator_modifier.js +21 -0
- django_smartbase_admin/static/sb_admin/src/js/table.js +38 -13
- django_smartbase_admin/static/sb_admin/src/js/table_modules/advanced_filter_module.js +43 -20
- django_smartbase_admin/static/sb_admin/src/js/table_modules/data_edit_module.js +8 -10
- django_smartbase_admin/static/sb_admin/src/js/table_modules/filter_module.js +3 -3
- django_smartbase_admin/static/sb_admin/src/js/table_modules/header_tabs_module.js +11 -11
- django_smartbase_admin/static/sb_admin/src/js/table_modules/selection_module.js +28 -8
- django_smartbase_admin/static/sb_admin/src/js/table_modules/table_params_module.js +6 -0
- django_smartbase_admin/static/sb_admin/src/js/table_modules/views_module.js +19 -3
- django_smartbase_admin/static/sb_admin/src/js/tree_widget.js +406 -0
- django_smartbase_admin/static/sb_admin/src/js/utils.js +56 -21
- django_smartbase_admin/templates/sb_admin/actions/change_form.html +169 -117
- django_smartbase_admin/templates/sb_admin/actions/dashboard.html +2 -2
- django_smartbase_admin/templates/sb_admin/actions/delete_selected_confirmation.html +56 -32
- django_smartbase_admin/templates/sb_admin/actions/list.html +79 -42
- django_smartbase_admin/templates/sb_admin/actions/object_history.html +2 -2
- django_smartbase_admin/templates/sb_admin/actions/partials/action_link.html +14 -0
- django_smartbase_admin/templates/sb_admin/actions/partials/selected_rows_actions.html +2 -2
- django_smartbase_admin/templates/sb_admin/actions/partials/tabulator_header_v2.html +2 -2
- django_smartbase_admin/templates/sb_admin/actions/tree_list.html +63 -0
- django_smartbase_admin/templates/sb_admin/authentification/login_base.html +5 -1
- django_smartbase_admin/templates/sb_admin/components/columns.html +1 -1
- django_smartbase_admin/templates/sb_admin/components/filters_v2.html +99 -85
- django_smartbase_admin/templates/sb_admin/config/view.html +0 -1
- django_smartbase_admin/templates/sb_admin/dashboard/calendar_widget.html +69 -0
- django_smartbase_admin/templates/sb_admin/dashboard/chart_widget.html +21 -2
- django_smartbase_admin/templates/sb_admin/dashboard/list_widget.html +6 -0
- django_smartbase_admin/templates/sb_admin/dashboard/widget_base.html +1 -1
- django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/date_field.html +18 -8
- django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/multiple_choice_field.html +1 -1
- django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/tree_select_filter.html +2 -0
- django_smartbase_admin/templates/sb_admin/filter_widgets/date_field.html +18 -4
- django_smartbase_admin/templates/sb_admin/filter_widgets/multiple_choice_field.html +14 -0
- django_smartbase_admin/templates/sb_admin/filter_widgets/partials/clear.html +10 -5
- django_smartbase_admin/templates/sb_admin/filter_widgets/radio_choice_field.html +2 -2
- django_smartbase_admin/templates/sb_admin/filter_widgets/tree_select_filter.html +16 -0
- django_smartbase_admin/templates/sb_admin/includes/change_form_title.html +3 -1
- django_smartbase_admin/templates/sb_admin/includes/components.html +5 -1
- django_smartbase_admin/templates/sb_admin/includes/inline_fieldset.html +48 -39
- django_smartbase_admin/templates/sb_admin/includes/notifications.html +2 -1
- django_smartbase_admin/templates/sb_admin/includes/readonly_boolean_field.html +9 -0
- django_smartbase_admin/templates/sb_admin/includes/readonly_field.html +12 -0
- django_smartbase_admin/templates/sb_admin/includes/table_inline_delete_button.html +4 -5
- django_smartbase_admin/templates/sb_admin/inlines/stacked_inline.html +68 -40
- django_smartbase_admin/templates/sb_admin/inlines/table_inline.html +78 -36
- django_smartbase_admin/templates/sb_admin/integrations/filer/folder_list.html +18 -0
- django_smartbase_admin/templates/sb_admin/navigation.html +166 -158
- django_smartbase_admin/templates/sb_admin/partials/modal/modal_content.html +2 -6
- django_smartbase_admin/templates/sb_admin/sb_admin_base.html +49 -4
- django_smartbase_admin/templates/sb_admin/sb_admin_base_no_sidebar.html +35 -11
- django_smartbase_admin/templates/sb_admin/sb_admin_js_trans.html +3 -0
- django_smartbase_admin/templates/sb_admin/sprites/sb_admin.svg +1 -1
- django_smartbase_admin/templates/sb_admin/tailwind_whitelist.html +6 -3
- django_smartbase_admin/templates/sb_admin/widgets/array.html +0 -1
- django_smartbase_admin/templates/sb_admin/widgets/attributes.html +68 -0
- django_smartbase_admin/templates/sb_admin/widgets/autocomplete.html +13 -2
- django_smartbase_admin/templates/sb_admin/widgets/{checkbox_select.html → checkbox_dropdown.html} +2 -2
- django_smartbase_admin/templates/sb_admin/widgets/checkbox_group.html +15 -0
- django_smartbase_admin/templates/sb_admin/widgets/clearable_file_input.html +2 -2
- django_smartbase_admin/templates/sb_admin/widgets/color_field.html +30 -0
- django_smartbase_admin/templates/sb_admin/widgets/date.html +8 -1
- django_smartbase_admin/templates/sb_admin/widgets/filer_file.html +84 -0
- django_smartbase_admin/templates/sb_admin/widgets/html_read_only.html +1 -0
- django_smartbase_admin/templates/sb_admin/widgets/includes/related_item_buttons.html +38 -0
- django_smartbase_admin/templates/sb_admin/widgets/multiwidget.html +1 -1
- django_smartbase_admin/templates/sb_admin/widgets/radio.html +3 -2
- django_smartbase_admin/templates/sb_admin/widgets/radio_dropdown.html +30 -0
- django_smartbase_admin/templates/sb_admin/widgets/read_only_password_hash.html +3 -0
- django_smartbase_admin/templates/sb_admin/widgets/time.html +8 -1
- django_smartbase_admin/templates/sb_admin/widgets/toggle.html +1 -1
- django_smartbase_admin/templates/sb_admin/widgets/tree_base.html +59 -0
- django_smartbase_admin/templates/sb_admin/widgets/tree_select.html +24 -0
- django_smartbase_admin/templates/sb_admin/widgets/tree_select_inline.html +12 -0
- django_smartbase_admin/templatetags/sb_admin_tags.py +163 -4
- django_smartbase_admin/utils.py +22 -3
- django_smartbase_admin/views/dashboard_view.py +6 -0
- django_smartbase_admin/views/global_filter_view.py +8 -2
- django_smartbase_admin/views/translations_view.py +12 -5
- django_smartbase_admin/views/user_config_view.py +52 -0
- django_smartbase_admin-1.0.38.dist-info/METADATA +166 -0
- {django_smartbase_admin-0.2.47.dist-info → django_smartbase_admin-1.0.38.dist-info}/RECORD +186 -121
- {django_smartbase_admin-0.2.47.dist-info → django_smartbase_admin-1.0.38.dist-info}/WHEEL +1 -1
- django_smartbase_admin/templates/sb_admin/integrations/sorting/change_list.html +0 -401
- django_smartbase_admin-0.2.47.dist-info/METADATA +0 -25
- {django_smartbase_admin-0.2.47.dist-info → django_smartbase_admin-1.0.38.dist-info}/LICENSE.md +0 -0
|
@@ -1,23 +1,45 @@
|
|
|
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
|
|
6
11
|
from django import forms
|
|
7
12
|
from django.contrib import admin
|
|
13
|
+
from django.contrib.admin.options import get_content_type_for_model
|
|
14
|
+
from django.contrib.admin.utils import unquote
|
|
8
15
|
from django.contrib.admin.widgets import AdminTextareaWidget
|
|
9
16
|
from django.contrib.auth.forms import UsernameField, ReadOnlyPasswordHashWidget
|
|
10
|
-
from django.contrib.
|
|
11
|
-
from django.
|
|
17
|
+
from django.contrib.contenttypes.forms import BaseGenericInlineFormSet
|
|
18
|
+
from django.contrib.contenttypes.models import ContentType
|
|
19
|
+
from django.core.exceptions import (
|
|
20
|
+
FieldDoesNotExist,
|
|
21
|
+
ImproperlyConfigured,
|
|
22
|
+
PermissionDenied,
|
|
23
|
+
)
|
|
12
24
|
from django.db import models
|
|
25
|
+
from django.db.models import QuerySet, Q, Model
|
|
13
26
|
from django.forms import HiddenInput
|
|
14
|
-
from django.forms.models import
|
|
27
|
+
from django.forms.models import (
|
|
28
|
+
ModelFormMetaclass,
|
|
29
|
+
modelform_factory,
|
|
30
|
+
)
|
|
31
|
+
from django.http import HttpResponse
|
|
15
32
|
from django.template.loader import render_to_string
|
|
16
|
-
from django.
|
|
17
|
-
from django.
|
|
33
|
+
from django.template.response import TemplateResponse
|
|
34
|
+
from django.urls import reverse, NoReverseMatch, resolve
|
|
35
|
+
from django.utils.safestring import mark_safe, SafeString
|
|
36
|
+
from django.utils.text import capfirst
|
|
18
37
|
from django.utils.translation import gettext_lazy as _
|
|
19
38
|
from django_admin_inline_paginator.admin import TabularInlinePaginated
|
|
20
|
-
from
|
|
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
|
|
21
43
|
from nested_admin.nested import (
|
|
22
44
|
NestedModelAdmin,
|
|
23
45
|
NestedTabularInline,
|
|
@@ -26,12 +48,14 @@ from nested_admin.nested import (
|
|
|
26
48
|
NestedGenericStackedInline,
|
|
27
49
|
)
|
|
28
50
|
|
|
29
|
-
from django_smartbase_admin.actions.admin_action_list import SBAdminListAction
|
|
30
51
|
from django_smartbase_admin.engine.actions import SBAdminCustomAction
|
|
31
|
-
from django_smartbase_admin.
|
|
52
|
+
from django_smartbase_admin.services.thread_local import SBAdminThreadLocalService
|
|
53
|
+
from django_smartbase_admin.utils import FormFieldsetMixin, is_modal
|
|
32
54
|
|
|
33
55
|
parler_enabled = None
|
|
34
56
|
try:
|
|
57
|
+
from parler.admin import TranslatableAdmin
|
|
58
|
+
|
|
35
59
|
from parler.forms import (
|
|
36
60
|
TranslatableModelForm,
|
|
37
61
|
TranslatableModelFormMetaclass,
|
|
@@ -45,6 +69,30 @@ try:
|
|
|
45
69
|
except ImportError:
|
|
46
70
|
pass
|
|
47
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
|
+
|
|
48
96
|
from django_smartbase_admin.admin.widgets import (
|
|
49
97
|
SBAdminTextInputWidget,
|
|
50
98
|
SBAdminTextareaWidget,
|
|
@@ -57,7 +105,6 @@ from django_smartbase_admin.admin.widgets import (
|
|
|
57
105
|
SBAdminSplitDateTimeWidget,
|
|
58
106
|
SBAdminTimeWidget,
|
|
59
107
|
SBAdminDateTimeWidget,
|
|
60
|
-
SBAdminAutocompleteWidget,
|
|
61
108
|
SBAdminFileWidget,
|
|
62
109
|
SBAdminToggleWidget,
|
|
63
110
|
SBAdminNullBooleanSelectWidget,
|
|
@@ -66,19 +113,32 @@ from django_smartbase_admin.admin.widgets import (
|
|
|
66
113
|
SBAdminPasswordInputWidget,
|
|
67
114
|
SBAdminReadOnlyPasswordHashWidget,
|
|
68
115
|
SBAdminHiddenWidget,
|
|
116
|
+
SBAdminCKEditorUploadingWidget,
|
|
117
|
+
SBAdminAttributesWidget,
|
|
118
|
+
SBAdminMultipleChoiceInlineWidget,
|
|
119
|
+
SBAdminColorWidget,
|
|
120
|
+
SBAdminFilerFileWidget,
|
|
69
121
|
)
|
|
70
122
|
from django_smartbase_admin.engine.admin_base_view import (
|
|
71
123
|
SBAdminBaseListView,
|
|
72
124
|
SBAdminBaseView,
|
|
73
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,
|
|
74
131
|
)
|
|
75
132
|
from django_smartbase_admin.engine.const import (
|
|
76
133
|
OBJECT_ID_PLACEHOLDER,
|
|
77
134
|
TRANSLATIONS_SELECTED_LANGUAGES,
|
|
135
|
+
ROW_CLASS_FIELD,
|
|
78
136
|
)
|
|
79
137
|
from django_smartbase_admin.services.translations import SBAdminTranslationsService
|
|
80
138
|
from django_smartbase_admin.services.views import SBAdminViewService
|
|
81
139
|
|
|
140
|
+
logger = logging.getLogger(__name__)
|
|
141
|
+
|
|
82
142
|
|
|
83
143
|
class SBAdminFormFieldWidgetsMixin:
|
|
84
144
|
formfield_widgets = {
|
|
@@ -101,15 +161,26 @@ class SBAdminFormFieldWidgetsMixin:
|
|
|
101
161
|
forms.BooleanField: SBAdminToggleWidget,
|
|
102
162
|
forms.SlugField: SBAdminTextInputWidget,
|
|
103
163
|
RichTextFormField: SBAdminCKEditorWidget,
|
|
104
|
-
RichTextUploadingFormField:
|
|
164
|
+
RichTextUploadingFormField: SBAdminCKEditorUploadingWidget,
|
|
105
165
|
forms.ChoiceField: SBAdminSelectWidget,
|
|
106
166
|
forms.TypedChoiceField: SBAdminSelectWidget,
|
|
167
|
+
forms.MultipleChoiceField: SBAdminMultipleChoiceInlineWidget,
|
|
168
|
+
forms.TypedMultipleChoiceField: SBAdminMultipleChoiceInlineWidget,
|
|
107
169
|
forms.NullBooleanField: SBAdminNullBooleanSelectWidget,
|
|
108
|
-
SimpleArrayField: SBAdminArrayWidget,
|
|
109
170
|
AdminImageFormField: SBAdminImageWidget,
|
|
110
171
|
ReadOnlyPasswordHashWidget: SBAdminReadOnlyPasswordHashWidget,
|
|
111
172
|
forms.HiddenInput: SBAdminHiddenWidget,
|
|
112
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
|
|
113
184
|
|
|
114
185
|
django_widget_to_widget = {
|
|
115
186
|
forms.PasswordInput: SBAdminPasswordInputWidget,
|
|
@@ -117,7 +188,9 @@ class SBAdminFormFieldWidgetsMixin:
|
|
|
117
188
|
}
|
|
118
189
|
|
|
119
190
|
def get_form_field_widget_class(self, form_field, db_field, request):
|
|
120
|
-
default_widget_class = self.
|
|
191
|
+
default_widget_class = self.db_field_widgets.get(
|
|
192
|
+
db_field.__class__, self.formfield_widgets.get(form_field.__class__)
|
|
193
|
+
)
|
|
121
194
|
if not hasattr(request, "request_data"):
|
|
122
195
|
# in case of login the view is not wrapped and we have no request_data present
|
|
123
196
|
return default_widget_class
|
|
@@ -153,7 +226,19 @@ class SBAdminFormFieldWidgetsMixin:
|
|
|
153
226
|
widget_attrs.pop(
|
|
154
227
|
"class", None
|
|
155
228
|
) # remove origin classes to prevent override our custom widget class
|
|
156
|
-
|
|
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)
|
|
157
242
|
return form_field
|
|
158
243
|
|
|
159
244
|
def formfield_for_foreignkey(self, db_field, request, **kwargs):
|
|
@@ -225,13 +310,21 @@ class SBAdminFormFieldWidgetsMixin:
|
|
|
225
310
|
|
|
226
311
|
|
|
227
312
|
class SBAdminBaseFormInit(SBAdminFormFieldWidgetsMixin, FormFieldsetMixin):
|
|
228
|
-
threadsafe_request = None
|
|
229
313
|
view = None
|
|
230
314
|
|
|
231
315
|
def __init__(self, *args, **kwargs):
|
|
232
316
|
self.view = kwargs.pop("view", self.view)
|
|
233
|
-
|
|
317
|
+
threadsafe_request = kwargs.pop(
|
|
318
|
+
"request", SBAdminThreadLocalService.get_request()
|
|
319
|
+
)
|
|
234
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):
|
|
235
328
|
for field in self.fields:
|
|
236
329
|
if not hasattr(self.fields[field].widget, "init_widget_dynamic"):
|
|
237
330
|
continue
|
|
@@ -240,19 +333,11 @@ class SBAdminBaseFormInit(SBAdminFormFieldWidgetsMixin, FormFieldsetMixin):
|
|
|
240
333
|
self.fields[field],
|
|
241
334
|
field,
|
|
242
335
|
self.view,
|
|
243
|
-
|
|
336
|
+
request,
|
|
244
337
|
)
|
|
245
|
-
for field in self.declared_fields:
|
|
246
|
-
form_field = self.fields.get(field)
|
|
247
|
-
if form_field:
|
|
248
|
-
self.assign_widget_to_form_field(
|
|
249
|
-
form_field, request=self.threadsafe_request
|
|
250
|
-
)
|
|
251
338
|
|
|
252
339
|
|
|
253
|
-
class SBAdminBaseForm(
|
|
254
|
-
SBAdminBaseFormInit, forms.ModelForm, SBAdminFormFieldWidgetsMixin
|
|
255
|
-
):
|
|
340
|
+
class SBAdminBaseForm(SBAdminBaseFormInit, forms.ModelForm):
|
|
256
341
|
pass
|
|
257
342
|
|
|
258
343
|
|
|
@@ -395,6 +480,7 @@ if parler_enabled:
|
|
|
395
480
|
|
|
396
481
|
class SBAdminInlineAndAdminCommon(SBAdminFormFieldWidgetsMixin):
|
|
397
482
|
sbadmin_fake_inlines = None
|
|
483
|
+
all_base_fields_form = None
|
|
398
484
|
|
|
399
485
|
def init_view_static(self, configuration, model, admin_site):
|
|
400
486
|
configuration.view_map[self.get_id()] = self
|
|
@@ -405,35 +491,33 @@ class SBAdminInlineAndAdminCommon(SBAdminFormFieldWidgetsMixin):
|
|
|
405
491
|
for inline_view in inlines:
|
|
406
492
|
if issubclass(inline_view, SBAdminInline):
|
|
407
493
|
inline_view_instance = inline_view(model, admin_site)
|
|
408
|
-
configuration.view_map[inline_view_instance.get_id()] = (
|
|
409
|
-
inline_view_instance
|
|
410
|
-
)
|
|
411
494
|
inline_view_instance.init_view_static(
|
|
412
495
|
configuration, inline_view_instance.model, admin_site
|
|
413
496
|
)
|
|
414
497
|
|
|
415
|
-
def get_sbadmin_fake_inlines(self, request, obj):
|
|
498
|
+
def get_sbadmin_fake_inlines(self, request, obj) -> Iterable:
|
|
416
499
|
return self.sbadmin_fake_inlines or []
|
|
417
500
|
|
|
418
|
-
def get_inline_instances(self, request, obj=None):
|
|
501
|
+
def get_inline_instances(self, request, obj=None) -> list:
|
|
419
502
|
inline_classes = self.get_inlines(request, obj)
|
|
420
503
|
inline_classes = [*inline_classes] or []
|
|
421
504
|
inline_classes.extend(self.get_sbadmin_fake_inlines(request, obj))
|
|
422
505
|
inlines = []
|
|
423
506
|
for inline_class in inline_classes:
|
|
424
507
|
inline = inline_class(self.model, self.admin_site)
|
|
508
|
+
if hasattr(inline, "init_inline_dynamic"):
|
|
509
|
+
inline.init_inline_dynamic(request, obj)
|
|
425
510
|
if request:
|
|
426
511
|
if not inline.has_view_or_change_permission(request, obj):
|
|
427
512
|
continue
|
|
428
513
|
if not inline.has_add_permission(request, obj):
|
|
429
514
|
inline.max_num = 0
|
|
430
|
-
if hasattr(inline, "
|
|
431
|
-
inline.init_inline_dynamic(request, obj)
|
|
515
|
+
if hasattr(inline, "init_view_dynamic"):
|
|
432
516
|
inline.init_view_dynamic(request, request.request_data)
|
|
433
517
|
inlines.append(inline)
|
|
434
518
|
return inlines
|
|
435
519
|
|
|
436
|
-
def init_view_dynamic(self, request, request_data=None, **kwargs):
|
|
520
|
+
def init_view_dynamic(self, request, request_data=None, **kwargs) -> None:
|
|
437
521
|
if SBAdminTranslationsService.is_translated_model(self.model):
|
|
438
522
|
has_default_form = (
|
|
439
523
|
self.form == TranslatableModelForm or self.form == forms.ModelForm
|
|
@@ -445,23 +529,26 @@ class SBAdminInlineAndAdminCommon(SBAdminFormFieldWidgetsMixin):
|
|
|
445
529
|
f"Admin '{self}' form class '{self.form}' needs to extend SBTranslatableModelForm in case of translatable model."
|
|
446
530
|
)
|
|
447
531
|
super().init_view_dynamic(request, request_data, **kwargs)
|
|
448
|
-
self.initialize_form_class(self.form)
|
|
532
|
+
self.initialize_form_class(self.form, request)
|
|
449
533
|
|
|
450
|
-
def initialize_form_class(self, form):
|
|
534
|
+
def initialize_form_class(self, form, request) -> None:
|
|
451
535
|
if form:
|
|
452
536
|
form.view = self
|
|
453
537
|
|
|
454
|
-
def
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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)
|
|
458
545
|
|
|
459
546
|
|
|
460
547
|
class SBAdminThirdParty(SBAdminInlineAndAdminCommon, SBAdminBaseView):
|
|
461
|
-
def get_menu_view_url(self, request):
|
|
548
|
+
def get_menu_view_url(self, request) -> str:
|
|
462
549
|
return reverse(f"sb_admin:{self.get_id()}_changelist")
|
|
463
550
|
|
|
464
|
-
def get_id(self):
|
|
551
|
+
def get_id(self) -> str:
|
|
465
552
|
return self.get_model_path()
|
|
466
553
|
|
|
467
554
|
def change_view(self, request, object_id, form_url="", extra_context=None):
|
|
@@ -474,10 +561,14 @@ class SBAdminThirdParty(SBAdminInlineAndAdminCommon, SBAdminBaseView):
|
|
|
474
561
|
extra_context.update(self.get_global_context(request))
|
|
475
562
|
return super().changelist_view(request, extra_context)
|
|
476
563
|
|
|
477
|
-
def get_action_url(self, action, modifier="template"):
|
|
564
|
+
def get_action_url(self, action, modifier="template") -> str:
|
|
478
565
|
return reverse(
|
|
479
|
-
|
|
480
|
-
kwargs={
|
|
566
|
+
"sb_admin:sb_admin_base",
|
|
567
|
+
kwargs={
|
|
568
|
+
"view": self.get_id(),
|
|
569
|
+
"action": action,
|
|
570
|
+
"modifier": modifier,
|
|
571
|
+
},
|
|
481
572
|
)
|
|
482
573
|
|
|
483
574
|
|
|
@@ -489,7 +580,7 @@ class SBAdminTranslationStatusMixin:
|
|
|
489
580
|
main_language_code,
|
|
490
581
|
current_lang_code,
|
|
491
582
|
translations_edit_url,
|
|
492
|
-
):
|
|
583
|
+
) -> dict[str, Any]:
|
|
493
584
|
language_code = language[0]
|
|
494
585
|
language_title = language[1]
|
|
495
586
|
this_lang_count = languages_count.get(language_code, 0)
|
|
@@ -525,11 +616,11 @@ class SBAdminTranslationStatusMixin:
|
|
|
525
616
|
}
|
|
526
617
|
|
|
527
618
|
@classmethod
|
|
528
|
-
def get_empty_state(cls):
|
|
619
|
+
def get_empty_state(cls) -> SafeString:
|
|
529
620
|
return mark_safe("<div class='is-empty'></div>")
|
|
530
621
|
|
|
531
622
|
@admin.display(description="")
|
|
532
|
-
def sbadmin_translation_status(self, obj):
|
|
623
|
+
def sbadmin_translation_status(self, obj) -> SafeString:
|
|
533
624
|
if not SBAdminTranslationsService.is_i18n_enabled():
|
|
534
625
|
return self.get_empty_state()
|
|
535
626
|
|
|
@@ -573,6 +664,29 @@ class SBAdminTranslationStatusMixin:
|
|
|
573
664
|
return mark_safe(result)
|
|
574
665
|
|
|
575
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
|
+
|
|
576
690
|
class SBAdmin(
|
|
577
691
|
SBAdminInlineAndAdminCommon,
|
|
578
692
|
SBAdminBaseQuerysetMixin,
|
|
@@ -581,8 +695,8 @@ class SBAdmin(
|
|
|
581
695
|
NestedModelAdmin,
|
|
582
696
|
):
|
|
583
697
|
change_list_template = "sb_admin/actions/list.html"
|
|
698
|
+
reorder_list_template = "sb_admin/actions/list.html"
|
|
584
699
|
change_form_template = "sb_admin/actions/change_form.html"
|
|
585
|
-
delete_confirmation_template = "sb_admin/actions/delete_confirmation.html"
|
|
586
700
|
delete_selected_confirmation_template = (
|
|
587
701
|
"sb_admin/actions/delete_selected_confirmation.html"
|
|
588
702
|
)
|
|
@@ -593,19 +707,29 @@ class SBAdmin(
|
|
|
593
707
|
sbadmin_tabs = None
|
|
594
708
|
request_data = None
|
|
595
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)
|
|
596
717
|
|
|
597
|
-
def get_sbadmin_list_filter(self, request):
|
|
718
|
+
def get_sbadmin_list_filter(self, request) -> Iterable:
|
|
598
719
|
return self.sbadmin_list_filter or self.get_list_filter(request)
|
|
599
720
|
|
|
600
721
|
def get_form(self, request, obj=None, **kwargs):
|
|
722
|
+
self.initialize_all_base_fields_form(request)
|
|
601
723
|
form = super().get_form(request, obj, **kwargs)
|
|
602
|
-
self.
|
|
724
|
+
self.initialize_form_class(form, request)
|
|
603
725
|
return form
|
|
604
726
|
|
|
605
|
-
def get_id(self):
|
|
727
|
+
def get_id(self) -> str:
|
|
606
728
|
return self.get_model_path()
|
|
607
729
|
|
|
608
|
-
def get_sbadmin_fieldsets(
|
|
730
|
+
def get_sbadmin_fieldsets(
|
|
731
|
+
self, request, object_id=None
|
|
732
|
+
) -> list[tuple[str | None, dict[str, Any]]]:
|
|
609
733
|
fieldsets = self.sbadmin_fieldsets or self.fieldsets
|
|
610
734
|
if fieldsets:
|
|
611
735
|
return fieldsets
|
|
@@ -614,9 +738,12 @@ class SBAdmin(
|
|
|
614
738
|
)
|
|
615
739
|
|
|
616
740
|
def register_autocomplete_views(self, request):
|
|
741
|
+
super().register_autocomplete_views(request)
|
|
617
742
|
self.get_form(request)()
|
|
618
743
|
|
|
619
|
-
def get_fieldsets(
|
|
744
|
+
def get_fieldsets(
|
|
745
|
+
self, request, obj=None
|
|
746
|
+
) -> list[tuple[str | None, dict[str, Any]]]:
|
|
620
747
|
fieldsets = []
|
|
621
748
|
object_id = obj.id if obj else None
|
|
622
749
|
for fieldset in self.get_sbadmin_fieldsets(request, object_id):
|
|
@@ -631,7 +758,9 @@ class SBAdmin(
|
|
|
631
758
|
fieldsets.append(fieldset_django)
|
|
632
759
|
return fieldsets
|
|
633
760
|
|
|
634
|
-
def get_fieldsets_context(
|
|
761
|
+
def get_fieldsets_context(
|
|
762
|
+
self, request, object_id
|
|
763
|
+
) -> dict[str, dict[str | None, dict[str, Any]]]:
|
|
635
764
|
fielsets_context = {}
|
|
636
765
|
for fieldset in self.get_sbadmin_fieldsets(request, object_id):
|
|
637
766
|
actions = fieldset[1].get("actions", [])
|
|
@@ -645,53 +774,69 @@ class SBAdmin(
|
|
|
645
774
|
fielsets_context[fieldset[0]] = fieldset[1]
|
|
646
775
|
return {"fieldsets_context": fielsets_context}
|
|
647
776
|
|
|
648
|
-
def get_sbadmin_tabs(self, request, object_id):
|
|
777
|
+
def get_sbadmin_tabs(self, request, object_id) -> Iterable:
|
|
649
778
|
return self.sbadmin_tabs
|
|
650
779
|
|
|
651
|
-
def get_tabs_context(self, request, object_id):
|
|
780
|
+
def get_tabs_context(self, request, object_id) -> dict[str, Iterable]:
|
|
652
781
|
return {"tabs_context": self.get_sbadmin_tabs(request, object_id)}
|
|
653
782
|
|
|
654
|
-
def get_context_data(self, request):
|
|
783
|
+
def get_context_data(self, request) -> dict[str, Any]:
|
|
655
784
|
return {
|
|
656
785
|
"base_change_list_template": self.change_list_template,
|
|
657
786
|
}
|
|
658
787
|
|
|
659
|
-
def get_menu_view_url(self, request):
|
|
788
|
+
def get_menu_view_url(self, request) -> str:
|
|
660
789
|
all_config = self.get_all_config(request)
|
|
661
790
|
url_suffix = ""
|
|
662
791
|
if all_config and all_config.get("all_params_changed", False):
|
|
663
|
-
url_params_dict =
|
|
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
|
+
)
|
|
664
797
|
if url_params_dict:
|
|
665
798
|
url_suffix = f"?{SBAdminViewService.build_list_url(self.get_id(), url_params_dict)}"
|
|
666
799
|
|
|
667
800
|
return f'{reverse(f"sb_admin:{self.get_id()}_changelist")}{url_suffix}'
|
|
668
801
|
|
|
669
|
-
def get_menu_label(self):
|
|
802
|
+
def get_menu_label(self) -> str:
|
|
670
803
|
return self.menu_label or self.model._meta.verbose_name_plural
|
|
671
804
|
|
|
672
|
-
def get_action_url(self, action, modifier="template"):
|
|
805
|
+
def get_action_url(self, action, modifier="template") -> str:
|
|
673
806
|
if not hasattr(self, action):
|
|
674
807
|
raise ImproperlyConfigured(f"Action {action} does not exist on {self}")
|
|
675
808
|
return reverse(
|
|
676
|
-
|
|
809
|
+
"sb_admin:sb_admin_base",
|
|
677
810
|
kwargs={
|
|
811
|
+
"view": self.get_id(),
|
|
678
812
|
"action": action,
|
|
679
|
-
"modifier":
|
|
680
|
-
urllib.parse.quote(str(modifier), safe="") if modifier else None
|
|
681
|
-
),
|
|
813
|
+
"modifier": modifier,
|
|
682
814
|
},
|
|
683
815
|
)
|
|
684
816
|
|
|
685
|
-
def get_detail_url(self, object_id=None):
|
|
817
|
+
def get_detail_url(self, object_id=None) -> str:
|
|
686
818
|
return reverse(
|
|
687
819
|
f"sb_admin:{self.get_id()}_change",
|
|
688
820
|
kwargs={"object_id": object_id or OBJECT_ID_PLACEHOLDER},
|
|
689
821
|
)
|
|
690
822
|
|
|
691
|
-
def get_new_url(self):
|
|
823
|
+
def get_new_url(self, request) -> str:
|
|
692
824
|
return reverse(f"sb_admin:{self.get_id()}_add")
|
|
693
825
|
|
|
694
|
-
def
|
|
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]:
|
|
695
840
|
if not self.sbadmin_previous_next_buttons_enabled or not object_id:
|
|
696
841
|
return {}
|
|
697
842
|
changelist_filters = request.GET.get("_changelist_filters", "")
|
|
@@ -703,15 +848,20 @@ class SBAdmin(
|
|
|
703
848
|
)
|
|
704
849
|
except:
|
|
705
850
|
all_params = {}
|
|
706
|
-
list_action =
|
|
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
|
+
)
|
|
707
857
|
all_ids = list(
|
|
708
|
-
list_action.build_final_data_count_queryset()
|
|
858
|
+
list_action.build_final_data_count_queryset(additional_filter)
|
|
709
859
|
.order_by(*list_action.get_order_by_from_request())
|
|
710
860
|
.values_list("id", flat=True)
|
|
711
861
|
)
|
|
712
862
|
index = all_ids.index(int(object_id))
|
|
713
|
-
previous_id =
|
|
714
|
-
next_id =
|
|
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]
|
|
715
865
|
return {
|
|
716
866
|
"previous_url": (
|
|
717
867
|
f"{self.get_detail_url(previous_id)}?_changelist_filters={changelist_filters}"
|
|
@@ -727,8 +877,16 @@ class SBAdmin(
|
|
|
727
877
|
),
|
|
728
878
|
}
|
|
729
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
|
+
|
|
730
887
|
def change_view(self, request, object_id, form_url="", extra_context=None):
|
|
731
888
|
extra_context = extra_context or {}
|
|
889
|
+
extra_context.update(self.get_change_view_context(request, object_id))
|
|
732
890
|
extra_context.update(self.get_global_context(request, object_id))
|
|
733
891
|
extra_context.update(self.get_fieldsets_context(request, object_id))
|
|
734
892
|
extra_context.update(self.get_tabs_context(request, object_id))
|
|
@@ -738,6 +896,116 @@ class SBAdmin(
|
|
|
738
896
|
def changelist_view(self, request, extra_context=None):
|
|
739
897
|
return self.action_list(request, extra_context=extra_context)
|
|
740
898
|
|
|
899
|
+
def history_view(self, request, object_id, extra_context=None):
|
|
900
|
+
try:
|
|
901
|
+
"The 'history' admin view for this model."
|
|
902
|
+
from django.contrib.admin.models import LogEntry
|
|
903
|
+
from django.contrib.admin.views.main import PAGE_VAR
|
|
904
|
+
|
|
905
|
+
# First check if the user can see this history.
|
|
906
|
+
model = self.model
|
|
907
|
+
obj = self.get_object(request, unquote(object_id))
|
|
908
|
+
if obj is None:
|
|
909
|
+
return self._get_obj_does_not_exist_redirect(
|
|
910
|
+
request, model._meta, object_id
|
|
911
|
+
)
|
|
912
|
+
|
|
913
|
+
if not self.has_view_or_change_permission(request, obj):
|
|
914
|
+
raise PermissionDenied
|
|
915
|
+
|
|
916
|
+
# Then get the history for this object.
|
|
917
|
+
app_label = self.opts.app_label
|
|
918
|
+
action_list = (
|
|
919
|
+
LogEntry.objects.filter(
|
|
920
|
+
object_id=unquote(object_id),
|
|
921
|
+
content_type=get_content_type_for_model(model),
|
|
922
|
+
)
|
|
923
|
+
.select_related()
|
|
924
|
+
.order_by("-action_time")
|
|
925
|
+
)
|
|
926
|
+
|
|
927
|
+
paginator = self.get_paginator(request, action_list, 100)
|
|
928
|
+
page_number = request.GET.get(PAGE_VAR, 1)
|
|
929
|
+
page_obj = paginator.get_page(page_number)
|
|
930
|
+
page_range = paginator.get_elided_page_range(page_obj.number)
|
|
931
|
+
|
|
932
|
+
context = {
|
|
933
|
+
**self.admin_site.each_context(request),
|
|
934
|
+
"title": _("Change history: %s") % obj,
|
|
935
|
+
"subtitle": None,
|
|
936
|
+
"action_list": page_obj,
|
|
937
|
+
"page_range": page_range,
|
|
938
|
+
"page_var": PAGE_VAR,
|
|
939
|
+
"pagination_required": paginator.count > 100,
|
|
940
|
+
"module_name": str(capfirst(self.opts.verbose_name_plural)),
|
|
941
|
+
"object": obj,
|
|
942
|
+
"opts": self.opts,
|
|
943
|
+
"preserved_filters": self.get_preserved_filters(request),
|
|
944
|
+
**(extra_context or {}),
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
request.current_app = self.admin_site.name
|
|
948
|
+
|
|
949
|
+
return TemplateResponse(
|
|
950
|
+
request,
|
|
951
|
+
self.object_history_template
|
|
952
|
+
or [
|
|
953
|
+
"admin/%s/%s/object_history.html"
|
|
954
|
+
% (app_label, self.opts.model_name),
|
|
955
|
+
"admin/%s/object_history.html" % app_label,
|
|
956
|
+
"admin/object_history.html",
|
|
957
|
+
],
|
|
958
|
+
context,
|
|
959
|
+
)
|
|
960
|
+
except Exception as e:
|
|
961
|
+
return super().history_view(request, object_id, extra_context)
|
|
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
|
+
|
|
741
1009
|
|
|
742
1010
|
class SBAdminInline(
|
|
743
1011
|
SBAdminInlineAndAdminCommon, SBAdminBaseQuerysetMixin, SBAdminBaseView
|
|
@@ -748,21 +1016,43 @@ class SBAdminInline(
|
|
|
748
1016
|
sbadmin_inline_list_actions = None
|
|
749
1017
|
extra = 0
|
|
750
1018
|
ordering = None
|
|
751
|
-
|
|
752
|
-
|
|
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]:
|
|
753
1043
|
"""
|
|
754
1044
|
Hook for specifying field ordering.
|
|
755
1045
|
"""
|
|
756
1046
|
return self.ordering or ("-id",)
|
|
757
1047
|
|
|
758
|
-
def get_queryset(self, request=None):
|
|
1048
|
+
def get_queryset(self, request=None) -> QuerySet:
|
|
759
1049
|
qs = super().get_queryset(request)
|
|
760
1050
|
return qs.order_by(*self.get_ordering(request))
|
|
761
1051
|
|
|
762
|
-
def get_sbadmin_inline_list_actions(self):
|
|
1052
|
+
def get_sbadmin_inline_list_actions(self, request) -> list:
|
|
763
1053
|
return [*(self.sbadmin_inline_list_actions or [])]
|
|
764
1054
|
|
|
765
|
-
def get_action_url(self, action, modifier="template"):
|
|
1055
|
+
def get_action_url(self, action, modifier="template") -> str:
|
|
766
1056
|
return reverse(
|
|
767
1057
|
"sb_admin:sb_admin_base",
|
|
768
1058
|
kwargs={
|
|
@@ -772,17 +1062,72 @@ class SBAdminInline(
|
|
|
772
1062
|
},
|
|
773
1063
|
)
|
|
774
1064
|
|
|
775
|
-
def register_autocomplete_views(self, request):
|
|
1065
|
+
def register_autocomplete_views(self, request) -> None:
|
|
776
1066
|
super().register_autocomplete_views(request)
|
|
777
1067
|
form_class = self.get_formset(request, self.model()).form
|
|
778
|
-
self.
|
|
1068
|
+
self.initialize_form_class(form_class, request)
|
|
779
1069
|
form_class()
|
|
780
1070
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
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)
|
|
784
1095
|
|
|
785
|
-
def
|
|
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
|
|
1129
|
+
|
|
1130
|
+
def init_sortable_field(self) -> None:
|
|
786
1131
|
if not self.sortable_field_name:
|
|
787
1132
|
for field_name in self.sbadmin_sortable_field_options:
|
|
788
1133
|
is_sortable_field_present = False
|
|
@@ -798,15 +1143,15 @@ class SBAdminInline(
|
|
|
798
1143
|
self.init_sortable_field()
|
|
799
1144
|
super().__init__(parent_model, admin_site)
|
|
800
1145
|
|
|
801
|
-
def init_view_dynamic(self, request, request_data=None, **kwargs):
|
|
1146
|
+
def init_view_dynamic(self, request, request_data=None, **kwargs) -> None:
|
|
802
1147
|
return super().init_view_dynamic(request, request_data, **kwargs)
|
|
803
1148
|
|
|
804
|
-
def get_id(self):
|
|
1149
|
+
def get_id(self) -> str:
|
|
805
1150
|
return (
|
|
806
1151
|
f"{self.__class__.__name__}_{SBAdminViewService.get_model_path(self.model)}"
|
|
807
1152
|
)
|
|
808
1153
|
|
|
809
|
-
def init_inline_dynamic(self, request, obj=None):
|
|
1154
|
+
def init_inline_dynamic(self, request, obj=None) -> None:
|
|
810
1155
|
self.threadsafe_request = request
|
|
811
1156
|
self.parent_instance = obj
|
|
812
1157
|
|
|
@@ -817,18 +1162,21 @@ class SBAdminInline(
|
|
|
817
1162
|
return formfield
|
|
818
1163
|
|
|
819
1164
|
def get_formset(self, request, obj=None, **kwargs):
|
|
1165
|
+
self.initialize_all_base_fields_form(request)
|
|
820
1166
|
formset = super().get_formset(request, obj, **kwargs)
|
|
821
1167
|
form_class = formset.form
|
|
822
|
-
self.
|
|
1168
|
+
self.initialize_form_class(form_class, request)
|
|
823
1169
|
return formset
|
|
824
1170
|
|
|
825
1171
|
|
|
826
1172
|
class SBAdminTableInline(SBAdminInline, NestedTabularInline):
|
|
827
1173
|
template = "sb_admin/inlines/table_inline.html"
|
|
1174
|
+
formset = SBAdminNestedInlineFormSet
|
|
828
1175
|
|
|
829
1176
|
|
|
830
1177
|
class SBAdminGenericTableInline(SBAdminInline, NestedGenericTabularInline):
|
|
831
1178
|
template = "sb_admin/inlines/table_inline.html"
|
|
1179
|
+
formset = SBAdminGenericInlineFormSet
|
|
832
1180
|
|
|
833
1181
|
|
|
834
1182
|
class SBAdminTableInlinePaginated(SBAdminTableInline, TabularInlinePaginated):
|
|
@@ -841,11 +1189,51 @@ class SBAdminGenericTableInlinePaginated(SBAdminGenericTableInline):
|
|
|
841
1189
|
per_page = 50
|
|
842
1190
|
|
|
843
1191
|
|
|
844
|
-
class
|
|
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):
|
|
845
1216
|
template = "sb_admin/inlines/stacked_inline.html"
|
|
846
1217
|
fieldset_template = "sb_admin/includes/inline_fieldset.html"
|
|
1218
|
+
formset = SBAdminNestedInlineFormSet
|
|
847
1219
|
|
|
848
1220
|
|
|
849
|
-
class SBAdminGenericStackedInline(
|
|
1221
|
+
class SBAdminGenericStackedInline(SBAdminStackedInlineBase, NestedGenericStackedInline):
|
|
850
1222
|
template = "sb_admin/inlines/stacked_inline.html"
|
|
851
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
|