django-unfold 0.22.0__tar.gz → 0.24.0__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (186) hide show
  1. {django_unfold-0.22.0 → django_unfold-0.24.0}/PKG-INFO +102 -4
  2. {django_unfold-0.22.0 → django_unfold-0.24.0}/README.md +101 -3
  3. {django_unfold-0.22.0 → django_unfold-0.24.0}/pyproject.toml +4 -2
  4. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/admin.py +29 -22
  5. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/apps.py +5 -0
  6. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/checks.py +1 -1
  7. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/filters/admin.py +116 -0
  8. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/filters/forms.py +29 -1
  9. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/filters/templates/unfold/filters/filters_date_range.html +1 -1
  10. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/filters/templates/unfold/filters/filters_datetime_range.html +1 -1
  11. django_unfold-0.24.0/src/unfold/contrib/filters/templates/unfold/filters/filters_field.html +7 -0
  12. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/filters/templates/unfold/filters/filters_numeric_range.html +1 -1
  13. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/filters/templates/unfold/filters/filters_numeric_single.html +1 -1
  14. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/filters/templates/unfold/filters/filters_numeric_slider.html +1 -1
  15. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/import_export/admin.py +1 -1
  16. django_unfold-0.24.0/src/unfold/contrib/import_export/forms.py +15 -0
  17. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/import_export/templates/admin/import_export/export.html +1 -1
  18. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/import_export/templates/admin/import_export/import_form.html +2 -2
  19. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/forms.py +6 -1
  20. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/sites.py +3 -3
  21. django_unfold-0.24.0/src/unfold/static/unfold/css/styles.css +1 -0
  22. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/styles.css +11 -4
  23. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/actions.html +9 -9
  24. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/app_list.html +6 -8
  25. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/base.html +1 -3
  26. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/change_form.html +1 -2
  27. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/change_list.html +2 -2
  28. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/change_list_results.html +15 -3
  29. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/edit_inline/tabular.html +3 -3
  30. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/filter.html +2 -2
  31. django_unfold-0.24.0/src/unfold/templates/admin/search_form.html +17 -0
  32. django_unfold-0.24.0/src/unfold/templates/unfold/helpers/display_header.html +20 -0
  33. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/tab_list.html +1 -1
  34. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/layouts/base_simple.html +1 -1
  35. django_unfold-0.24.0/src/unfold/templates/unfold/widgets/date.html +3 -0
  36. {django_unfold-0.22.0/src/unfold/templates/admin → django_unfold-0.24.0/src/unfold/templates/unfold}/widgets/related_widget_wrapper.html +4 -4
  37. django_unfold-0.24.0/src/unfold/templates/unfold/widgets/time.html +3 -0
  38. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templatetags/unfold_list.py +8 -6
  39. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/widgets.py +18 -5
  40. django_unfold-0.22.0/src/unfold/contrib/import_export/forms.py +0 -16
  41. django_unfold-0.22.0/src/unfold/static/unfold/css/styles.css +0 -1
  42. django_unfold-0.22.0/src/unfold/templates/admin/search_form.html +0 -19
  43. django_unfold-0.22.0/src/unfold/templates/unfold/helpers/display_header.html +0 -17
  44. django_unfold-0.22.0/src/unfold/templates/unfold/widgets/date.html +0 -3
  45. django_unfold-0.22.0/src/unfold/templates/unfold/widgets/time.html +0 -3
  46. {django_unfold-0.22.0 → django_unfold-0.24.0}/LICENSE.md +0 -0
  47. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/__init__.py +0 -0
  48. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/__init__.py +0 -0
  49. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/filters/__init__.py +0 -0
  50. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/filters/apps.py +0 -0
  51. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/filters/static/unfold/filters/css/nouislider.min.css +0 -0
  52. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/filters/static/unfold/filters/js/DateTimeShortcuts.js +0 -0
  53. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/filters/static/unfold/filters/js/admin-numeric-filter.js +0 -0
  54. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/filters/static/unfold/filters/js/nouislider.min.js +0 -0
  55. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/filters/static/unfold/filters/js/wNumb.min.js +0 -0
  56. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/forms/__init__.py +0 -0
  57. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/forms/apps.py +0 -0
  58. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/forms/static/unfold/forms/css/trix.css +0 -0
  59. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/forms/static/unfold/forms/js/trix.config.js +0 -0
  60. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/forms/static/unfold/forms/js/trix.js +0 -0
  61. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html +0 -0
  62. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/forms/templates/unfold/forms/wysiwyg.html +0 -0
  63. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/forms/widgets.py +0 -0
  64. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/guardian/__init__.py +0 -0
  65. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/guardian/apps.py +0 -0
  66. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/guardian/templates/admin/guardian/model/change_form.html +0 -0
  67. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/guardian/templates/admin/guardian/model/field.html +0 -0
  68. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage.html +0 -0
  69. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_group.html +0 -0
  70. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_user.html +0 -0
  71. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/guardian/templates/unfold/guardian/group_form.html +0 -0
  72. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/guardian/templates/unfold/guardian/user_form.html +0 -0
  73. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/import_export/__init__.py +0 -0
  74. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/import_export/apps.py +0 -0
  75. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/import_export/templates/admin/import_export/base.html +0 -0
  76. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/import_export/templates/admin/import_export/change_list_export_item.html +0 -0
  77. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/import_export/templates/admin/import_export/change_list_import_export.html +0 -0
  78. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/import_export/templates/admin/import_export/change_list_import_item.html +0 -0
  79. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/import_export/templates/admin/import_export/import.html +0 -0
  80. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/import_export/templates/admin/import_export/import_confirm.html +0 -0
  81. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/import_export/templates/admin/import_export/import_errors.html +0 -0
  82. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/import_export/templates/admin/import_export/import_preview.html +0 -0
  83. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/import_export/templates/admin/import_export/import_validation.html +0 -0
  84. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/simple_history/__init__.py +0 -0
  85. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/simple_history/apps.py +0 -0
  86. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/simple_history/templates/simple_history/_object_history_list.html +0 -0
  87. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/simple_history/templates/simple_history/object_history.html +0 -0
  88. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/simple_history/templates/simple_history/object_history_form.html +0 -0
  89. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/contrib/simple_history/templates/simple_history/submit_line.html +0 -0
  90. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/dataclasses.py +0 -0
  91. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/decorators.py +0 -0
  92. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/exceptions.py +0 -0
  93. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/settings.py +0 -0
  94. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/static/unfold/fonts/inter/Inter-Bold.woff2 +0 -0
  95. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/static/unfold/fonts/inter/Inter-Medium.woff2 +0 -0
  96. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/static/unfold/fonts/inter/Inter-Regular.woff2 +0 -0
  97. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/static/unfold/fonts/inter/Inter-SemiBold.woff2 +0 -0
  98. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/static/unfold/fonts/inter/styles.css +0 -0
  99. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/static/unfold/fonts/material-symbols/Material-Symbols-Outlined.woff2 +0 -0
  100. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/static/unfold/fonts/material-symbols/styles.css +0 -0
  101. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/static/unfold/js/alpine.js +0 -0
  102. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/static/unfold/js/alpine.persist.js +0 -0
  103. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/static/unfold/js/app.js +0 -0
  104. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/static/unfold/js/chart.js +0 -0
  105. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/static/unfold/js/htmx.js +0 -0
  106. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/app_index.html +0 -0
  107. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/auth/user/add_form.html +0 -0
  108. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/auth/user/change_password.html +0 -0
  109. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/base_site.html +0 -0
  110. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/change_form_object_tools.html +0 -0
  111. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/change_list_object_tools.html +0 -0
  112. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/date_hierarchy.html +0 -0
  113. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/delete_confirmation.html +0 -0
  114. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/delete_selected_confirmation.html +0 -0
  115. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/edit_inline/stacked.html +0 -0
  116. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/includes/fieldset.html +0 -0
  117. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/includes/object_delete_summary.html +0 -0
  118. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/index.html +0 -0
  119. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/login.html +0 -0
  120. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/nav_sidebar.html +0 -0
  121. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/object_history.html +0 -0
  122. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/pagination.html +0 -0
  123. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/admin/submit_line.html +0 -0
  124. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/auth/widgets/read_only_password_hash.html +0 -0
  125. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/registration/logged_out.html +0 -0
  126. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/registration/password_change_done.html +0 -0
  127. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/registration/password_change_form.html +0 -0
  128. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/change_list_filter.html +0 -0
  129. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/components/card.html +0 -0
  130. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/components/chart/bar.html +0 -0
  131. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/components/chart/line.html +0 -0
  132. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/components/container.html +0 -0
  133. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/components/flex.html +0 -0
  134. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/components/navigation.html +0 -0
  135. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/components/progress.html +0 -0
  136. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/components/separator.html +0 -0
  137. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/components/text.html +0 -0
  138. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/components/title.html +0 -0
  139. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/account_links.html +0 -0
  140. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/actions_row.html +0 -0
  141. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/add_link.html +0 -0
  142. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/app_list.html +0 -0
  143. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/app_list_default.html +0 -0
  144. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/boolean.html +0 -0
  145. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/breadcrumb_item.html +0 -0
  146. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/display_label.html +0 -0
  147. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/field.html +0 -0
  148. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/field_readonly.html +0 -0
  149. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/fieldsets_tabs.html +0 -0
  150. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/form_errors.html +0 -0
  151. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/form_label.html +0 -0
  152. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/header.html +0 -0
  153. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/help_text.html +0 -0
  154. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/history.html +0 -0
  155. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/label.html +0 -0
  156. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/messages/error.html +0 -0
  157. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/messages/errornote.html +0 -0
  158. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/messages/info.html +0 -0
  159. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/messages.html +0 -0
  160. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/navigation.html +0 -0
  161. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/pagination_current_item.html +0 -0
  162. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/pagination_ellipsis.html +0 -0
  163. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/search.html +0 -0
  164. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/search_results.html +0 -0
  165. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/site_icon.html +0 -0
  166. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/site_logo.html +0 -0
  167. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/submit.html +0 -0
  168. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/tab_action.html +0 -0
  169. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/theme_switch.html +0 -0
  170. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/userlinks.html +0 -0
  171. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/helpers/welcomemsg.html +0 -0
  172. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/layouts/skeleton.html +0 -0
  173. {django_unfold-0.22.0/src/unfold/templates/admin → django_unfold-0.24.0/src/unfold/templates/unfold}/widgets/clearable_file_input.html +0 -0
  174. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/widgets/clearable_file_input_small.html +0 -0
  175. {django_unfold-0.22.0/src/unfold/templates/admin → django_unfold-0.24.0/src/unfold/templates/unfold}/widgets/radio.html +0 -0
  176. {django_unfold-0.22.0/src/unfold/templates/admin → django_unfold-0.24.0/src/unfold/templates/unfold}/widgets/radio_option.html +0 -0
  177. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/widgets/range.html +0 -0
  178. {django_unfold-0.22.0/src/unfold/templates/admin → django_unfold-0.24.0/src/unfold/templates/unfold}/widgets/split_datetime.html +0 -0
  179. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/widgets/split_datetime_vertical.html +0 -0
  180. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/widgets/split_money.html +0 -0
  181. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templates/unfold/widgets/textarea.html +0 -0
  182. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templatetags/__init__.py +0 -0
  183. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/templatetags/unfold.py +0 -0
  184. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/typing.py +0 -0
  185. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/utils.py +0 -0
  186. {django_unfold-0.22.0 → django_unfold-0.24.0}/src/unfold/views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-unfold
