django-unfold 0.47.0__py3-none-any.whl → 0.49.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.47.0.dist-info → django_unfold-0.49.0.dist-info}/METADATA +1 -1
  2. {django_unfold-0.47.0.dist-info → django_unfold-0.49.0.dist-info}/RECORD +37 -31
  3. unfold/admin.py +17 -412
  4. unfold/dataclasses.py +8 -0
  5. unfold/decorators.py +14 -1
  6. unfold/fields.py +6 -5
  7. unfold/mixins/__init__.py +4 -0
  8. unfold/mixins/action_model_admin.py +329 -0
  9. unfold/mixins/base_model_admin.py +110 -0
  10. unfold/overrides.py +73 -0
  11. unfold/settings.py +2 -0
  12. unfold/sites.py +25 -5
  13. unfold/static/unfold/css/styles.css +1 -1
  14. unfold/static/unfold/fonts/inter/Inter-Bold.woff2 +0 -0
  15. unfold/static/unfold/fonts/inter/Inter-Medium.woff2 +0 -0
  16. unfold/static/unfold/fonts/inter/Inter-Regular.woff2 +0 -0
  17. unfold/static/unfold/fonts/inter/Inter-SemiBold.woff2 +0 -0
  18. unfold/templates/admin/app_index.html +1 -5
  19. unfold/templates/admin/base_site.html +1 -1
  20. unfold/templates/admin/index.html +1 -5
  21. unfold/templates/admin/login.html +1 -1
  22. unfold/templates/admin/search_form.html +4 -2
  23. unfold/templates/admin/submit_line.html +7 -1
  24. unfold/templates/unfold/helpers/account_links.html +1 -1
  25. unfold/templates/unfold/helpers/actions_row.html +14 -4
  26. unfold/templates/unfold/helpers/language_switch.html +1 -1
  27. unfold/templates/unfold/helpers/navigation_header.html +18 -5
  28. unfold/templates/unfold/helpers/site_branding.html +9 -0
  29. unfold/templates/unfold/helpers/site_dropdown.html +19 -0
  30. unfold/templates/unfold/helpers/site_icon.html +20 -10
  31. unfold/templates/unfold/helpers/tab_action.html +35 -2
  32. unfold/templates/unfold/helpers/theme_switch.html +1 -1
  33. unfold/templates/unfold/layouts/base.html +1 -5
  34. unfold/typing.py +2 -1
  35. unfold/widgets.py +2 -0
  36. {django_unfold-0.47.0.dist-info → django_unfold-0.49.0.dist-info}/LICENSE.md +0 -0
  37. {django_unfold-0.47.0.dist-info → django_unfold-0.49.0.dist-info}/WHEEL +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: django-unfold
3
- Version: 0.47.0
3
+ Version: 0.49.0
4
4
  Summary: Modern Django admin theme for seamless interface development
5
5
  License: MIT
6
6
  Keywords: django,admin,tailwind,theme
@@ -1,5 +1,5 @@
1
1
  unfold/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- unfold/admin.py,sha256=kIMEWKwiqnBo5eEDa_sBUlHh1XsZUhCt-Ct9bQ_H9vg,20018
2
+ unfold/admin.py,sha256=JOT4AbbCcGKDi_Qwi_yuFngkLbo78SHd-aHiHNLaI8M,6093
3
3
  unfold/apps.py,sha256=SlBXPYrUd2uXn67qFbRvbXSUk3XFWrF4-5WELgDCvho,381
4
4
  unfold/checks.py,sha256=8I3i4xR_KgyJdpQyZUZzKNeyYf-sNzg6PAlsREuMfgI,1664
5
5
  unfold/components.py,sha256=vqkQzseYUvLXDohmTVAlbKopALjyX4WA9yglvdfhqu4,1283
@@ -72,21 +72,25 @@ unfold/contrib/simple_history/templates/simple_history/object_history.html,sha25
72
72
  unfold/contrib/simple_history/templates/simple_history/object_history_form.html,sha256=JKCe-QTBZMB2ol5LPiX3gNuNxFpi_rqlXC9X92PFBPE,2296
73
73
  unfold/contrib/simple_history/templates/simple_history/object_history_list.html,sha256=ls_xEAAgGzbu0IXdjZk7scEyEJx0S7i3Khr9ND98gzY,6842
74
74
  unfold/contrib/simple_history/templates/simple_history/submit_line.html,sha256=y8BleGHPNztA_R37rTB-zjJ68uDbE8flYSvMD0Jfl5Y,1734
75
- unfold/dataclasses.py,sha256=KeFYN7m6AAUdtJKH0m7TGk8czU9pAt2MpWDAcdbSeR8,477
76
- unfold/decorators.py,sha256=7VNVSz3KoaMwCRAcBGO18wURXozXWrf_B0qPQity46c,3359
75
+ unfold/dataclasses.py,sha256=p3sqaIRfmGYEMq_-cSQIRYdsTeuqgiJ4ge_neQQrpd4,618
76
+ unfold/decorators.py,sha256=xofDxfJhVSmeyTh3fXiCXZtUKliuJOlxCQVQg3f1Fss,3740
77
77
  unfold/exceptions.py,sha256=gcCj1ox61E137bk_0Cqy4YC3SttdPgB-fiJUqpmyHSE,43
78
- unfold/fields.py,sha256=yhtpDfqycpOxqdQlbncCg9qhELxGk3AtXizkZyzzH0g,7410
78
+ unfold/fields.py,sha256=J3goIMlP_XOmQ85Xgh2v5MylXSP1o5I7o9_So2FQn2k,7461
79
79
  unfold/forms.py,sha256=BKv7eCbv29eCIuf1d_ZBmD4-_OIIzRaopiFd2nKbXNY,4520
