django-smartbase-admin 1.0.5__py3-none-any.whl → 1.0.6b2__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/admin/admin_base.py +142 -35
- django_smartbase_admin/admin/widgets.py +63 -1
- django_smartbase_admin/engine/admin_base_view.py +30 -21
- django_smartbase_admin/engine/configuration.py +30 -21
- 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/sprites/sb_admin/Caution.svg +3 -0
- django_smartbase_admin/static/sb_admin/src/css/components/_input.css +19 -15
- django_smartbase_admin/static/sb_admin/src/js/autocomplete.js +18 -0
- django_smartbase_admin/static/sb_admin/src/js/choices.js +8 -0
- django_smartbase_admin/static/sb_admin/src/js/datepicker.js +7 -5
- django_smartbase_admin/static/sb_admin/src/js/main.js +64 -26
- django_smartbase_admin/static/sb_admin/src/js/range.js +3 -2
- django_smartbase_admin/templates/sb_admin/actions/change_form.html +75 -36
- django_smartbase_admin/templates/sb_admin/inlines/table_inline.html +49 -33
- django_smartbase_admin/templates/sb_admin/sprites/sb_admin.svg +1 -1
- django_smartbase_admin/templates/sb_admin/widgets/autocomplete.html +13 -2
- django_smartbase_admin/templates/sb_admin/widgets/date.html +1 -1
- django_smartbase_admin/templates/sb_admin/widgets/includes/related_item_buttons.html +31 -0
- django_smartbase_admin/templates/sb_admin/widgets/time.html +1 -1
- django_smartbase_admin/utils.py +5 -0
- {django_smartbase_admin-1.0.5.dist-info → django_smartbase_admin-1.0.6b2.dist-info}/METADATA +2 -2
- {django_smartbase_admin-1.0.5.dist-info → django_smartbase_admin-1.0.6b2.dist-info}/RECORD +25 -23
- {django_smartbase_admin-1.0.5.dist-info → django_smartbase_admin-1.0.6b2.dist-info}/LICENSE.md +0 -0
- {django_smartbase_admin-1.0.5.dist-info → django_smartbase_admin-1.0.6b2.dist-info}/WHEEL +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import logging
|
|
2
3
|
import urllib.parse
|
|
3
4
|
from collections.abc import Iterable
|
|
4
5
|
from functools import partial
|
|
@@ -12,6 +13,8 @@ from django.contrib.admin.options import get_content_type_for_model
|
|
|
12
13
|
from django.contrib.admin.utils import unquote
|
|
13
14
|
from django.contrib.admin.widgets import AdminTextareaWidget
|
|
14
15
|
from django.contrib.auth.forms import UsernameField, ReadOnlyPasswordHashWidget
|
|
16
|
+
from django.contrib.contenttypes.forms import BaseGenericInlineFormSet
|
|
17
|
+
from django.contrib.contenttypes.models import ContentType
|
|
15
18
|
from django.core.exceptions import (
|
|
16
19
|
FieldDoesNotExist,
|
|
17
20
|
ImproperlyConfigured,
|
|
@@ -24,15 +27,18 @@ from django.forms.models import (
|
|
|
24
27
|
ModelFormMetaclass,
|
|
25
28
|
modelform_factory,
|
|
26
29
|
)
|
|
30
|
+
from django.http import HttpResponse
|
|
27
31
|
from django.template.loader import render_to_string
|
|
28
32
|
from django.template.response import TemplateResponse
|
|
29
|
-
from django.urls import reverse
|
|
33
|
+
from django.urls import reverse, NoReverseMatch
|
|
30
34
|
from django.utils.safestring import mark_safe, SafeString
|
|
31
35
|
from django.utils.text import capfirst
|
|
32
36
|
from django.utils.translation import gettext_lazy as _
|
|
33
37
|
from django_admin_inline_paginator.admin import TabularInlinePaginated
|
|
38
|
+
from django_htmx.http import trigger_client_event
|
|
34
39
|
from filer.fields.file import FilerFileField
|
|
35
40
|
from filer.fields.image import AdminImageFormField, FilerImageField
|
|
41
|
+
from nested_admin.formsets import NestedInlineFormSet
|
|
36
42
|
from nested_admin.nested import (
|
|
37
43
|
NestedModelAdmin,
|
|
38
44
|
NestedTabularInline,
|
|
@@ -43,7 +49,7 @@ from nested_admin.nested import (
|
|
|
43
49
|
|
|
44
50
|
from django_smartbase_admin.engine.actions import SBAdminCustomAction
|
|
45
51
|
from django_smartbase_admin.services.thread_local import SBAdminThreadLocalService
|
|
46
|
-
from django_smartbase_admin.utils import FormFieldsetMixin
|
|
52
|
+
from django_smartbase_admin.utils import FormFieldsetMixin, is_modal
|
|
47
53
|
|
|
48
54
|
parler_enabled = None
|
|
49
55
|
try:
|
|
@@ -68,7 +74,6 @@ try:
|
|
|
68
74
|
except ImportError:
|
|
69
75
|
pass
|
|
70
76
|
|
|
71
|
-
|
|
72
77
|
django_cms_attributes = None
|
|
73
78
|
try:
|
|
74
79
|
from djangocms_attributes_field.fields import AttributesFormField
|
|
@@ -77,7 +82,6 @@ try:
|
|
|
77
82
|
except ImportError:
|
|
78
83
|
pass
|
|
79
84
|
|
|
80
|
-
|
|
81
85
|
color_field_enabled = None
|
|
82
86
|
try:
|
|
83
87
|
from colorfield.fields import ColorField
|
|
@@ -86,7 +90,6 @@ try:
|
|
|
86
90
|
except ImportError:
|
|
87
91
|
pass
|
|
88
92
|
|
|
89
|
-
|
|
90
93
|
from django_smartbase_admin.admin.widgets import (
|
|
91
94
|
SBAdminTextInputWidget,
|
|
92
95
|
SBAdminTextareaWidget,
|
|
@@ -117,6 +120,10 @@ from django_smartbase_admin.engine.admin_base_view import (
|
|
|
117
120
|
SBAdminBaseListView,
|
|
118
121
|
SBAdminBaseView,
|
|
119
122
|
SBAdminBaseQuerysetMixin,
|
|
123
|
+
SBADMIN_IS_MODAL_VAR,
|
|
124
|
+
SBADMIN_PARENT_INSTANCE_PK_VAR,
|
|
125
|
+
SBADMIN_PARENT_INSTANCE_LABEL_VAR,
|
|
126
|
+
SBADMIN_PARENT_INSTANCE_FIELD_NAME_VAR,
|
|
120
127
|
)
|
|
121
128
|
from django_smartbase_admin.engine.const import (
|
|
122
129
|
OBJECT_ID_PLACEHOLDER,
|
|
@@ -126,6 +133,8 @@ from django_smartbase_admin.engine.const import (
|
|
|
126
133
|
from django_smartbase_admin.services.translations import SBAdminTranslationsService
|
|
127
134
|
from django_smartbase_admin.services.views import SBAdminViewService
|
|
128
135
|
|
|
136
|
+
logger = logging.getLogger(__name__)
|
|
137
|
+
|
|
129
138
|
|
|
130
139
|
class SBAdminFormFieldWidgetsMixin:
|
|
131
140
|
formfield_widgets = {
|
|
@@ -186,7 +195,7 @@ class SBAdminFormFieldWidgetsMixin:
|
|
|
186
195
|
)
|
|
187
196
|
|
|
188
197
|
def get_autocomplete_widget(
|
|
189
|
-
|
|
198
|
+
self, request, form_field, db_field, model, multiselect=False
|
|
190
199
|
):
|
|
191
200
|
return request.request_data.configuration.get_autocomplete_widget(
|
|
192
201
|
self, request, form_field, db_field, model, multiselect
|
|
@@ -277,7 +286,7 @@ class SBAdminFormFieldWidgetsMixin:
|
|
|
277
286
|
)
|
|
278
287
|
form_field.widget = form_field_widget_instance
|
|
279
288
|
if form_field.help_text == _(
|
|
280
|
-
|
|
289
|
+
"Hold down “Control”, or “Command” on a Mac, to select more than one."
|
|
281
290
|
):
|
|
282
291
|
form_field.help_text = ""
|
|
283
292
|
return form_field
|
|
@@ -358,25 +367,25 @@ if parler_enabled:
|
|
|
358
367
|
for translations_model in form_model._parler_meta.get_all_models():
|
|
359
368
|
fields = getattr(form_new_meta, "fields", form_meta.fields)
|
|
360
369
|
exclude = (
|
|
361
|
-
|
|
370
|
+
getattr(form_new_meta, "exclude", form_meta.exclude) or ()
|
|
362
371
|
)
|
|
363
372
|
widgets = (
|
|
364
|
-
|
|
373
|
+
getattr(form_new_meta, "widgets", form_meta.widgets) or ()
|
|
365
374
|
)
|
|
366
375
|
labels = (
|
|
367
|
-
|
|
376
|
+
getattr(form_new_meta, "labels", form_meta.labels) or ()
|
|
368
377
|
)
|
|
369
378
|
help_texts = (
|
|
370
|
-
|
|
371
|
-
|
|
379
|
+
getattr(form_new_meta, "help_texts", form_meta.help_texts)
|
|
380
|
+
or ()
|
|
372
381
|
)
|
|
373
382
|
error_messages = (
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
383
|
+
getattr(
|
|
384
|
+
form_new_meta,
|
|
385
|
+
"error_messages",
|
|
386
|
+
form_meta.error_messages,
|
|
387
|
+
)
|
|
388
|
+
or ()
|
|
380
389
|
)
|
|
381
390
|
formfield_callback = attrs.get("formfield_callback", None)
|
|
382
391
|
|
|
@@ -397,10 +406,10 @@ if parler_enabled:
|
|
|
397
406
|
# The next code holds the same logic as fields_for_model()
|
|
398
407
|
# The f.editable check happens in _get_model_form_field()
|
|
399
408
|
elif (
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
409
|
+
f_name not in form_base_fields
|
|
410
|
+
and (fields is None or f_name in fields)
|
|
411
|
+
and f_name not in exclude
|
|
412
|
+
and not f_name in attrs
|
|
404
413
|
):
|
|
405
414
|
# Get declared widget kwargs
|
|
406
415
|
if f_name in widgets:
|
|
@@ -421,7 +430,7 @@ if parler_enabled:
|
|
|
421
430
|
# See if this formfield was previously defined using a TranslatedField placeholder.
|
|
422
431
|
placeholder = _get_mro_attribute(bases, f_name)
|
|
423
432
|
if placeholder and isinstance(
|
|
424
|
-
|
|
433
|
+
placeholder, TranslatedField
|
|
425
434
|
):
|
|
426
435
|
kwargs.update(placeholder.kwargs)
|
|
427
436
|
|
|
@@ -454,6 +463,7 @@ if parler_enabled:
|
|
|
454
463
|
mcs.parler_orig__new__(mcs, name, bases, attrs)
|
|
455
464
|
return super().__new__(mcs, name, bases, attrs)
|
|
456
465
|
|
|
466
|
+
|
|
457
467
|
class SBTranslatableModelForm(
|
|
458
468
|
BaseTranslatableModelForm,
|
|
459
469
|
SBAdminBaseForm,
|
|
@@ -504,7 +514,7 @@ class SBAdminInlineAndAdminCommon(SBAdminFormFieldWidgetsMixin):
|
|
|
504
514
|
def init_view_dynamic(self, request, request_data=None, **kwargs) -> None:
|
|
505
515
|
if SBAdminTranslationsService.is_translated_model(self.model):
|
|
506
516
|
has_default_form = (
|
|
507
|
-
|
|
517
|
+
self.form == TranslatableModelForm or self.form == forms.ModelForm
|
|
508
518
|
)
|
|
509
519
|
if not self.form or has_default_form:
|
|
510
520
|
self.form = SBTranslatableModelForm
|
|
@@ -558,12 +568,12 @@ class SBAdminThirdParty(SBAdminInlineAndAdminCommon, SBAdminBaseView):
|
|
|
558
568
|
|
|
559
569
|
class SBAdminTranslationStatusMixin:
|
|
560
570
|
def sbadmin_translation_status_row_context(
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
571
|
+
self,
|
|
572
|
+
language,
|
|
573
|
+
languages_count,
|
|
574
|
+
main_language_code,
|
|
575
|
+
current_lang_code,
|
|
576
|
+
translations_edit_url,
|
|
567
577
|
) -> dict[str, Any]:
|
|
568
578
|
language_code = language[0]
|
|
569
579
|
language_title = language[1]
|
|
@@ -648,6 +658,27 @@ class SBAdminTranslationStatusMixin:
|
|
|
648
658
|
return mark_safe(result)
|
|
649
659
|
|
|
650
660
|
|
|
661
|
+
class SBAdminInlineFormSetMixin:
|
|
662
|
+
@classmethod
|
|
663
|
+
def get_default_prefix(cls):
|
|
664
|
+
view = getattr(cls.form, "view", None)
|
|
665
|
+
if view and view.parent_model and view.opts:
|
|
666
|
+
parent_opts = view.parent_model._meta
|
|
667
|
+
opts = view.opts
|
|
668
|
+
modal_prefix = "modal__" if is_modal(SBAdminThreadLocalService.get_request()) else ""
|
|
669
|
+
return f"{modal_prefix}{parent_opts.app_label}_{parent_opts.model_name}_{opts.app_label}-{opts.model_name}"
|
|
670
|
+
|
|
671
|
+
return super().get_default_prefix()
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
class SBAdminGenericInlineFormSet(SBAdminInlineFormSetMixin, BaseGenericInlineFormSet):
|
|
675
|
+
pass
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
class SBAdminNestedInlineFormSet(SBAdminInlineFormSetMixin, NestedInlineFormSet):
|
|
679
|
+
pass
|
|
680
|
+
|
|
681
|
+
|
|
651
682
|
class SBAdmin(
|
|
652
683
|
SBAdminInlineAndAdminCommon,
|
|
653
684
|
SBAdminBaseQuerysetMixin,
|
|
@@ -668,6 +699,10 @@ class SBAdmin(
|
|
|
668
699
|
sbadmin_tabs = None
|
|
669
700
|
request_data = None
|
|
670
701
|
menu_label = None
|
|
702
|
+
sbadmin_is_generic_model = False
|
|
703
|
+
|
|
704
|
+
def __init__(self, model, admin_site):
|
|
705
|
+
super().__init__(model, admin_site)
|
|
671
706
|
|
|
672
707
|
def save_formset(self, request, form, formset, change):
|
|
673
708
|
if not change and hasattr(formset, "inline_instance"):
|
|
@@ -688,7 +723,7 @@ class SBAdmin(
|
|
|
688
723
|
return self.get_model_path()
|
|
689
724
|
|
|
690
725
|
def get_sbadmin_fieldsets(
|
|
691
|
-
|
|
726
|
+
self, request, object_id=None
|
|
692
727
|
) -> Iterable[tuple[str | None, dict[str, Any]]]:
|
|
693
728
|
fieldsets = self.sbadmin_fieldsets or self.fieldsets
|
|
694
729
|
if fieldsets:
|
|
@@ -702,7 +737,7 @@ class SBAdmin(
|
|
|
702
737
|
self.get_form(request)()
|
|
703
738
|
|
|
704
739
|
def get_fieldsets(
|
|
705
|
-
|
|
740
|
+
self, request, obj=None
|
|
706
741
|
) -> list[tuple[str | None, dict[str, Any]]]:
|
|
707
742
|
fieldsets = []
|
|
708
743
|
object_id = obj.id if obj else None
|
|
@@ -719,7 +754,7 @@ class SBAdmin(
|
|
|
719
754
|
return fieldsets
|
|
720
755
|
|
|
721
756
|
def get_fieldsets_context(
|
|
722
|
-
|
|
757
|
+
self, request, object_id
|
|
723
758
|
) -> dict[str, dict[str | None, dict[str, Any]]]:
|
|
724
759
|
fielsets_context = {}
|
|
725
760
|
for fieldset in self.get_sbadmin_fieldsets(request, object_id):
|
|
@@ -903,6 +938,50 @@ class SBAdmin(
|
|
|
903
938
|
except Exception as e:
|
|
904
939
|
return super().history_view(request, object_id, extra_context)
|
|
905
940
|
|
|
941
|
+
@classmethod
|
|
942
|
+
def get_modal_save_response(cls, request, obj):
|
|
943
|
+
response = HttpResponse()
|
|
944
|
+
trigger_client_event(
|
|
945
|
+
response,
|
|
946
|
+
"sbadmin:modal-change-form-response",
|
|
947
|
+
{
|
|
948
|
+
"field": request.POST.get("sb_admin_source_field"),
|
|
949
|
+
"id": obj.pk,
|
|
950
|
+
"label": str(obj),
|
|
951
|
+
"reload": request.POST.get("sbadmin_reload_on_save") == "1",
|
|
952
|
+
},
|
|
953
|
+
)
|
|
954
|
+
trigger_client_event(response, "hideModal", {"elt": "sb-admin-modal"})
|
|
955
|
+
return response
|
|
956
|
+
|
|
957
|
+
def response_add(self, request, obj, post_url_continue=None):
|
|
958
|
+
if is_modal(request):
|
|
959
|
+
return self.get_modal_save_response(request, obj)
|
|
960
|
+
return super().response_add(request, obj, post_url_continue)
|
|
961
|
+
|
|
962
|
+
def response_change(self, request, obj):
|
|
963
|
+
if is_modal(request):
|
|
964
|
+
return self.get_modal_save_response(request, obj)
|
|
965
|
+
return super().response_change(request, obj)
|
|
966
|
+
|
|
967
|
+
@classmethod
|
|
968
|
+
def set_generic_relation_from_parent(cls, request, obj):
|
|
969
|
+
parent_model_path = request.POST.get(SBADMIN_PARENT_INSTANCE_FIELD_NAME_VAR)
|
|
970
|
+
parent_pk = request.POST.get(SBADMIN_PARENT_INSTANCE_PK_VAR)
|
|
971
|
+
|
|
972
|
+
if parent_model_path and parent_pk:
|
|
973
|
+
app_label, model_name, field, parent_model = parent_model_path.split("_", 4)
|
|
974
|
+
content_type = ContentType.objects.get(
|
|
975
|
+
app_label=app_label, model=parent_model
|
|
976
|
+
)
|
|
977
|
+
obj.content_type = content_type
|
|
978
|
+
obj.object_id = int(parent_pk)
|
|
979
|
+
|
|
980
|
+
def save_model(self, request, obj, form, change):
|
|
981
|
+
if self.sbadmin_is_generic_model and SBADMIN_IS_MODAL_VAR in request.POST:
|
|
982
|
+
self.set_generic_relation_from_parent(request, obj)
|
|
983
|
+
super().save_model(request, obj, form, change)
|
|
984
|
+
|
|
906
985
|
|
|
907
986
|
class SBAdminInline(
|
|
908
987
|
SBAdminInlineAndAdminCommon, SBAdminBaseQuerysetMixin, SBAdminBaseView
|
|
@@ -914,6 +993,7 @@ class SBAdminInline(
|
|
|
914
993
|
extra = 0
|
|
915
994
|
ordering = None
|
|
916
995
|
all_base_fields_form = None
|
|
996
|
+
sb_admin_add_modal = False
|
|
917
997
|
|
|
918
998
|
def get_readonly_fields(self, request, obj=None):
|
|
919
999
|
readonly_fields = super().get_readonly_fields(request, obj)
|
|
@@ -961,12 +1041,37 @@ class SBAdminInline(
|
|
|
961
1041
|
|
|
962
1042
|
def get_context_data(self, request) -> dict[str, Any]:
|
|
963
1043
|
is_sortable_active: bool = self.sortable_field_name and (
|
|
964
|
-
|
|
1044
|
+
self.has_add_permission(request) or self.has_change_permission(request)
|
|
965
1045
|
)
|
|
966
|
-
|
|
1046
|
+
add_url = None
|
|
1047
|
+
try:
|
|
1048
|
+
if self.sb_admin_add_modal and self.has_add_permission(request):
|
|
1049
|
+
add_url = reverse(
|
|
1050
|
+
"sb_admin:{}_{}_add".format(
|
|
1051
|
+
self.opts.app_label, self.opts.model_name
|
|
1052
|
+
)
|
|
1053
|
+
)
|
|
1054
|
+
except NoReverseMatch:
|
|
1055
|
+
logger.warning(
|
|
1056
|
+
"To use Add in modal, You have to specify SBAdmin view for %s model",
|
|
1057
|
+
self.opts.model_name,
|
|
1058
|
+
)
|
|
1059
|
+
context_data = {
|
|
967
1060
|
"inline_list_actions": self.get_sbadmin_inline_list_actions(request),
|
|
968
1061
|
"is_sortable_active": is_sortable_active,
|
|
1062
|
+
"add_url": add_url,
|
|
969
1063
|
}
|
|
1064
|
+
if self.parent_instance:
|
|
1065
|
+
context_data["parent_data"] = {
|
|
1066
|
+
SBADMIN_PARENT_INSTANCE_PK_VAR: self.parent_instance.pk,
|
|
1067
|
+
SBADMIN_PARENT_INSTANCE_LABEL_VAR: str(self.parent_instance),
|
|
1068
|
+
SBADMIN_PARENT_INSTANCE_FIELD_NAME_VAR: "{}_{}_id_{}".format(
|
|
1069
|
+
self.model._meta.app_label,
|
|
1070
|
+
self.model._meta.model_name,
|
|
1071
|
+
self.parent_model._meta.model_name,
|
|
1072
|
+
),
|
|
1073
|
+
}
|
|
1074
|
+
return context_data
|
|
970
1075
|
|
|
971
1076
|
def init_sortable_field(self) -> None:
|
|
972
1077
|
if not self.sortable_field_name:
|
|
@@ -1012,10 +1117,12 @@ class SBAdminInline(
|
|
|
1012
1117
|
|
|
1013
1118
|
class SBAdminTableInline(SBAdminInline, NestedTabularInline):
|
|
1014
1119
|
template = "sb_admin/inlines/table_inline.html"
|
|
1120
|
+
formset = SBAdminNestedInlineFormSet
|
|
1015
1121
|
|
|
1016
1122
|
|
|
1017
1123
|
class SBAdminGenericTableInline(SBAdminInline, NestedGenericTabularInline):
|
|
1018
1124
|
template = "sb_admin/inlines/table_inline.html"
|
|
1125
|
+
formset = SBAdminGenericInlineFormSet
|
|
1019
1126
|
|
|
1020
1127
|
|
|
1021
1128
|
class SBAdminTableInlinePaginated(SBAdminTableInline, TabularInlinePaginated):
|
|
@@ -12,7 +12,7 @@ from django.contrib.admin.widgets import (
|
|
|
12
12
|
from django.contrib.auth.forms import ReadOnlyPasswordHashWidget
|
|
13
13
|
from django.core.exceptions import ValidationError
|
|
14
14
|
from django.template.loader import render_to_string
|
|
15
|
-
from django.urls import reverse
|
|
15
|
+
from django.urls import reverse, NoReverseMatch
|
|
16
16
|
from django.utils.formats import get_format
|
|
17
17
|
from django.utils.http import urlencode
|
|
18
18
|
from django.utils.safestring import mark_safe
|
|
@@ -22,12 +22,17 @@ from filer.fields.file import AdminFileWidget as FilerAdminFileWidget
|
|
|
22
22
|
from filer.fields.image import AdminImageWidget
|
|
23
23
|
from filer.models import File
|
|
24
24
|
|
|
25
|
+
from django_smartbase_admin.engine.admin_base_view import (
|
|
26
|
+
SBADMIN_PARENT_INSTANCE_PK_VAR,
|
|
27
|
+
SBADMIN_PARENT_INSTANCE_LABEL_VAR,
|
|
28
|
+
)
|
|
25
29
|
from django_smartbase_admin.engine.filter_widgets import (
|
|
26
30
|
AutocompleteFilterWidget,
|
|
27
31
|
SBAdminTreeWidgetMixin,
|
|
28
32
|
)
|
|
29
33
|
from django_smartbase_admin.services.thread_local import SBAdminThreadLocalService
|
|
30
34
|
from django_smartbase_admin.templatetags.sb_admin_tags import SBAdminJSONEncoder
|
|
35
|
+
from django_smartbase_admin.utils import is_modal
|
|
31
36
|
|
|
32
37
|
logger = logging.getLogger(__name__)
|
|
33
38
|
|
|
@@ -55,6 +60,25 @@ class SBAdminBaseWidget(ContextMixin):
|
|
|
55
60
|
def get_context(self, name, value, attrs):
|
|
56
61
|
context = super().get_context(name, value, attrs)
|
|
57
62
|
context["widget"]["form_field"] = self.form_field
|
|
63
|
+
opts = (
|
|
64
|
+
self.form_field.view.opts
|
|
65
|
+
if self.form_field
|
|
66
|
+
and hasattr(self.form_field, "view")
|
|
67
|
+
and hasattr(self.form_field.view, "opts")
|
|
68
|
+
else None
|
|
69
|
+
)
|
|
70
|
+
modal_prefix = ""
|
|
71
|
+
try:
|
|
72
|
+
modal_prefix = (
|
|
73
|
+
"modal__" if is_modal(SBAdminThreadLocalService.get_request()) else ""
|
|
74
|
+
)
|
|
75
|
+
except:
|
|
76
|
+
pass
|
|
77
|
+
if opts:
|
|
78
|
+
context["widget"]["attrs"][
|
|
79
|
+
"id"
|
|
80
|
+
] = f"{modal_prefix}{opts.app_label}_{opts.model_name}_{context['widget']['attrs']['id']}"
|
|
81
|
+
|
|
58
82
|
return context
|
|
59
83
|
|
|
60
84
|
|
|
@@ -353,6 +377,16 @@ class SBAdminAutocompleteWidget(
|
|
|
353
377
|
if not self.is_multiselect():
|
|
354
378
|
query_suffix = ""
|
|
355
379
|
self.multiselect = False
|
|
380
|
+
context["widget"]["attrs"]["preselect_field"] = threadsafe_request.GET.get(
|
|
381
|
+
"sbadmin_parent_instance_field"
|
|
382
|
+
)
|
|
383
|
+
context["widget"]["attrs"]["preselect_field_label"] = (
|
|
384
|
+
threadsafe_request.GET.get(SBADMIN_PARENT_INSTANCE_LABEL_VAR)
|
|
385
|
+
)
|
|
386
|
+
context["widget"]["attrs"]["preselect_field_value"] = (
|
|
387
|
+
threadsafe_request.GET.get(SBADMIN_PARENT_INSTANCE_PK_VAR)
|
|
388
|
+
)
|
|
389
|
+
parsed_value = None
|
|
356
390
|
if value:
|
|
357
391
|
parsed_value = self.parse_value_from_input(threadsafe_request, value)
|
|
358
392
|
if parsed_value:
|
|
@@ -366,10 +400,38 @@ class SBAdminAutocompleteWidget(
|
|
|
366
400
|
"label": self.get_label(threadsafe_request, item),
|
|
367
401
|
}
|
|
368
402
|
)
|
|
403
|
+
|
|
369
404
|
context["widget"]["value"] = json.dumps(selected_options)
|
|
370
405
|
context["widget"]["value_list"] = selected_options
|
|
406
|
+
|
|
407
|
+
if threadsafe_request.request_data.configuration.autocomplete_show_related_buttons(
|
|
408
|
+
self.model,
|
|
409
|
+
field_name=self.field_name,
|
|
410
|
+
current_view=self.view,
|
|
411
|
+
request=threadsafe_request,
|
|
412
|
+
):
|
|
413
|
+
self.add_related_buttons_urls(parsed_value, context)
|
|
414
|
+
|
|
371
415
|
return context
|
|
372
416
|
|
|
417
|
+
def add_related_buttons_urls(self, parsed_value, context):
|
|
418
|
+
related_model = self.model
|
|
419
|
+
app_label = related_model._meta.app_label
|
|
420
|
+
model_name = related_model._meta.model_name
|
|
421
|
+
|
|
422
|
+
try:
|
|
423
|
+
if parsed_value:
|
|
424
|
+
change_url = reverse(
|
|
425
|
+
"sb_admin:{}_{}_change".format(app_label, model_name),
|
|
426
|
+
args=(parsed_value,),
|
|
427
|
+
)
|
|
428
|
+
context["widget"]["attrs"]["related_edit_url"] = change_url
|
|
429
|
+
|
|
430
|
+
add_url = reverse("sb_admin:{}_{}_add".format(app_label, model_name))
|
|
431
|
+
context["widget"]["attrs"]["related_add_url"] = add_url
|
|
432
|
+
except NoReverseMatch:
|
|
433
|
+
pass
|
|
434
|
+
|
|
373
435
|
def is_multiselect(self):
|
|
374
436
|
if self.multiselect is not None:
|
|
375
437
|
return self.multiselect
|
|
@@ -41,7 +41,13 @@ from django_smartbase_admin.services.xlsx_export import (
|
|
|
41
41
|
SBAdminXLSXOptions,
|
|
42
42
|
SBAdminXLSXFormat,
|
|
43
43
|
)
|
|
44
|
-
from django_smartbase_admin.utils import is_htmx_request, render_notifications
|
|
44
|
+
from django_smartbase_admin.utils import is_htmx_request, render_notifications, is_modal
|
|
45
|
+
|
|
46
|
+
SBADMIN_IS_MODAL_VAR = "sbadmin_is_modal"
|
|
47
|
+
SBADMIN_PARENT_INSTANCE_FIELD_NAME_VAR = "sbadmin_parent_instance_field"
|
|
48
|
+
SBADMIN_PARENT_INSTANCE_PK_VAR = "sbadmin_parent_instance_pk"
|
|
49
|
+
SBADMIN_PARENT_INSTANCE_LABEL_VAR = "sbadmin_parent_instance_label"
|
|
50
|
+
SBADMIN_RELOAD_ON_SAVE_VAR = "sbadmin_reload_on_save"
|
|
45
51
|
|
|
46
52
|
|
|
47
53
|
class SBAdminBaseView(object):
|
|
@@ -96,7 +102,7 @@ class SBAdminBaseView(object):
|
|
|
96
102
|
return inner_view
|
|
97
103
|
|
|
98
104
|
def process_actions(
|
|
99
|
-
|
|
105
|
+
self, request, actions: list[SBAdminCustomAction]
|
|
100
106
|
) -> list[SBAdminCustomAction]:
|
|
101
107
|
processed_actions = self.process_actions_permissions(request, actions)
|
|
102
108
|
for processed_action in processed_actions:
|
|
@@ -113,7 +119,7 @@ class SBAdminBaseView(object):
|
|
|
113
119
|
return processed_actions
|
|
114
120
|
|
|
115
121
|
def process_actions_permissions(
|
|
116
|
-
|
|
122
|
+
self, request, actions: list[SBAdminCustomAction]
|
|
117
123
|
) -> list[SBAdminCustomAction]:
|
|
118
124
|
result = []
|
|
119
125
|
for action in actions:
|
|
@@ -192,12 +198,12 @@ class SBAdminBaseView(object):
|
|
|
192
198
|
}
|
|
193
199
|
|
|
194
200
|
def get_sbadmin_detail_actions(
|
|
195
|
-
|
|
201
|
+
self, request, object_id: int | str | None = None
|
|
196
202
|
) -> Iterable[SBAdminCustomAction] | None:
|
|
197
203
|
return self.sbadmin_detail_actions
|
|
198
204
|
|
|
199
205
|
def get_global_context(
|
|
200
|
-
|
|
206
|
+
self, request, object_id: int | str | None = None
|
|
201
207
|
) -> dict[str, Any]:
|
|
202
208
|
return {
|
|
203
209
|
"view_id": self.get_id(),
|
|
@@ -207,6 +213,9 @@ class SBAdminBaseView(object):
|
|
|
207
213
|
"OVERRIDE_CONTENT_OF_NOTIFICATION": OVERRIDE_CONTENT_OF_NOTIFICATION,
|
|
208
214
|
"username_data": self.get_username_data(request),
|
|
209
215
|
"detail_actions": self.get_sbadmin_detail_actions(request, object_id),
|
|
216
|
+
SBADMIN_IS_MODAL_VAR: is_modal(request),
|
|
217
|
+
SBADMIN_RELOAD_ON_SAVE_VAR: SBADMIN_RELOAD_ON_SAVE_VAR in request.GET
|
|
218
|
+
or SBADMIN_RELOAD_ON_SAVE_VAR in request.POST,
|
|
210
219
|
"const": json.dumps(
|
|
211
220
|
{
|
|
212
221
|
"MULTISELECT_FILTER_MAX_CHOICES_SHOWN": MULTISELECT_FILTER_MAX_CHOICES_SHOWN,
|
|
@@ -287,8 +296,8 @@ class SBAdminBaseListView(SBAdminBaseView):
|
|
|
287
296
|
|
|
288
297
|
def is_reorder_active(self, request) -> bool:
|
|
289
298
|
return (
|
|
290
|
-
|
|
291
|
-
|
|
299
|
+
self.is_reorder_available(request)
|
|
300
|
+
and getattr(request, "reorder_active", False) == True
|
|
292
301
|
)
|
|
293
302
|
|
|
294
303
|
def is_reorder_available(self, request) -> str | None:
|
|
@@ -323,7 +332,7 @@ class SBAdminBaseListView(SBAdminBaseView):
|
|
|
323
332
|
qs.filter(**{f"{pk_field}__in": item_ids}).update(
|
|
324
333
|
**{
|
|
325
334
|
self.sbadmin_list_reorder_field: F(self.sbadmin_list_reorder_field)
|
|
326
|
-
|
|
335
|
+
+ int(diff)
|
|
327
336
|
}
|
|
328
337
|
)
|
|
329
338
|
return JsonResponse({"message": request.POST})
|
|
@@ -502,7 +511,7 @@ class SBAdminBaseListView(SBAdminBaseView):
|
|
|
502
511
|
return self.sbadmin_list_selection_actions
|
|
503
512
|
|
|
504
513
|
def get_sbadmin_list_selection_actions_grouped(
|
|
505
|
-
|
|
514
|
+
self, request
|
|
506
515
|
) -> dict[str, list[SBAdminCustomAction]]:
|
|
507
516
|
result = {}
|
|
508
517
|
list_selection_actions = self.process_actions(
|
|
@@ -534,8 +543,8 @@ class SBAdminBaseListView(SBAdminBaseView):
|
|
|
534
543
|
def action_bulk_delete(self, request, modifier):
|
|
535
544
|
action = self.sbadmin_list_action_class(self, request)
|
|
536
545
|
if (
|
|
537
|
-
|
|
538
|
-
|
|
546
|
+
request.request_data.request_method == "POST"
|
|
547
|
+
and request.headers.get("X-TabulatorRequest", None) == "true"
|
|
539
548
|
):
|
|
540
549
|
return redirect(
|
|
541
550
|
self.get_action_url("action_bulk_delete")
|
|
@@ -613,13 +622,13 @@ class SBAdminBaseListView(SBAdminBaseView):
|
|
|
613
622
|
return redirect_to
|
|
614
623
|
|
|
615
624
|
def action_list(
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
625
|
+
self,
|
|
626
|
+
request,
|
|
627
|
+
page_size=None,
|
|
628
|
+
tabulator_definition=None,
|
|
629
|
+
extra_context=None,
|
|
630
|
+
list_actions=None,
|
|
631
|
+
template=None,
|
|
623
632
|
):
|
|
624
633
|
action = self.sbadmin_list_action_class(
|
|
625
634
|
self,
|
|
@@ -672,8 +681,8 @@ class SBAdminBaseListView(SBAdminBaseView):
|
|
|
672
681
|
getattr(field, "filter_field", field): ""
|
|
673
682
|
for field in list_fields
|
|
674
683
|
if field in list_filter
|
|
675
|
-
|
|
676
|
-
|
|
684
|
+
or getattr(field, "name", None) in list_filter
|
|
685
|
+
or getattr(field, "filter_field", None) in list_filter
|
|
677
686
|
}
|
|
678
687
|
url_params = None
|
|
679
688
|
if base_filter:
|
|
@@ -748,7 +757,7 @@ class SBAdminBaseListView(SBAdminBaseView):
|
|
|
748
757
|
|
|
749
758
|
def get_filters_version(self, request) -> FilterVersions:
|
|
750
759
|
return (
|
|
751
|
-
|
|
760
|
+
self.filters_version or request.request_data.configuration.filters_version
|
|
752
761
|
)
|
|
753
762
|
|
|
754
763
|
def get_filters_template_name(self, request) -> str:
|