3
- Version: 0.22.0
3
+ Version: 0.24.0
4
4
  Summary: Modern Django admin theme for seamless interface development
5
5
  Home-page: https://unfoldadmin.com
6
6
  License: MIT
@@ -52,13 +52,14 @@ Did you decide to start using Unfold but you don't have time to make the switch
52
52
  - **Dependencies:** completely based only on `django.contrib.admin`
53
53
  - **Actions:** multiple ways how to define actions within different parts of admin
54
54
  - **WYSIWYG:** built-in support for WYSIWYG (Trix)
55
- - **Custom filters:** widgets for filtering number & datetime values
55
+ - **Filters:** custom dropdown, numeric, datetime, and text fields
56
56
  - **Dashboard:** custom components for rapid dashboard development
57
57
  - **Model tabs:** define custom tab navigations for models
58
58
  - **Fieldset tabs:** merge several fielsets into tabs in change form
59
59
  - **Colors:** possibility to override default color scheme
60
60
  - **Third party packages:** default support for multiple popular applications
61
61
  - **Environment label**: distinguish between environments by displaying a label
62
+ - **Parallel admin**: support for having default admin in parallel with Unfold. [Admin migration guide](https://unfoldadmin.com/blog/migrating-django-admin-unfold/)
62
63
  - **VS Code**: project configuration and development container is included
63
64
 
64
65
  ## Table of contents <!-- omit from toc -->
@@ -73,6 +74,8 @@ Did you decide to start using Unfold but you don't have time to make the switch
73
74
  - [Action handler functions](#action-handler-functions)
74
75
  - [Action examples](#action-examples)
75
76
  - [Filters](#filters)
77
+ - [Text filters](#text-filters)
78
+ - [Dropdown filters](#dropdown-filters)
76
79
  - [Numeric filters](#numeric-filters)
77
80
  - [Date/time filters](#datetime-filters)
78
81
  - [Display decorator](#display-decorator)
@@ -480,6 +483,88 @@ By default, Django admin handles all filters as regular HTML links pointing at t
480
483
 
481
484
  **Note:** when implementing a filter which contains input fields, there is a no way that user can submit the values, because default filters does not contain submit button. To implement submit button, `unfold.admin.ModelAdmin` contains boolean `list_filter_submit` flag which enables submit button in filter form.
482
485
 
486
+ ### Text filters
487
+
488
+ Text input field which allows filtering by the free string submitted by the user. There are two different variants of this filter: `FieldTextFilter` and `TextFilter`.
489
+
490
+ `FieldTextFilter` requires just a model field name and the filter will make `__icontains` search on this field. There are no other things to configure so the integration in `list_filter` will be just one new row looking like `("model_field_name", FieldTextFilter)`.
491
+
492
+ In the case of the `TextFilter`, it is needed the write a whole new class inheriting from `TextFilter` with a custom implementation of the `queryset` method and the `parameter_name` attribute. This attribute will be a representation of the search query parameter name in URI. The benefit of the `TextFilter` is the possibility of writing complex queries.
493
+
494
+ ```python
495
+ from django.contrib import admin
496
+ from django.contrib.auth.models import User
497
+ from django.core.validators import EMPTY_VALUES
498
+ from django.utils.translation import gettext_lazy as _
499
+ from unfold.admin import ModelAdmin
500
+ from unfold.contrib.filters.admin import TextFilter, FieldTextFilter
501
+
502
+ class CustomTextFilter(TextFilter):
503
+ title = _("Custom filter")
504
+ parameter_name = "query_param_in_uri"
505
+
506
+ def queryset(self, request, queryset):
507
+ if self.value() not in EMPTY_VALUES:
508
+ # Here write custom query
509
+ return queryset.filter(your_field=self.value())
510
+
511
+ return queryset
512
+
513
+
514
+ @admin.register(User)
515
+ class MyAdmin(ModelAdmin):
516
+ list_filter_submit = True
517
+ list_filter = [
518
+ ("model_charfield", FieldTextFilter),
519
+ CustomTextFilter
520
+ ]
521
+ ```
522
+
523
+ ### Dropdown filters
524
+
525
+ Dropdown filters will display a select field with a list of options. Unfold contains two types of dropdowns: `ChoicesDropdownFilter` and `RelatedDropdownFilter`.
526
+
527
+ The difference between them is that `ChoicesDropdownFilter` will collect a list of options based on the `choices` attribute of the model field so most commonly it will be used in combination with `CharField` with specified `choices`. On the other side, `RelatedDropdownFilter` needs a one-to-many or many-to-many foreign key to display options.
528
+
529
+ **Note:** At the moment Unfold does not implement a dropdown with an autocomplete functionality, so it is important not to use dropdowns displaying large datasets.
530
+
531
+ ```python
532
+ # admin.py
533
+
534
+ from django.contrib import admin
535
+ from django.contrib.auth.models import User
536
+ from unfold.admin import ModelAdmin
537
+ from unfold.contrib.filters.admin import ChoicesDropdownFilter, RelatedDropdownFilter, DropdownFilter
538
+
539
+
540
+ class CustomDropdownFilter(DropdownFilter):
541
+ title = _("Custom dropdown filter")
542
+ parameter_name = "query_param_in_uri"
543
+
544
+ def lookups(self, request, model_admin):
545
+ return [
546
+ ["option_1", _("Option 1")],
547
+ ["option_2", _("Option 2")],
548
+ ]
549
+
550
+ def queryset(self, request, queryset):
551
+ if self.value() not in EMPTY_VALUES:
552
+ # Here write custom query
553
+ return queryset.filter(your_field=self.value())
554
+
555
+ return queryset
556
+
557
+
558
+ @admin.register(User)
559
+ class MyAdmin(ModelAdmin):
560
+ list_filter_submit = True
561
+ list_filter = [
562
+ CustomDropdownFilter,
563
+ ("modelfield_with_choices", ChoicesDropdownFilter),
564
+ ("modelfield_with_foreign_key", RelatedDropdownFilter)
565
+ ]
566
+ ```
567
+
483
568
  ### Numeric filters
484
569
 
485
570
  Currently, Unfold implements numeric filters inside `unfold.contrib.filters` application. In order to use these filters, it is required to add this application into `INSTALLED_APPS` in `settings.py` right after `unfold` application.
@@ -610,7 +695,16 @@ class UserAdmin(ModelAdmin):
610
695
  """
611
696
  Third argument is short text which will appear as prefix in circle
612
697
  """
613
- return "First main heading", "Smaller additional description", "AB"
698
+ return [
699
+ "First main heading",
700
+ "Smaller additional description", # Use None in case you don't need it
701
+ "AB", # Short text which will appear in front of
702
+ # Image instead of initials. Initials are ignored if image is available
703
+ {
704
+ "path": "some/path/picture.jpg,
705
+ "squared": True, # Picture is displayed in square format, if empty circle
706
+ }
707
+ ]
614
708
  ```
615
709
 
616
710
  ## Change form tabs
@@ -708,6 +802,10 @@ class CrontabScheduleAdmin(ModelAdmin):
708
802
  @admin.register(SolarSchedule)
709
803
  class SolarScheduleAdmin(ModelAdmin):
710
804
  pass
805
+
806
+ @admin.register(ClockedSchedule)
807
+ class ClockedScheduleAdmin(ModelAdmin):
808
+ pass
711
809
  ```
712
810
 
713
811
  ### django-guardian
@@ -737,7 +835,7 @@ When implementing `import_export.admin.ExportActionModelAdmin` class in admin pa
737
835
  admin.py
738
836
 
739
837
  from unfold.admin import ModelAdmin
740
- from unfold.contrib.import_export import ExportActionModelAdmin
838
+ from unfold.contrib.import_export.admin import ExportActionModelAdmin
741
839
 
742
840
  class ExampleAdmin(ModelAdmin, ExportActionModelAdmin):
743
841
  pass
@@ -28,13 +28,14 @@ Did you decide to start using Unfold but you don't have time to make the switch
28
28
  - **Dependencies:** completely based only on `django.contrib.admin`
29
29
  - **Actions:** multiple ways how to define actions within different parts of admin
30
30
  - **WYSIWYG:** built-in support for WYSIWYG (Trix)
31
- - **Custom filters:** widgets for filtering number & datetime values
31
+ - **Filters:** custom dropdown, numeric, datetime, and text fields
32
32
  - **Dashboard:** custom components for rapid dashboard development
33
33
  - **Model tabs:** define custom tab navigations for models
34
34
  - **Fieldset tabs:** merge several fielsets into tabs in change form
35
35
  - **Colors:** possibility to override default color scheme
36
36
  - **Third party packages:** default support for multiple popular applications
37
37
  - **Environment label**: distinguish between environments by displaying a label
38
+ - **Parallel admin**: support for having default admin in parallel with Unfold. [Admin migration guide](https://unfoldadmin.com/blog/migrating-django-admin-unfold/)
38
39
  - **VS Code**: project configuration and development container is included
39
40
 
40
41
  ## Table of contents <!-- omit from toc -->
@@ -49,6 +50,8 @@ Did you decide to start using Unfold but you don't have time to make the switch
49
50
  - [Action handler functions](#action-handler-functions)
50
51
  - [Action examples](#action-examples)
51
52
  - [Filters](#filters)
53
+ - [Text filters](#text-filters)
54
+ - [Dropdown filters](#dropdown-filters)
52
55
  - [Numeric filters](#numeric-filters)
53
56
  - [Date/time filters](#datetime-filters)
54
57
  - [Display decorator](#display-decorator)
@@ -456,6 +459,88 @@ By default, Django admin handles all filters as regular HTML links pointing at t
456
459
 
457
460
  **Note:** when implementing a filter which contains input fields, there is a no way that user can submit the values, because default filters does not contain submit button. To implement submit button, `unfold.admin.ModelAdmin` contains boolean `list_filter_submit` flag which enables submit button in filter form.
458
461
 
462
+ ### Text filters
463
+
464
+ Text input field which allows filtering by the free string submitted by the user. There are two different variants of this filter: `FieldTextFilter` and `TextFilter`.
465
+
466
+ `FieldTextFilter` requires just a model field name and the filter will make `__icontains` search on this field. There are no other things to configure so the integration in `list_filter` will be just one new row looking like `("model_field_name", FieldTextFilter)`.
467
+
468
+ In the case of the `TextFilter`, it is needed the write a whole new class inheriting from `TextFilter` with a custom implementation of the `queryset` method and the `parameter_name` attribute. This attribute will be a representation of the search query parameter name in URI. The benefit of the `TextFilter` is the possibility of writing complex queries.
469
+
470
+ ```python
471
+ from django.contrib import admin
472
+ from django.contrib.auth.models import User
473
+ from django.core.validators import EMPTY_VALUES
474
+ from django.utils.translation import gettext_lazy as _
475
+ from unfold.admin import ModelAdmin
476
+ from unfold.contrib.filters.admin import TextFilter, FieldTextFilter
477
+
478
+ class CustomTextFilter(TextFilter):
479
+ title = _("Custom filter")
480
+ parameter_name = "query_param_in_uri"
481
+
482
+ def queryset(self, request, queryset):
483
+ if self.value() not in EMPTY_VALUES:
484
+ # Here write custom query
485
+ return queryset.filter(your_field=self.value())
486
+
487
+ return queryset
488
+
489
+
490
+ @admin.register(User)
491
+ class MyAdmin(ModelAdmin):
492
+ list_filter_submit = True
493
+ list_filter = [
494
+ ("model_charfield", FieldTextFilter),
495
+ CustomTextFilter
496
+ ]
497
+ ```
498
+
499
+ ### Dropdown filters
500
+
501
+ Dropdown filters will display a select field with a list of options. Unfold contains two types of dropdowns: `ChoicesDropdownFilter` and `RelatedDropdownFilter`.
502
+
503
+ The difference between them is that `ChoicesDropdownFilter` will collect a list of options based on the `choices` attribute of the model field so most commonly it will be used in combination with `CharField` with specified `choices`. On the other side, `RelatedDropdownFilter` needs a one-to-many or many-to-many foreign key to display options.
504
+
505
+ **Note:** At the moment Unfold does not implement a dropdown with an autocomplete functionality, so it is important not to use dropdowns displaying large datasets.
506
+
507
+ ```python
508
+ # admin.py
509
+
510
+ from django.contrib import admin
511
+ from django.contrib.auth.models import User
512
+ from unfold.admin import ModelAdmin
513
+ from unfold.contrib.filters.admin import ChoicesDropdownFilter, RelatedDropdownFilter, DropdownFilter
514
+
515
+
516
+ class CustomDropdownFilter(DropdownFilter):
517
+ title = _("Custom dropdown filter")
518
+ parameter_name = "query_param_in_uri"
519
+
520
+ def lookups(self, request, model_admin):
521
+ return [
522
+ ["option_1", _("Option 1")],
523
+ ["option_2", _("Option 2")],
524
+ ]
525
+
526
+ def queryset(self, request, queryset):
527
+ if self.value() not in EMPTY_VALUES:
528
+ # Here write custom query
529
+ return queryset.filter(your_field=self.value())
530
+
531
+ return queryset
532
+
533
+
534
+ @admin.register(User)
535
+ class MyAdmin(ModelAdmin):
536
+ list_filter_submit = True
537
+ list_filter = [
538
+ CustomDropdownFilter,
539
+ ("modelfield_with_choices", ChoicesDropdownFilter),
540
+ ("modelfield_with_foreign_key", RelatedDropdownFilter)
541
+ ]
542
+ ```
543
+
459
544
  ### Numeric filters
460
545
 
461
546
  Currently, Unfold implements numeric filters inside `unfold.contrib.filters` application. In order to use these filters, it is required to add this application into `INSTALLED_APPS` in `settings.py` right after `unfold` application.
@@ -586,7 +671,16 @@ class UserAdmin(ModelAdmin):
586
671
  """
587
672
  Third argument is short text which will appear as prefix in circle
588
673
  """
589
- return "First main heading", "Smaller additional description", "AB"
674
+ return [
675
+ "First main heading",
676
+ "Smaller additional description", # Use None in case you don't need it
677
+ "AB", # Short text which will appear in front of
678
+ # Image instead of initials. Initials are ignored if image is available
679
+ {
680
+ "path": "some/path/picture.jpg,
681
+ "squared": True, # Picture is displayed in square format, if empty circle
682
+ }
683
+ ]
590
684
  ```
591
685
 
592
686
  ## Change form tabs
@@ -684,6 +778,10 @@ class CrontabScheduleAdmin(ModelAdmin):
684
778
  @admin.register(SolarSchedule)
685
779
  class SolarScheduleAdmin(ModelAdmin):
686
780
  pass
781
+
782
+ @admin.register(ClockedSchedule)
783
+ class ClockedScheduleAdmin(ModelAdmin):
784
+ pass
687
785
  ```
688
786
 
689
787
  ### django-guardian
@@ -713,7 +811,7 @@ When implementing `import_export.admin.ExportActionModelAdmin` class in admin pa
713
811
  admin.py
714
812
 
715
813
  from unfold.admin import ModelAdmin
716
- from unfold.contrib.import_export import ExportActionModelAdmin
814
+ from unfold.contrib.import_export.admin import ExportActionModelAdmin
717
815
 
718
816
  class ExampleAdmin(ModelAdmin, ExportActionModelAdmin):
719
817
  pass
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "django-unfold"
3
- version = "0.22.0"
3
+ version = "0.24.0"
4
4
  description = "Modern Django admin theme for seamless interface development"
5
5
  license = "MIT"
6
6
  readme = "README.md"
@@ -38,6 +38,8 @@ python-semantic-release = "^8.7.0"
38
38
  [tool.ruff]
39
39
  fix = true
40
40
  line-length = 88
41
+
42
+ [tool.ruff.lint]
41
43
  select = [
42
44
  "E", # pycodestyle errors
43
45
  "W", # pycodestyle warnings
@@ -60,7 +62,7 @@ version_toml = ["pyproject.toml:tool.poetry.version"]
60
62
 
61
63
  [tool.semantic_release.changelog]
62
64
  template_dir = ".github/templates"
63
- exclude_commit_patterns = ["chore: version bump"]
65
+ exclude_commit_patterns = ["chore: version bump", "chore(deps)", "chore(deps-dev)"]
64
66
 
65
67
  [tool.semantic_release.changelog.environment]
66
68
  trim_blocks = true
@@ -8,6 +8,7 @@ from django.contrib.admin import StackedInline as BaseStackedInline
8
8
  from django.contrib.admin import TabularInline as BaseTabularInline
9
9
  from django.contrib.admin import display, helpers
10
10
  from django.contrib.admin.utils import lookup_field
11
+ from django.contrib.admin.widgets import RelatedFieldWidgetWrapper
11
12
  from django.core.exceptions import ObjectDoesNotExist
12
13
  from django.db import models
13
14
  from django.db.models import (
@@ -39,7 +40,6 @@ from django.utils.safestring import SafeText, mark_safe
39
40
  from django.utils.text import capfirst
40
41
  from django.utils.translation import gettext_lazy as _
41
42
  from django.views import View
42
- from unfold.utils import display_for_field
43
43
 
44
44
  from .checks import UnfoldModelAdminChecks
45
45
  from .dataclasses import UnfoldAction
@@ -47,9 +47,9 @@ from .exceptions import UnfoldException
47
47
  from .forms import ActionForm
48
48
  from .settings import get_config
49
49
  from .typing import FieldsetsType
50
+ from .utils import display_for_field
50
51
  from .widgets import (
51
52
  CHECKBOX_LABEL_CLASSES,
52
- INPUT_CLASSES,
53
53
  LABEL_CLASSES,
54
54
  SELECT_CLASSES,
55
55
  UnfoldAdminBigIntegerFieldWidget,
@@ -62,6 +62,7 @@ from .widgets import (
62
62
  UnfoldAdminMoneyWidget,
63
63
  UnfoldAdminNullBooleanSelectWidget,
64
64
  UnfoldAdminRadioSelectWidget,
65
+ UnfoldAdminSelectWidget,
65
66
  UnfoldAdminSingleDateWidget,
66
67
  UnfoldAdminSingleTimeWidget,
67
68
  UnfoldAdminSplitDateTimeWidget,
@@ -142,6 +143,11 @@ class UnfoldAdminField(helpers.AdminField):
142
143
  def label_tag(self) -> SafeText:
143
144
  classes = []
144
145
 
146
+ if not self.field.field.widget.__class__.__name__.startswith(
147
+ "Unfold"
148
+ ) and not self.field.field.widget.template_name.startswith("unfold"):
149
+ return super().label_tag()
150
+
145
151
  # TODO load config from current AdminSite (override Fieldline.__iter__ method)
146
152
  for lang, flag in get_config()["EXTENSIONS"]["modeltranslation"][
147
153
  "flags"
@@ -175,6 +181,9 @@ helpers.AdminField = UnfoldAdminField
175
181
 
176
182
  class UnfoldAdminReadonlyField(helpers.AdminReadonlyField):
177
183
  def label_tag(self) -> SafeText:
184
+ if not isinstance(self.model_admin, ModelAdmin):
185
+ return super().label_tag()
186
+
178
187
  attrs = {
179
188
  "class": " ".join(LABEL_CLASSES + ["mb-2"]),
180
189
  }
@@ -285,9 +294,7 @@ class ModelAdminMixin:
285
294
  radio_style=self.radio_fields[db_field.name]
286
295
  )
287
296
  else:
288
- kwargs["widget"] = forms.Select(
289
- attrs={"class": " ".join(SELECT_CLASSES)}
290
- )
297
+ kwargs["widget"] = UnfoldAdminSelectWidget()
291
298
 
292
299
  kwargs["choices"] = db_field.get_choices(
293
300
  include_blank=db_field.blank, blank_choice=[("", _("Select value"))]
@@ -301,16 +308,12 @@ class ModelAdminMixin:
301
308
  # Overrides widgets for all related fields
302
309
  if "widget" not in kwargs:
303
310
  if db_field.name in self.raw_id_fields:
304
- kwargs["widget"] = forms.TextInput(
305
- attrs={"class": " ".join(INPUT_CLASSES)}
306
- )
311
+ kwargs["widget"] = UnfoldAdminTextInputWidget()
307
312
  elif (
308
313
  db_field.name not in self.get_autocomplete_fields(request)
309
314
  and db_field.name not in self.radio_fields
310
315
  ):
311
- kwargs["widget"] = forms.Select(
312
- attrs={"class": " ".join(SELECT_CLASSES)}
313
- )
316
+ kwargs["widget"] = UnfoldAdminSelectWidget()
314
317
  kwargs["empty_label"] = _("Select value")
315
318
 
316
319
  return super().formfield_for_foreignkey(db_field, request, **kwargs)
@@ -323,9 +326,7 @@ class ModelAdminMixin:
323
326
  ) -> ModelMultipleChoiceField:
324
327
  if "widget" not in kwargs:
325
328
  if db_field.name in self.raw_id_fields:
326
- kwargs["widget"] = forms.TextInput(
327
- attrs={"class": " ".join(INPUT_CLASSES)}
328
- )
329
+ kwargs["widget"] = UnfoldAdminTextInputWidget()
329
330
 
330
331
  form_field = super().formfield_for_manytomany(db_field, request, **kwargs)
331
332
 
@@ -342,9 +343,7 @@ class ModelAdminMixin:
342
343
  self, db_field: Field, request: HttpRequest, **kwargs
343
344
  ) -> Optional[Field]:
344
345
  if "widget" not in kwargs:
345
- kwargs["widget"] = forms.NullBooleanSelect(
346
- attrs={"class": " ".join(SELECT_CLASSES)}
347
- )
346
+ kwargs["widget"] = UnfoldAdminNullBooleanSelectWidget()
348
347
 
349
348
  return db_field.formfield(**kwargs)
350
349
 
@@ -354,7 +353,14 @@ class ModelAdminMixin:
354
353
  if isinstance(db_field, models.BooleanField) and db_field.null is True:
355
354
  return self.formfield_for_nullboolean_field(db_field, request, **kwargs)
356
355
 
357
- return super().formfield_for_dbfield(db_field, request, **kwargs)
356
+ formfield = super().formfield_for_dbfield(db_field, request, **kwargs)
357
+
358
+ if formfield and isinstance(formfield.widget, RelatedFieldWidgetWrapper):
359
+ formfield.widget.template_name = (
360
+ "unfold/widgets/related_widget_wrapper.html"
361
+ )
362
+
363
+ return formfield
358
364
 
359
365
 
360
366
  class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
@@ -400,7 +406,7 @@ class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
400
406
  filtered_actions.append(action)
401
407
  continue
402
408
  permission_checks = (
403
- getattr(self, "has_%s_permission" % permission)
409
+ getattr(self, f"has_{permission}_permission")
404
410
  for permission in action.method.allowed_permissions
405
411
  )
406
412
  if any(has_permission(request) for has_permission in permission_checks):
@@ -546,7 +552,8 @@ class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
546
552
  "title": action.description,
547
553
  "attrs": action.method.attrs,
548
554
  "path": reverse(
549
- f"admin:{action.action_name}", args=(object_id,)
555
+ f"{self.admin_site.name}:{action.action_name}",
556
+ args=(object_id,),
550
557
  ),
551
558
  }
552
559
  )
@@ -570,7 +577,7 @@ class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
570
577
  {
571
578
  "title": action.description,
572
579
  "attrs": action.method.attrs,
573
- "path": reverse(f"admin:{action.action_name}"),
580
+ "path": reverse(f"{self.admin_site.name}:{action.action_name}"),
574
581
  }
575
582
  for action in self.get_actions_list(request)
576
583
  ]
@@ -579,7 +586,7 @@ class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
579
586
  {
580
587
  "title": action.description,
581
588
  "attrs": action.method.attrs,
582
- "raw_path": f"admin:{action.action_name}",
589
+ "raw_path": f"{self.admin_site.name}:{action.action_name}",
583
590
  }
584
591
  for action in self.get_actions_row(request)
585
592
  ]
@@ -7,9 +7,14 @@ from .sites import UnfoldAdminSite
7
7
 
8
8
  class DefaultAppConfig(AppConfig):
9
9
  name = "unfold"
10
+ default = True
10
11
 
11
12
  def ready(self):
12
13
  site = UnfoldAdminSite()
13
14
 
14
15
  admin.site = site
15
16
  sites.site = site
17
+
18
+
19
+ class BasicAppConfig(AppConfig):
20
+ name = "unfold"
@@ -30,7 +30,7 @@ class UnfoldModelAdminChecks(ModelAdminChecks):
30
30
  if not hasattr(action.method, "allowed_permissions"):
31
31
  continue
32
32
  for permission in action.method.allowed_permissions:
33
- method_name = "has_%s_permission" % permission
33
+ method_name = f"has_{permission}_permission"
34
34
  if not hasattr(obj, method_name):
35
35
  errors.append(
36
36
  checks.Error(
@@ -17,16 +17,132 @@ from django.db.models.fields import (
17
17
  from django.forms import ValidationError
18
18
  from django.http import HttpRequest
19
19
  from django.utils.dateparse import parse_datetime
20
+ from django.utils.translation import gettext_lazy as _
20
21
 
21
22
  from .forms import (
23
+ DropdownForm,
22
24
  RangeDateForm,
23
25
  RangeDateTimeForm,
24
26
  RangeNumericForm,
27
+ SearchForm,
25
28
  SingleNumericForm,
26
29
  SliderNumericForm,
27
30
  )
28
31
 
29
32
 
33
+ class ValueMixin:
34
+ def value(self) -> Optional[str]:
35
+ return (
36
+ self.lookup_val[0]
37
+ if self.lookup_val not in EMPTY_VALUES
38
+ and isinstance(self.lookup_val, List)
39
+ and len(self.lookup_val) > 0
40
+ else self.lookup_val
41
+ )
42
+
43
+
44
+ class DropdownMixin:
45
+ template = "unfold/filters/filters_field.html"
46
+ form_class = DropdownForm
47
+ all_option = ["", _("All")]
48
+
49
+ def queryset(self, request, queryset) -> QuerySet:
50
+ if self.value() not in EMPTY_VALUES:
51
+ return super().queryset(request, queryset)
52
+
53
+ return queryset
54
+
55
+
56
+ class TextFilter(admin.SimpleListFilter):
57
+ template = "unfold/filters/filters_field.html"
58
+ form_class = SearchForm
59
+
60
+ def has_output(self) -> bool:
61
+ return True
62
+
63
+ def lookups(self, request: HttpRequest, model_admin: ModelAdmin) -> Tuple:
64
+ return ()
65
+
66
+ def choices(self, changelist: ChangeList) -> Tuple[Dict[str, Any], ...]:
67
+ return (
68
+ {
69
+ "form": self.form_class(
70
+ name=self.parameter_name,
71
+ label=_("By {}").format(self.title),
72
+ data={self.parameter_name: self.value()},
73
+ ),
74
+ },
75
+ )
76
+
77
+
78
+ class FieldTextFilter(ValueMixin, admin.FieldListFilter):
79
+ template = "unfold/filters/filters_field.html"
80
+ form_class = SearchForm
81
+
82
+ def __init__(self, field, request, params, model, model_admin, field_path):
83
+ self.lookup_kwarg = f"{field_path}__icontains"
84
+ self.lookup_val = params.get(self.lookup_kwarg)
85
+ super().__init__(field, request, params, model, model_admin, field_path)
86
+
87
+ def expected_parameters(self) -> List[str]:
88
+ return [self.lookup_kwarg]
89
+
90
+ def choices(self, changelist: ChangeList) -> Tuple[Dict[str, Any], ...]:
91
+ return (
92
+ {
93
+ "form": self.form_class(
94
+ label=_("By {}").format(self.title),
95
+ name=self.lookup_kwarg,
96
+ data={self.lookup_kwarg: self.value()},
97
+ ),
98
+ },
99
+ )
100
+
101
+
102
+ class DropdownFilter(admin.SimpleListFilter):
103
+ template = "unfold/filters/filters_field.html"
104
+ form_class = DropdownForm
105
+ all_option = ["", _("All")]
106
+
107
+ def choices(self, changelist: ChangeList) -> Tuple[Dict[str, Any], ...]:
108
+ return (
109
+ {
110
+ "form": self.form_class(
111
+ label=_("By {}").format(self.title),
112
+ name=self.parameter_name,
113
+ choices=[self.all_option, *self.lookup_choices],
114
+ data={self.parameter_name: self.value()},
115
+ ),
116
+ },
117
+ )
118
+
119
+
120
+ class ChoicesDropdownFilter(ValueMixin, DropdownMixin, admin.ChoicesFieldListFilter):
121
+ def choices(self, changelist: ChangeList):
122
+ choices = [self.all_option, *self.field.flatchoices]
123
+
124
+ yield {
125
+ "form": self.form_class(
126
+ label=_("By {}").format(self.title),
127
+ name=self.lookup_kwarg,
128
+ choices=choices,
129
+ data={self.lookup_kwarg: self.value()},
130
+ ),
131
+ }
132
+
133
+
134
+ class RelatedDropdownFilter(ValueMixin, DropdownMixin, admin.RelatedFieldListFilter):
135
+ def choices(self, changelist: ChangeList):
136
+ yield {
137
+ "form": self.form_class(
138
+ label=_("By {}").format(self.title),
139
+ name=self.lookup_kwarg,
140
+ choices=[self.all_option, *self.lookup_choices],
141
+ data={self.lookup_kwarg: self.value()},
142
+ ),
143
+ }
144
+
145
+
30
146
  class SingleNumericFilter(admin.FieldListFilter):
31
147
  request = None
32
148
  parameter_name = None
@@ -1,7 +1,35 @@
1
1
  from django import forms
2
2
  from django.utils.translation import gettext_lazy as _
3
3
 
4
- from ...widgets import INPUT_CLASSES, UnfoldAdminSplitDateTimeVerticalWidget
4
+ from ...widgets import (
5
+ INPUT_CLASSES,
6
+ UnfoldAdminSelectWidget,
7
+ UnfoldAdminSplitDateTimeVerticalWidget,
8
+ UnfoldAdminTextInputWidget,
9
+ )
10
+
11
+
12
+ class SearchForm(forms.Form):
13
+ def __init__(self, name, label, *args, **kwargs):
14
+ super().__init__(*args, **kwargs)
15
+
16
+ self.fields[name] = forms.CharField(
17
+ label=label,
18
+ required=False,
19
+ widget=UnfoldAdminTextInputWidget,
20
+ )
21
+
22
+
23
+ class DropdownForm(forms.Form):
24
+ def __init__(self, name, label, choices, *args, **kwargs):
25
+ super().__init__(*args, **kwargs)
26
+
27
+ self.fields[name] = forms.ChoiceField(
28
+ label=label,
29
+ required=False,
30
+ choices=choices,
31
+ widget=UnfoldAdminSelectWidget,
32
+ )
5
33
 
6
34
 
7
35
  class SingleNumericForm(forms.Form):