80
- unfold/settings.py,sha256=Puhu2puoEuTImkEPjWv4J8OdnB_Az6iVrzQps1wWQKY,2616
81
- unfold/sites.py,sha256=mO_BpbtkMs38AGOVOSLlo0eCasIg6YY9zg1gtnXIQKM,15287
80
+ unfold/mixins/__init__.py,sha256=YlhoDs9Y_DXQ8Ejr3enxuHMS1-Ij9fwukxNwpeOS4Co,190
81
+ unfold/mixins/action_model_admin.py,sha256=Km-_Tz17EKEgPr8ZvDT21KCx0JCOb_iWEPdxux2K54Y,11717
82
+ unfold/mixins/base_model_admin.py,sha256=PR4eqU48yB4CNy9Bjd7fjxbOFiFpZM4etYmBOSaRX_M,4140
83
+ unfold/overrides.py,sha256=ERVY9boZiXgjDYcTEZcIp7KWoeBZ45VM_zN31yAOjyQ,2922
84
+ unfold/settings.py,sha256=5gs0iewBoUUrsF9LHHqcGOhW1HnyfZWY_zXuATjJOzE,2671
85
+ unfold/sites.py,sha256=iwpJ9Qykxe0q4-alGE_07bJdBSpmIeTDxZil-C_vWSc,16054
82
86
  unfold/static/admin/js/admin/RelatedObjectLookups.js,sha256=alI0-yq7YPDJJJn-yg1ce79-Cv88yQDUrfaGqFZnsaY,9048
83
87
  unfold/static/admin/js/inlines.js,sha256=gxmQEUlJ9yasELjz9qpluogFZB-bD1KJfHs4PI7UjsA,14687
84
88
  unfold/static/unfold/css/simplebar.css,sha256=5LLaEM11pKi6JFCOLt4XKuZxTpT9rpdq_tNlaQytFlU,4647
85
- unfold/static/unfold/css/styles.css,sha256=qc7Zrl9JGiAylsSH8Nq6TqChuQ4aEuSnB7Ij1-J3rWQ,157285
86
- unfold/static/unfold/fonts/inter/Inter-Bold.woff2,sha256=O88EyjAeRPE_QEyKBKpK5wf2epUOEu8wwjj5bnhCZqE,46552
87
- unfold/static/unfold/fonts/inter/Inter-Medium.woff2,sha256=O88EyjAeRPE_QEyKBKpK5wf2epUOEu8wwjj5bnhCZqE,46552
88
- unfold/static/unfold/fonts/inter/Inter-Regular.woff2,sha256=O88EyjAeRPE_QEyKBKpK5wf2epUOEu8wwjj5bnhCZqE,46552
89
- unfold/static/unfold/fonts/inter/Inter-SemiBold.woff2,sha256=O88EyjAeRPE_QEyKBKpK5wf2epUOEu8wwjj5bnhCZqE,46552
89
+ unfold/static/unfold/css/styles.css,sha256=ORq4t5sGDPl16B-05v-3ahusgoLVu9r6pH2kiYuNbPY,158172
90
+ unfold/static/unfold/fonts/inter/Inter-Bold.woff2,sha256=-oiBJ7baAVtlVp8DUfO1w5GtkokElR8cIOn4RiqNleo,114840
91
+ unfold/static/unfold/fonts/inter/Inter-Medium.woff2,sha256=D_PpRhThST61VjFP0keubEqFp3g7TMhr5TmUDPg_Kkg,114348
92
+ unfold/static/unfold/fonts/inter/Inter-Regular.woff2,sha256=4G9rG8VTqupORmgCPtCrChRxKcMQf1Ebx9A9NhsK4IU,111268
93
+ unfold/static/unfold/fonts/inter/Inter-SemiBold.woff2,sha256=XLcQPk5gWYmv68A9mJx5IB5UshtRg9szmB9w25F4owE,114812
90
94
  unfold/static/unfold/fonts/inter/styles.css,sha256=QqdgevXVfcD6vZGo8ciSkaEG_62_YcZg0hvHTNgJ56E,608
91
95
  unfold/static/unfold/fonts/material-symbols/Material-Symbols-Outlined.woff2,sha256=V6mmjk69Ee-coQvMsGV0tcNgV3E1nb7N9IKW8HVUBzk,256424
92
96
  unfold/static/unfold/fonts/material-symbols/styles.css,sha256=ZX1icA_BXCVL0eoY2H6XR8VT_Fxl_KnV6vFBm_OImHA,535
@@ -102,12 +106,12 @@ unfold/static/unfold/js/select2.init.js,sha256=siSDCwzy2PsOwA0Kj26JA_ELBtZlS70O8
102
106
  unfold/static/unfold/js/simplebar.js,sha256=t-uG1FAD6ZoiMeN--wac0XRS7SxoDVG6zvRnGuEp7X8,27176
103
107
  unfold/styles.css,sha256=XrdDgMYgyCw77ieNcxhHcwhkRLBvQCa-5PBf66JLZvU,18796
104
108
  unfold/templates/admin/actions.html,sha256=aroIid16mORcx-0p8O2dOoWupEoTGX66pt-PAXjSYZ0,3063
105
- unfold/templates/admin/app_index.html,sha256=fphodUAgaL7X4pOz-QxiWOKnESEs8wRqPoShSF6iBaU,1345
109
+ unfold/templates/admin/app_index.html,sha256=GSBry97gDcK3scTP-Fs4FXcSItRxxE87Wv4sDtMTlbc,1244
106
110
  unfold/templates/admin/app_list.html,sha256=VtntgHpbZdXbt_HXBLAtbuMdz1alS7F_S7eG37M5Rig,2855
107
111
  unfold/templates/admin/auth/user/add_form.html,sha256=kZDtUyhrvCIH9rlAq0iXLKYt97j1a-GsrWZQCQDASYM,479
108
112
  unfold/templates/admin/auth/user/change_password.html,sha256=OxpsKnVQm5lDrK5tGH0EAPFCPs288m0Jcy5WNYsdBXw,2896
109
113
  unfold/templates/admin/base.html,sha256=3xQmoD_7cu2UlgEdvnt7v_CibT9t1Iul0b-VU2Awu_U,2395
