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.
- django_smartbase_admin/actions/admin_action_list.py +79 -38
- django_smartbase_admin/actions/advanced_filters.py +24 -1
- django_smartbase_admin/admin/admin_base.py +402 -97
- django_smartbase_admin/admin/site.py +93 -35
- django_smartbase_admin/admin/widgets.py +636 -26
- django_smartbase_admin/apps.py +2 -0
- django_smartbase_admin/engine/actions.py +34 -16
- django_smartbase_admin/engine/admin_base_view.py +252 -115
- django_smartbase_admin/engine/configuration.py +186 -4
- django_smartbase_admin/engine/const.py +6 -0
- django_smartbase_admin/engine/dashboard.py +49 -24
- django_smartbase_admin/engine/fake_inline.py +15 -11
- django_smartbase_admin/engine/field.py +42 -12
- django_smartbase_admin/engine/field_formatter.py +38 -14
- django_smartbase_admin/engine/filter_widgets.py +348 -24
- 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 +80 -13
- 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/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/js/sbadmin_prepopulated_fields_init.js +25 -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 +61 -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 +31 -7
- 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 +69 -11
- 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 +306 -31
- django_smartbase_admin/static/sb_admin/src/js/multiselect.js +50 -41
- django_smartbase_admin/static/sb_admin/src/js/radio.js +31 -0
- django_smartbase_admin/static/sb_admin/src/js/range.js +3 -2
- django_smartbase_admin/static/sb_admin/src/js/table.js +34 -5
- 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/detail_view_module.js +50 -1
- django_smartbase_admin/static/sb_admin/src/js/table_modules/filter_module.js +10 -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 +6 -0
- 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 +176 -116
- django_smartbase_admin/templates/sb_admin/actions/dashboard.html +2 -2
- django_smartbase_admin/templates/sb_admin/actions/list.html +79 -39
- django_smartbase_admin/templates/sb_admin/actions/partials/action_link.html +14 -0
- 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.html +1 -0
- django_smartbase_admin/templates/sb_admin/components/filters_v2.html +99 -85
- 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/boolean_field.html +1 -14
- 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 +12 -6
- django_smartbase_admin/templates/sb_admin/filter_widgets/radio_choice_field.html +5 -3
- 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/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 +76 -34
- 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 +27 -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/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/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 +115 -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.42.dist-info/METADATA +166 -0
- {django_smartbase_admin-0.2.54.dist-info → django_smartbase_admin-1.0.42.dist-info}/RECORD +182 -118
- {django_smartbase_admin-0.2.54.dist-info → django_smartbase_admin-1.0.42.dist-info}/WHEEL +1 -1
- django_smartbase_admin/templates/sb_admin/integrations/sorting/change_list.html +0 -401
- django_smartbase_admin-0.2.54.dist-info/METADATA +0 -25
- {django_smartbase_admin-0.2.54.dist-info → django_smartbase_admin-1.0.42.dist-info}/LICENSE.md +0 -0
|
@@ -1,18 +1,65 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
2
4
|
|
|
3
5
|
from ckeditor.widgets import CKEditorWidget
|
|
6
|
+
from ckeditor_uploader.widgets import CKEditorUploadingWidget
|
|
4
7
|
from django import forms
|
|
8
|
+
from django.conf import settings
|
|
5
9
|
from django.contrib.admin.widgets import (
|
|
6
10
|
AdminURLFieldWidget,
|
|
11
|
+
ForeignKeyRawIdWidget,
|
|
7
12
|
)
|
|
8
13
|
from django.contrib.auth.forms import ReadOnlyPasswordHashWidget
|
|
9
|
-
from django.
|
|
14
|
+
from django.core.exceptions import (
|
|
15
|
+
FieldDoesNotExist,
|
|
16
|
+
ImproperlyConfigured,
|
|
17
|
+
ValidationError,
|
|
18
|
+
)
|
|
19
|
+
from django.db.models import ForeignKey, OneToOneField
|
|
20
|
+
from django.template.loader import render_to_string
|
|
21
|
+
from django.urls import reverse
|
|
22
|
+
from django.utils.formats import get_format
|
|
23
|
+
from django.utils.http import urlencode
|
|
24
|
+
from django.utils.safestring import mark_safe
|
|
25
|
+
from django.utils.translation import gettext_lazy as _, get_language
|
|
10
26
|
from django.views.generic.base import ContextMixin
|
|
27
|
+
from filer.fields.file import AdminFileWidget as FilerAdminFileWidget
|
|
11
28
|
from filer.fields.image import AdminImageWidget
|
|
29
|
+
from filer.models import File
|
|
12
30
|
|
|
31
|
+
from django_smartbase_admin.admin.site import sb_admin_site
|
|
32
|
+
from django_smartbase_admin.engine.admin_base_view import (
|
|
33
|
+
SBADMIN_PARENT_INSTANCE_PK_VAR,
|
|
34
|
+
SBADMIN_PARENT_INSTANCE_LABEL_VAR,
|
|
35
|
+
)
|
|
13
36
|
from django_smartbase_admin.engine.filter_widgets import (
|
|
14
37
|
AutocompleteFilterWidget,
|
|
38
|
+
SBAdminTreeWidgetMixin,
|
|
39
|
+
)
|
|
40
|
+
from django_smartbase_admin.services.thread_local import SBAdminThreadLocalService
|
|
41
|
+
from django_smartbase_admin.templatetags.sb_admin_tags import (
|
|
42
|
+
SBAdminJSONEncoder,
|
|
15
43
|
)
|
|
44
|
+
from django_smartbase_admin.utils import is_modal
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
# Django >= 5.0
|
|
48
|
+
from django.contrib.admin.exceptions import NotRegistered
|
|
49
|
+
except ImportError:
|
|
50
|
+
from django.contrib.admin.sites import NotRegistered
|
|
51
|
+
|
|
52
|
+
logger = logging.getLogger(__name__)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_datetime_placeholder(lang=None):
|
|
56
|
+
lang = lang or get_language()
|
|
57
|
+
sb_admin_settings = getattr(settings, "SB_ADMIN_SETTINGS", {})
|
|
58
|
+
placeholder_setting = sb_admin_settings.get("DATETIME_PLACEHOLDER", {})
|
|
59
|
+
return placeholder_setting.get(
|
|
60
|
+
lang,
|
|
61
|
+
placeholder_setting.get("default", {"date": "mm.dd.yyyy", "time": "hh:mm"}),
|
|
62
|
+
)
|
|
16
63
|
|
|
17
64
|
|
|
18
65
|
class SBAdminBaseWidget(ContextMixin):
|
|
@@ -28,6 +75,30 @@ class SBAdminBaseWidget(ContextMixin):
|
|
|
28
75
|
def get_context(self, name, value, attrs):
|
|
29
76
|
context = super().get_context(name, value, attrs)
|
|
30
77
|
context["widget"]["form_field"] = self.form_field
|
|
78
|
+
opts = None
|
|
79
|
+
|
|
80
|
+
if self.form_field:
|
|
81
|
+
view = getattr(self.form_field, "view", None)
|
|
82
|
+
if view:
|
|
83
|
+
if hasattr(view, "opts"):
|
|
84
|
+
opts = view.opts
|
|
85
|
+
elif hasattr(view, "view") and hasattr(view.view, "opts"):
|
|
86
|
+
opts = view.view.opts
|
|
87
|
+
|
|
88
|
+
if opts:
|
|
89
|
+
modal_prefix = ""
|
|
90
|
+
try:
|
|
91
|
+
modal_prefix = (
|
|
92
|
+
"modal_"
|
|
93
|
+
if is_modal(SBAdminThreadLocalService.get_request())
|
|
94
|
+
else ""
|
|
95
|
+
)
|
|
96
|
+
except:
|
|
97
|
+
pass
|
|
98
|
+
widget_id = f"{modal_prefix}{opts.app_label}_{opts.model_name}_{context['widget']['attrs']['id']}"
|
|
99
|
+
context["widget"]["attrs"]["id"] = widget_id
|
|
100
|
+
# needed for BoundField.id_for_label to work correctly
|
|
101
|
+
self.attrs["id"] = widget_id
|
|
31
102
|
return context
|
|
32
103
|
|
|
33
104
|
|
|
@@ -90,12 +161,28 @@ class SBAdminToggleWidget(SBAdminBaseWidget, forms.CheckboxInput):
|
|
|
90
161
|
|
|
91
162
|
class SBAdminCKEditorWidget(SBAdminBaseWidget, CKEditorWidget):
|
|
92
163
|
|
|
93
|
-
def __init__(
|
|
164
|
+
def __init__(
|
|
165
|
+
self,
|
|
166
|
+
config_name="default",
|
|
167
|
+
extra_plugins=None,
|
|
168
|
+
external_plugin_resources=None,
|
|
169
|
+
form_field=None,
|
|
170
|
+
attrs=None,
|
|
171
|
+
):
|
|
94
172
|
super().__init__(
|
|
95
|
-
form_field,
|
|
173
|
+
form_field,
|
|
174
|
+
template_name="sb_admin/widgets/ckeditor.html",
|
|
175
|
+
attrs=attrs,
|
|
176
|
+
config_name=config_name,
|
|
177
|
+
extra_plugins=extra_plugins,
|
|
178
|
+
external_plugin_resources=external_plugin_resources,
|
|
96
179
|
)
|
|
97
180
|
|
|
98
181
|
|
|
182
|
+
class SBAdminCKEditorUploadingWidget(CKEditorUploadingWidget, SBAdminCKEditorWidget):
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
|
|
99
186
|
class SBAdminSelectWidget(SBAdminBaseWidget, forms.Select):
|
|
100
187
|
template_name = "sb_admin/widgets/select.html"
|
|
101
188
|
option_template_name = "sb_admin/widgets/select_option.html"
|
|
@@ -116,8 +203,20 @@ class SBAdminRadioWidget(SBAdminBaseWidget, forms.RadioSelect):
|
|
|
116
203
|
)
|
|
117
204
|
|
|
118
205
|
|
|
206
|
+
class SBAdminRadioDropdownWidget(SBAdminBaseWidget, forms.RadioSelect):
|
|
207
|
+
template_name = "sb_admin/widgets/radio_dropdown.html"
|
|
208
|
+
option_template_name = "sb_admin/widgets/radio_option.html"
|
|
209
|
+
|
|
210
|
+
def __init__(self, form_field=None, attrs=None, choices=()):
|
|
211
|
+
super().__init__(
|
|
212
|
+
form_field,
|
|
213
|
+
attrs={"class": "radio radio-list", **(attrs or {})},
|
|
214
|
+
choices=choices,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
119
218
|
class SBAdminMultipleChoiceWidget(SBAdminBaseWidget, forms.CheckboxSelectMultiple):
|
|
120
|
-
template_name = "sb_admin/widgets/
|
|
219
|
+
template_name = "sb_admin/widgets/checkbox_dropdown.html"
|
|
121
220
|
option_template_name = "sb_admin/widgets/checkbox_option.html"
|
|
122
221
|
|
|
123
222
|
def __init__(self, form_field=None, attrs=None, choices=()):
|
|
@@ -146,7 +245,26 @@ class SBAdminDateWidget(SBAdminBaseWidget, forms.DateInput):
|
|
|
146
245
|
|
|
147
246
|
def __init__(self, form_field=None, attrs=None):
|
|
148
247
|
super().__init__(
|
|
149
|
-
form_field,
|
|
248
|
+
form_field,
|
|
249
|
+
format="%Y-%m-%d",
|
|
250
|
+
attrs={
|
|
251
|
+
"class": "input js-datepicker",
|
|
252
|
+
"data-sbadmin-datepicker": self.get_data(),
|
|
253
|
+
"placeholder": get_datetime_placeholder()["date"],
|
|
254
|
+
**(attrs or {}),
|
|
255
|
+
},
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
def get_data(self):
|
|
259
|
+
return json.dumps(
|
|
260
|
+
{
|
|
261
|
+
"flatpickrOptions": {
|
|
262
|
+
"dateFormat": "Y-m-d",
|
|
263
|
+
"altInput": True,
|
|
264
|
+
"altFormat": get_format("SHORT_DATE_FORMAT"),
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
cls=SBAdminJSONEncoder,
|
|
150
268
|
)
|
|
151
269
|
|
|
152
270
|
|
|
@@ -155,7 +273,13 @@ class SBAdminTimeWidget(SBAdminBaseWidget, forms.TimeInput):
|
|
|
155
273
|
|
|
156
274
|
def __init__(self, form_field=None, attrs=None):
|
|
157
275
|
super().__init__(
|
|
158
|
-
form_field,
|
|
276
|
+
form_field,
|
|
277
|
+
attrs={
|
|
278
|
+
"class": "input js-timepicker",
|
|
279
|
+
"placeholder": get_datetime_placeholder()["time"],
|
|
280
|
+
"autocomplete": "do-not-autofill",
|
|
281
|
+
**(attrs or {}),
|
|
282
|
+
},
|
|
159
283
|
)
|
|
160
284
|
|
|
161
285
|
|
|
@@ -209,6 +333,29 @@ class SBAdminArrayWidget(SBAdminTextInputWidget):
|
|
|
209
333
|
return context
|
|
210
334
|
|
|
211
335
|
|
|
336
|
+
class SBAdminAttributesWidget(SBAdminTextInputWidget):
|
|
337
|
+
template_name = "sb_admin/widgets/attributes.html"
|
|
338
|
+
|
|
339
|
+
def get_context(self, name, value, attrs):
|
|
340
|
+
context = super().get_context(name, value, attrs)
|
|
341
|
+
widget = context.get("widget", None)
|
|
342
|
+
dict_widgets = []
|
|
343
|
+
template_widget = {"attrs": {"class": "input"}}
|
|
344
|
+
if widget and value:
|
|
345
|
+
if isinstance(value, str):
|
|
346
|
+
value = json.loads(value)
|
|
347
|
+
dict_widgets = [
|
|
348
|
+
{
|
|
349
|
+
"key": {"value": key, **template_widget},
|
|
350
|
+
"value": {"value": value, **template_widget},
|
|
351
|
+
}
|
|
352
|
+
for key, value in value.items()
|
|
353
|
+
]
|
|
354
|
+
context["dict_widgets"] = dict_widgets
|
|
355
|
+
context["template_widget"] = template_widget
|
|
356
|
+
return context
|
|
357
|
+
|
|
358
|
+
|
|
212
359
|
class SBAdminAutocompleteWidget(
|
|
213
360
|
SBAdminBaseWidget, AutocompleteFilterWidget, forms.Widget
|
|
214
361
|
):
|
|
@@ -216,15 +363,36 @@ class SBAdminAutocompleteWidget(
|
|
|
216
363
|
view = None
|
|
217
364
|
form = None
|
|
218
365
|
field_name = None
|
|
219
|
-
threadsafe_request = None
|
|
220
366
|
initialised = None
|
|
367
|
+
allow_add = None
|
|
368
|
+
create_value_field = None
|
|
369
|
+
default_create_data = None
|
|
370
|
+
forward_to_create = None
|
|
371
|
+
reload_on_save = None
|
|
372
|
+
REQUEST_CREATED_DATA_KEY = "autocomplete_created_data"
|
|
221
373
|
|
|
222
374
|
def __init__(self, form_field=None, *args, **kwargs):
|
|
223
375
|
attrs = kwargs.pop("attrs", None)
|
|
376
|
+
self.reload_on_save = kwargs.pop("reload_on_save", False)
|
|
377
|
+
self.allow_add = kwargs.pop("allow_add", None)
|
|
378
|
+
self.create_value_field = kwargs.pop("create_value_field", None)
|
|
379
|
+
self.forward_to_create = kwargs.pop("forward_to_create", [])
|
|
224
380
|
super().__init__(form_field, *args, **kwargs)
|
|
225
381
|
self.attrs = {} if attrs is None else attrs.copy()
|
|
226
|
-
|
|
227
|
-
|
|
382
|
+
if self.multiselect and self.allow_add:
|
|
383
|
+
raise ImproperlyConfigured(
|
|
384
|
+
"Multiselect with creation is currently not supported."
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
def get_id(self):
|
|
388
|
+
base_id = super().get_id()
|
|
389
|
+
if self.form:
|
|
390
|
+
base_id += f"_{self.form.__class__.__name__}"
|
|
391
|
+
return base_id
|
|
392
|
+
|
|
393
|
+
def init_widget_dynamic(
|
|
394
|
+
self, form, form_field, field_name, view, request, default_create_data=None
|
|
395
|
+
):
|
|
228
396
|
super().init_widget_dynamic(form, form_field, field_name, view, request)
|
|
229
397
|
if self.initialised:
|
|
230
398
|
return
|
|
@@ -232,11 +400,11 @@ class SBAdminAutocompleteWidget(
|
|
|
232
400
|
self.field_name = field_name
|
|
233
401
|
self.view = view
|
|
234
402
|
self.form = form
|
|
235
|
-
self.
|
|
403
|
+
self.default_create_data = default_create_data or {}
|
|
236
404
|
self.init_autocomplete_widget_static(
|
|
237
405
|
self.field_name,
|
|
238
406
|
self.model,
|
|
239
|
-
|
|
407
|
+
request.request_data.configuration,
|
|
240
408
|
)
|
|
241
409
|
|
|
242
410
|
def get_field_name(self):
|
|
@@ -247,6 +415,7 @@ class SBAdminAutocompleteWidget(
|
|
|
247
415
|
self.input_id = (
|
|
248
416
|
context["widget"]["attrs"]["id"] or f'id_{context["widget"]["name"]}'
|
|
249
417
|
)
|
|
418
|
+
|
|
250
419
|
context["widget"]["type"] = "hidden"
|
|
251
420
|
context["widget"]["attrs"]["id"] = self.input_id
|
|
252
421
|
context["widget"]["attrs"]["class"] = "js-autocomplete-detail"
|
|
@@ -254,38 +423,304 @@ class SBAdminAutocompleteWidget(
|
|
|
254
423
|
getattr(self.form_field, "empty_label", "---------") or "---------"
|
|
255
424
|
)
|
|
256
425
|
query_suffix = "__in"
|
|
426
|
+
threadsafe_request = SBAdminThreadLocalService.get_request()
|
|
257
427
|
if not self.is_multiselect():
|
|
258
428
|
query_suffix = ""
|
|
259
429
|
self.multiselect = False
|
|
430
|
+
context["widget"]["attrs"]["preselect_field"] = threadsafe_request.GET.get(
|
|
431
|
+
"sbadmin_parent_instance_field"
|
|
432
|
+
)
|
|
433
|
+
context["widget"]["attrs"]["preselect_field_label"] = (
|
|
434
|
+
threadsafe_request.GET.get(SBADMIN_PARENT_INSTANCE_LABEL_VAR)
|
|
435
|
+
)
|
|
436
|
+
context["widget"]["attrs"]["preselect_field_value"] = (
|
|
437
|
+
threadsafe_request.GET.get(SBADMIN_PARENT_INSTANCE_PK_VAR)
|
|
438
|
+
)
|
|
439
|
+
parsed_value = None
|
|
260
440
|
if value:
|
|
261
|
-
parsed_value = self.parse_value_from_input(
|
|
441
|
+
parsed_value = self.parse_value_from_input(threadsafe_request, value)
|
|
442
|
+
is_create = self.parse_is_create_from_input(
|
|
443
|
+
threadsafe_request,
|
|
444
|
+
threadsafe_request.request_data.request_post.get(name),
|
|
445
|
+
)
|
|
446
|
+
selected_options = []
|
|
447
|
+
if is_create:
|
|
448
|
+
errors = getattr(self.form, "errors", {})
|
|
449
|
+
if errors.get(self.field_name):
|
|
450
|
+
parsed_value = None
|
|
262
451
|
if parsed_value:
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
{
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
452
|
+
if self.is_multiselect() and not isinstance(parsed_value, list):
|
|
453
|
+
parsed_value = [parsed_value]
|
|
454
|
+
|
|
455
|
+
try:
|
|
456
|
+
for item in self.get_queryset(threadsafe_request).filter(
|
|
457
|
+
**{f"{self.get_value_field()}{query_suffix}": parsed_value}
|
|
458
|
+
):
|
|
459
|
+
selected_options.append(
|
|
460
|
+
{
|
|
461
|
+
"value": self.get_value(threadsafe_request, item),
|
|
462
|
+
"label": self.get_label(threadsafe_request, item),
|
|
463
|
+
}
|
|
464
|
+
)
|
|
465
|
+
except ValueError as e:
|
|
466
|
+
new_object_id = threadsafe_request.request_data.additional_data.get(
|
|
467
|
+
self.REQUEST_CREATED_DATA_KEY, {}
|
|
468
|
+
).get(self.field_name)
|
|
469
|
+
if new_object_id:
|
|
470
|
+
selected_options.append(
|
|
471
|
+
{
|
|
472
|
+
"value": new_object_id,
|
|
473
|
+
"label": value,
|
|
474
|
+
}
|
|
475
|
+
)
|
|
476
|
+
elif hasattr(self.form, "add_error"):
|
|
477
|
+
self.form.add_error(
|
|
478
|
+
self.field_name,
|
|
479
|
+
_(
|
|
480
|
+
"The new value was created but became unselected due to another validation error. Please select it again."
|
|
481
|
+
),
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
context["widget"]["value"] = json.dumps(selected_options)
|
|
485
|
+
context["widget"]["value_list"] = selected_options
|
|
486
|
+
|
|
487
|
+
if (
|
|
488
|
+
threadsafe_request.request_data.configuration.autocomplete_show_related_buttons(
|
|
489
|
+
self.model,
|
|
490
|
+
field_name=self.field_name,
|
|
491
|
+
current_view=self.view,
|
|
492
|
+
request=threadsafe_request,
|
|
493
|
+
)
|
|
494
|
+
and not self.is_multiselect()
|
|
495
|
+
):
|
|
496
|
+
self.add_related_buttons_urls(parsed_value, threadsafe_request, context)
|
|
497
|
+
context["reload_on_save"] = self.reload_on_save
|
|
498
|
+
|
|
275
499
|
return context
|
|
276
500
|
|
|
501
|
+
def add_related_buttons_urls(self, parsed_value, request, context):
|
|
502
|
+
try:
|
|
503
|
+
if hasattr(sb_admin_site, "get_model_admin"):
|
|
504
|
+
# Django >= 5.0
|
|
505
|
+
related_model_admin = sb_admin_site.get_model_admin(self.model)
|
|
506
|
+
else:
|
|
507
|
+
related_model_admin = sb_admin_site._registry.get(self.model)
|
|
508
|
+
if not related_model_admin:
|
|
509
|
+
return
|
|
510
|
+
if parsed_value and related_model_admin.has_view_or_change_permission(
|
|
511
|
+
request
|
|
512
|
+
):
|
|
513
|
+
context["widget"]["attrs"]["related_edit_url"] = (
|
|
514
|
+
related_model_admin.get_detail_url(parsed_value)
|
|
515
|
+
)
|
|
516
|
+
if related_model_admin.has_add_permission(request):
|
|
517
|
+
context["widget"]["attrs"]["related_add_url"] = (
|
|
518
|
+
related_model_admin.get_new_url(request)
|
|
519
|
+
)
|
|
520
|
+
except NotRegistered:
|
|
521
|
+
pass
|
|
522
|
+
|
|
277
523
|
def is_multiselect(self):
|
|
278
524
|
if self.multiselect is not None:
|
|
279
525
|
return self.multiselect
|
|
280
526
|
model_field = getattr(self.field, "model_field", None)
|
|
281
527
|
return not (model_field and (model_field.one_to_one or model_field.many_to_one))
|
|
282
528
|
|
|
529
|
+
def _is_in_validation_context(self):
|
|
530
|
+
"""
|
|
531
|
+
Check if value_from_datadict is being called during form validation
|
|
532
|
+
(full_clean, _clean_fields, etc.) vs. during change detection by formsets.
|
|
533
|
+
|
|
534
|
+
Returns True if called during actual validation, False if called during
|
|
535
|
+
change detection or other non-validation contexts.
|
|
536
|
+
|
|
537
|
+
Uses sys._getframe() instead of inspect.currentframe() for better performance,
|
|
538
|
+
as this method is called frequently during form processing.
|
|
539
|
+
"""
|
|
540
|
+
# Get the call stack - using sys._getframe() for better performance
|
|
541
|
+
# sys._getframe(1) gets the caller's frame (skipping this method)
|
|
542
|
+
try:
|
|
543
|
+
current_frame = sys._getframe(1)
|
|
544
|
+
except ValueError:
|
|
545
|
+
# Fallback if _getframe is not available (unlikely in CPython)
|
|
546
|
+
return False
|
|
547
|
+
|
|
548
|
+
# Look for validation-related methods in the call stack
|
|
549
|
+
validation_methods = {
|
|
550
|
+
"_clean_bound_field",
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
# Walk up the call stack
|
|
554
|
+
depth = 0
|
|
555
|
+
while current_frame and depth < 5: # Limit depth to avoid infinite loops
|
|
556
|
+
method_name = current_frame.f_code.co_name
|
|
557
|
+
if method_name in validation_methods:
|
|
558
|
+
return True
|
|
559
|
+
current_frame = current_frame.f_back
|
|
560
|
+
depth += 1
|
|
561
|
+
|
|
562
|
+
return False
|
|
563
|
+
|
|
564
|
+
def get_forward_data(self, request, name):
|
|
565
|
+
"""
|
|
566
|
+
Parse forward data from request.request_data.request_post.
|
|
567
|
+
|
|
568
|
+
For each field in self.forward, use name as base field name and replace
|
|
569
|
+
in it current field name with forward field name, return dict.
|
|
570
|
+
|
|
571
|
+
Args:
|
|
572
|
+
request: The request object
|
|
573
|
+
name: The base field name (e.g., "product__category")
|
|
574
|
+
|
|
575
|
+
Returns:
|
|
576
|
+
dict: Forward data with keys being forward field names and values
|
|
577
|
+
from request data
|
|
578
|
+
"""
|
|
579
|
+
forward_data = {}
|
|
580
|
+
if not getattr(self, "forward", None):
|
|
581
|
+
return forward_data
|
|
582
|
+
|
|
583
|
+
post_data = getattr(request.request_data, "request_post", {})
|
|
584
|
+
if not post_data:
|
|
585
|
+
return forward_data
|
|
586
|
+
|
|
587
|
+
# For each field in self.forward list
|
|
588
|
+
for forward_field in self.forward:
|
|
589
|
+
# Replace only from end of name, separated by last -
|
|
590
|
+
# Example: if name="prefix-field_name", self.field_name="field_name",
|
|
591
|
+
# forward_field="parent" -> result="prefix-parent"
|
|
592
|
+
name_parts = name.split("-")
|
|
593
|
+
|
|
594
|
+
# Replace only if the last part matches self.field_name
|
|
595
|
+
if name_parts and name_parts[-1] == self.field_name:
|
|
596
|
+
# Replace the last part with forward_field and join back
|
|
597
|
+
name_parts[-1] = forward_field
|
|
598
|
+
forward_field_name = "-".join(name_parts)
|
|
599
|
+
else:
|
|
600
|
+
# If last part doesn't match, don't create forward field name
|
|
601
|
+
continue
|
|
602
|
+
|
|
603
|
+
# Get value from post_data if it exists
|
|
604
|
+
if forward_field_name in post_data:
|
|
605
|
+
forward_data[forward_field] = post_data.get(forward_field_name)
|
|
606
|
+
|
|
607
|
+
return forward_data
|
|
608
|
+
|
|
609
|
+
def get_forward_data_to_create(self, request, forward_data):
|
|
610
|
+
forward_data_to_create = {}
|
|
611
|
+
for field_name in self.forward_to_create:
|
|
612
|
+
value = forward_data.get(field_name)
|
|
613
|
+
if value is None:
|
|
614
|
+
continue
|
|
615
|
+
# If forwarding a FK value from the parent form (e.g. for dependent dropdowns),
|
|
616
|
+
# store it under `<field>_id` so `Model(**kwargs)` accepts the raw PK.
|
|
617
|
+
store_key = field_name
|
|
618
|
+
form_model = getattr(getattr(self, "form", None), "model", None)
|
|
619
|
+
if form_model is not None:
|
|
620
|
+
try:
|
|
621
|
+
form_model_field = form_model._meta.get_field(field_name)
|
|
622
|
+
except FieldDoesNotExist:
|
|
623
|
+
form_model_field = None
|
|
624
|
+
if isinstance(form_model_field, (ForeignKey, OneToOneField)):
|
|
625
|
+
store_key = form_model_field.attname
|
|
626
|
+
|
|
627
|
+
forward_data_to_create[store_key] = self.parse_value_from_input(
|
|
628
|
+
request, value
|
|
629
|
+
)
|
|
630
|
+
if not self.is_multiselect():
|
|
631
|
+
forward_data_to_create[store_key] = next(
|
|
632
|
+
iter(forward_data_to_create[store_key]), None
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
return forward_data_to_create
|
|
636
|
+
|
|
283
637
|
def value_from_datadict(self, data, files, name):
|
|
284
638
|
input_value = super().value_from_datadict(data, files, name)
|
|
285
|
-
|
|
639
|
+
threadsafe_request = SBAdminThreadLocalService.get_request()
|
|
640
|
+
parsed_value = self.parse_value_from_input(threadsafe_request, input_value)
|
|
286
641
|
if parsed_value is None:
|
|
287
642
|
return parsed_value
|
|
288
|
-
|
|
643
|
+
|
|
644
|
+
if not self.is_multiselect():
|
|
645
|
+
parsed_value = next(iter(parsed_value), None)
|
|
646
|
+
|
|
647
|
+
# Only perform validation during actual form cleaning, not during change detection
|
|
648
|
+
# by inline formsets or during HTML rendering
|
|
649
|
+
is_in_validation = self._is_in_validation_context()
|
|
650
|
+
if is_in_validation:
|
|
651
|
+
try:
|
|
652
|
+
has_changed = self.form_field.has_changed(
|
|
653
|
+
self.form.initial.get(self.field_name, None), parsed_value
|
|
654
|
+
)
|
|
655
|
+
except AttributeError:
|
|
656
|
+
has_changed = False
|
|
657
|
+
if has_changed:
|
|
658
|
+
parsed_is_create = self.parse_is_create_from_input(
|
|
659
|
+
threadsafe_request, input_value
|
|
660
|
+
)
|
|
661
|
+
if not self.is_multiselect():
|
|
662
|
+
parsed_is_create = next(iter(parsed_is_create), None)
|
|
663
|
+
base_qs = self.get_queryset(threadsafe_request)
|
|
664
|
+
forward_data = self.get_forward_data(threadsafe_request, name)
|
|
665
|
+
qs = self.filter_search_queryset(
|
|
666
|
+
threadsafe_request,
|
|
667
|
+
base_qs,
|
|
668
|
+
forward_data=forward_data,
|
|
669
|
+
)
|
|
670
|
+
self.form_field.queryset = qs
|
|
671
|
+
parsed_value = self.validate(
|
|
672
|
+
parsed_value,
|
|
673
|
+
qs,
|
|
674
|
+
threadsafe_request,
|
|
675
|
+
forward_data,
|
|
676
|
+
parsed_is_create,
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
return parsed_value
|
|
680
|
+
|
|
681
|
+
def should_create_new_obj(self):
|
|
682
|
+
return self.allow_add and self.create_value_field
|
|
683
|
+
|
|
684
|
+
def create_new_obj(self, value, queryset, request, forward_data):
|
|
685
|
+
if isinstance(value, list):
|
|
686
|
+
# TODO: multiselect creation
|
|
687
|
+
return self.form_field.to_python(value)
|
|
688
|
+
else:
|
|
689
|
+
forward_data_to_create = self.get_forward_data_to_create(
|
|
690
|
+
request, forward_data
|
|
691
|
+
)
|
|
692
|
+
data_to_create = {
|
|
693
|
+
self.create_value_field: value,
|
|
694
|
+
**self.default_create_data,
|
|
695
|
+
**forward_data_to_create,
|
|
696
|
+
}
|
|
697
|
+
new_obj = queryset.model.objects.create(**data_to_create)
|
|
698
|
+
try:
|
|
699
|
+
return self.form_field.to_python(new_obj.id)
|
|
700
|
+
except ValidationError:
|
|
701
|
+
new_obj.delete()
|
|
702
|
+
raise ValidationError(
|
|
703
|
+
self.form_field.error_messages["invalid_choice"],
|
|
704
|
+
code="invalid_choice",
|
|
705
|
+
params={"value": value},
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
def validate(self, value, queryset, request, forward_data, is_create=False):
|
|
709
|
+
is_create_value = (
|
|
710
|
+
True in is_create if isinstance(is_create, list) else is_create
|
|
711
|
+
)
|
|
712
|
+
if is_create_value and self.should_create_new_obj():
|
|
713
|
+
new_object = self.create_new_obj(value, queryset, request, forward_data)
|
|
714
|
+
request.request_data.additional_data[self.REQUEST_CREATED_DATA_KEY] = (
|
|
715
|
+
request.request_data.additional_data.get(
|
|
716
|
+
self.REQUEST_CREATED_DATA_KEY, {}
|
|
717
|
+
)
|
|
718
|
+
)
|
|
719
|
+
request.request_data.additional_data[self.REQUEST_CREATED_DATA_KEY][
|
|
720
|
+
self.field_name
|
|
721
|
+
] = new_object.pk
|
|
722
|
+
return new_object
|
|
723
|
+
return self.form_field.to_python(value)
|
|
289
724
|
|
|
290
725
|
@classmethod
|
|
291
726
|
def apply_to_model_field(cls, model_field):
|
|
@@ -311,6 +746,75 @@ class SBAdminImageWidget(SBAdminBaseWidget, AdminImageWidget):
|
|
|
311
746
|
)
|
|
312
747
|
|
|
313
748
|
|
|
749
|
+
class SBAdminFilerFileWidget(SBAdminBaseWidget, FilerAdminFileWidget):
|
|
750
|
+
def __init__(self, form_field=None, *args, **kwargs):
|
|
751
|
+
self.form_field = form_field
|
|
752
|
+
super(FilerAdminFileWidget, self).__init__(
|
|
753
|
+
form_field.rel, form_field.view.admin_site, *args, **kwargs
|
|
754
|
+
)
|
|
755
|
+
|
|
756
|
+
def render(self, name, value, attrs=None, renderer=None):
|
|
757
|
+
obj = self.obj_for_value(value)
|
|
758
|
+
css_id = attrs.get("id", "id_image_x")
|
|
759
|
+
related_url = None
|
|
760
|
+
change_url = ""
|
|
761
|
+
if value:
|
|
762
|
+
try:
|
|
763
|
+
file_obj = File.objects.get(pk=value)
|
|
764
|
+
if file_obj.logical_folder.is_root:
|
|
765
|
+
related_url = reverse("sb_admin:filer-directory_listing-root")
|
|
766
|
+
else:
|
|
767
|
+
related_url = reverse(
|
|
768
|
+
"sb_admin:filer-directory_listing",
|
|
769
|
+
args=(file_obj.logical_folder.id,),
|
|
770
|
+
)
|
|
771
|
+
change_url = reverse(
|
|
772
|
+
"sb_admin:{}_{}_change".format(
|
|
773
|
+
file_obj._meta.app_label,
|
|
774
|
+
file_obj._meta.model_name,
|
|
775
|
+
),
|
|
776
|
+
args=(file_obj.pk,),
|
|
777
|
+
)
|
|
778
|
+
except Exception as e:
|
|
779
|
+
# catch exception and manage it. We can re-raise it for debugging
|
|
780
|
+
# purposes and/or just logging it, provided user configured
|
|
781
|
+
# proper logging configuration
|
|
782
|
+
if settings.FILER_ENABLE_LOGGING:
|
|
783
|
+
logger.error("Error while rendering file widget: %s", e)
|
|
784
|
+
if settings.FILER_DEBUG:
|
|
785
|
+
raise
|
|
786
|
+
if not related_url:
|
|
787
|
+
related_url = reverse("sb_admin:filer-directory_listing-last")
|
|
788
|
+
params = self.url_parameters()
|
|
789
|
+
params["_pick"] = "file"
|
|
790
|
+
if params:
|
|
791
|
+
lookup_url = "?" + urlencode(sorted(params.items()))
|
|
792
|
+
else:
|
|
793
|
+
lookup_url = ""
|
|
794
|
+
if "class" not in attrs:
|
|
795
|
+
# The JavaScript looks for this hook.
|
|
796
|
+
attrs["class"] = "vForeignKeyRawIdAdminField"
|
|
797
|
+
# rendering the super for ForeignKeyRawIdWidget on purpose here because
|
|
798
|
+
# we only need the input and none of the other stuff that
|
|
799
|
+
# ForeignKeyRawIdWidget adds
|
|
800
|
+
hidden_input = super(ForeignKeyRawIdWidget, self).render(
|
|
801
|
+
name, value, attrs
|
|
802
|
+
) # grandparent super
|
|
803
|
+
context = {
|
|
804
|
+
"hidden_input": hidden_input,
|
|
805
|
+
"lookup_url": "{}{}".format(related_url, lookup_url),
|
|
806
|
+
"change_url": change_url,
|
|
807
|
+
"object": obj,
|
|
808
|
+
"lookup_name": name,
|
|
809
|
+
"id": css_id,
|
|
810
|
+
"admin_icon_delete": "admin/img/icon-deletelink.svg",
|
|
811
|
+
}
|
|
812
|
+
# using template name directly to prevent override of template_name
|
|
813
|
+
# when calling render of ForeignKeyRawIdWidget
|
|
814
|
+
html = render_to_string("sb_admin/widgets/filer_file.html", context)
|
|
815
|
+
return mark_safe(html)
|
|
816
|
+
|
|
817
|
+
|
|
314
818
|
class SBAdminReadOnlyPasswordHashWidget(SBAdminBaseWidget, ReadOnlyPasswordHashWidget):
|
|
315
819
|
template_name = "sb_admin/widgets/read_only_password_hash.html"
|
|
316
820
|
|
|
@@ -355,3 +859,109 @@ class SBAdminCodeWidget(SBAdminBaseWidget, forms.Widget):
|
|
|
355
859
|
|
|
356
860
|
class SBAdminHTMLWidget(SBAdminBaseWidget, forms.Widget):
|
|
357
861
|
template_name = "sb_admin/widgets/html_read_only.html"
|
|
862
|
+
|
|
863
|
+
|
|
864
|
+
class SBAdminColorWidget(SBAdminTextInputWidget):
|
|
865
|
+
template_name = "sb_admin/widgets/color_field.html"
|
|
866
|
+
color_swatches = getattr(
|
|
867
|
+
settings,
|
|
868
|
+
"SB_ADMIN_COLOR_SWATCHES",
|
|
869
|
+
[
|
|
870
|
+
"#ffbe76",
|
|
871
|
+
"#f9ca24",
|
|
872
|
+
"#f0932b",
|
|
873
|
+
"#ff7979",
|
|
874
|
+
"#eb4d4b",
|
|
875
|
+
"#badc58",
|
|
876
|
+
"#6ab04c",
|
|
877
|
+
"#c7ecee",
|
|
878
|
+
"#7ed6df",
|
|
879
|
+
"#22a6b3",
|
|
880
|
+
"#e056fd",
|
|
881
|
+
"#be2edd",
|
|
882
|
+
"#686de0",
|
|
883
|
+
"#4834d4",
|
|
884
|
+
"#30336b",
|
|
885
|
+
"#130f40",
|
|
886
|
+
"#95afc0",
|
|
887
|
+
"#535c68",
|
|
888
|
+
],
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
class Media:
|
|
892
|
+
css = {
|
|
893
|
+
"all": [
|
|
894
|
+
"sb_admin/css/coloris/coloris.min.css",
|
|
895
|
+
],
|
|
896
|
+
}
|
|
897
|
+
js = [
|
|
898
|
+
"sb_admin/js/coloris/coloris.min.js",
|
|
899
|
+
]
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
class SBAdminTreeWidget(SBAdminTreeWidgetMixin, SBAdminAutocompleteWidget):
|
|
903
|
+
template_name = "sb_admin/widgets/tree_select.html"
|
|
904
|
+
|
|
905
|
+
def get_context(self, name, value, attrs):
|
|
906
|
+
context = super().get_context(name, value, attrs)
|
|
907
|
+
context["widget"]["raw_value"] = value
|
|
908
|
+
context["widget"]["relationship_pick_mode"] = self.relationship_pick_mode
|
|
909
|
+
context["widget"]["value_dict"] = {
|
|
910
|
+
item["value"]: item["label"]
|
|
911
|
+
for item in context["widget"].get("value_list", [])
|
|
912
|
+
}
|
|
913
|
+
context["widget"]["additional_columns"] = self.additional_columns
|
|
914
|
+
context["widget"]["tree_strings"] = self.tree_strings
|
|
915
|
+
context["fancytree_filter_settings"] = {}
|
|
916
|
+
return context
|
|
917
|
+
|
|
918
|
+
@classmethod
|
|
919
|
+
def get_descendants_from_tree_data(cls, tree_data, parent_id):
|
|
920
|
+
parent_item = cls.find_parent_in_tree_data(tree_data, parent_id)
|
|
921
|
+
descendants = cls.get_descendats_from_item(parent_item)
|
|
922
|
+
return descendants
|
|
923
|
+
|
|
924
|
+
@classmethod
|
|
925
|
+
def get_descendats_from_item(cls, item):
|
|
926
|
+
descendants = []
|
|
927
|
+
if not item:
|
|
928
|
+
return descendants
|
|
929
|
+
for child in item.get("children", []):
|
|
930
|
+
descendants.append(child)
|
|
931
|
+
descendants.extend(cls.get_descendats_from_item(child))
|
|
932
|
+
return descendants
|
|
933
|
+
|
|
934
|
+
@classmethod
|
|
935
|
+
def find_parent_in_tree_data(cls, tree_data, parent_id):
|
|
936
|
+
str_parent_id = str(parent_id)
|
|
937
|
+
for item in tree_data:
|
|
938
|
+
if item["key"] == str_parent_id:
|
|
939
|
+
return item
|
|
940
|
+
parent = cls.find_parent_in_tree_data(
|
|
941
|
+
item.get("children", []), str_parent_id
|
|
942
|
+
)
|
|
943
|
+
if parent:
|
|
944
|
+
return parent
|
|
945
|
+
return None
|
|
946
|
+
|
|
947
|
+
def value_from_datadict(self, data, files, name):
|
|
948
|
+
input_value = data.get(name)
|
|
949
|
+
threadsafe_request = SBAdminThreadLocalService.get_request()
|
|
950
|
+
parsed_value = self.parse_value_from_input(threadsafe_request, input_value)
|
|
951
|
+
obj = self.form.instance
|
|
952
|
+
if (
|
|
953
|
+
obj
|
|
954
|
+
and parsed_value
|
|
955
|
+
and self.relationship_pick_mode == self.RELATIONSHIP_PICK_MODE_PARENT
|
|
956
|
+
):
|
|
957
|
+
if obj.id == parsed_value:
|
|
958
|
+
raise ValidationError(_("Cannot set parent to itself"))
|
|
959
|
+
qs = self.get_queryset(threadsafe_request).order_by(*self.order_by)
|
|
960
|
+
tree_data = self.format_tree_data(threadsafe_request, qs)
|
|
961
|
+
children = self.get_descendants_from_tree_data(tree_data, obj.id)
|
|
962
|
+
children_ids = []
|
|
963
|
+
for child in children:
|
|
964
|
+
children_ids.append(child.get("key"))
|
|
965
|
+
if input_value in children_ids:
|
|
966
|
+
raise ValidationError(_("Cannot set parent to it's own child"))
|
|
967
|
+
return parsed_value
|