django-unfold 0.47.0__py3-none-any.whl → 0.49.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.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