110
- unfold/templates/admin/base_site.html,sha256=FM8OOaXsQqJlYapeJvBznXCOt7poMiWrweTWcsMHzeI,365
114
+ unfold/templates/admin/base_site.html,sha256=UOsecaCycpyn_ygJNIKO7MQtDFJuHv--r83-mzaVY30,300
111
115
  unfold/templates/admin/change_form.html,sha256=LfeAeLYpJvNhOMJWQFeb9oSrAznlB4pnFrqTVMCIy4o,5340
112
116
  unfold/templates/admin/change_form_object_tools.html,sha256=eyeH-i2HgEM0Yi-OJA2D1VnKJyC19A_my1IDGxxoP8Y,593
113
117
  unfold/templates/admin/change_list.html,sha256=V2DgxlGaQvcOS1qAxe4no1EJCq-9nIOa10kYLkcE2Ag,5008
@@ -121,13 +125,13 @@ unfold/templates/admin/edit_inline/tabular.html,sha256=FBgzUb-U0cZCN28-cpHzRSyPE
121
125
  unfold/templates/admin/filter.html,sha256=ZnnJbg-9e6LM0OyKJW7V-DA7mmxkGhJeOpEfnM5JGM8,1998
122
126
  unfold/templates/admin/includes/fieldset.html,sha256=gX-dc-jPLswk21XSKUXzAU8WFl3CFfeVrIAU2bviPyg,812
123
127
  unfold/templates/admin/includes/object_delete_summary.html,sha256=FgwfapZCKWtnFtTHqh1MRuxxLot4bAw92WCcR_V8ImI,444
124
- unfold/templates/admin/index.html,sha256=pkGdKWdD3zzOvkRdELvdb15sleSpfl4eHPA14PAh7z0,684
125
- unfold/templates/admin/login.html,sha256=cer0pvCSNHrXltlgE1-2j9u4hK4XQ1yUjQbWXHaBYPQ,4445
128
+ unfold/templates/admin/index.html,sha256=JnNDpNKqFKVKMQvpUQo6ddcFqGadK5g78pxpKAHLuMU,583
129
+ unfold/templates/admin/login.html,sha256=5khGm_cafDVQaDRwaTtix4g2DH4R4LrwgqO0S4aumaE,4476
126
130
  unfold/templates/admin/nav_sidebar.html,sha256=cadJwXcReRHwVTpXGycF3uv5VZ5JDcIbY1pCabeQlCo,250
127
131
  unfold/templates/admin/object_history.html,sha256=bfad4_lfLQFcoMV67yW7BnAi1Hwrptmgxj2tgPYQjtI,4816
128
132
  unfold/templates/admin/pagination.html,sha256=1UIc6Hm2LnDDAHUE5UjuPzdFdiAat4tF6sk058UfRP0,2251
129
- unfold/templates/admin/search_form.html,sha256=FUSQPwjjKrsarJxar6O2wFhucy0Eq7EOc6V-EHt86sE,1370
130
- unfold/templates/admin/submit_line.html,sha256=5mxHoCVwMuocYW27P2YORmXduCNd251jTuJgHjxXvfo,4501
133
+ unfold/templates/admin/search_form.html,sha256=eQkwq4OWVgNUSjZPzxAjtT4LcxUNcTSgDVKJ4lWwslQ,1447
134
+ unfold/templates/admin/submit_line.html,sha256=cN2bUfhNBz6OCdCeHqNYhXT2SD01YDwsQ-PVQaCGPPY,4782
131
135
  unfold/templates/auth/widgets/read_only_password_hash.html,sha256=bQiYKDKJkyVP0_2eJG16oKviB4WEA0V7lSp6sGfgows,751
132
136
  unfold/templates/registration/logged_out.html,sha256=GgLzqmOGpiw5FcA-Aw_T6YvhYHhGFWhyES40T28waLQ,1002
133
137
  unfold/templates/registration/password_change_done.html,sha256=9HDpSraz3Kxdy0Fghbn-Lv0GAtpPgG6cuO7fvfMiYg8,1077
@@ -147,8 +151,8 @@ unfold/templates/unfold/components/table.html,sha256=_mIfauxVCt7gFC8CJpDdBp3TaGc
147
151
  unfold/templates/unfold/components/text.html,sha256=-GjxvdiaBQIaNfPSzT6SSIwnc3R27FkSDQMoF3FDPso,102
148
152
  unfold/templates/unfold/components/title.html,sha256=aMYSO30ybFYqPnqwlJVh2uNmcgnVrRycZkHTjJTjpX8,171
149
153
  unfold/templates/unfold/components/tracker.html,sha256=-PvDHdAAB2G5mKXpQUtljWJYhhavsa1aEoBvLrCiCJo,284
150
- unfold/templates/unfold/helpers/account_links.html,sha256=dU72CTNO1KubSa-lhTedM1KaGqES8HmkoTp33KBDWAw,1869
151
- unfold/templates/unfold/helpers/actions_row.html,sha256=p_Rn9B-izHYAQTGiivm4NDqfvX2N5h0Mva4Ak1wHBLA,1960
154
+ unfold/templates/unfold/helpers/account_links.html,sha256=8LBsa4uvwcbqc2kewHfSC8HaQAL3B-hPQIzV81rw_TU,1886
155
+ unfold/templates/unfold/helpers/actions_row.html,sha256=ZENvQJM2hGG3qvfwm-o9Ip665zXwqV-gl8VBof2WXgQ,2531
152
156
  unfold/templates/unfold/helpers/add_link.html,sha256=Z0TzUavXH8AQ7A-I0SQ79t-UI87dyP_PZEpsDrm13Q8,810
153
157
  unfold/templates/unfold/helpers/app_list.html,sha256=eIvBuP_g8oHvXZTy-IMjyynFCC1CChtMq4qZia6i77E,5751
154
158
  unfold/templates/unfold/helpers/app_list_default.html,sha256=74x9GIhXHp4J-qxfyS7siSaV8A4XlRwc_ivzvvKw_FY,3902
