django-smartbase-admin 1.0.4b1__py3-none-any.whl → 1.0.6b1__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 +81 -23
- django_smartbase_admin/engine/actions.py +8 -0
- django_smartbase_admin/engine/admin_base_view.py +30 -21
- django_smartbase_admin/engine/configuration.py +30 -21
- django_smartbase_admin/engine/fake_inline.py +0 -3
- django_smartbase_admin/monkeypatch/fake_inline_monkeypatch.py +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/sprites/sb_admin/Bolt-one.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/Printer.svg +3 -0
- django_smartbase_admin/static/sb_admin/sprites/sb_admin/Pull.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/actions/list.html +40 -14
- django_smartbase_admin/templates/sb_admin/actions/partials/action_link.html +14 -0
- 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/views/translations_view.py +4 -3
- {django_smartbase_admin-1.0.4b1.dist-info → django_smartbase_admin-1.0.6b1.dist-info}/METADATA +13 -4
- {django_smartbase_admin-1.0.4b1.dist-info → django_smartbase_admin-1.0.6b1.dist-info}/RECORD +38 -28
- {django_smartbase_admin-1.0.4b1.dist-info → django_smartbase_admin-1.0.6b1.dist-info}/LICENSE.md +0 -0
- {django_smartbase_admin-1.0.4b1.dist-info → django_smartbase_admin-1.0.6b1.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,19 @@ 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 = "modal__" if is_modal(SBAdminThreadLocalService.get_request()) else ""
|
|
71
|
+
if opts:
|
|
72
|
+
context["widget"]["attrs"][
|
|
73
|
+
"id"
|
|
74
|
+
] = f"{modal_prefix}{opts.app_label}_{opts.model_name}_{context['widget']['attrs']['id']}"
|
|
75
|
+
|
|
58
76
|
return context
|
|
59
77
|
|
|
60
78
|
|
|
@@ -118,12 +136,12 @@ class SBAdminToggleWidget(SBAdminBaseWidget, forms.CheckboxInput):
|
|
|
118
136
|
class SBAdminCKEditorWidget(SBAdminBaseWidget, CKEditorWidget):
|
|
119
137
|
|
|
120
138
|
def __init__(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
139
|
+
self,
|
|
140
|
+
config_name="default",
|
|
141
|
+
extra_plugins=None,
|
|
142
|
+
external_plugin_resources=None,
|
|
143
|
+
form_field=None,
|
|
144
|
+
attrs=None,
|
|
127
145
|
):
|
|
128
146
|
super().__init__(
|
|
129
147
|
form_field,
|
|
@@ -340,25 +358,35 @@ class SBAdminAutocompleteWidget(
|
|
|
340
358
|
def get_context(self, name, value, attrs):
|
|
341
359
|
context = super().get_context(name, value, attrs)
|
|
342
360
|
self.input_id = (
|
|
343
|
-
|
|
361
|
+
context["widget"]["attrs"]["id"] or f'id_{context["widget"]["name"]}'
|
|
344
362
|
)
|
|
345
363
|
context["widget"]["type"] = "hidden"
|
|
346
364
|
context["widget"]["attrs"]["id"] = self.input_id
|
|
347
365
|
context["widget"]["attrs"]["class"] = "js-autocomplete-detail"
|
|
348
366
|
context["widget"]["attrs"]["data-empty-label"] = (
|
|
349
|
-
|
|
367
|
+
getattr(self.form_field, "empty_label", "---------") or "---------"
|
|
350
368
|
)
|
|
351
369
|
query_suffix = "__in"
|
|
352
370
|
threadsafe_request = SBAdminThreadLocalService.get_request()
|
|
353
371
|
if not self.is_multiselect():
|
|
354
372
|
query_suffix = ""
|
|
355
373
|
self.multiselect = False
|
|
374
|
+
context["widget"]["attrs"]["preselect_field"] = threadsafe_request.GET.get(
|
|
375
|
+
"sbadmin_parent_instance_field"
|
|
376
|
+
)
|
|
377
|
+
context["widget"]["attrs"]["preselect_field_label"] = (
|
|
378
|
+
threadsafe_request.GET.get(SBADMIN_PARENT_INSTANCE_LABEL_VAR)
|
|
379
|
+
)
|
|
380
|
+
context["widget"]["attrs"]["preselect_field_value"] = (
|
|
381
|
+
threadsafe_request.GET.get(SBADMIN_PARENT_INSTANCE_PK_VAR)
|
|
382
|
+
)
|
|
383
|
+
parsed_value = None
|
|
356
384
|
if value:
|
|
357
385
|
parsed_value = self.parse_value_from_input(threadsafe_request, value)
|
|
358
386
|
if parsed_value:
|
|
359
387
|
selected_options = []
|
|
360
388
|
for item in self.get_queryset(threadsafe_request).filter(
|
|
361
|
-
|
|
389
|
+
**{f"{self.get_value_field()}{query_suffix}": parsed_value}
|
|
362
390
|
):
|
|
363
391
|
selected_options.append(
|
|
364
392
|
{
|
|
@@ -366,10 +394,40 @@ class SBAdminAutocompleteWidget(
|
|
|
366
394
|
"label": self.get_label(threadsafe_request, item),
|
|
367
395
|
}
|
|
368
396
|
)
|
|
397
|
+
|
|
369
398
|
context["widget"]["value"] = json.dumps(selected_options)
|
|
370
399
|
context["widget"]["value_list"] = selected_options
|
|
400
|
+
|
|
401
|
+
if (
|
|
402
|
+
threadsafe_request.request_data.configuration.autocomplete_show_related_buttons(
|
|
403
|
+
self.model,
|
|
404
|
+
field_name=self.field_name,
|
|
405
|
+
current_view=self.view,
|
|
406
|
+
request=threadsafe_request,
|
|
407
|
+
)
|
|
408
|
+
):
|
|
409
|
+
self.add_related_buttons_urls(parsed_value, context)
|
|
410
|
+
|
|
371
411
|
return context
|
|
372
412
|
|
|
413
|
+
def add_related_buttons_urls(self, parsed_value, context):
|
|
414
|
+
related_model = self.model
|
|
415
|
+
app_label = related_model._meta.app_label
|
|
416
|
+
model_name = related_model._meta.model_name
|
|
417
|
+
|
|
418
|
+
try:
|
|
419
|
+
if parsed_value:
|
|
420
|
+
change_url = reverse(
|
|
421
|
+
"sb_admin:{}_{}_change".format(app_label, model_name),
|
|
422
|
+
args=(parsed_value,),
|
|
423
|
+
)
|
|
424
|
+
context["widget"]["attrs"]["related_edit_url"] = change_url
|
|
425
|
+
|
|
426
|
+
add_url = reverse("sb_admin:{}_{}_add".format(app_label, model_name))
|
|
427
|
+
context["widget"]["attrs"]["related_add_url"] = add_url
|
|
428
|
+
except NoReverseMatch:
|
|
429
|
+
pass
|
|
430
|
+
|
|
373
431
|
def is_multiselect(self):
|
|
374
432
|
if self.multiselect is not None:
|
|
375
433
|
return self.multiselect
|
|
@@ -493,16 +551,16 @@ class SBAdminCodeWidget(SBAdminBaseWidget, forms.Widget):
|
|
|
493
551
|
def __init__(self, form_field=None, *args, **kwargs):
|
|
494
552
|
super().__init__(form_field, *args, **kwargs)
|
|
495
553
|
self.attrs = {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
554
|
+
"code-mirror-options": json.dumps(
|
|
555
|
+
{
|
|
556
|
+
"mode": "django",
|
|
557
|
+
"theme": "dracula",
|
|
558
|
+
"lineWrapping": "true",
|
|
559
|
+
}
|
|
560
|
+
),
|
|
561
|
+
"code-mirror-width": "100%",
|
|
562
|
+
"code-mirror-height": "300",
|
|
563
|
+
} | self.attrs
|
|
506
564
|
|
|
507
565
|
class Media:
|
|
508
566
|
css = {
|
|
@@ -612,9 +670,9 @@ class SBAdminTreeWidget(SBAdminTreeWidgetMixin, SBAdminAutocompleteWidget):
|
|
|
612
670
|
parsed_value = self.parse_value_from_input(threadsafe_request, input_value)
|
|
613
671
|
obj = self.form.instance
|
|
614
672
|
if (
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
673
|
+
obj
|
|
674
|
+
and parsed_value
|
|
675
|
+
and self.relationship_pick_mode == self.RELATIONSHIP_PICK_MODE_PARENT
|
|
618
676
|
):
|
|
619
677
|
if obj.id == parsed_value:
|
|
620
678
|
raise ValidationError(_("Cannot set parent to itself"))
|
|
@@ -22,6 +22,8 @@ class SBAdminCustomAction(object):
|
|
|
22
22
|
no_params=False,
|
|
23
23
|
open_in_modal=False,
|
|
24
24
|
group=None,
|
|
25
|
+
sub_actions=None,
|
|
26
|
+
icon=None,
|
|
25
27
|
) -> None:
|
|
26
28
|
super().__init__()
|
|
27
29
|
self.title = title
|
|
@@ -33,9 +35,15 @@ class SBAdminCustomAction(object):
|
|
|
33
35
|
self.no_params = no_params
|
|
34
36
|
self.open_in_modal = open_in_modal
|
|
35
37
|
self.group = group
|
|
38
|
+
self.sub_actions = sub_actions
|
|
39
|
+
self.icon = icon
|
|
36
40
|
self.resolve_url()
|
|
37
41
|
|
|
38
42
|
def resolve_url(self):
|
|
43
|
+
if self.sub_actions:
|
|
44
|
+
for sub_action in self.sub_actions:
|
|
45
|
+
sub_action.resolve_url()
|
|
46
|
+
return
|
|
39
47
|
if not (self.url or (self.view and self.action_id)):
|
|
40
48
|
raise ImproperlyConfigured(
|
|
41
49
|
"You must provide either url or view and action_id"
|