django-unfold 0.25.0__py3-none-any.whl → 0.26.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. {django_unfold-0.25.0.dist-info → django_unfold-0.26.0.dist-info}/METADATA +51 -6
  2. {django_unfold-0.25.0.dist-info → django_unfold-0.26.0.dist-info}/RECORD +36 -27
  3. unfold/admin.py +11 -3
  4. unfold/contrib/import_export/forms.py +17 -0
  5. unfold/contrib/import_export/templates/admin/import_export/change_form.html +10 -0
  6. unfold/contrib/import_export/templates/admin/import_export/export.html +38 -3
  7. unfold/contrib/import_export/templates/admin/import_export/import_form.html +8 -12
  8. unfold/contrib/import_export/templates/admin/import_export/resource_fields_list.html +1 -1
  9. unfold/contrib/inlines/__init__.py +0 -0
  10. unfold/contrib/inlines/admin.py +141 -0
  11. unfold/contrib/inlines/apps.py +6 -0
  12. unfold/contrib/inlines/checks.py +18 -0
  13. unfold/contrib/inlines/forms.py +43 -0
  14. unfold/forms.py +6 -0
  15. unfold/static/unfold/css/simplebar.css +230 -0
  16. unfold/static/unfold/css/styles.css +1 -1
  17. unfold/static/unfold/js/simplebar.js +10 -0
  18. unfold/styles.css +9 -1
  19. unfold/templates/admin/app_list.html +1 -1
  20. unfold/templates/admin/change_form.html +11 -9
  21. unfold/templates/admin/change_list_results.html +2 -2
  22. unfold/templates/admin/edit_inline/stacked.html +6 -6
  23. unfold/templates/admin/edit_inline/tabular.html +3 -3
  24. unfold/templates/admin/includes/fieldset.html +1 -1
  25. unfold/templates/unfold/helpers/app_list.html +1 -1
  26. unfold/templates/unfold/helpers/display_header.html +11 -8
  27. unfold/templates/unfold/helpers/field.html +20 -6
  28. unfold/templates/unfold/helpers/fieldsets_tabs.html +4 -4
  29. unfold/templates/unfold/helpers/form_label.html +1 -1
  30. unfold/templates/unfold/layouts/skeleton.html +2 -0
  31. unfold/templates/unfold/widgets/foreign_key_raw_id.html +21 -0
  32. unfold/templates/unfold/widgets/textarea.html +1 -7
  33. unfold/templates/unfold/widgets/textarea_expandable.html +7 -0
  34. unfold/widgets.py +36 -3
  35. unfold/contrib/import_export/admin.py +0 -37
  36. {django_unfold-0.25.0.dist-info → django_unfold-0.26.0.dist-info}/LICENSE.md +0 -0
  37. {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.25.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
  [![Build](https://img.shields.io/github/actions/workflow/status/unfoldadmin/django-unfold/release.yml?style=for-the-badge)](https://github.com/unfoldadmin/django-unfold/actions?query=workflow%3Arelease)
32
32
  [![PyPI - Version](https://img.shields.io/pypi/v/django-unfold.svg?style=for-the-badge)](https://pypi.org/project/django-unfold/)
33
- ![Code Style - Black](https://img.shields.io/badge/code%20style-black-000000.svg?style=for-the-badge)
33
+ ![Code Style - Ruff](https://img.shields.io/badge/code%20style-ruff-30173D.svg?style=for-the-badge)
34
34
  ![Pre Commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=for-the-badge)
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
- - **Parallel admin**: support for having default admin in parallel with Unfold. [Admin migration guide](https://unfoldadmin.com/blog/migrating-django-admin-unfold/)
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", # Use None in case you don't need it
701
- "AB", # Short text which will appear in front of
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=iNlDiZy_kl4lk2We4VDKOK5lMam4GeNMOA-bo67ykqU,23810
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=o1dH1F6hOdpWdeFCxVRGe0Y5iA9BA7CaWxVHWX_WlvM,756
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=W-ZId8LJCzA3Kuzxun7v-RNzqo7q8cxRk-vmd8xaC0s,1786
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=-oyUFQCLZG5Q3RVQE6ad07ccRXVE006f5YeqoWTHc18,1114
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=NuIp058PRnp7upIwXYeGX-u2tMv_iJKciRZ1MVmORlQ,871
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=SzdyBC5wqE5IcoZWS2oXObycmfmICHsqlS3X03mw4QA,3468
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/styles.css,sha256=HfOY9Ylgrlzs5o3KVIVx29gKNpBdsmml8VERROruJuQ,91363
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/styles.css,sha256=NfhUTzvCY01TO3uRxotLA4wCmXI4C92MENXJTg2c75E,17593
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=7u2Ex0ArdC0CJNCTDnTEZpAXI_xbNQ1nrzAH4BuInNA,3009
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=-X_fQOGaa6k0WVbMO7he9bZIdIY3oj8PyRSV4dnt6qA,5320
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=1NeZibVKz9roYtdpYJGrlHj7HnyCQGzM_zSUg8JNGcg,5329
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=_6kR4ANSiZomJnjgCXB7zEiFbPIuwZoaC0KYoN3is0A,4465
96
- unfold/templates/admin/edit_inline/tabular.html,sha256=rkwd4RwvCU9rg_sH9MiNV20knpd_Hyx7EViavhHR6fk,12892
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=r4XjcZAOkWxHQExHZBWoCGtO3LYL0Iwkw1C55oR5V6M,2898
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=FvL-cEOVxwckP5TqzLEYL34EMlYiCd6gRy0oguBaRMw,4691
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=J49ReIVl8Z29714HMIH-zFfaQdraMRS32VL1Ewg4W9M,808
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=oEhGUrLZi2hiuLaC96R2zdwD8DNZqX2_sJIxTpPTJDM,340
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=-l8ni5jeJQPNxucAn4ujbts08Bp2WuGlQGnHpBMk5Os,1316
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=5wKKBXVPwziV5v5fbYatpRvnvSJBDJf_7znNdE6V8IM,237
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=D35u-G6-h5SwTMzIZaEURedsPObSjoB198lLsJwmZWs,3278
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=4xIGWb20DuwiwumndByrAyh7xF-ReXKLulSpDImX_cs,459
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=JfcWOWtPPxqosXMyP-BcfO6UK53eoEU3obRpMZijED4,14216
178
- django_unfold-0.25.0.dist-info/LICENSE.md,sha256=Ltk_quRyyvV3J5v3brtOqmibeZSw2Hrb8bY1W3ya0Ik,1077
179
- django_unfold-0.25.0.dist-info/METADATA,sha256=6O8FhlWME8UWFxFe0HpOacXPUi0pIwfYLtMrvFIMRH4,46669
180
- django_unfold-0.25.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
181
- django_unfold-0.25.0.dist-info/RECORD,,
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"] = UnfoldAdminTextInputWidget()
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
- <fieldset class="border border-gray-200 mb-8 rounded-md pt-3 px-3 shadow-sm dark:border-gray-800">
40
- {% include "unfold/helpers/field.html" with field=form.format %}
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
- {% if form.resource.field.widget.attrs.readonly %}
11
- {% include "unfold/helpers/field_readonly.html" with title=form.resource.field.label value=form.resource.field.value %}
12
- {{ form.resource.as_hidden }}
13
- {% else %}
14
- {% include "unfold/helpers/field.html" with field=form.resource %}
15
- {% endif %}
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-8 text-blue-500 px-3 py-3 rounded-md text-sm dark:bg-blue-500/20 dark:border-blue-500/10">
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,6 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class DefaultAppConfig(AppConfig):
5
+ name = "unfold.contrib.inlines"
6
+ label = "unfold_inlines"
@@ -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 []