@@ -181,7 +185,7 @@ unfold/templates/unfold/helpers/header_back_button.html,sha256=6DXkxsZJnIt0Z_AIL
181
185
  unfold/templates/unfold/helpers/help_text.html,sha256=o-2_hyruIr_Xt0fo38aGgrpptPJFhddnVMPOVQOYhE4,113
182
186
  unfold/templates/unfold/helpers/history.html,sha256=QUYUQXcccyIl9pKPwVS_R8Is2Zo096rLZA6inlGNhkw,1921
183
187
  unfold/templates/unfold/helpers/label.html,sha256=pGzRBfuY8tos-imyA9psBxj7r93LUTfr78ff2rYPvmc,900
184
- unfold/templates/unfold/helpers/language_switch.html,sha256=WDVFkPrym3Kd5PKlULSxhZLuk0w3DCaIZVvjgPvW1SE,1469
188
+ unfold/templates/unfold/helpers/language_switch.html,sha256=RB7TMbLFaDejOLoNtE1mQoCtqVMfLTvgpLfTBXj85_s,1486
185
189
  unfold/templates/unfold/helpers/messages/debug.html,sha256=7GhaVXLkrdyecr6yAnLxyyPT5a8TMHkRFq-EfXJrymI,141
186
190
  unfold/templates/unfold/helpers/messages/error.html,sha256=5s51qKr0vHnIWngsc2U1TK9mAV3yBqwa7ekAYMyumlI,592
187
191
  unfold/templates/unfold/helpers/messages/errornote.html,sha256=pJ4Xjmp6ZimpimqMYUdCK6VZcWn_qlCSNTjRyyiZu-E,393
@@ -190,20 +194,22 @@ unfold/templates/unfold/helpers/messages/success.html,sha256=FVg3HNLW7i6VVZLqiUz
190
194
  unfold/templates/unfold/helpers/messages/warning.html,sha256=1bJj813EfxNSWcdQj6rM9bon4QTgMyrMydZY521h53E,149
191
195
  unfold/templates/unfold/helpers/messages.html,sha256=I9RIKfi4T65AG16-2s0C32RwbFkn0qLhdSqDfXpSbyE,915
192
196
  unfold/templates/unfold/helpers/navigation.html,sha256=HNr9bxZOg84zsbgORkOhJFXKSp0c2im438yiWCfonb0,787
193
- unfold/templates/unfold/helpers/navigation_header.html,sha256=LFC-Wwhq0Zow9wMBxz8LE-TBVywN0qC2KnLFFakO-kc,591
197
+ unfold/templates/unfold/helpers/navigation_header.html,sha256=upSSMGsqVzk_HfNINcapUtmR_d8kV2yxdwPEjMbRknY,1402
194
198
  unfold/templates/unfold/helpers/pagination_current_item.html,sha256=4cZ2KLVcP0Y7xuGyXgexDQ07r94cgM5Gnmtv11dkRPQ,69
195
199
  unfold/templates/unfold/helpers/pagination_ellipsis.html,sha256=8g0KUUKtqRkXx_EBLGtsJsiYQO4tPS3GazZjxT90e0M,56
196
200
  unfold/templates/unfold/helpers/search.html,sha256=w8Ute3qVfSIqUXzA3TKOx91RzW_qnffFi2Oe9qedtV4,1643
197
201
  unfold/templates/unfold/helpers/search_results.html,sha256=5gxOAfFBPOkRgDziyYio6ZXcDG1bzFw7t-KXGFaWrUk,653
198
- unfold/templates/unfold/helpers/site_icon.html,sha256=KYN0gI8U2Eoh2CFJxxYrwA0Oj6dTx04lnCdxMNRG2Hk,874
202
+ unfold/templates/unfold/helpers/site_branding.html,sha256=_pUyOvdjlV624eRq0wjpJkgXpn9DCDGc-0U6Mw0FupI,266
203
+ unfold/templates/unfold/helpers/site_dropdown.html,sha256=Zwg5ZGVIEHAI2h-YcWDDBz3QD2nYsP9m530wYArFk5I,878
204
+ unfold/templates/unfold/helpers/site_icon.html,sha256=rB6oc6VBB0T7wtyGOPcrq6qepDRaA54F6-Wxtaru-ks,1297
199
205
  unfold/templates/unfold/helpers/site_logo.html,sha256=S_QJoT2qh0xw0ciaKxoT4GJ6QIH5eqgRSC0abbWWkOI,423
200
206
  unfold/templates/unfold/helpers/submit.html,sha256=4Mgf4lx7Atm8GPqD6LTJK3NA9zoSJjs9VPig7sDp8Ao,203
201
- unfold/templates/unfold/helpers/tab_action.html,sha256=l7TBZm5FjDjlJpTsYXFonLSFBDpN91jDiILY3hjjj5s,376
207
+ unfold/templates/unfold/helpers/tab_action.html,sha256=qnMmPJOeMLAx64uTf1pdPq1OIKm1MQ2Et6vTU6WQb-8,2065
202
208
  unfold/templates/unfold/helpers/tab_list.html,sha256=ULuTZ7VaUFsgtqNr9eCb-A_Qmhp7gqt43V8sdu5fEII,5032
203
- unfold/templates/unfold/helpers/theme_switch.html,sha256=jiHqwz46GFb_pXjP23VTzV0S4E2LB-fdKpsIfjDaB8w,2201
209
+ unfold/templates/unfold/helpers/theme_switch.html,sha256=Kh3Q8RuBWCUuY8YJLwBwd5yKzDhK6y0Ggx_ERoGEGv8,2218
204
210
  unfold/templates/unfold/helpers/userlinks.html,sha256=oZqiwCxG_zRecAbzYrr8_hQvkndVB6-liP6LEZM1UZc,863
205
211
  unfold/templates/unfold/helpers/welcomemsg.html,sha256=a7VjZ8J1mn-DAo57nhUP096SEcBOqcCWrWt2qoT0cyg,1882
