django-unfold 0.38.0__py3-none-any.whl → 0.40.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,69 @@
1
+ Metadata-Version: 2.1
2
+ Name: django-unfold
3
+ Version: 0.40.0
4
+ Summary: Modern Django admin theme for seamless interface development
5
+ Home-page: https://unfoldadmin.com
6
+ License: MIT
7
+ Keywords: django,admin,tailwind,theme
8
+ Requires-Python: >=3.8
9
+ Classifier: Environment :: Web Environment
10
+ Classifier: Framework :: Django
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Requires-Dist: django (>=3.2)
22
+ Project-URL: Repository, https://github.com/unfoldadmin/django-unfold
23
+ Description-Content-Type: text/markdown
24
+
25
+ ![screenshot](https://github.com/user-attachments/assets/b5ec09b6-c8c7-454a-85af-1bba97593632)
26
+
27
+ ## Unfold Django Admin Theme
28
+
29
+ [![PyPI - Version](https://img.shields.io/pypi/v/django-unfold.svg?style=for-the-badge)](https://pypi.org/project/django-unfold/)
30
+ [![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)
31
+ ![Pre Commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=for-the-badge)
32
+ ![Code Style - Ruff](https://img.shields.io/badge/code%20style-ruff-30173D.svg?style=for-the-badge)
33
+
34
+ Unfold is a theme for Django admin that incorporates common best practices for building full-fledged admin areas. It is designed to work on top of the default administration provided by Django.
35
+
36
+ - **Documentation:** Full docs are available at [unfoldadmin.com](https://unfoldadmin.com?utm_medium=github&utm_source=unfold).
37
+ - **Unfold:** Demo site is available at [unfoldadmin.com](https://unfoldadmin.com?utm_medium=github&utm_source=unfold).
38
+ - **Formula:** Repository with demo implementation at [github.com/unfoldadmin/formula](https://github.com/unfoldadmin/formula?utm_medium=github&utm_source=unfold).
39
+ - **Turbo:** Django & Next.js boilerplate implementing Unfold at [github.com/unfoldadmin/turbo](https://github.com/unfoldadmin/turbo?utm_medium=github&utm_source=unfold).
40
+
41
+ ## Are you using Unfold and need help?
42
+
43
+ Have you decided to start using Unfold but don’t have time to make the switch from the native Django admin? [Get in touch with us](https://unfoldadmin.com/consulting?utm_medium=github&utm_source=unfold) and let’s supercharge your development with our expertise.
44
+
45
+ ## Features
46
+
47
+ - **Visual**: Provides a new user interface based on the Tailwind CSS framework.
48
+ - **Sidebar:** Simplifies the creation of sidebar navigation with icons, collapsibles, and more.
49
+ - **Dark mode:** Supports both light and dark mode versions.
50
+ - **Actions:** Offers multiple ways to define actions within different parts of the admin interface.
51
+ - **Filters:** Custom dropdowns, numeric, datetime, and text fields.
52
+ - **Dashboard:** Includes helpers for creating custom dashboard pages.
53
+ - **Components:** Reusable UI components such as cards, buttons, and charts.
54
+ - **WYSIWYG widget:** Built-in support for WYSIWYG (Trix).
55
+ - **Array widget:** Built-in widget for `django.contrib.postgres.fields.ArrayField`.
56
+ - **Inline tabs:** Groups inlines into tab navigation in the change form.
57
+ - **Model tabs:** Allows defining custom tab navigation for models.
58
+ - **Fieldset tabs:** Merges multiple fieldsets into tabs in the change form.
59
+ - **Environment label**: Distinguishes between environments by displaying a label.
60
+ - **Nonrelated inlines**: Displays nonrelated models as inlines in the change form.
61
+ - **Favicons**: Built-in support for configuring various site favicons.
62
+ - **Colors:** Allows customization of the default color scheme.
63
+ - **Changeform modes:** Displays fields in compressed mode in the change form.
64
+ - **Parallel admin**: Supports [running the default admin](https://unfoldadmin.com/blog/migrating-django-admin-unfold/?utm_medium=github&utm_source=unfold) alongside Unfold.
65
+ - **Third party packages:** Default support for multiple popular applications.
66
+ - **Configuration:** Most basic options can be changed in `settings.py`.
67
+ - **Dependencies:** Fully based on `django.contrib.admin`.
68
+ - **VS Code**: Project configuration and development container included.
69
+
@@ -1,5 +1,5 @@
1
1
  unfold/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- unfold/admin.py,sha256=f9SvMmy5U1IbQq-6M2yTetbe9puZ-B0NMSk2CYI7bCo,19617
2
+ unfold/admin.py,sha256=GgzQctPm2zdCOd4NHfhWXFDtqmY_byvGOkFuKc7ma1Q,19757
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
@@ -34,8 +34,8 @@ unfold/contrib/guardian/templates/admin/guardian/model/field.html,sha256=V9ZgmYi
34
34
  unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage.html,sha256=p7GmZOyDqj1urDAh9pyZhatnSpe-G6gXtQ_DpRK-J8k,1426
35
35
  unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_group.html,sha256=mcm8nqyzz0-cIHQME8QLDNGZUmatwxorvnTAE82tHiU,2322
36
36
  unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_user.html,sha256=mqASTrSSATOCJlG3G7qlWHQbLAMgLKhhKbn1klQAZAE,2320
37
- unfold/contrib/guardian/templates/unfold/guardian/group_form.html,sha256=LwBAUTk7PzFhokjdfxA93xb6U54FIGGwvcrZREwbcps,3925
38
- unfold/contrib/guardian/templates/unfold/guardian/user_form.html,sha256=SJaMowt_92CXS59dKaWoXMOgXAS4rEOJPKlvnf5gOuY,3915
37
+ unfold/contrib/guardian/templates/unfold/guardian/group_form.html,sha256=3W-CCW8eb_VAzjLHtbNYBzlxFP_wofQFl9EMO-E4ivQ,3925
38
+ unfold/contrib/guardian/templates/unfold/guardian/user_form.html,sha256=ZGcndXOn-MeFW3b2bNUr5-PFS85HxLhDoPeR7AIKQ4Q,3914
39
39
  unfold/contrib/import_export/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
40
  unfold/contrib/import_export/apps.py,sha256=SdJu6Qh90VqGWY19FSDhhpUqhTbaIYsJKny3zX5baHI,149
41
41
  unfold/contrib/import_export/forms.py,sha256=kh8psUoTObrRb2wtkYhvRu1D5QM02xWk4UNLQT5lQZA,2038
@@ -68,11 +68,12 @@ unfold/dataclasses.py,sha256=4PAWLVlUhyGMq1Mwai7afCgrRO0Inulw0opFUKVEOBk,483
68
68
  unfold/decorators.py,sha256=jyjE2sunazp8gK1QIsNRJGEEDwO7RDKWDKnOI3GDWZE,3338
69
69
  unfold/exceptions.py,sha256=gcCj1ox61E137bk_0Cqy4YC3SttdPgB-fiJUqpmyHSE,43
70
70
  unfold/fields.py,sha256=yhtpDfqycpOxqdQlbncCg9qhELxGk3AtXizkZyzzH0g,7410
71
- unfold/forms.py,sha256=8-IoFZLwUNJKTNdev_oJwdxm-_xekvrn_84IXKse4Q8,4100
71
+ unfold/forms.py,sha256=raPZVYuZ1UQwo5qv8M6KDdprfCvRgjFnYfLdkmBRojI,4118
72
72
  unfold/settings.py,sha256=8rjtIipRX3IrkmsSy5OrbheGhjmkYvKxueihSRIM9c8,2111
73
- unfold/sites.py,sha256=yr66aXr1UYSZw9I9VRKpmkQxUtUP9336zs1GZJWJqis,13505
73
+ unfold/sites.py,sha256=JXl1-epLzodNp4--lueQZBaZ-X6JfZ0U2U1CEpvAARM,13841
74
+ unfold/static/admin/js/admin/RelatedObjectLookups.js,sha256=alI0-yq7YPDJJJn-yg1ce79-Cv88yQDUrfaGqFZnsaY,9048
74
75
  unfold/static/unfold/css/simplebar.css,sha256=5LLaEM11pKi6JFCOLt4XKuZxTpT9rpdq_tNlaQytFlU,4647
75
- unfold/static/unfold/css/styles.css,sha256=Qfovqaj0HIvTY7IjpOjq8B08_99KX4kXl-lRBiEmBF4,97177
76
+ unfold/static/unfold/css/styles.css,sha256=XeyDlhHiJodpPBgFv9GUnCL4u_FleYXAK2frlP3C334,98178
76
77
  unfold/static/unfold/fonts/inter/Inter-Bold.woff2,sha256=O88EyjAeRPE_QEyKBKpK5wf2epUOEu8wwjj5bnhCZqE,46552
77
78
  unfold/static/unfold/fonts/inter/Inter-Medium.woff2,sha256=O88EyjAeRPE_QEyKBKpK5wf2epUOEu8wwjj5bnhCZqE,46552
78
79
  unfold/static/unfold/fonts/inter/Inter-Regular.woff2,sha256=O88EyjAeRPE_QEyKBKpK5wf2epUOEu8wwjj5bnhCZqE,46552
@@ -83,7 +84,7 @@ unfold/static/unfold/fonts/material-symbols/styles.css,sha256=U0oiGd2DS7LXnbuqgs
83
84
  unfold/static/unfold/js/alpine.anchor.js,sha256=2Jg9aUq749pjFynzr_H1NK3lf-nXrbHMtO9wvlWJyIQ,15524
84
85
  unfold/static/unfold/js/alpine.js,sha256=NY2a-7GrW--i9IBhowd25bzXcH9BCmBrqYX5i8OxwDQ,44659
85
86
  unfold/static/unfold/js/alpine.persist.js,sha256=jFBwr6faTqqhp3sVi4_VTxJ0FpaF9YGZN1ZGLl_5QYM,837
86
- unfold/static/unfold/js/app.js,sha256=wd6GCMh0D_bPYnyVRVI6UeLwlSWOTui-LkByCtQ_1O0,7409
87
+ unfold/static/unfold/js/app.js,sha256=VirG6nw5PPnKMxQqd3eGfmzWH_MaoutOTPxWHfMo7-M,7440
87
88
  unfold/static/unfold/js/chart.js,sha256=22W6cFERR-CElMOKRgMMicueMVP0Vf7FBEBYH8Z8tCk,200633
88
89
  unfold/static/unfold/js/htmx.js,sha256=XOLqvnZiyEx46EW9vaJTBUaaWg8CGVVfXJkVsUmJbpI,42820
89
90
  unfold/static/unfold/js/select2.init.js,sha256=SzpjSL1mD9tHedjSeXlmEwHjuuyqlnpilpSeEEKeYLE,265
@@ -91,26 +92,26 @@ unfold/static/unfold/js/simplebar.js,sha256=t-uG1FAD6ZoiMeN--wac0XRS7SxoDVG6zvRn
91
92
  unfold/styles.css,sha256=7BtTH3fddTLCVDdMqohVzUXePMY_xoMaufLJbyOtY7M,18226
92
93
  unfold/templates/admin/actions.html,sha256=8GqELAxzywGyfQJiQeuhGIQUakdK_WeneILY06C7mEo,2746
93
94
  unfold/templates/admin/app_index.html,sha256=lVjMIFsspHQ09LGHKfdfg7TlqlL39AX5LbwoeoZjFhk,1335
94
- unfold/templates/admin/app_list.html,sha256=aXlMo2OONF-eoTBg5kdYip8vFsu8Rb88EQhXPZEL0w8,2766
95
+ unfold/templates/admin/app_list.html,sha256=avKk-Dj_4GY_izBKWsb_yNHxUyVpdRbDBZuoxlIEdN0,2817
95
96
  unfold/templates/admin/auth/user/add_form.html,sha256=L0ylU7BLEIoZc3nS4Rq6DA0H94HQ3aXFr-iwK0vp2Bs,482
96
97
  unfold/templates/admin/auth/user/change_password.html,sha256=-Wa9ml3yss-kDz0YQxCiwoxs91KQD8eetCt5l6xekWM,2892
97
98
  unfold/templates/admin/base.html,sha256=RptiEpgXZ6seYrPu-1Stoe8ECHpi9PjkJjFheGecYF0,2358
98
99
  unfold/templates/admin/base_site.html,sha256=FM8OOaXsQqJlYapeJvBznXCOt7poMiWrweTWcsMHzeI,365
99
- unfold/templates/admin/change_form.html,sha256=QwDlOhZZOspy5crZu_8e7CtM7FEUtfnQt3cINRbZBtQ,4620
100
+ unfold/templates/admin/change_form.html,sha256=2EKrHLNmKdg2cTmHW9N9Om6zpOxBDIzkOb6UoMKtr6U,4981
100
101
  unfold/templates/admin/change_form_object_tools.html,sha256=eyeH-i2HgEM0Yi-OJA2D1VnKJyC19A_my1IDGxxoP8Y,593
101
- unfold/templates/admin/change_list.html,sha256=DzC236cq-B8uXnb6F4bzgcQFwMLI3uKOIHNyjVtCKA0,4977
102
+ unfold/templates/admin/change_list.html,sha256=yuiCYvLa46ZBmhT-gHxQ3tHH-meiJCAEeOhVvsnexyU,5295
102
103
  unfold/templates/admin/change_list_object_tools.html,sha256=cmMiT2nT20Ph5yfpj9aHPr76Z-JP4aSXp0o-Rnad28s,147
103
104
  unfold/templates/admin/change_list_results.html,sha256=Nb-NbQYalqGJ9TRW4B1RZrS2ePhbyWtXi_K-tBn1DQs,5502
104
105
  unfold/templates/admin/date_hierarchy.html,sha256=BfUPbsLpHZVa40BHBahz1H9RSVuz36Jc3yrlobOiIpw,1306
105
106
  unfold/templates/admin/delete_confirmation.html,sha256=pg2Ei9ebjmsoM_GqaK0uasuGFYsoiFeoxsxGrOYk3kE,5082
106
107
  unfold/templates/admin/delete_selected_confirmation.html,sha256=MRh1_fC5deThAb42kyoMu3t-o9OiF7UDAbRgun23KFQ,4865
107
- unfold/templates/admin/edit_inline/stacked.html,sha256=K6sA05tN1r-6FyOePo15SF2WBbuOaA6_3FAoptWJCdk,7127
108
- unfold/templates/admin/edit_inline/tabular.html,sha256=vch3ZQ7UVw6-6roX8EYbolk8ua98uIwNQx4-FjzE3tM,14195
108
+ unfold/templates/admin/edit_inline/stacked.html,sha256=cdj3JsLoZPL92NMeqKw2IIIxKQF_BFwMsuJnRaygUfk,6638
109
+ unfold/templates/admin/edit_inline/tabular.html,sha256=o1FQcVuoxJow-TE_1BjnnZdeqRiEEM3TcUesEEjnM14,12841
109
110
  unfold/templates/admin/filter.html,sha256=wgYuP_ZmTofljsavLe4t8djAZnXRfEmz5U40SI-2Hjc,1640
110
111
  unfold/templates/admin/includes/fieldset.html,sha256=eyo5icIIrihKBLT7k3FCgoVhK55Lb78eIp7tIUNXLvU,975
111
112
  unfold/templates/admin/includes/object_delete_summary.html,sha256=w_lPVnBrng0oqvFy6rzvr8c5bzdU8GpqvYUAvaXza0c,444
112
113
  unfold/templates/admin/index.html,sha256=pkGdKWdD3zzOvkRdELvdb15sleSpfl4eHPA14PAh7z0,684
113
- unfold/templates/admin/login.html,sha256=8qSiSCjZy1D76Z0HocQvDzSA6xfXjqsZoM9Bbg4Ebas,4108
114
+ unfold/templates/admin/login.html,sha256=Xw50tZZxESEwz9YOcV1CSPeyyI2H1l0q4x4zBrC4dps,4170
114
115
  unfold/templates/admin/nav_sidebar.html,sha256=31g1dbGHe7m_8GXFrehLqWD84Nx7jEoeLhyNBZbaqRM,1163
115
116
  unfold/templates/admin/object_history.html,sha256=mRD6nbIVmnsz2OG6UsuXdI55bflY3Vs8zbIE9X-Oi74,4816
116
117
  unfold/templates/admin/pagination.html,sha256=KrwOM6gRizfpTMP59TESQaNGcQzkga2k02opl_7BxLo,1130
@@ -121,23 +122,23 @@ unfold/templates/registration/logged_out.html,sha256=cg4YjMbVtuMdOJD3hkkJZntv0G8
121
122
  unfold/templates/registration/password_change_done.html,sha256=bTZHs1KsGcRgT6WI5exF4ANFEXatgI24rIeENxJZl6Q,1070
122
123
  unfold/templates/registration/password_change_form.html,sha256=zzmqFHfMExS7czgEBdEtetXk2Ae_7jzaID7CpE_xlgo,2225
123
124
  unfold/templates/unfold/change_list_filter.html,sha256=9wP_sZF8uKmJhegPC3Z-7ZZXLwVIYDrjzzgTNxLuFbc,3344
124
- unfold/templates/unfold/components/button.html,sha256=M_kn5fJPadvBfTUdN56-LMkouvfpUMga7orRJoVifgc,178
125
- unfold/templates/unfold/components/card.html,sha256=3DtjaeMvLNUBzbnnUn7zU15V2X_k2HBdxSV55S31IoA,1086
125
+ unfold/templates/unfold/components/button.html,sha256=E9B5Y0ZzUrFmw50tK8Fi8-V0V6-ONbWcBMuyoxaDjSI,476
126
+ unfold/templates/unfold/components/card.html,sha256=3HHpqXOYS1b9yPa-ipbsWHBJUuI4knmk2od5B9kMcIs,1087
126
127
  unfold/templates/unfold/components/chart/bar.html,sha256=nDdDCUXvFd793OpNgNPQo0vBmvYngMsQyPHVTf1vfOw,297
127
128
  unfold/templates/unfold/components/chart/line.html,sha256=DHaZUFTlCs5cX7ioYdTzoN4ITHWNcEoNSUp7v3hlo_E,298
128
129
  unfold/templates/unfold/components/container.html,sha256=WBk1rGHnTsObi3pJCpL3dfFAUQ2abDmokU0Lee7R9CI,83
129
130
  unfold/templates/unfold/components/flex.html,sha256=_hBsQLJ6-7MfEfXNp5Wy-ykNtPMnnNg_io2IMGegn3s,134
130
131
  unfold/templates/unfold/components/icon.html,sha256=Qlhcf41FOwNVJ-nvA0RJ_Dn6JW6PUFXO6tOR3Xtf6e8,99
131
132
  unfold/templates/unfold/components/navigation.html,sha256=KNDfB6oT_dgbD8Fy09oRt6Txw7ERHa9iv-TPyFEo2wg,892
132
- unfold/templates/unfold/components/progress.html,sha256=Dh8Q5rW5oVoRFrAxCcTbId3qI_SeA7V5V8KsmLgA8Bc,631
133
- unfold/templates/unfold/components/separator.html,sha256=IuQQu_kMgZY9D9w9-l6PHuVF0cX0bJuxcJHGXWswEhU,168
133
+ unfold/templates/unfold/components/progress.html,sha256=BnH2uC_ZL5ITvMjHiHoM0gjtbRTLNU1-tFcfCuJO0BE,808
134
+ unfold/templates/unfold/components/separator.html,sha256=TlKnvg1h3diDvm9A_FWc2atflAxb4e3HtnxYTxR75SU,168
134
135
  unfold/templates/unfold/components/table.html,sha256=iC7mhLlPLRgCaV0sVjEkxIR7A4NLV2-rSHqEbmwpaHw,2028
135
136
  unfold/templates/unfold/components/text.html,sha256=-GjxvdiaBQIaNfPSzT6SSIwnc3R27FkSDQMoF3FDPso,102
136
137
  unfold/templates/unfold/components/title.html,sha256=aMYSO30ybFYqPnqwlJVh2uNmcgnVrRycZkHTjJTjpX8,171
137
138
  unfold/templates/unfold/helpers/account_links.html,sha256=Vd80PCa91D5v3gJJKKuEJ7ycL80n54XJEo-039ELsRU,1879
138
139
  unfold/templates/unfold/helpers/actions_row.html,sha256=d80pwsZ80J1pvlBMDj9dOfC-ybZ6KV9bNGsv4Yny8ZQ,1819
139
140
  unfold/templates/unfold/helpers/add_link.html,sha256=CUFZ3quvp1JMAPmTkFRjpUXSy9GVXnIWNcRzv_gpq1A,810
140
- unfold/templates/unfold/helpers/app_list.html,sha256=mhjpBMPh13VcMKn_vg_WyCnvt6c2CJBnIvEU2bjTaog,5456
141
+ unfold/templates/unfold/helpers/app_list.html,sha256=zeMeiSK-vlxrgP-jDkIN1k1mYCog033ybBSxw5POWvs,5516
141
142
  unfold/templates/unfold/helpers/app_list_default.html,sha256=T8xXI0LsEZCyttxXZGomGMkqEd8Wxj61RCYQ4YDxlHE,3908
142
143
  unfold/templates/unfold/helpers/attrs.html,sha256=Mwpj72kuwYj8hOT3J2T8qx6f1r_4xwwaS1slHA-82jI,166
143
144
  unfold/templates/unfold/helpers/boolean.html,sha256=6gXAMEJcNVB14je9uN32dxImXP3KMCiWPeKU5EAnawA,569
@@ -171,7 +172,7 @@ unfold/templates/unfold/helpers/tab_action.html,sha256=Hq20p5yRWi-wPwkFVI5uekoAF
171
172
  unfold/templates/unfold/helpers/tab_list.html,sha256=b84OUC12G1VpiL9jD8KWU2Xtvs5A8MZRxamjVTqGv2c,4497
172
173
  unfold/templates/unfold/helpers/theme_switch.html,sha256=21r_6v7rdpv1favvw5-5uwkRMUWwsAxcghdqDP38HYw,2212
173
174
  unfold/templates/unfold/helpers/userlinks.html,sha256=qWjtBt9Q_tU8a874ii0Qqg8t_d-SSYBTB_3QZfNlx9g,634
174
- unfold/templates/unfold/helpers/welcomemsg.html,sha256=RVIIwl9GXmdWauy1Q6vaiyPGGRpc3QWSZ07VLbb7mfM,1201
175
+ unfold/templates/unfold/helpers/welcomemsg.html,sha256=u-svEbNA-lg_hYM2ZGWJWuffSjkDNyyu8qa0O-5Q0yE,1263
175
176
  unfold/templates/unfold/layouts/base.html,sha256=bAXZDbyiyxNiE-49mqr7pHUFhC2mHZQzIDUY-js_yZ0,379
176
177
  unfold/templates/unfold/layouts/base_simple.html,sha256=T37-ncKZBLNnOlsUQq90rGl3sLdRjFaxWCPG399pkbE,1179
177
178
  unfold/templates/unfold/layouts/skeleton.html,sha256=J8ch2DbrGLeKdggdtjpuVG1m7VwcJoLjfBKROsOESKI,4068
@@ -197,7 +198,7 @@ unfold/typing.py,sha256=1P8PWM2oeaceUJtA5j071RbKEBpHYaux441u7Hd6wv4,643
197
198
  unfold/utils.py,sha256=hs16AzAEtKb_tdTpLXuz3NqYhtP5JipSc_lkv5ULbbY,4921
198
199
  unfold/views.py,sha256=hQCyeeMa9kcJV1IZeeYqj8PGW7J4QWME8n-5n0UGmiU,1003
199
200
  unfold/widgets.py,sha256=HXihQ-dCvlN8uvHq1B1tzJlyBt4x800k4_5X14eiqKg,17086
200
- django_unfold-0.38.0.dist-info/LICENSE.md,sha256=Ltk_quRyyvV3J5v3brtOqmibeZSw2Hrb8bY1W3ya0Ik,1077
201
- django_unfold-0.38.0.dist-info/METADATA,sha256=2HyXZMTymQnAUT4peiuT7BepCLzeUs08kL7JjVSxFCo,4685
202
- django_unfold-0.38.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
203
- django_unfold-0.38.0.dist-info/RECORD,,
201
+ django_unfold-0.40.0.dist-info/LICENSE.md,sha256=Ltk_quRyyvV3J5v3brtOqmibeZSw2Hrb8bY1W3ya0Ik,1077
202
+ django_unfold-0.40.0.dist-info/METADATA,sha256=k65GzN27Ixtkay0eI2PMLrT00g23rxQh3SMbTok8APQ,4487
203
+ django_unfold-0.40.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
204
+ django_unfold-0.40.0.dist-info/RECORD,,
unfold/admin.py CHANGED
@@ -235,6 +235,10 @@ class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
235
235
  list_filter_submit = False
236
236
  list_fullwidth = False
237
237
  list_disable_select_all = False
238
+ list_before_template = None
239
+ list_after_template = None
240
+ change_form_before_template = None
241
+ change_form_after_template = None
238
242
  compressed_fields = False
239
243
  readonly_preprocess_fields = {}
240
244
  warn_unsaved_form = False
@@ -39,9 +39,9 @@
39
39
  {% for perm in model_perms %}
40
40
  <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto first:border-t-0 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{{ perm.name }}">
41
41
  {% if perm.codename in group_perms %}
42
- {% include "unfold/helpers/boolean.html" with value=False %}
43
- {% else %}
44
42
  {% include "unfold/helpers/boolean.html" with value=True %}
43
+ {% else %}
44
+ {% include "unfold/helpers/boolean.html" with value=False %}
45
45
  {% endif %}
46
46
  </td>
47
47
  {% endfor %}
@@ -38,10 +38,10 @@
38
38
 
39
39
  {% for perm in model_perms %}
40
40
  <td class="align-middle flex border-t border-gray-200 font-normal px-3 py-2 text-left before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto first:border-t-0 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-gray-800" data-label="{{ perm.name }}">
41
- {% if perm.codename in group_perms %}
42
- {% include "unfold/helpers/boolean.html" with value=False %}
43
- {% else %}
41
+ {% if perm.codename in user_perms %}
44
42
  {% include "unfold/helpers/boolean.html" with value=True %}
43
+ {% else %}
44
+ {% include "unfold/helpers/boolean.html" with value=False %}
45
45
  {% endif %}
46
46
  </td>
47
47
  {% endfor %}
unfold/forms.py CHANGED
@@ -10,9 +10,14 @@ from django.contrib.admin.forms import (
10
10
  from django.contrib.auth.forms import (
11
11
  AdminPasswordChangeForm as BaseAdminPasswordChangeForm,
12
12
  )
13
+ from django.contrib.auth.models import User
14
+
15
+ try:
16
+ from django.contrib.auth.forms import AdminUserCreationForm as BaseUserCreationForm
17
+ except ImportError:
18
+ from django.contrib.auth.forms import UserCreationForm as BaseUserCreationForm
13
19
  from django.contrib.auth.forms import ReadOnlyPasswordHashWidget
14
20
  from django.contrib.auth.forms import UserChangeForm as BaseUserChangeForm
15
- from django.contrib.auth.forms import UserCreationForm as BaseUserCreationForm
16
21
  from django.http import HttpRequest
17
22
  from django.utils.safestring import mark_safe
18
23
  from django.utils.translation import gettext_lazy as _
@@ -66,11 +71,10 @@ class AuthenticationForm(AdminAuthenticationForm):
66
71
  class UserCreationForm(BaseUserCreationForm):
67
72
  def __init__(
68
73
  self,
69
- request: Optional[HttpRequest] = None,
70
74
  *args,
71
75
  **kwargs,
72
76
  ) -> None:
73
- super().__init__(request, *args, **kwargs)
77
+ super().__init__(*args, **kwargs)
74
78
 
75
79
  self.fields["password1"].widget = UnfoldAdminPasswordInput(
76
80
  attrs={"autocomplete": "new-password"}
@@ -88,11 +92,10 @@ class UserCreationForm(BaseUserCreationForm):
88
92
  class UserChangeForm(BaseUserChangeForm):
89
93
  def __init__(
90
94
  self,
91
- request: Optional[HttpRequest] = None,
92
95
  *args,
93
96
  **kwargs,
94
97
  ) -> None:
95
- super().__init__(request, *args, **kwargs)
98
+ super().__init__(*args, **kwargs)
96
99
  self.fields["password"].widget = UnfoldReadOnlyPasswordHashWidget()
97
100
 
98
101
  self.fields["password"].help_text = _(
@@ -109,19 +112,19 @@ class UserChangeForm(BaseUserChangeForm):
109
112
  class AdminPasswordChangeForm(BaseAdminPasswordChangeForm):
110
113
  def __init__(
111
114
  self,
112
- request: Optional[HttpRequest] = None,
115
+ user: User,
113
116
  *args,
114
117
  **kwargs,
115
118
  ) -> None:
116
- super().__init__(request, *args, **kwargs)
119
+ super().__init__(user, *args, **kwargs)
117
120
 
118
121
  self.fields["password1"].widget.attrs["class"] = " ".join(INPUT_CLASSES)
119
122
  self.fields["password2"].widget.attrs["class"] = " ".join(INPUT_CLASSES)
120
123
 
121
124
 
122
125
  class AdminOwnPasswordChangeForm(BaseAdminOwnPasswordChangeForm):
123
- def __init__(self, *args, **kwargs) -> None:
124
- super().__init__(kwargs.pop("user"), *args, **kwargs)
126
+ def __init__(self, user: User, *args, **kwargs) -> None:
127
+ super().__init__(user, *args, **kwargs)
125
128
 
126
129
  self.fields["old_password"].widget.attrs["class"] = " ".join(INPUT_CLASSES)
127
130
  self.fields["new_password1"].widget.attrs["class"] = " ".join(INPUT_CLASSES)
unfold/sites.py CHANGED
@@ -7,8 +7,18 @@ from django.core.validators import EMPTY_VALUES
7
7
  from django.http import HttpRequest, HttpResponse
8
8
  from django.template.response import TemplateResponse
9
9
  from django.urls import URLPattern, path, reverse, reverse_lazy
10
+ from django.utils.decorators import method_decorator
10
11
  from django.utils.functional import lazy
11
12
  from django.utils.module_loading import import_string
13
+ from django.views.decorators.cache import never_cache
14
+
15
+ try:
16
+ from django.contrib.auth.decorators import login_not_required
17
+ except ImportError:
18
+
19
+ def login_not_required(func: Callable) -> Callable:
20
+ return func
21
+
12
22
 
13
23
  from .dataclasses import Favicon
14
24
  from .settings import get_config
@@ -178,6 +188,8 @@ class UnfoldAdminSite(AdminSite):
178
188
  },
179
189
  )
180
190
 
191
+ @method_decorator(never_cache)
192
+ @login_not_required
181
193
  def login(
182
194
  self, request: HttpRequest, extra_context: Optional[Dict[str, Any]] = None
183
195
  ) -> HttpResponse:
@@ -0,0 +1,295 @@
1
+ /*global SelectBox, interpolate*/
2
+ // Handles related-objects functionality: lookup link for raw_id_fields
3
+ // and Add Another links.
4
+ "use strict";
5
+ {
6
+ const $ = django.jQuery;
7
+ let popupIndex = 0;
8
+ const relatedWindows = [];
9
+
10
+ function dismissChildPopups() {
11
+ relatedWindows.forEach(function (win) {
12
+ if (!win.closed) {
13
+ win.dismissChildPopups();
14
+ win.close();
15
+ }
16
+ });
17
+ }
18
+
19
+ function setPopupIndex() {
20
+ if (document.getElementsByName("_popup").length > 0) {
21
+ const index = window.name.lastIndexOf("__") + 2;
22
+ popupIndex = parseInt(window.name.substring(index));
23
+ } else {
24
+ popupIndex = 0;
25
+ }
26
+ }
27
+
28
+ function addPopupIndex(name) {
29
+ return name + "__" + (popupIndex + 1);
30
+ }
31
+
32
+ function removePopupIndex(name) {
33
+ return name.replace(new RegExp("__" + (popupIndex + 1) + "$"), "");
34
+ }
35
+
36
+ function showAdminPopup(triggeringLink, name_regexp, add_popup) {
37
+ const name = addPopupIndex(triggeringLink.id.replace(name_regexp, ""));
38
+ const href = new URL(triggeringLink.href);
39
+ if (add_popup) {
40
+ href.searchParams.set("_popup", 1);
41
+ }
42
+ const win = window.open(
43
+ href,
44
+ name,
45
+ "height=768,width=1024,resizable=yes,scrollbars=yes"
46
+ );
47
+ relatedWindows.push(win);
48
+ win.focus();
49
+ return false;
50
+ }
51
+
52
+ function showRelatedObjectLookupPopup(triggeringLink) {
53
+ return showAdminPopup(triggeringLink, /^lookup_/, true);
54
+ }
55
+
56
+ function dismissRelatedLookupPopup(win, chosenId) {
57
+ const name = removePopupIndex(win.name);
58
+ const elem = document.getElementById(name);
59
+ if (elem.classList.contains("vManyToManyRawIdAdminField") && elem.value) {
60
+ elem.value += "," + chosenId;
61
+ } else {
62
+ document.getElementById(name).value = chosenId;
63
+ }
64
+ const index = relatedWindows.indexOf(win);
65
+ if (index > -1) {
66
+ relatedWindows.splice(index, 1);
67
+ }
68
+ win.close();
69
+ }
70
+
71
+ function showRelatedObjectPopup(triggeringLink) {
72
+ return showAdminPopup(triggeringLink, /^(change|add|delete)_/, false);
73
+ }
74
+
75
+ function updateRelatedObjectLinks(triggeringLink) {
76
+ const $this = $(triggeringLink);
77
+ const siblings = $this.nextAll(
78
+ ".view-related, .change-related, .delete-related"
79
+ );
80
+ if (!siblings.length) {
81
+ return;
82
+ }
83
+ const value = $this.val();
84
+ if (value) {
85
+ siblings.each(function () {
86
+ const elm = $(this);
87
+ elm.attr(
88
+ "href",
89
+ elm.attr("data-href-template").replace("__fk__", value)
90
+ );
91
+ elm.removeAttr("aria-disabled");
92
+ });
93
+ } else {
94
+ siblings.removeAttr("href");
95
+ siblings.attr("aria-disabled", true);
96
+ }
97
+ }
98
+
99
+ function updateRelatedSelectsOptions(
100
+ currentSelect,
101
+ win,
102
+ objId,
103
+ newRepr,
104
+ newId,
105
+ skipIds = []
106
+ ) {
107
+ // After create/edit a model from the options next to the current
108
+ // select (+ or :pencil:) update ForeignKey PK of the rest of selects
109
+ // in the page.
110
+
111
+ const path = win.location.pathname;
112
+ // Extract the model from the popup url '.../<model>/add/' or
113
+ // '.../<model>/<id>/change/' depending the action (add or change).
114
+ const modelName = path.split("/")[path.split("/").length - (objId ? 4 : 3)];
115
+ // Select elements with a specific model reference and context of "available-source".
116
+ const selectsRelated = document.querySelectorAll(
117
+ `[data-model-ref="${modelName}"] [data-context="available-source"]`
118
+ );
119
+
120
+ selectsRelated.forEach(function (select) {
121
+ if (
122
+ currentSelect === select ||
123
+ (skipIds && skipIds.includes(select.id))
124
+ ) {
125
+ return;
126
+ }
127
+
128
+ let option = select.querySelector(`option[value="${objId}"]`);
129
+
130
+ if (!option) {
131
+ option = new Option(newRepr, newId);
132
+ select.options.add(option);
133
+ // Update SelectBox cache for related fields.
134
+ if (
135
+ window.SelectBox !== undefined &&
136
+ !SelectBox.cache[currentSelect.id]
137
+ ) {
138
+ SelectBox.add_to_cache(select.id, option);
139
+ SelectBox.redisplay(select.id);
140
+ }
141
+ return;
142
+ }
143
+
144
+ option.textContent = newRepr;
145
+ option.value = newId;
146
+ });
147
+ }
148
+
149
+ function dismissAddRelatedObjectPopup(win, newId, newRepr) {
150
+ const name = removePopupIndex(win.name);
151
+ const elem = document.getElementById(name);
152
+ if (elem) {
153
+ const elemName = elem.nodeName.toUpperCase();
154
+ if (elemName === "SELECT") {
155
+ elem.options[elem.options.length] = new Option(
156
+ newRepr,
157
+ newId,
158
+ true,
159
+ true
160
+ );
161
+ updateRelatedSelectsOptions(elem, win, null, newRepr, newId);
162
+ } else if (elemName === "INPUT") {
163
+ if (
164
+ elem.classList.contains("vManyToManyRawIdAdminField") &&
165
+ elem.value
166
+ ) {
167
+ elem.value += "," + newId;
168
+ } else {
169
+ elem.value = newId;
170
+ }
171
+ }
172
+ // Trigger a change event to update related links if required.
173
+ $(elem).trigger("change");
174
+ } else {
175
+ const toId = name + "_to";
176
+ const toElem = document.getElementById(toId);
177
+ const o = new Option(newRepr, newId);
178
+ SelectBox.add_to_cache(toId, o);
179
+ SelectBox.redisplay(toId);
180
+ if (toElem && toElem.nodeName.toUpperCase() === "SELECT") {
181
+ const skipIds = [name + "_from"];
182
+ updateRelatedSelectsOptions(toElem, win, null, newRepr, newId, skipIds);
183
+ }
184
+ }
185
+ const index = relatedWindows.indexOf(win);
186
+ if (index > -1) {
187
+ relatedWindows.splice(index, 1);
188
+ }
189
+ win.close();
190
+ }
191
+
192
+ function dismissChangeRelatedObjectPopup(win, objId, newRepr, newId) {
193
+ const id = removePopupIndex(win.name.replace(/^edit_/, ""));
194
+ const selectsSelector = interpolate("#%s, #%s_from, #%s_to", [id, id, id]);
195
+ const selects = $(selectsSelector);
196
+ selects
197
+ .find("option")
198
+ .each(function () {
199
+ if (this.value === objId) {
200
+ this.textContent = newRepr;
201
+ this.value = newId;
202
+ }
203
+ })
204
+ .trigger("change");
205
+ updateRelatedSelectsOptions(selects[0], win, objId, newRepr, newId);
206
+ selects
207
+ .next()
208
+ .find(".select2-selection__rendered")
209
+ .each(function () {
210
+ // The element can have a clear button as a child.
211
+ // Use the lastChild to modify only the displayed value.
212
+ this.lastChild.textContent = newRepr;
213
+ this.title = newRepr;
214
+ });
215
+ const index = relatedWindows.indexOf(win);
216
+ if (index > -1) {
217
+ relatedWindows.splice(index, 1);
218
+ }
219
+ win.close();
220
+ }
221
+
222
+ function dismissDeleteRelatedObjectPopup(win, objId) {
223
+ const id = removePopupIndex(win.name.replace(/^delete_/, ""));
224
+ const selectsSelector = interpolate("#%s, #%s_from, #%s_to", [id, id, id]);
225
+ const selects = $(selectsSelector);
226
+ selects
227
+ .find("option")
228
+ .each(function () {
229
+ if (this.value === objId) {
230
+ $(this).remove();
231
+ }
232
+ })
233
+ .trigger("change");
234
+ const index = relatedWindows.indexOf(win);
235
+ if (index > -1) {
236
+ relatedWindows.splice(index, 1);
237
+ }
238
+ win.close();
239
+ }
240
+
241
+ window.showRelatedObjectLookupPopup = showRelatedObjectLookupPopup;
242
+ window.dismissRelatedLookupPopup = dismissRelatedLookupPopup;
243
+ window.showRelatedObjectPopup = showRelatedObjectPopup;
244
+ window.updateRelatedObjectLinks = updateRelatedObjectLinks;
245
+ window.dismissAddRelatedObjectPopup = dismissAddRelatedObjectPopup;
246
+ window.dismissChangeRelatedObjectPopup = dismissChangeRelatedObjectPopup;
247
+ window.dismissDeleteRelatedObjectPopup = dismissDeleteRelatedObjectPopup;
248
+ window.dismissChildPopups = dismissChildPopups;
249
+
250
+ // Kept for backward compatibility
251
+ window.showAddAnotherPopup = showRelatedObjectPopup;
252
+ window.dismissAddAnotherPopup = dismissAddRelatedObjectPopup;
253
+
254
+ window.addEventListener("unload", function (evt) {
255
+ window.dismissChildPopups();
256
+ });
257
+
258
+ $(document).ready(function () {
259
+ setPopupIndex();
260
+ $("a[data-popup-opener]").on("click", function (event) {
261
+ event.preventDefault();
262
+ opener.dismissRelatedLookupPopup(window, $(this).data("popup-opener"));
263
+ });
264
+ $("body").on(
265
+ "click",
266
+ '.related-widget-wrapper-link[data-popup="yes"]',
267
+ function (e) {
268
+ e.preventDefault();
269
+ if (this.href) {
270
+ const event = $.Event("django:show-related", { href: this.href });
271
+ $(this).trigger(event);
272
+ if (!event.isDefaultPrevented()) {
273
+ showRelatedObjectPopup(this);
274
+ }
275
+ }
276
+ }
277
+ );
278
+ $("body").on("change", ".related-widget-wrapper select", function (e) {
279
+ const event = $.Event("django:update-related");
280
+ $(this).trigger(event);
281
+ if (!event.isDefaultPrevented()) {
282
+ updateRelatedObjectLinks(this);
283
+ }
284
+ });
285
+ $(".related-widget-wrapper select").trigger("change");
286
+ $("body").on("click", ".related-lookup", function (e) {
287
+ e.preventDefault();
288
+ const event = $.Event("django:lookup-related");
289
+ $(this).trigger(event);
290
+ if (!event.isDefaultPrevented()) {
291
+ showRelatedObjectLookupPopup(this);
292
+ }
293
+ });
294
+ });
295
+ }