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.
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 []