206
- unfold/templates/unfold/layouts/base.html,sha256=bAXZDbyiyxNiE-49mqr7pHUFhC2mHZQzIDUY-js_yZ0,379
212
+ unfold/templates/unfold/layouts/base.html,sha256=e9T-vuPvrySHf23LLwRuTQXUg0Q4x-6gmT_yXghqc-I,278
207
213
  unfold/templates/unfold/layouts/base_simple.html,sha256=VigjNWnWOt_XtbahvqsroPf8mdwl7cDGbg5fEaLmG9s,1211
208
214
  unfold/templates/unfold/layouts/skeleton.html,sha256=cE29ydXMseyXut1k7Yme3alnUBFXhBDxT0WFZCgrvGQ,4600
209
215
  unfold/templates/unfold/templatetags/preserve_changelist_filters.html,sha256=sx2jUhogNY4emBhY8PqxQ6mXifbXQb_IQI9Hd0boJI0,111
@@ -225,11 +231,11 @@ unfold/templates/unfold/widgets/url.html,sha256=IRLgW44VTKN7UrSWeywJwaxQhfG5jhhX
225
231
  unfold/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
226
232
  unfold/templatetags/unfold.py,sha256=C2x_fqjVCWku4WdyaD_u4UjXq9eue1CgnmEz5PCfrS8,12364
227
233
  unfold/templatetags/unfold_list.py,sha256=-KOU6Ib2k4gokuw_CgRooFCto8xJkiDyotbQbKAqkEY,14233
228
- unfold/typing.py,sha256=ZVeXbABOYjUFgtGlFLMh6jdiM9bHVCwxMW0pNtbvwuw,651
234
+ unfold/typing.py,sha256=i7LM2LiwYTAjT5-OLDUPVn5b9X-DMmHnjlZG2toWwSE,692
229
235
  unfold/utils.py,sha256=L0oC9-j1B__BZ21M7i6rTXNbzCQaZ4T0qHYQcNupk0A,5480
230
236
  unfold/views.py,sha256=nqv-meiwGtCeET2r8WBVyijWnE94Dssc4QmdUVMMcEM,997
231
- unfold/widgets.py,sha256=ZsjOAlQgEGmllPvXfN5ApbtW3VFs51lJzW4V44v9Ilg,20172
232
- django_unfold-0.47.0.dist-info/LICENSE.md,sha256=Ltk_quRyyvV3J5v3brtOqmibeZSw2Hrb8bY1W3ya0Ik,1077
233
- django_unfold-0.47.0.dist-info/METADATA,sha256=_tzSJ-F79LJ9a81ddDwS69HpaSi4cYWVWCMCnE1LHb0,4840
234
- django_unfold-0.47.0.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
235
- django_unfold-0.47.0.dist-info/RECORD,,
237
+ unfold/widgets.py,sha256=esW1wxdbZwUhF31ensitBF8_6kVq_Gp0_iDgv6F5Rr0,20246
238
+ django_unfold-0.49.0.dist-info/LICENSE.md,sha256=Ltk_quRyyvV3J5v3brtOqmibeZSw2Hrb8bY1W3ya0Ik,1077
239
+ django_unfold-0.49.0.dist-info/METADATA,sha256=Rn9yuoaPYQ_xomHRKms5bRBF_qlQvArpTT5lJE38r3Q,4840
240
+ django_unfold-0.49.0.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
241
+ django_unfold-0.49.0.dist-info/RECORD,,
unfold/admin.py CHANGED
@@ -1,127 +1,33 @@
1
- import copy
2
1
  from functools import update_wrapper
3
- from typing import Any, Callable, Optional, Union
2
+ from typing import Optional
4
3
 
5
4
  from django import forms
6
5
  from django.contrib.admin import ModelAdmin as BaseModelAdmin
7
6
  from django.contrib.admin import StackedInline as BaseStackedInline
8
7
  from django.contrib.admin import TabularInline as BaseTabularInline
9
8
  from django.contrib.admin import display, helpers
10
- from django.contrib.admin.widgets import RelatedFieldWidgetWrapper
11
- from django.db import models
12
9
  from django.db.models import BLANK_CHOICE_DASH, Model
13
- from django.db.models.fields import Field
14
- from django.db.models.fields.related import ForeignKey, ManyToManyField
15
- from django.forms import Form
16
- from django.forms.fields import TypedChoiceField
17
- from django.forms.models import ModelChoiceField, ModelMultipleChoiceField
18
- from django.forms.widgets import SelectMultiple
19
10
  from django.http import HttpRequest, HttpResponse
20
11
  from django.shortcuts import redirect
21
- from django.template.response import TemplateResponse
22
- from django.urls import URLPattern, path, reverse
12
+ from django.urls import URLPattern, path
23
13
  from django.utils.safestring import mark_safe
24
14
  from django.utils.translation import gettext_lazy as _
25
15
  from django.views import View
26
16
 
