django-unfold 0.25.0__py3-none-any.whl → 0.26.0__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_unfold-0.25.0.dist-info → django_unfold-0.26.0.dist-info}/METADATA +51 -6
- {django_unfold-0.25.0.dist-info → django_unfold-0.26.0.dist-info}/RECORD +36 -27
- unfold/admin.py +11 -3
- unfold/contrib/import_export/forms.py +17 -0
- unfold/contrib/import_export/templates/admin/import_export/change_form.html +10 -0
- unfold/contrib/import_export/templates/admin/import_export/export.html +38 -3
- unfold/contrib/import_export/templates/admin/import_export/import_form.html +8 -12
- unfold/contrib/import_export/templates/admin/import_export/resource_fields_list.html +1 -1
- unfold/contrib/inlines/__init__.py +0 -0
- unfold/contrib/inlines/admin.py +141 -0
- unfold/contrib/inlines/apps.py +6 -0
- unfold/contrib/inlines/checks.py +18 -0
- unfold/contrib/inlines/forms.py +43 -0
- unfold/forms.py +6 -0
- unfold/static/unfold/css/simplebar.css +230 -0
- unfold/static/unfold/css/styles.css +1 -1
- unfold/static/unfold/js/simplebar.js +10 -0
- unfold/styles.css +9 -1
- unfold/templates/admin/app_list.html +1 -1
- unfold/templates/admin/change_form.html +11 -9
- unfold/templates/admin/change_list_results.html +2 -2
- unfold/templates/admin/edit_inline/stacked.html +6 -6
- unfold/templates/admin/edit_inline/tabular.html +3 -3
- unfold/templates/admin/includes/fieldset.html +1 -1
- unfold/templates/unfold/helpers/app_list.html +1 -1
- unfold/templates/unfold/helpers/display_header.html +11 -8
- unfold/templates/unfold/helpers/field.html +20 -6
- unfold/templates/unfold/helpers/fieldsets_tabs.html +4 -4
- unfold/templates/unfold/helpers/form_label.html +1 -1
- unfold/templates/unfold/layouts/skeleton.html +2 -0
- unfold/templates/unfold/widgets/foreign_key_raw_id.html +21 -0
- unfold/templates/unfold/widgets/textarea.html +1 -7
- unfold/templates/unfold/widgets/textarea_expandable.html +7 -0
- unfold/widgets.py +36 -3
- unfold/contrib/import_export/admin.py +0 -37
- {django_unfold-0.25.0.dist-info → django_unfold-0.26.0.dist-info}/LICENSE.md +0 -0
- {django_unfold-0.25.0.dist-info → django_unfold-0.26.0.dist-info}/WHEEL +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: django-unfold
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.26.0
|
4
4
|
Summary: Modern Django admin theme for seamless interface development
|
5
5
|
Home-page: https://unfoldadmin.com
|
6
6
|
License: MIT
|
@@ -30,7 +30,7 @@ Description-Content-Type: text/markdown
|
|
30
30
|
|
31
31
|
[](https://github.com/unfoldadmin/django-unfold/actions?query=workflow%3Arelease)
|
32
32
|
[](https://pypi.org/project/django-unfold/)
|
33
|
-

|
34
34
|

|
35
35
|
|
36
36
|
Unfold is theme for Django admin incorporating most common practises for building full-fledged admin areas. It is designed to work at the top of default administration provided by Django.
|
@@ -59,7 +59,8 @@ Did you decide to start using Unfold but you don't have time to make the switch
|
|
59
59
|
- **Colors:** possibility to override default color scheme
|
60
60
|
- **Third party packages:** default support for multiple popular applications
|
61
61
|
- **Environment label**: distinguish between environments by displaying a label
|
62
|
-
- **
|
62
|
+
- **Nonrelated inlines**: displays nonrelated model as inline in changeform
|
63
|
+
- **Parallel admin**: support for default admin in parallel with Unfold. [Admin migration guide](https://unfoldadmin.com/blog/migrating-django-admin-unfold/)
|
63
64
|
- **VS Code**: project configuration and development container is included
|
64
65
|
|
65
66
|
## Table of contents <!-- omit from toc -->
|
@@ -78,6 +79,7 @@ Did you decide to start using Unfold but you don't have time to make the switch
|
|
78
79
|
- [Dropdown filters](#dropdown-filters)
|
79
80
|
- [Numeric filters](#numeric-filters)
|
80
81
|
- [Date/time filters](#datetime-filters)
|
82
|
+
- [Nonrelated inlines](#nonrelated-inlines)
|
81
83
|
- [Display decorator](#display-decorator)
|
82
84
|
- [Change form tabs](#change-form-tabs)
|
83
85
|
- [Third party packages](#third-party-packages)
|
@@ -114,6 +116,7 @@ INSTALLED_APPS = [
|
|
114
116
|
"unfold", # before django.contrib.admin
|
115
117
|
"unfold.contrib.filters", # optional, if special filters are needed
|
116
118
|
"unfold.contrib.forms", # optional, if special form elements are needed
|
119
|
+
"unfold.contrib.inlines", # optional, if special inlines are needed
|
117
120
|
"unfold.contrib.import_export", # optional, if django-import-export package is used
|
118
121
|
"unfold.contrib.guardian", # optional, if django-guardian package is used
|
119
122
|
"unfold.contrib.simple_history", # optional, if django-simple-history package is used
|
@@ -633,6 +636,41 @@ class YourModelAdmin(ModelAdmin):
|
|
633
636
|
)
|
634
637
|
```
|
635
638
|
|
639
|
+
## Nonrelated inlines
|
640
|
+
|
641
|
+
To display inlines which are not related (no foreign key pointing at the main model) to the model instance in changeform, you can use nonrelated inlines which are included in `unfold.contrib.inlines` module. Make sure this module is included in `INSTALLED_APPS` in settings.py.
|
642
|
+
|
643
|
+
```python
|
644
|
+
from django.contrib.auth.models import User
|
645
|
+
from unfold.admin import ModelAdmin
|
646
|
+
from unfold.contrib.inlines.admin import NonrelatedTabularInline
|
647
|
+
from .models import OtherModel
|
648
|
+
|
649
|
+
class OtherNonrelatedInline(NonrelatedTabularInline): # NonrelatedStackedInline is available as well
|
650
|
+
model = OtherModel
|
651
|
+
fields = ["field1", "field2"] # Ignore property to display all fields
|
652
|
+
|
653
|
+
def get_form_queryset(self, obj):
|
654
|
+
"""
|
655
|
+
Gets all nonrelated objects needed for inlines. Method must be implemented.
|
656
|
+
"""
|
657
|
+
return self.model.objects.all()
|
658
|
+
|
659
|
+
def save_new_instance(self, parent, instance):
|
660
|
+
"""
|
661
|
+
Extra save method which can for example update inline instances based on current
|
662
|
+
main model object. Method must be implemented.
|
663
|
+
"""
|
664
|
+
pass
|
665
|
+
|
666
|
+
|
667
|
+
@admin.register(User)
|
668
|
+
class UserAdmin(ModelAdmin):
|
669
|
+
inlines = [OtherNonrelatedInline]
|
670
|
+
```
|
671
|
+
|
672
|
+
**NOTE:** credit for this functionality goes to [django-nonrelated-inlines](https://github.com/bhomnick/django-nonrelated-inlines)
|
673
|
+
|
636
674
|
## Display decorator
|
637
675
|
|
638
676
|
Unfold introduces it's own `unfold.decorators.display` decorator. By default it has exactly same behavior as native `django.contrib.admin.decorators.display` but it adds same customizations which helps to extends default logic.
|
@@ -697,12 +735,15 @@ class UserAdmin(ModelAdmin):
|
|
697
735
|
"""
|
698
736
|
return [
|
699
737
|
"First main heading",
|
700
|
-
"Smaller additional description",
|
701
|
-
"AB",
|
738
|
+
"Smaller additional description", # Use None in case you don't need it
|
739
|
+
"AB", # Short text which will appear in front of
|
702
740
|
# Image instead of initials. Initials are ignored if image is available
|
703
741
|
{
|
704
742
|
"path": "some/path/picture.jpg,
|
705
743
|
"squared": True, # Picture is displayed in square format, if empty circle
|
744
|
+
"borderless": True # Picture will be displayed without border
|
745
|
+
"width": 64, # Removes default width. Use together with height
|
746
|
+
"height": 48, # Removes default height. Use together with width
|
706
747
|
}
|
707
748
|
]
|
708
749
|
```
|
@@ -822,15 +863,18 @@ Adding support for django-guardian is quote straightforward in Unfold, just add
|
|
822
863
|
|
823
864
|
from unfold.admin import ModelAdmin
|
824
865
|
from import_export.admin import ImportExportModelAdmin
|
825
|
-
from unfold.contrib.import_export.forms import ExportForm, ImportForm
|
866
|
+
from unfold.contrib.import_export.forms import ExportForm, ImportForm, SelectableFieldsExportForm
|
826
867
|
|
827
868
|
class ExampleAdmin(ModelAdmin, ImportExportModelAdmin):
|
828
869
|
import_form_class = ImportForm
|
829
870
|
export_form_class = ExportForm
|
871
|
+
# export_form_class = SelectableFieldsExportForm
|
830
872
|
```
|
831
873
|
|
832
874
|
When implementing `import_export.admin.ExportActionModelAdmin` class in admin panel, import_export plugin adds its own implementation of action form which is not incorporating Unfold CSS classes. For this reason, `unfold.contrib.import_export.admin` contains class with the same name `ExportActionModelAdmin` which inherits behavior of parent form and adds appropriate CSS classes.
|
833
875
|
|
876
|
+
**Note:** This class has been removed and in new version (4.x) of django-import-export it is not needed.
|
877
|
+
|
834
878
|
```python
|
835
879
|
admin.py
|
836
880
|
|
@@ -1135,6 +1179,7 @@ The container has already a node preinstalled so it is possible to compile a new
|
|
1135
1179
|
|
1136
1180
|
## Credits
|
1137
1181
|
|
1182
|
+
- [django-nonrelated-inlines](https://github.com/bhomnick/django-nonrelated-inlines) - Django admin inlines for unrelated models
|
1138
1183
|
- [TailwindCSS](https://tailwindcss.com/) - CSS framework
|
1139
1184
|
- [HTMX](https://htmx.org/) - AJAX communication with backend
|
1140
1185
|
- [Material Icons](https://fonts.google.com/icons) - Icons from Google Fonts
|
@@ -1,5 +1,5 @@
|
|
1
1
|
unfold/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
unfold/admin.py,sha256=
|
2
|
+
unfold/admin.py,sha256=ATDHEq87eC1zgtoObwqTCpRKqJbl0yrS0rf6AAHZTxM,24133
|
3
3
|
unfold/apps.py,sha256=SlBXPYrUd2uXn67qFbRvbXSUk3XFWrF4-5WELgDCvho,381
|
4
4
|
unfold/checks.py,sha256=Smgji9w19hnYjJElJ_FJnnyTEAE-E-OUB6otHu7lasY,1670
|
5
5
|
unfold/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -36,21 +36,26 @@ unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_user.htm
|
|
36
36
|
unfold/contrib/guardian/templates/unfold/guardian/group_form.html,sha256=P8WMC5EejUHV5AxEiIQ2LOGzefLHk5J5UHiNq9wnBgY,4145
|
37
37
|
unfold/contrib/guardian/templates/unfold/guardian/user_form.html,sha256=ci7FRrhTEKbFKKxsJ-07_dWXBYz4mqXPoqu5HfqYLaM,4132
|
38
38
|
unfold/contrib/import_export/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
39
|
-
unfold/contrib/import_export/admin.py,sha256=h6CKRuvloEdxcVScycTSAShXEfzEAsL75uMj2ullhOM,1151
|
40
39
|
unfold/contrib/import_export/apps.py,sha256=SdJu6Qh90VqGWY19FSDhhpUqhTbaIYsJKny3zX5baHI,149
|
41
|
-
unfold/contrib/import_export/forms.py,sha256=
|
40
|
+
unfold/contrib/import_export/forms.py,sha256=cmUUiULJo771rbxf-uCarrbRNwvzCIketbP_7eFnvGc,1496
|
42
41
|
unfold/contrib/import_export/templates/admin/import_export/base.html,sha256=loL2qcV-f8aAzkHss_I4IkwfgemVW2CjOu_aiBxdwX0,357
|
42
|
+
unfold/contrib/import_export/templates/admin/import_export/change_form.html,sha256=_YorgltKO0CRSaLBAp67XnGMUBWEP_XewsUff0hlrE4,446
|
43
43
|
unfold/contrib/import_export/templates/admin/import_export/change_list_export_item.html,sha256=pTDeqPKOlCPKH2dxMIfPnWuc2wVDzB7AzL73WbxSnRY,257
|
44
44
|
unfold/contrib/import_export/templates/admin/import_export/change_list_import_export.html,sha256=JdKd6P2Ot9Ou4yg4CywTauuE1UiTz_mRvDwlx3vj3LI,229
|
45
45
|
unfold/contrib/import_export/templates/admin/import_export/change_list_import_item.html,sha256=XUuRxnsx9YQbKvW-E_JGl_ha7kpTSGSoRefOTTizuX0,233
|
46
|
-
unfold/contrib/import_export/templates/admin/import_export/export.html,sha256=
|
46
|
+
unfold/contrib/import_export/templates/admin/import_export/export.html,sha256=a0gL1YvlFTYQEpJuab-Ue_YL86UO5VkfREqy5CepZUg,3481
|
47
47
|
unfold/contrib/import_export/templates/admin/import_export/import.html,sha256=P54_f3s96PV87Bo-FCZfmsn9DkRXLOB36r7HYF6y7GM,2075
|
48
48
|
unfold/contrib/import_export/templates/admin/import_export/import_confirm.html,sha256=M-acK4XSLHuPFD_NJashGYvPPeJrJsC-3LMvHs3lRis,867
|
49
49
|
unfold/contrib/import_export/templates/admin/import_export/import_errors.html,sha256=0DmJvZs31u-E2Y53yySci86cTnG9aUnOzvfYrOo0lYA,1422
|
50
|
-
unfold/contrib/import_export/templates/admin/import_export/import_form.html,sha256
|
50
|
+
unfold/contrib/import_export/templates/admin/import_export/import_form.html,sha256=mMuMTAbgMbJ1IyCQPBkzmob4m5lJmZ69_QSo3GvvSiY,990
|
51
51
|
unfold/contrib/import_export/templates/admin/import_export/import_preview.html,sha256=pNuLDW6zc5yOF1jurL2EgR0j05RL9ZVJLZiV4R21GJc,2413
|
52
52
|
unfold/contrib/import_export/templates/admin/import_export/import_validation.html,sha256=1wQOiXN_Ga9VO6GGyl__KEiuJlCh4gTqzZdzIbmKxG0,4880
|
53
|
-
unfold/contrib/import_export/templates/admin/import_export/resource_fields_list.html,sha256=
|
53
|
+
unfold/contrib/import_export/templates/admin/import_export/resource_fields_list.html,sha256=7LzgBiT73c4GHVZx3-ap_cuMz57EnIJ1ESGA5xR31mM,871
|
54
|
+
unfold/contrib/inlines/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
55
|
+
unfold/contrib/inlines/admin.py,sha256=p55cNAFOwEFToyFdXUtGl6Vt6j89s9i42V4kHYSZ5Ws,5928
|
56
|
+
unfold/contrib/inlines/apps.py,sha256=Z9JBnzywq-DanZbD56fG0ndBfLXbojzkjVBleqoOBSU,136
|
57
|
+
unfold/contrib/inlines/checks.py,sha256=8sdyBcxw0erqQvp9sHlpGgy0rXfum-cd2eQE0rXFKQ0,559
|
58
|
+
unfold/contrib/inlines/forms.py,sha256=R9OJvrbqNLlKvTxw97JjElCY4CQ3IyRIkjIJUN0gJ9k,1323
|
54
59
|
unfold/contrib/simple_history/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
55
60
|
unfold/contrib/simple_history/apps.py,sha256=eF_KVYb60CAnGgWk2Z1YKYGfgA3TJBMr229qI7e2pgU,153
|
56
61
|
unfold/contrib/simple_history/templates/simple_history/_object_history_list.html,sha256=aXOQ1zwsRBlFmzODsZApvMtb8t1IPXim6i4plXUR5XE,5112
|
@@ -60,10 +65,11 @@ unfold/contrib/simple_history/templates/simple_history/submit_line.html,sha256=n
|
|
60
65
|
unfold/dataclasses.py,sha256=JJdGYzQ8MpeOe2FQPJqrMn_UJcxUz1VJgHCuCtkZCA8,199
|
61
66
|
unfold/decorators.py,sha256=BVDlxhZxB4ND3f5-5oiENRTv_W_Q_Eu-gZlsrYKOxiU,3272
|
62
67
|
unfold/exceptions.py,sha256=gcCj1ox61E137bk_0Cqy4YC3SttdPgB-fiJUqpmyHSE,43
|
63
|
-
unfold/forms.py,sha256=
|
68
|
+
unfold/forms.py,sha256=GXEm3CFwglyuEbGdVyEMJTB45Gs-_RvGGlXJEkPy2kw,3688
|
64
69
|
unfold/settings.py,sha256=--TdTSWdOA8TQGW4-vjJkjy_zEyd_kZwBr3BIuQ8hzI,1208
|
65
70
|
unfold/sites.py,sha256=Gy_i43j2nizW2g8-mas5icvtk-beKism_CznATW6Ia8,12586
|
66
|
-
unfold/static/unfold/css/
|
71
|
+
unfold/static/unfold/css/simplebar.css,sha256=5LLaEM11pKi6JFCOLt4XKuZxTpT9rpdq_tNlaQytFlU,4647
|
72
|
+
unfold/static/unfold/css/styles.css,sha256=hlPYMtAEeIPl7yIFpqMR4DqJwz9K2RLhpbI4aSNq6mU,91675
|
67
73
|
unfold/static/unfold/fonts/inter/Inter-Bold.woff2,sha256=O88EyjAeRPE_QEyKBKpK5wf2epUOEu8wwjj5bnhCZqE,46552
|
68
74
|
unfold/static/unfold/fonts/inter/Inter-Medium.woff2,sha256=O88EyjAeRPE_QEyKBKpK5wf2epUOEu8wwjj5bnhCZqE,46552
|
69
75
|
unfold/static/unfold/fonts/inter/Inter-Regular.woff2,sha256=O88EyjAeRPE_QEyKBKpK5wf2epUOEu8wwjj5bnhCZqE,46552
|
@@ -76,26 +82,27 @@ unfold/static/unfold/js/alpine.persist.js,sha256=84PZYnPi25AFm7wIWRe1gzA74c5Rv2V
|
|
76
82
|
unfold/static/unfold/js/app.js,sha256=CIitJoFqpeZYPw8icGVXYX9tVRUgqFxcPZ2WjWS8Ylk,5288
|
77
83
|
unfold/static/unfold/js/chart.js,sha256=22W6cFERR-CElMOKRgMMicueMVP0Vf7FBEBYH8Z8tCk,200633
|
78
84
|
unfold/static/unfold/js/htmx.js,sha256=XOLqvnZiyEx46EW9vaJTBUaaWg8CGVVfXJkVsUmJbpI,42820
|
79
|
-
unfold/
|
85
|
+
unfold/static/unfold/js/simplebar.js,sha256=t-uG1FAD6ZoiMeN--wac0XRS7SxoDVG6zvRnGuEp7X8,27176
|
86
|
+
unfold/styles.css,sha256=JA2ybI-pzRM4ZOKshI187oJ5Z3gMNWHQAuEfwq5Xb_E,17854
|
80
87
|
unfold/templates/admin/actions.html,sha256=1tVlUpLoM72K2Ew4vQGcRwPjHuAtO5Jm4QdDsDLOq0I,2625
|
81
88
|
unfold/templates/admin/app_index.html,sha256=lVjMIFsspHQ09LGHKfdfg7TlqlL39AX5LbwoeoZjFhk,1335
|
82
|
-
unfold/templates/admin/app_list.html,sha256=
|
89
|
+
unfold/templates/admin/app_list.html,sha256=krDzw2EXqqvIi8bJtPhJsNran9H7hwdhM6ZW_IRlDwQ,3038
|
83
90
|
unfold/templates/admin/auth/user/add_form.html,sha256=iLig-vd2YExXsj0xGBwYhZ4kGUihwYtLtNVhzObgSzg,478
|
84
91
|
unfold/templates/admin/auth/user/change_password.html,sha256=-Wa9ml3yss-kDz0YQxCiwoxs91KQD8eetCt5l6xekWM,2892
|
85
92
|
unfold/templates/admin/base.html,sha256=MGqtCcydXZPnp6dasaWktyd9D6rdUYX01rFGAv7Zkm4,2226
|
86
93
|
unfold/templates/admin/base_site.html,sha256=3ckWrcAdd7Pw1hk6Zwyknab_Qb-rteV9-mXhMnfo6VI,361
|
87
|
-
unfold/templates/admin/change_form.html,sha256
|
94
|
+
unfold/templates/admin/change_form.html,sha256=OvegZJTH4eGHXf5RJOHfy9tXKCtyml_Yl8pENb9kNq8,5431
|
88
95
|
unfold/templates/admin/change_form_object_tools.html,sha256=eyeH-i2HgEM0Yi-OJA2D1VnKJyC19A_my1IDGxxoP8Y,593
|
89
96
|
unfold/templates/admin/change_list.html,sha256=18GDZswc1c0xtw2BcKti9SX95Ar9e1BX_HSY0K79g_8,5102
|
90
97
|
unfold/templates/admin/change_list_object_tools.html,sha256=cmMiT2nT20Ph5yfpj9aHPr76Z-JP4aSXp0o-Rnad28s,147
|
91
|
-
unfold/templates/admin/change_list_results.html,sha256=
|
98
|
+
unfold/templates/admin/change_list_results.html,sha256=Ie2NGfIiU0HHy1a5wJ-YJV1doZkibVRCmR3DRZdoV2I,5395
|
92
99
|
unfold/templates/admin/date_hierarchy.html,sha256=BfUPbsLpHZVa40BHBahz1H9RSVuz36Jc3yrlobOiIpw,1306
|
93
100
|
unfold/templates/admin/delete_confirmation.html,sha256=hpa2E14oZEXBBs6W1qdNQuF650TIO2Rhr52Q6UfwVeQ,5166
|
94
101
|
unfold/templates/admin/delete_selected_confirmation.html,sha256=Foka2yvwAMEZre-Kh1KNadRzrCotdKM2U4e6AJQYZu8,4941
|
95
|
-
unfold/templates/admin/edit_inline/stacked.html,sha256=
|
96
|
-
unfold/templates/admin/edit_inline/tabular.html,sha256=
|
102
|
+
unfold/templates/admin/edit_inline/stacked.html,sha256=HG-Dj42gcKNofHRVjg0ltIER2oJGYUd9GN_B7lDv7rQ,4580
|
103
|
+
unfold/templates/admin/edit_inline/tabular.html,sha256=mVlQ5aJr6-GdHM2zaWVUKaa6gok3Vv3MAMQj0sIX-lQ,12976
|
97
104
|
unfold/templates/admin/filter.html,sha256=dkrFkei-EAlldIU8DrgvSChzWQuUOu6-LS_qlZxdfFw,1708
|
98
|
-
unfold/templates/admin/includes/fieldset.html,sha256=
|
105
|
+
unfold/templates/admin/includes/fieldset.html,sha256=qVxXy7KRI8GC4bIBPO9hjQlMtDg83vhEZbmVXqdfrgg,2929
|
99
106
|
unfold/templates/admin/includes/object_delete_summary.html,sha256=Nv69SCzyJHFX14iJFfodxKM0IIpQegKZH0fvKB15QJI,468
|
100
107
|
unfold/templates/admin/index.html,sha256=pkGdKWdD3zzOvkRdELvdb15sleSpfl4eHPA14PAh7z0,684
|
101
108
|
unfold/templates/admin/login.html,sha256=WdOfFLofwBWj9VKCq1U22uLY19J2YQY6vRaE4OOSKfQ,3681
|
@@ -122,17 +129,17 @@ unfold/templates/unfold/components/title.html,sha256=PSiNK-s8jUJfu6f9zCcGOOyLiKx
|
|
122
129
|
unfold/templates/unfold/helpers/account_links.html,sha256=Kc1FobAVN3B34x9vcMoEXyuQ45d5SjfjaJSo3UErL3Q,1934
|
123
130
|
unfold/templates/unfold/helpers/actions_row.html,sha256=1xd39zx38NOoKuDuxAG7PHeu5x2OTIraQGFkm15Erqg,1681
|
124
131
|
unfold/templates/unfold/helpers/add_link.html,sha256=mIgpKrwqBO1oJ4cwPQWSX1oUHBwHJmy5-2TxUHf-1bo,808
|
125
|
-
unfold/templates/unfold/helpers/app_list.html,sha256=
|
132
|
+
unfold/templates/unfold/helpers/app_list.html,sha256=lFnW8p9DcZbI9t3_ee9JX9ERHA0NRL2V88zpzuG4jq8,4720
|
126
133
|
unfold/templates/unfold/helpers/app_list_default.html,sha256=vZkw1F7oHOKReNkdHRYjhuNdA1nNdvSD4wbDmf0bnsM,4102
|
127
134
|
unfold/templates/unfold/helpers/boolean.html,sha256=p_WOlytoXvDwta76WgcV4JSWKpBgKf4amhqmHF798F8,564
|
128
135
|
unfold/templates/unfold/helpers/breadcrumb_item.html,sha256=k_1j57UV0WtzFFlMKaewj4NLbR_DhXI6RzCHThblZLw,234
|
129
|
-
unfold/templates/unfold/helpers/display_header.html,sha256=
|
136
|
+
unfold/templates/unfold/helpers/display_header.html,sha256=RR3HexzdO3YazPSJYfF9UfujCDiJ_EDrmnfxDFNLd7U,1160
|
130
137
|
unfold/templates/unfold/helpers/display_label.html,sha256=LS9DWzYjHkYLV27sZDwyXlg2sLJ0AlId9FbjnXpsbfg,317
|
131
|
-
unfold/templates/unfold/helpers/field.html,sha256=
|
138
|
+
unfold/templates/unfold/helpers/field.html,sha256=Ds-zUHkdyxamfUCVNhxvtM0XoJg9OCA0QcsLbLWv4oo,882
|
132
139
|
unfold/templates/unfold/helpers/field_readonly.html,sha256=v7-2oSSDgOsuYpP70y8DqdBqbRybubAfSDzstveoBuw,382
|
133
|
-
unfold/templates/unfold/helpers/fieldsets_tabs.html,sha256
|
140
|
+
unfold/templates/unfold/helpers/fieldsets_tabs.html,sha256=V3bgW75eozaBDty-xfciGafhCWq_Ba5HfQkk92yRc9A,1445
|
134
141
|
unfold/templates/unfold/helpers/form_errors.html,sha256=EwerIJptSCWXvtAJ1IZKfEn98qlShBIGavsTThbklAs,266
|
135
|
-
unfold/templates/unfold/helpers/form_label.html,sha256=
|
142
|
+
unfold/templates/unfold/helpers/form_label.html,sha256=SR4U6iK9w4oels6iGY_Da-yN4BbXQVN9zCDlBGGXcw8,310
|
136
143
|
unfold/templates/unfold/helpers/header.html,sha256=jklASqVEJRa22jxdXq_X2UZb2uYn_ywaZbI2N4nBv20,998
|
137
144
|
unfold/templates/unfold/helpers/help_text.html,sha256=iBXw0LWfoYT5PoPLJek9ixv4SuK7hfVkjPgmv2ODW-s,146
|
138
145
|
unfold/templates/unfold/helpers/history.html,sha256=0_Imm7Fc1PqjjXEmHh8QEQg_qIqBZWx2BeK5o3axfvY,2017
|
@@ -155,10 +162,11 @@ unfold/templates/unfold/helpers/theme_switch.html,sha256=skkl6fYUnYLM7fAPivHxWjn
|
|
155
162
|
unfold/templates/unfold/helpers/userlinks.html,sha256=qWjtBt9Q_tU8a874ii0Qqg8t_d-SSYBTB_3QZfNlx9g,634
|
156
163
|
unfold/templates/unfold/helpers/welcomemsg.html,sha256=noRysgSENef4_53pXaTiBCy2or6lQm1ZtmCQVODAB1c,1120
|
157
164
|
unfold/templates/unfold/layouts/base_simple.html,sha256=rki7n7QagHFAaCEn488pTOj9dpNL9AwwzKps8Ipiubk,993
|
158
|
-
unfold/templates/unfold/layouts/skeleton.html,sha256=
|
165
|
+
unfold/templates/unfold/layouts/skeleton.html,sha256=iXrUiggVp36vmBIia5p32c-9Ruy0PxkFFQogDpvENbk,3419
|
159
166
|
unfold/templates/unfold/widgets/clearable_file_input.html,sha256=vXsyP0-YD-z3z6VL4vXW9pJH9_-ZU9u-3AnmZkni-R4,1994
|
160
167
|
unfold/templates/unfold/widgets/clearable_file_input_small.html,sha256=rqUnHF4jwL8_RySUuq2aXgj-0P_usgo1HeVT_IcfyFY,2531
|
161
168
|
unfold/templates/unfold/widgets/date.html,sha256=WXo2LG1v_gBZBSg-zocj7oujMKI0MWLYCIFfB04HMLQ,122
|
169
|
+
unfold/templates/unfold/widgets/foreign_key_raw_id.html,sha256=26UGK04ojS-ZsvhWpPIbr6VE9b91Q2sEhB7FBkMY1NI,917
|
162
170
|
unfold/templates/unfold/widgets/radio.html,sha256=3WcmclQNg7R_pRjEHL1dHkGjAzWlWNYnhHkAirC4nuA,646
|
163
171
|
unfold/templates/unfold/widgets/radio_option.html,sha256=IZgPx-aWKJuxrSalJ3K50RFd1vwSpb9Qk0yZwfV78_A,368
|
164
172
|
unfold/templates/unfold/widgets/range.html,sha256=28FBtSUgUcG82vpk_I27Lbs5oWZOV_oMzVhx4wj3-Ik,262
|
@@ -166,7 +174,8 @@ unfold/templates/unfold/widgets/related_widget_wrapper.html,sha256=0I6wSu8z_sJPq
|
|
166
174
|
unfold/templates/unfold/widgets/split_datetime.html,sha256=eXLFZyCv84LCTFWAUhNO3xAIzWvGBvI1ZpYbB38_HOI,862
|
167
175
|
unfold/templates/unfold/widgets/split_datetime_vertical.html,sha256=xinCH4kkQ-yKUqcSI7-m-_UEzOEKWqvLTjUa3i-e8EM,881
|
168
176
|
unfold/templates/unfold/widgets/split_money.html,sha256=AFLUBmzGbY-RXgsfz7gaDxVRhplaIPhXjg_hWYo9xcY,352
|
169
|
-
unfold/templates/unfold/widgets/textarea.html,sha256
|
177
|
+
unfold/templates/unfold/widgets/textarea.html,sha256=-ZLDGrtASero7L-J3VvTNq_KjPAZh_kLVw0Ow3awqXM,144
|
178
|
+
unfold/templates/unfold/widgets/textarea_expandable.html,sha256=4xIGWb20DuwiwumndByrAyh7xF-ReXKLulSpDImX_cs,459
|
170
179
|
unfold/templates/unfold/widgets/time.html,sha256=WXo2LG1v_gBZBSg-zocj7oujMKI0MWLYCIFfB04HMLQ,122
|
171
180
|
unfold/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
172
181
|
unfold/templatetags/unfold.py,sha256=HFe0GrTD4va0lLzsZhxqjEOONmehqOWdf5vulkxgfGU,6302
|
@@ -174,8 +183,8 @@ unfold/templatetags/unfold_list.py,sha256=5xAjQX0_JnVwDaj-wGkGqbjOAtp-a18koWIKj5
|
|
174
183
|
unfold/typing.py,sha256=1P8PWM2oeaceUJtA5j071RbKEBpHYaux441u7Hd6wv4,643
|
175
184
|
unfold/utils.py,sha256=5OIgDcwvIJQbwbnnqHx61cHh-2T1h184mTAuNq5WXLI,4088
|
176
185
|
unfold/views.py,sha256=Ml3XlEoHLcbEWof59Dw8ihKBMcmp-gBAibThtBFj55A,708
|
177
|
-
unfold/widgets.py,sha256=
|
178
|
-
django_unfold-0.
|
179
|
-
django_unfold-0.
|
180
|
-
django_unfold-0.
|
181
|
-
django_unfold-0.
|
186
|
+
unfold/widgets.py,sha256=iiI73XznYH34LgXBHu1oVtYb76lLO3HiOD9blmm89rY,15236
|
187
|
+
django_unfold-0.26.0.dist-info/LICENSE.md,sha256=Ltk_quRyyvV3J5v3brtOqmibeZSw2Hrb8bY1W3ya0Ik,1077
|
188
|
+
django_unfold-0.26.0.dist-info/METADATA,sha256=UUv_H7BwNNquFABkpQ1PObEBBKknsGFxkyDnkk3l5oU,48746
|
189
|
+
django_unfold-0.26.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
190
|
+
django_unfold-0.26.0.dist-info/RECORD,,
|
unfold/admin.py
CHANGED
@@ -55,6 +55,7 @@ from .widgets import (
|
|
55
55
|
UnfoldAdminBigIntegerFieldWidget,
|
56
56
|
UnfoldAdminDecimalFieldWidget,
|
57
57
|
UnfoldAdminEmailInputWidget,
|
58
|
+
UnfoldAdminFileFieldWidget,
|
58
59
|
UnfoldAdminImageFieldWidget,
|
59
60
|
UnfoldAdminImageSmallFieldWidget,
|
60
61
|
UnfoldAdminIntegerFieldWidget,
|
@@ -71,6 +72,7 @@ from .widgets import (
|
|
71
72
|
UnfoldAdminUUIDInputWidget,
|
72
73
|
UnfoldBooleanSwitchWidget,
|
73
74
|
UnfoldBooleanWidget,
|
75
|
+
UnfoldForeignKeyRawIdWidget,
|
74
76
|
)
|
75
77
|
|
76
78
|
try:
|
@@ -109,6 +111,7 @@ FORMFIELD_OVERRIDES = {
|
|
109
111
|
models.BigIntegerField: {"widget": UnfoldAdminBigIntegerFieldWidget},
|
110
112
|
models.DecimalField: {"widget": UnfoldAdminDecimalFieldWidget},
|
111
113
|
models.FloatField: {"widget": UnfoldAdminDecimalFieldWidget},
|
114
|
+
models.FileField: {"widget": UnfoldAdminFileFieldWidget},
|
112
115
|
models.ImageField: {"widget": UnfoldAdminImageFieldWidget},
|
113
116
|
models.JSONField: {"widget": UnfoldAdminTextareaWidget},
|
114
117
|
models.DurationField: {"widget": UnfoldAdminTextInputWidget},
|
@@ -142,7 +145,6 @@ FORMFIELD_OVERRIDES_INLINE.update(
|
|
142
145
|
class UnfoldAdminField(helpers.AdminField):
|
143
146
|
def label_tag(self) -> SafeText:
|
144
147
|
classes = []
|
145
|
-
|
146
148
|
if not self.field.field.widget.__class__.__name__.startswith(
|
147
149
|
"Unfold"
|
148
150
|
) and not self.field.field.widget.template_name.startswith("unfold"):
|
@@ -181,7 +183,9 @@ helpers.AdminField = UnfoldAdminField
|
|
181
183
|
|
182
184
|
class UnfoldAdminReadonlyField(helpers.AdminReadonlyField):
|
183
185
|
def label_tag(self) -> SafeText:
|
184
|
-
if not isinstance(self.model_admin, ModelAdmin)
|
186
|
+
if not isinstance(self.model_admin, ModelAdmin) and not isinstance(
|
187
|
+
self.model_admin, ModelAdminMixin
|
188
|
+
):
|
185
189
|
return super().label_tag()
|
186
190
|
|
187
191
|
attrs = {
|
@@ -305,10 +309,14 @@ class ModelAdminMixin:
|
|
305
309
|
def formfield_for_foreignkey(
|
306
310
|
self, db_field: ForeignKey, request: HttpRequest, **kwargs
|
307
311
|
) -> Optional[ModelChoiceField]:
|
312
|
+
db = kwargs.get("using")
|
313
|
+
|
308
314
|
# Overrides widgets for all related fields
|
309
315
|
if "widget" not in kwargs:
|
310
316
|
if db_field.name in self.raw_id_fields:
|
311
|
-
kwargs["widget"] =
|
317
|
+
kwargs["widget"] = UnfoldForeignKeyRawIdWidget(
|
318
|
+
db_field.remote_field, self.admin_site, using=db
|
319
|
+
)
|
312
320
|
elif (
|
313
321
|
db_field.name not in self.get_autocomplete_fields(request)
|
314
322
|
and db_field.name not in self.radio_fields
|
@@ -1,8 +1,13 @@
|
|
1
|
+
from django.forms.fields import BooleanField
|
1
2
|
from import_export.forms import ExportForm as BaseExportForm
|
2
3
|
from import_export.forms import ImportForm as BaseImportForm
|
4
|
+
from import_export.forms import (
|
5
|
+
SelectableFieldsExportForm as BaseSelectableFieldsExportForm,
|
6
|
+
)
|
3
7
|
from unfold.widgets import (
|
4
8
|
SELECT_CLASSES,
|
5
9
|
UnfoldAdminFileFieldWidget,
|
10
|
+
UnfoldBooleanWidget,
|
6
11
|
)
|
7
12
|
|
8
13
|
|
@@ -18,4 +23,16 @@ class ImportForm(BaseImportForm):
|
|
18
23
|
class ExportForm(BaseExportForm):
|
19
24
|
def __init__(self, *args, **kwargs):
|
20
25
|
super().__init__(*args, **kwargs)
|
26
|
+
self.fields["resource"].widget.attrs["class"] = " ".join(SELECT_CLASSES)
|
21
27
|
self.fields["format"].widget.attrs["class"] = " ".join(SELECT_CLASSES)
|
28
|
+
|
29
|
+
|
30
|
+
class SelectableFieldsExportForm(BaseSelectableFieldsExportForm):
|
31
|
+
def __init__(self, formats, resources, **kwargs):
|
32
|
+
super().__init__(formats, resources, **kwargs)
|
33
|
+
self.fields["resource"].widget.attrs["class"] = " ".join(SELECT_CLASSES)
|
34
|
+
self.fields["format"].widget.attrs["class"] = " ".join(SELECT_CLASSES)
|
35
|
+
|
36
|
+
for _key, field in self.fields.items():
|
37
|
+
if isinstance(field, BooleanField):
|
38
|
+
field.widget = UnfoldBooleanWidget()
|
@@ -0,0 +1,10 @@
|
|
1
|
+
{% extends 'admin/change_form.html' %}
|
2
|
+
{% load i18n %}
|
3
|
+
|
4
|
+
{% block actions %}
|
5
|
+
{{ block.super }}
|
6
|
+
|
7
|
+
{% if show_change_form_export %}
|
8
|
+
<input type="submit" value="{% translate 'Export' %}" name="_export-item" class="bg-white text-gray-500 border cursor-pointer flex font-medium items-center px-3 py-2 mr-3 rounded-md shadow-sm text-sm dark:bg-gray-900 dark:border dark:border-gray-700 dark:text-gray-400">
|
9
|
+
{% endif %}
|
10
|
+
{% endblock %}
|
@@ -33,11 +33,46 @@
|
|
33
33
|
{% endblock %}
|
34
34
|
|
35
35
|
{% block content %}
|
36
|
-
<form action="" method="POST">
|
36
|
+
<form action="{{ export_url }}" method="POST">
|
37
37
|
{% csrf_token %}
|
38
38
|
|
39
|
-
|
40
|
-
|
39
|
+
{% if form.initial.export_items %}
|
40
|
+
<p class="bg-blue-50 mb-4 text-blue-500 px-3 py-3 rounded-md text-sm dark:bg-blue-500/20 dark:border-blue-500/10">
|
41
|
+
{% blocktranslate count len=form.initial.export_items|length %}
|
42
|
+
Export {{ len }} selected item.
|
43
|
+
{% plural %}
|
44
|
+
Export {{ len }} selected items.
|
45
|
+
{% endblocktranslate %}
|
46
|
+
</p>
|
47
|
+
{% endif %}
|
48
|
+
|
49
|
+
{% if not form.is_selectable_fields_form %}
|
50
|
+
{% include "admin/import_export/resource_fields_list.html" with import_or_export="export" %}
|
51
|
+
{% endif %}
|
52
|
+
|
53
|
+
{{ form.non_field_errors }}
|
54
|
+
|
55
|
+
<fieldset class="border border-gray-200 mb-4 rounded-md pt-3 px-3 shadow-sm dark:border-gray-800">
|
56
|
+
{% for field in form.visible_fields %}
|
57
|
+
<div {% if field.field.is_selectable_field %}class="selectable-field-export-row" resource-index="{{ field.field.resource_index }}"{% else %}class="form-row aligned"{% endif %}>
|
58
|
+
{% if field.field.initial_field %}
|
59
|
+
<p class="block font-medium mb-2 text-gray-900 text-sm dark:text-gray-200">
|
60
|
+
{% trans "This exporter will export the following fields" %}
|
61
|
+
</p>
|
62
|
+
{% endif %}
|
63
|
+
|
64
|
+
{% if field.field.widget.attrs.readonly %}
|
65
|
+
{% include "unfold/helpers/field_readonly.html" with title=field.label value=field.field.value %}
|
66
|
+
{{ field.as_hidden }}
|
67
|
+
{% else %}
|
68
|
+
{% include "unfold/helpers/field.html" with field=field %}
|
69
|
+
{% endif %}
|
70
|
+
</div>
|
71
|
+
{% endfor %}
|
72
|
+
|
73
|
+
{% for field in form.hidden_fields %}
|
74
|
+
{{ field }}
|
75
|
+
{% endfor %}
|
41
76
|
</fieldset>
|
42
77
|
|
43
78
|
<button type="submit" class="bg-primary-600 border border-transparent font-medium px-3 py-2 rounded-md text-sm text-white">
|
@@ -7,20 +7,16 @@
|
|
7
7
|
{% include "admin/import_export/resource_fields_list.html" with import_or_export="import" %}
|
8
8
|
|
9
9
|
<fieldset class="border border-gray-200 mb-8 rounded-md pt-3 px-3 shadow-sm dark:border-gray-800">
|
10
|
-
{%
|
11
|
-
{%
|
12
|
-
|
13
|
-
|
14
|
-
{%
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
{% include "unfold/helpers/field.html" with field=form.import_file %}
|
19
|
-
|
20
|
-
{% include "unfold/helpers/field.html" with field=form.format %}
|
10
|
+
{% for field in form %}
|
11
|
+
{% if field.field.widget.attrs.readonly %}
|
12
|
+
{% include "unfold/helpers/field_readonly.html" with title=field.label value=field.field.value %}
|
13
|
+
{{ field.as_hidden }}
|
14
|
+
{% else %}
|
15
|
+
{% include "unfold/helpers/field.html" with field=field %}
|
16
|
+
{% endif %}
|
17
|
+
{% endfor %}
|
21
18
|
</fieldset>
|
22
19
|
|
23
|
-
|
24
20
|
<button type="submit" class="bg-primary-600 border border-transparent font-medium px-3 py-2 rounded-md text-sm text-white">
|
25
21
|
{% translate 'Submit' %}
|
26
22
|
</button>
|
@@ -1,7 +1,7 @@
|
|
1
1
|
{% load i18n %}
|
2
2
|
|
3
3
|
{% block fields_help %}
|
4
|
-
<div class="bg-blue-50 mb-
|
4
|
+
<div class="bg-blue-50 mb-4 text-blue-500 px-3 py-3 rounded-md text-sm dark:bg-blue-500/20 dark:border-blue-500/10">
|
5
5
|
{% if import_or_export == "export" %}
|
6
6
|
{% trans "This exporter will export the following fields: " %}
|
7
7
|
{% elif import_or_export == "import" %}
|
File without changes
|
@@ -0,0 +1,141 @@
|
|
1
|
+
from functools import partial
|
2
|
+
from typing import Any, Optional
|
3
|
+
|
4
|
+
from django import forms
|
5
|
+
from django.contrib.admin.utils import NestedObjects, flatten_fieldsets
|
6
|
+
from django.core.exceptions import ValidationError
|
7
|
+
from django.db import router
|
8
|
+
from django.db.models import Model
|
9
|
+
from django.forms.formsets import DELETION_FIELD_NAME
|
10
|
+
from django.forms.models import modelform_defines_fields
|
11
|
+
from django.http import HttpRequest
|
12
|
+
from django.utils.text import get_text_list
|
13
|
+
from django.utils.translation import gettext_lazy as _
|
14
|
+
from unfold.admin import StackedInline, TabularInline
|
15
|
+
|
16
|
+
from .checks import NonrelatedModelAdminChecks
|
17
|
+
from .forms import NonrelatedInlineModelFormSet, nonrelated_inline_formset_factory
|
18
|
+
|
19
|
+
|
20
|
+
class NonrelatedInlineMixin:
|
21
|
+
checks_class = NonrelatedModelAdminChecks
|
22
|
+
formset = NonrelatedInlineModelFormSet
|
23
|
+
|
24
|
+
def get_formset(
|
25
|
+
self, request: HttpRequest, obj: Optional[Model] = None, **kwargs: Any
|
26
|
+
):
|
27
|
+
defaults = self._get_formset_defaults(request, obj, **kwargs)
|
28
|
+
|
29
|
+
defaults["queryset"] = (
|
30
|
+
self.get_form_queryset(obj) if obj else self.model.objects.none()
|
31
|
+
)
|
32
|
+
|
33
|
+
return nonrelated_inline_formset_factory(
|
34
|
+
self.model, save_new_instance=self.save_new_instance, **defaults
|
35
|
+
)
|
36
|
+
|
37
|
+
def _get_formset_defaults(
|
38
|
+
self, request: HttpRequest, obj: Optional[Model] = None, **kwargs: Any
|
39
|
+
):
|
40
|
+
"""Return a BaseInlineFormSet class for use in admin add/change views."""
|
41
|
+
if "fields" in kwargs:
|
42
|
+
fields = kwargs.pop("fields")
|
43
|
+
else:
|
44
|
+
fields = flatten_fieldsets(self.get_fieldsets(request, obj))
|
45
|
+
excluded = self.get_exclude(request, obj)
|
46
|
+
exclude = [] if excluded is None else list(excluded)
|
47
|
+
exclude.extend(self.get_readonly_fields(request, obj))
|
48
|
+
if excluded is None and hasattr(self.form, "_meta") and self.form._meta.exclude:
|
49
|
+
# Take the custom ModelForm's Meta.exclude into account only if the
|
50
|
+
# InlineModelAdmin doesn't define its own.
|
51
|
+
exclude.extend(self.form._meta.exclude)
|
52
|
+
# If exclude is an empty list we use None, since that's the actual
|
53
|
+
# default.
|
54
|
+
exclude = exclude or None
|
55
|
+
can_delete = self.can_delete and self.has_delete_permission(request, obj)
|
56
|
+
defaults = {
|
57
|
+
"form": self.form,
|
58
|
+
"formset": self.formset,
|
59
|
+
# "fk_name": self.fk_name,
|
60
|
+
"fields": fields,
|
61
|
+
"exclude": exclude,
|
62
|
+
"formfield_callback": partial(self.formfield_for_dbfield, request=request),
|
63
|
+
"extra": self.get_extra(request, obj, **kwargs),
|
64
|
+
"min_num": self.get_min_num(request, obj, **kwargs),
|
65
|
+
"max_num": self.get_max_num(request, obj, **kwargs),
|
66
|
+
"can_delete": can_delete,
|
67
|
+
**kwargs,
|
68
|
+
}
|
69
|
+
|
70
|
+
base_model_form = defaults["form"]
|
71
|
+
can_change = self.has_change_permission(request, obj) if request else True
|
72
|
+
can_add = self.has_add_permission(request, obj) if request else True
|
73
|
+
|
74
|
+
class DeleteProtectedModelForm(base_model_form):
|
75
|
+
def hand_clean_DELETE(self):
|
76
|
+
"""
|
77
|
+
We don't validate the 'DELETE' field itself because on
|
78
|
+
templates it's not rendered using the field information, but
|
79
|
+
just using a generic "deletion_field" of the InlineModelAdmin.
|
80
|
+
"""
|
81
|
+
if self.cleaned_data.get(DELETION_FIELD_NAME, False):
|
82
|
+
using = router.db_for_write(self._meta.model)
|
83
|
+
collector = NestedObjects(using=using)
|
84
|
+
if self.instance._state.adding:
|
85
|
+
return
|
86
|
+
collector.collect([self.instance])
|
87
|
+
if collector.protected:
|
88
|
+
objs = []
|
89
|
+
for p in collector.protected:
|
90
|
+
objs.append(
|
91
|
+
# Translators: Model verbose name and instance representation,
|
92
|
+
# suitable to be an item in a list.
|
93
|
+
_("%(class_name)s %(instance)s")
|
94
|
+
% {
|
95
|
+
"class_name": p._meta.verbose_name,
|
96
|
+
"instance": p,
|
97
|
+
}
|
98
|
+
)
|
99
|
+
params = {
|
100
|
+
"class_name": self._meta.model._meta.verbose_name,
|
101
|
+
"instance": self.instance,
|
102
|
+
"related_objects": get_text_list(objs, _("and")),
|
103
|
+
}
|
104
|
+
msg = _(
|
105
|
+
"Deleting %(class_name)s %(instance)s would require "
|
106
|
+
"deleting the following protected related objects: "
|
107
|
+
"%(related_objects)s"
|
108
|
+
)
|
109
|
+
raise ValidationError(
|
110
|
+
msg, code="deleting_protected", params=params
|
111
|
+
)
|
112
|
+
|
113
|
+
def is_valid(self):
|
114
|
+
result = super().is_valid()
|
115
|
+
self.hand_clean_DELETE()
|
116
|
+
return result
|
117
|
+
|
118
|
+
def has_changed(self):
|
119
|
+
# Protect against unauthorized edits.
|
120
|
+
if not can_change and not self.instance._state.adding:
|
121
|
+
return False
|
122
|
+
if not can_add and self.instance._state.adding:
|
123
|
+
return False
|
124
|
+
return super().has_changed()
|
125
|
+
|
126
|
+
defaults["form"] = DeleteProtectedModelForm
|
127
|
+
|
128
|
+
if defaults["fields"] is None and not modelform_defines_fields(
|
129
|
+
defaults["form"]
|
130
|
+
):
|
131
|
+
defaults["fields"] = forms.ALL_FIELDS
|
132
|
+
|
133
|
+
return defaults
|
134
|
+
|
135
|
+
|
136
|
+
class NonrelatedStackedInline(NonrelatedInlineMixin, StackedInline):
|
137
|
+
pass
|
138
|
+
|
139
|
+
|
140
|
+
class NonrelatedTabularInline(NonrelatedInlineMixin, TabularInline):
|
141
|
+
pass
|
@@ -0,0 +1,18 @@
|
|
1
|
+
from typing import List
|
2
|
+
|
3
|
+
from django.contrib.admin.checks import InlineModelAdminChecks
|
4
|
+
from django.contrib.admin.options import InlineModelAdmin
|
5
|
+
from django.core.checks import CheckMessage
|
6
|
+
from django.db.models import Model
|
7
|
+
|
8
|
+
|
9
|
+
class NonrelatedModelAdminChecks(InlineModelAdminChecks):
|
10
|
+
def _check_exclude_of_parent_model(
|
11
|
+
self, obj: InlineModelAdmin, parent_model: Model
|
12
|
+
) -> List[CheckMessage]:
|
13
|
+
return []
|
14
|
+
|
15
|
+
def _check_relation(
|
16
|
+
self, obj: InlineModelAdmin, parent_model: Model
|
17
|
+
) -> List[CheckMessage]:
|
18
|
+
return []
|