27
- from .checks import UnfoldModelAdminChecks
28
- from .dataclasses import UnfoldAction
29
- from .exceptions import UnfoldException
30
- from .fields import UnfoldAdminField, UnfoldAdminReadonlyField
31
- from .forms import ActionForm
32
- from .typing import FieldsetsType
33
- from .widgets import (
34
- SELECT_CLASSES,
35
- UnfoldAdminBigIntegerFieldWidget,
36
- UnfoldAdminDecimalFieldWidget,
37
- UnfoldAdminEmailInputWidget,
38
- UnfoldAdminFileFieldWidget,
39
- UnfoldAdminImageFieldWidget,
40
- UnfoldAdminImageSmallFieldWidget,
41
- UnfoldAdminIntegerFieldWidget,
42
- UnfoldAdminIntegerRangeWidget,
43
- UnfoldAdminMoneyWidget,
44
- UnfoldAdminNullBooleanSelectWidget,
45
- UnfoldAdminRadioSelectWidget,
46
- UnfoldAdminSelectWidget,
47
- UnfoldAdminSingleDateWidget,
48
- UnfoldAdminSingleTimeWidget,
49
- UnfoldAdminSplitDateTimeWidget,
50
- UnfoldAdminTextareaWidget,
51
- UnfoldAdminTextInputWidget,
52
- UnfoldAdminURLInputWidget,
53
- UnfoldAdminUUIDInputWidget,
54
- UnfoldBooleanSwitchWidget,
55
- UnfoldBooleanWidget,
56
- UnfoldForeignKeyRawIdWidget,
57
- )
58
-
59
- try:
60
- from django.contrib.postgres.fields import ArrayField, IntegerRangeField
61
- from django.contrib.postgres.search import SearchVectorField
62
-
63
- HAS_PSYCOPG = True
64
- except ImportError:
65
- HAS_PSYCOPG = False
66
-
67
- try:
68
- from djmoney.models.fields import MoneyField
69
-
70
- HAS_MONEY = True
71
- except ImportError:
72
- HAS_MONEY = False
73
-
74
- FORMFIELD_OVERRIDES = {
75
- models.DateTimeField: {
76
- "form_class": forms.SplitDateTimeField,
77
- "widget": UnfoldAdminSplitDateTimeWidget,
78
- },
79
- models.DateField: {"widget": UnfoldAdminSingleDateWidget},
80
- models.TimeField: {"widget": UnfoldAdminSingleTimeWidget},
81
- models.EmailField: {"widget": UnfoldAdminEmailInputWidget},
82
- models.CharField: {"widget": UnfoldAdminTextInputWidget},
83
- models.URLField: {"widget": UnfoldAdminURLInputWidget},
84
- models.GenericIPAddressField: {"widget": UnfoldAdminTextInputWidget},
85
- models.UUIDField: {"widget": UnfoldAdminUUIDInputWidget},
86
- models.TextField: {"widget": UnfoldAdminTextareaWidget},
87
- models.NullBooleanField: {"widget": UnfoldAdminNullBooleanSelectWidget},
88
- models.BooleanField: {"widget": UnfoldBooleanSwitchWidget},
89
- models.IntegerField: {"widget": UnfoldAdminIntegerFieldWidget},
90
- models.BigIntegerField: {"widget": UnfoldAdminBigIntegerFieldWidget},
91
- models.DecimalField: {"widget": UnfoldAdminDecimalFieldWidget},
92
- models.FloatField: {"widget": UnfoldAdminDecimalFieldWidget},
93
- models.FileField: {"widget": UnfoldAdminFileFieldWidget},
94
- models.ImageField: {"widget": UnfoldAdminImageFieldWidget},
95
- models.JSONField: {"widget": UnfoldAdminTextareaWidget},
96
- models.DurationField: {"widget": UnfoldAdminTextInputWidget},
97
- }
98
-
99
- if HAS_PSYCOPG:
100
- FORMFIELD_OVERRIDES.update(
101
- {
102
- ArrayField: {"widget": UnfoldAdminTextareaWidget},
103
- SearchVectorField: {"widget": UnfoldAdminTextareaWidget},
104
- IntegerRangeField: {"widget": UnfoldAdminIntegerRangeWidget},
105
- }
106
- )
107
-
108
- if HAS_MONEY:
109
- FORMFIELD_OVERRIDES.update(
110
- {
111
- MoneyField: {"widget": UnfoldAdminMoneyWidget},
112
- }
113
- )
114
-
115
- FORMFIELD_OVERRIDES_INLINE = copy.deepcopy(FORMFIELD_OVERRIDES)
116
-
117
- FORMFIELD_OVERRIDES_INLINE.update(
118
- {
119
- models.ImageField: {"widget": UnfoldAdminImageSmallFieldWidget},
120
- }
121
- )
17
+ from unfold.checks import UnfoldModelAdminChecks
18
+ from unfold.fields import UnfoldAdminField, UnfoldAdminReadonlyField
19
+ from unfold.forms import ActionForm
20
+ from unfold.mixins import ActionModelAdminMixin, BaseModelAdminMixin
21
+ from unfold.overrides import FORMFIELD_OVERRIDES_INLINE
22
+ from unfold.typing import FieldsetsType
23
+ from unfold.widgets import UnfoldBooleanWidget
122
24
 
123
25
  checkbox = UnfoldBooleanWidget(
124
- {"class": "action-select", "aria-label": _("Select record")}, lambda value: False
26
+ {
27
+ "class": "action-select",
28
+ "aria-label": _("Select record"),
29
+ },
30
+ lambda value: False,
125
31
  )
126
32
 
127
33
  helpers.AdminField = UnfoldAdminField
@@ -129,106 +35,8 @@ helpers.AdminField = UnfoldAdminField
129
35
  helpers.AdminReadonlyField = UnfoldAdminReadonlyField
130
36
 
131
37
 
132
- class ModelAdminMixin:
133
- def __init__(self, model, admin_site):
134
- overrides = copy.deepcopy(FORMFIELD_OVERRIDES)
135
-
136
- for k, v in self.formfield_overrides.items():
137
- overrides.setdefault(k, {}).update(v)
138
-
139
- self.formfield_overrides = overrides
140
-
141
- super().__init__(model, admin_site)
142
-
143
- def formfield_for_choice_field(
144
- self, db_field: Field, request: HttpRequest, **kwargs
145
- ) -> TypedChoiceField:
146
- if "widget" not in kwargs:
147
- if db_field.name in self.radio_fields:
148
- kwargs["widget"] = UnfoldAdminRadioSelectWidget(
149
- radio_style=self.radio_fields[db_field.name]
150
- )
151
- else:
152
- kwargs["widget"] = UnfoldAdminSelectWidget()
153
-
154
- if "choices" not in kwargs:
155
- kwargs["choices"] = db_field.get_choices(
156
- include_blank=db_field.blank, blank_choice=[("", _("Select value"))]
157
- )
158
-
159
- return super().formfield_for_choice_field(db_field, request, **kwargs)
160
-
161
- def formfield_for_foreignkey(
162
- self, db_field: ForeignKey, request: HttpRequest, **kwargs
163
- ) -> Optional[ModelChoiceField]:
164
- db = kwargs.get("using")
165
-
166
- # Overrides widgets for all related fields
167
- if "widget" not in kwargs:
168
- if db_field.name in self.raw_id_fields:
169
- kwargs["widget"] = UnfoldForeignKeyRawIdWidget(
170
- db_field.remote_field, self.admin_site, using=db
171
- )
172
- elif (
173
- db_field.name not in self.get_autocomplete_fields(request)
174
- and db_field.name not in self.radio_fields
175
- ):
176
- kwargs["widget"] = UnfoldAdminSelectWidget()
177
- kwargs["empty_label"] = _("Select value")
178
-
179
- return super().formfield_for_foreignkey(db_field, request, **kwargs)
180
-
181
- def formfield_for_manytomany(
182
- self,
183
- db_field: ManyToManyField,
184
- request: HttpRequest,
185
- **kwargs,
186
- ) -> ModelMultipleChoiceField:
187
- if "widget" not in kwargs:
188
- if db_field.name in self.raw_id_fields:
189
- kwargs["widget"] = UnfoldAdminTextInputWidget()
190
-
191
- form_field = super().formfield_for_manytomany(db_field, request, **kwargs)
192
-
193
- # If M2M uses intermediary model, form_field will be None
194
- if not form_field:
195
- return None
196
-
197
- if isinstance(form_field.widget, SelectMultiple):
198
- form_field.widget.attrs["class"] = " ".join(SELECT_CLASSES)
199
-
200
- return form_field
201
-
202
- def formfield_for_nullboolean_field(
203
- self, db_field: Field, request: HttpRequest, **kwargs
204
- ) -> Optional[Field]:
205
- if "widget" not in kwargs:
206
- kwargs["widget"] = UnfoldAdminNullBooleanSelectWidget()
207
-
208
- return db_field.formfield(**kwargs)
209
-
210
- def formfield_for_dbfield(
211
- self, db_field: Field, request: HttpRequest, **kwargs
212
- ) -> Optional[Field]:
213
- if isinstance(db_field, models.BooleanField) and db_field.null is True:
214
- return self.formfield_for_nullboolean_field(db_field, request, **kwargs)
215
-
216
- formfield = super().formfield_for_dbfield(db_field, request, **kwargs)
217
-
218
- if formfield and isinstance(formfield.widget, RelatedFieldWidgetWrapper):
219
- formfield.widget.template_name = (
220
- "unfold/widgets/related_widget_wrapper.html"
221
- )
222
-
223
- return formfield
224
-
225
-
226
- class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
38
+ class ModelAdmin(BaseModelAdminMixin, ActionModelAdminMixin, BaseModelAdmin):
227
39
  action_form = ActionForm
228
- actions_list = ()
229
- actions_row = ()
230
- actions_detail = ()
231
- actions_submit_line = ()
232
40
  custom_urls = ()
233
41
  add_fieldsets = ()
234
42
  list_horizontal_scrollbar_top = False
@@ -269,86 +77,6 @@ class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
269
77
  return self.add_fieldsets
270
78
  return super().get_fieldsets(request, obj)
271
79
 
272
- def _filter_unfold_actions_by_permissions(
273
- self,
274
- request: HttpRequest,
275
- actions: list[UnfoldAction],
276
- object_id: Optional[Union[int, str]] = None,
277
- ) -> list[UnfoldAction]:
278
- """Filter out any Unfold actions that the user doesn't have access to."""
279
- filtered_actions = []
280
- for action in actions:
281
- if not hasattr(action.method, "allowed_permissions"):
282
- filtered_actions.append(action)
283
- continue
284
-
285
- permission_checks = (
286
- getattr(self, f"has_{permission}_permission")
287
- for permission in action.method.allowed_permissions
288
- )
289
-
290
- if object_id:
291
- if any(
292
- has_permission(request, object_id)
293
- for has_permission in permission_checks
294
- ):
295
- filtered_actions.append(action)
296
- else:
297
- if any(has_permission(request) for has_permission in permission_checks):
298
- filtered_actions.append(action)
299
-
300
- return filtered_actions
301
-
302
- def get_actions_list(self, request: HttpRequest) -> list[UnfoldAction]:
303
- return self._filter_unfold_actions_by_permissions(
304
- request, self._get_base_actions_list()
305
- )
306
-
307
- def _get_base_actions_list(self) -> list[UnfoldAction]:
308
- """
309
- Returns all available list global actions, prior to any filtering
310
- """
311
- return [self.get_unfold_action(action) for action in self.actions_list or []]
312
-
313
- def get_actions_detail(
314
- self, request: HttpRequest, object_id: int
315
- ) -> list[UnfoldAction]:
316
- return self._filter_unfold_actions_by_permissions(
317
- request, self._get_base_actions_detail(), object_id
318
- )
319
-
320
- def _get_base_actions_detail(self) -> list[UnfoldAction]:
321
- """
322
- Returns all available detail actions, prior to any filtering
323
- """
324
- return [self.get_unfold_action(action) for action in self.actions_detail or []]
325
-
326
- def get_actions_row(self, request: HttpRequest) -> list[UnfoldAction]:
327
- return self._filter_unfold_actions_by_permissions(
328
- request, self._get_base_actions_row()
329
- )
330
-
331
- def _get_base_actions_row(self) -> list[UnfoldAction]:
332
- """
333
- Returns all available row actions, prior to any filtering
334
- """
335
- return [self.get_unfold_action(action) for action in self.actions_row or []]
336
-
337
- def get_actions_submit_line(
338
- self, request: HttpRequest, object_id: int
339
- ) -> list[UnfoldAction]:
340
- return self._filter_unfold_actions_by_permissions(
341
- request, self._get_base_actions_submit_line(), object_id
342
- )
343
-
344
- def _get_base_actions_submit_line(self) -> list[UnfoldAction]:
345
- """
346
- Returns all available submit row actions, prior to any filtering
347
- """
348
- return [
349
- self.get_unfold_action(action) for action in self.actions_submit_line or []
350
- ]
351
-
352
80
  def get_custom_urls(self) -> tuple[tuple[str, str, View], ...]:
353
81
  """
354
82
  Method to get custom views for ModelAdmin with their urls
@@ -416,129 +144,6 @@ class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
416
144
  name=custom_url[1],
417
145
  )
418
146
 
419
- def changeform_view(
420
- self,
421
- request: HttpRequest,
422
- object_id: Optional[str] = None,
423
- form_url: str = "",
424
- extra_context: Optional[dict[str, bool]] = None,
425
- ) -> Any:
426
- if extra_context is None:
427
- extra_context = {}
428
-
429
- actions = []
430
- if object_id:
431
- for action in self.get_actions_detail(request, object_id):
432
- actions.append(
433
- {
434
- "title": action.description,
435
- "attrs": action.method.attrs,
436
- "path": reverse(
437
- f"{self.admin_site.name}:{action.action_name}",
438
- args=(object_id,),
439
- ),
440
- }
441
- )
442
-
443
- extra_context.update(
444
- {
445
- "actions_submit_line": self.get_actions_submit_line(request, object_id),
446
- "actions_detail": actions,
447
- }
448
- )
449
-
450
- return super().changeform_view(request, object_id, form_url, extra_context)
451
-
452
- def changelist_view(
453
- self, request: HttpRequest, extra_context: Optional[dict[str, str]] = None
454
- ) -> TemplateResponse:
455
- if extra_context is None:
456
- extra_context = {}
457
-
458
- actions = [
459
- {
460
- "title": action.description,
461
- "attrs": action.method.attrs,
462
- "path": reverse(f"{self.admin_site.name}:{action.action_name}"),
463
- }
464
- for action in self.get_actions_list(request)
465
- ]
466
-
467
- actions_row = [
468
- {
469
- "title": action.description,
470
- "attrs": action.method.attrs,
471
- "raw_path": f"{self.admin_site.name}:{action.action_name}",
472
- }
473
- for action in self.get_actions_row(request)
474
- ]
475
-
476
- extra_context.update(
477
- {
478
- "actions_list": actions,
479
- "actions_row": actions_row,
480
- }
481
- )
482
-
483
- return super().changelist_view(request, extra_context)
484
-
485
- def get_unfold_action(self, action: str) -> UnfoldAction:
486
- """
487
- Converts action name to UnfoldAction
488
- :param action:
489
- :return:
490
- """
491
- method = self._get_instance_method(action)
492
-
493
- return UnfoldAction(
494
- action_name=f"{self.model._meta.app_label}_{self.model._meta.model_name}_{action}",
495
- method=method,
496
- description=self._get_action_description(method, action),
497
- path=self._get_action_url(method, action),
498
- attrs=method.attrs if hasattr(method, "attrs") else None,
499
- )
500
-
501
- @staticmethod
502
- def _get_action_url(func: Callable, name: str) -> str:
503
- """
504
- Returns action URL if it was specified in @action decorator.
505
- If it was not, name of the action is returned.
506
- :param func:
507
- :param name:
508
- :return:
509
- """
510
- return getattr(func, "url_path", name)
511
-
512
- def save_model(
513
- self, request: HttpRequest, obj: Model, form: Form, change: Any
514
- ) -> None:
515
- super().save_model(request, obj, form, change)
516
-
517
- for action in self.get_actions_submit_line(request, obj.pk):
518
- if action.action_name not in request.POST:
519
- continue
520
-
521
- action.method(request, obj)
522
-
523
- def _get_instance_method(self, method_name: str) -> Callable:
524
- """
525
- Searches for method on self instance based on method_name and returns it if it exists.
526
- If it does not exist or is not callable, it raises UnfoldException
527
- :param method_name: Name of the method to search for
528
- :return: method from self instance
529
- """
530
- try:
531
- method = getattr(self, method_name)
532
- except AttributeError as e:
533
- raise UnfoldException(
534
- f"Method {method_name} specified does not exist on current object"
535
- ) from e
536
-
537
- if not callable(method):
538
- raise UnfoldException(f"{method_name} is not callable")
539
-
540
- return method
541
-
542
147
  def get_action_choices(
543
148
  self, request: HttpRequest, default_choices=BLANK_CHOICE_DASH
544
149
  ):
@@ -564,14 +169,14 @@ class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
564
169
  return res
565
170
 
566
171
 
567
- class TabularInline(ModelAdminMixin, BaseTabularInline):
172
+ class TabularInline(BaseModelAdminMixin, BaseTabularInline):
568
173
  formfield_overrides = FORMFIELD_OVERRIDES_INLINE
569
174
  readonly_preprocess_fields = {}
570
175
  ordering_field = None
571
176
  hide_ordering_field = False
572
177
 
573
178
 
574
- class StackedInline(ModelAdminMixin, BaseStackedInline):
179
+ class StackedInline(BaseModelAdminMixin, BaseStackedInline):
575
180
  formfield_overrides = FORMFIELD_OVERRIDES_INLINE
576
181
  readonly_preprocess_fields = {}
577
182
  ordering_field = None
unfold/dataclasses.py CHANGED
@@ -12,6 +12,7 @@ class UnfoldAction:
12
12
  path: str
13
13
  attrs: Optional[dict] = None
14
14
  object_id: Optional[Union[int, str]] = None
15
+ icon: Optional[str] = None
15
16
 
16
17
 
17
18
  @dataclass
@@ -20,3 +21,10 @@ class Favicon:
20
21
  rel: Optional[str] = None
21
22
  type: Optional[str] = None
22
23
  sizes: Optional[str] = None
24
+
25
+
26
+ @dataclass
27
+ class DropdownItem:
28
+ title: str
29
+ link: Union[str, Callable]
30
+ icon: Optional[str] = None