wagtail 6.2.2__py3-none-any.whl → 6.3rc2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (386) hide show
  1. wagtail/__init__.py +1 -1
  2. wagtail/actions/copy_for_translation.py +6 -0
  3. wagtail/actions/publish_revision.py +3 -3
  4. wagtail/admin/action_menu.py +5 -3
  5. wagtail/admin/forms/account.py +1 -1
  6. wagtail/admin/icons.py +2 -6
  7. wagtail/admin/locale/cy/LC_MESSAGES/django.mo +0 -0
  8. wagtail/admin/locale/cy/LC_MESSAGES/django.po +32 -0
  9. wagtail/admin/locale/dv/LC_MESSAGES/django.mo +0 -0
  10. wagtail/admin/locale/dv/LC_MESSAGES/django.po +28 -0
  11. wagtail/admin/locale/en/LC_MESSAGES/django.po +451 -485
  12. wagtail/admin/locale/en/LC_MESSAGES/djangojs.po +7 -7
  13. wagtail/admin/locale/sl/LC_MESSAGES/django.mo +0 -0
  14. wagtail/admin/locale/sl/LC_MESSAGES/django.po +150 -0
  15. wagtail/admin/locale/sl/LC_MESSAGES/djangojs.mo +0 -0
  16. wagtail/admin/locale/sl/LC_MESSAGES/djangojs.po +9 -2
  17. wagtail/admin/locale/ug/LC_MESSAGES/django.mo +0 -0
  18. wagtail/admin/locale/ug/LC_MESSAGES/django.po +3250 -196
  19. wagtail/admin/localization.py +12 -2
  20. wagtail/admin/panels/model_utils.py +1 -1
  21. wagtail/admin/site_summary.py +0 -2
  22. wagtail/admin/static/wagtailadmin/css/core.css +1 -1
  23. wagtail/admin/static/wagtailadmin/css/panels/draftail.css +1 -1
  24. wagtail/admin/static/wagtailadmin/images/email-header.jpg +0 -0
  25. wagtail/admin/static/wagtailadmin/js/bulk-actions.js +1 -1
  26. wagtail/admin/static/wagtailadmin/js/core.js +1 -1
  27. wagtail/admin/static/wagtailadmin/js/core.js.LICENSE.txt +12 -0
  28. wagtail/admin/static/wagtailadmin/js/draftail.js +1 -1
  29. wagtail/admin/static/wagtailadmin/js/sidebar.js +1 -1
  30. wagtail/admin/static/wagtailadmin/js/telepath/telepath.js +1 -1
  31. wagtail/admin/static/wagtailadmin/js/telepath/widgets.js +1 -1
  32. wagtail/admin/static/wagtailadmin/js/userbar.js +1 -1
  33. wagtail/admin/static/wagtailadmin/js/userbar.js.LICENSE.txt +1 -1
  34. wagtail/admin/static/wagtailadmin/js/vendor.js +1 -1
  35. wagtail/admin/static/wagtailadmin/js/wagtailadmin.js +1 -1
  36. wagtail/admin/static/wagtailadmin/js/workflow-action.js +1 -1
  37. wagtail/admin/staticfiles.py +6 -4
  38. wagtail/admin/templates/wagtailadmin/account/account.html +42 -59
  39. wagtail/admin/templates/wagtailadmin/admin_base.html +0 -28
  40. wagtail/admin/templates/wagtailadmin/bulk_actions/footer.html +1 -1
  41. wagtail/admin/templates/wagtailadmin/chooser/_search_results.html +7 -5
  42. wagtail/admin/templates/wagtailadmin/chooser/tables/page_navigate_to_children_cell.html +1 -1
  43. wagtail/admin/templates/wagtailadmin/generic/chooser/results.html +7 -5
  44. wagtail/admin/templates/wagtailadmin/generic/edit.html +0 -8
  45. wagtail/admin/templates/wagtailadmin/generic/form.html +60 -17
  46. wagtail/admin/templates/wagtailadmin/generic/index.html +17 -0
  47. wagtail/admin/templates/wagtailadmin/generic/index_results.html +9 -35
  48. wagtail/admin/templates/wagtailadmin/generic/inspect.html +2 -21
  49. wagtail/admin/templates/wagtailadmin/generic/listing.html +11 -17
  50. wagtail/admin/templates/wagtailadmin/generic/listing_results.html +19 -1
  51. wagtail/admin/templates/wagtailadmin/home/account_summary.html +26 -0
  52. wagtail/admin/templates/wagtailadmin/home/locked_pages.html +28 -18
  53. wagtail/admin/templates/wagtailadmin/home/recent_edits.html +28 -17
  54. wagtail/admin/templates/wagtailadmin/home/site_summary_pages.html +2 -2
  55. wagtail/admin/templates/wagtailadmin/home/upgrade_notification.html +35 -13
  56. wagtail/admin/templates/wagtailadmin/home/user_objects_in_workflow_moderation.html +28 -18
  57. wagtail/admin/templates/wagtailadmin/home/workflow_objects_to_moderate.html +43 -32
  58. wagtail/admin/templates/wagtailadmin/home.html +22 -5
  59. wagtail/admin/templates/wagtailadmin/pages/_editor_js.html +0 -1
  60. wagtail/admin/templates/wagtailadmin/pages/action_menu/menu.html +1 -11
  61. wagtail/admin/templates/wagtailadmin/pages/action_menu/menu_item.html +1 -6
  62. wagtail/admin/templates/wagtailadmin/pages/action_menu/page_locked.html +1 -1
  63. wagtail/admin/templates/wagtailadmin/pages/action_menu/publish.html +1 -1
  64. wagtail/admin/templates/wagtailadmin/pages/action_menu/save_draft.html +1 -1
  65. wagtail/admin/templates/wagtailadmin/pages/bulk_actions/confirm_bulk_delete.html +12 -6
  66. wagtail/admin/templates/wagtailadmin/pages/bulk_actions/confirm_bulk_move.html +12 -7
  67. wagtail/admin/templates/wagtailadmin/pages/bulk_actions/confirm_bulk_publish.html +17 -11
  68. wagtail/admin/templates/wagtailadmin/pages/bulk_actions/confirm_bulk_unpublish.html +15 -9
  69. wagtail/admin/templates/wagtailadmin/pages/confirm_delete.html +9 -9
  70. wagtail/admin/templates/wagtailadmin/pages/confirm_move.html +2 -2
  71. wagtail/admin/templates/wagtailadmin/pages/confirm_unpublish.html +4 -4
  72. wagtail/admin/templates/wagtailadmin/pages/create.html +15 -34
  73. wagtail/admin/templates/wagtailadmin/pages/edit.html +15 -30
  74. wagtail/admin/templates/wagtailadmin/pages/explorable_index.html +6 -0
  75. wagtail/admin/templates/wagtailadmin/pages/explorable_index_results.html +60 -0
  76. wagtail/admin/templates/wagtailadmin/pages/index.html +1 -36
  77. wagtail/admin/templates/wagtailadmin/pages/index_results.html +2 -50
  78. wagtail/admin/templates/wagtailadmin/pages/listing/_locked_indicator.html +1 -1
  79. wagtail/admin/templates/wagtailadmin/pages/listing/_ordering_cell.html +1 -1
  80. wagtail/admin/templates/wagtailadmin/pages/listing/_page_header_buttons.html +1 -1
  81. wagtail/admin/templates/wagtailadmin/pages/listing/_page_title_column_header.html +3 -3
  82. wagtail/admin/templates/wagtailadmin/pages/listing/_pagination.html +1 -1
  83. wagtail/admin/templates/wagtailadmin/pages/listing.html +24 -0
  84. wagtail/admin/templates/wagtailadmin/pages/search.html +1 -20
  85. wagtail/admin/templates/wagtailadmin/pages/search_results.html +27 -25
  86. wagtail/admin/templates/wagtailadmin/permissions/includes/collection_member_permissions_formset.html +0 -1
  87. wagtail/admin/templates/wagtailadmin/reports/listing/_list_page_report.html +2 -2
  88. wagtail/admin/templates/wagtailadmin/reports/listing/_list_page_types_usage.html +6 -6
  89. wagtail/admin/templates/wagtailadmin/reports/workflow_tasks_results.html +1 -1
  90. wagtail/admin/templates/wagtailadmin/shared/action_menu/menu.html +14 -0
  91. wagtail/admin/templates/wagtailadmin/shared/action_menu/menu_item.html +6 -0
  92. wagtail/admin/templates/wagtailadmin/shared/avatar.html +13 -3
  93. wagtail/admin/templates/wagtailadmin/shared/header.html +0 -5
  94. wagtail/admin/templates/wagtailadmin/shared/headers/slim_header.html +1 -1
  95. wagtail/admin/templates/wagtailadmin/shared/page_status_tag_new.html +4 -1
  96. wagtail/admin/templates/wagtailadmin/shared/pagination_nav.html +1 -1
  97. wagtail/admin/templates/wagtailadmin/shared/side_panel_toggle.html +1 -1
  98. wagtail/admin/templates/wagtailadmin/shared/side_panels/includes/status/locale.html +1 -1
  99. wagtail/admin/templates/wagtailadmin/shared/side_panels/includes/status/usage.html +2 -2
  100. wagtail/admin/templates/wagtailadmin/shared/side_panels/preview.html +94 -52
  101. wagtail/admin/templates/wagtailadmin/{pages/_unsaved_changes_warning.html → shared/unsaved_changes_warning.html} +4 -4
  102. wagtail/admin/templates/wagtailadmin/shared/usage_summary.html +2 -2
  103. wagtail/admin/templates/wagtailadmin/shared/workflow_history/detail.html +1 -7
  104. wagtail/admin/templates/wagtailadmin/shared/workflow_history/listing.html +1 -0
  105. wagtail/admin/templates/wagtailadmin/shared/workflow_history/{list.html → listing_results.html} +33 -35
  106. wagtail/admin/templates/wagtailadmin/tables/references_cell.html +1 -1
  107. wagtail/admin/templates/wagtailadmin/workflows/create.html +11 -36
  108. wagtail/admin/templates/wagtailadmin/workflows/create_task.html +0 -38
  109. wagtail/admin/templates/wagtailadmin/workflows/edit.html +44 -74
  110. wagtail/admin/templates/wagtailadmin/workflows/edit_task.html +27 -66
  111. wagtail/admin/templates/wagtailadmin/workflows/includes/task_usage_cell.html +4 -2
  112. wagtail/admin/templates/wagtailadmin/workflows/includes/workflow_tasks_cell.html +4 -4
  113. wagtail/admin/templates/wagtailadmin/workflows/includes/workflow_used_by_cell.html +15 -11
  114. wagtail/admin/templates/wagtailadmin/workflows/task_chooser/includes/results.html +7 -5
  115. wagtail/admin/templatetags/wagtailadmin_tags.py +51 -22
  116. wagtail/admin/tests/pages/test_content_type_use_view.py +82 -2
  117. wagtail/admin/tests/pages/test_edit_page.py +70 -0
  118. wagtail/admin/tests/pages/test_explorer_view.py +65 -4
  119. wagtail/admin/tests/pages/test_page_search.py +8 -7
  120. wagtail/admin/tests/pages/test_page_usage.py +3 -1
  121. wagtail/admin/tests/pages/test_preview.py +208 -63
  122. wagtail/admin/tests/pages/test_reorder_page.py +91 -1
  123. wagtail/admin/tests/pages/test_view_draft.py +32 -1
  124. wagtail/admin/tests/pages/test_workflow_history.py +288 -7
  125. wagtail/admin/tests/test_account_management.py +23 -3
  126. wagtail/admin/tests/test_audit_log.py +24 -2
  127. wagtail/admin/tests/test_collections_views.py +17 -5
  128. wagtail/admin/tests/test_dashboard.py +1 -1
  129. wagtail/admin/tests/test_edit_handlers.py +3 -2
  130. wagtail/admin/tests/test_icon_sprite.py +4 -0
  131. wagtail/admin/tests/test_privacy.py +5 -19
  132. wagtail/admin/tests/test_reports_views.py +1 -1
  133. wagtail/admin/tests/test_site_summary.py +3 -3
  134. wagtail/admin/tests/test_templatetags.py +27 -3
  135. wagtail/admin/tests/test_upgrade_notification.py +71 -18
  136. wagtail/admin/tests/test_views.py +2 -2
  137. wagtail/admin/tests/test_views_generic.py +13 -0
  138. wagtail/admin/tests/test_widgets.py +3 -3
  139. wagtail/admin/tests/test_workflows.py +172 -27
  140. wagtail/admin/tests/tests.py +1 -1
  141. wagtail/admin/tests/viewsets/test_model_viewset.py +55 -3
  142. wagtail/admin/ui/side_panels.py +19 -0
  143. wagtail/admin/ui/sidebar.py +4 -3
  144. wagtail/admin/ui/tables/__init__.py +14 -1
  145. wagtail/admin/ui/tables/pages.py +6 -1
  146. wagtail/admin/urls/pages.py +10 -1
  147. wagtail/admin/urls/workflows.py +6 -1
  148. wagtail/admin/views/account.py +5 -1
  149. wagtail/admin/views/collections.py +0 -2
  150. wagtail/admin/views/generic/base.py +118 -1
  151. wagtail/admin/views/generic/history.py +158 -26
  152. wagtail/admin/views/generic/models.py +113 -99
  153. wagtail/admin/views/home.py +24 -9
  154. wagtail/admin/views/page_privacy.py +54 -30
  155. wagtail/admin/views/pages/history.py +11 -4
  156. wagtail/admin/views/pages/listing.py +76 -89
  157. wagtail/admin/views/pages/preview.py +1 -1
  158. wagtail/admin/views/pages/search.py +27 -71
  159. wagtail/admin/views/pages/usage.py +63 -24
  160. wagtail/admin/views/pages/utils.py +4 -3
  161. wagtail/admin/views/reports/base.py +0 -6
  162. wagtail/admin/views/workflows.py +67 -25
  163. wagtail/admin/viewsets/model.py +4 -3
  164. wagtail/admin/widgets/chooser.py +2 -1
  165. wagtail/api/v2/tests/test_pages.py +15 -2
  166. wagtail/blocks/field_block.py +15 -3
  167. wagtail/blocks/static_block.py +3 -0
  168. wagtail/contrib/forms/locale/cy/LC_MESSAGES/django.mo +0 -0
  169. wagtail/contrib/forms/locale/cy/LC_MESSAGES/django.po +29 -1
  170. wagtail/contrib/forms/locale/en/LC_MESSAGES/django.po +15 -11
  171. wagtail/contrib/forms/templates/wagtailforms/confirm_delete.html +1 -0
  172. wagtail/contrib/forms/templates/wagtailforms/index.html +1 -1
  173. wagtail/contrib/forms/templates/wagtailforms/index_results.html +1 -1
  174. wagtail/contrib/forms/templates/wagtailforms/list_submissions.html +2 -1
  175. wagtail/contrib/forms/templates/wagtailforms/panels/form_responses_panel.html +2 -2
  176. wagtail/contrib/forms/tests/test_views.py +234 -35
  177. wagtail/contrib/forms/utils.py +8 -14
  178. wagtail/contrib/forms/views.py +72 -27
  179. wagtail/contrib/frontend_cache/backends/azure.py +1 -1
  180. wagtail/contrib/frontend_cache/backends/cloudfront.py +2 -2
  181. wagtail/contrib/frontend_cache/backends/dummy.py +11 -0
  182. wagtail/contrib/frontend_cache/tests.py +31 -37
  183. wagtail/contrib/frontend_cache/utils.py +12 -5
  184. wagtail/contrib/redirects/locale/cy/LC_MESSAGES/django.mo +0 -0
  185. wagtail/contrib/redirects/locale/cy/LC_MESSAGES/django.po +16 -1
  186. wagtail/contrib/redirects/locale/en/LC_MESSAGES/django.po +20 -24
  187. wagtail/contrib/redirects/models.py +16 -1
  188. wagtail/contrib/redirects/signal_handlers.py +18 -3
  189. wagtail/contrib/redirects/templates/wagtailredirects/add.html +5 -16
  190. wagtail/contrib/redirects/templates/wagtailredirects/import_summary.html +1 -1
  191. wagtail/contrib/redirects/tests/test_redirects.py +49 -6
  192. wagtail/contrib/redirects/tests/test_signal_handlers.py +42 -1
  193. wagtail/contrib/redirects/views.py +27 -3
  194. wagtail/contrib/search_promotions/locale/cy/LC_MESSAGES/django.mo +0 -0
  195. wagtail/contrib/search_promotions/locale/cy/LC_MESSAGES/django.po +60 -1
  196. wagtail/contrib/search_promotions/locale/en/LC_MESSAGES/django.po +12 -18
  197. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/add.html +5 -8
  198. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/edit.html +15 -10
  199. wagtail/contrib/search_promotions/tests.py +13 -2
  200. wagtail/contrib/search_promotions/views.py +15 -3
  201. wagtail/contrib/settings/locale/cy/LC_MESSAGES/django.mo +0 -0
  202. wagtail/contrib/settings/locale/cy/LC_MESSAGES/django.po +6 -1
  203. wagtail/contrib/settings/locale/en/LC_MESSAGES/django.po +2 -10
  204. wagtail/contrib/settings/templates/wagtailsettings/edit.html +12 -45
  205. wagtail/contrib/simple_translation/locale/cy/LC_MESSAGES/django.mo +0 -0
  206. wagtail/contrib/simple_translation/locale/cy/LC_MESSAGES/django.po +13 -1
  207. wagtail/contrib/simple_translation/locale/dv/LC_MESSAGES/django.mo +0 -0
  208. wagtail/contrib/simple_translation/locale/dv/LC_MESSAGES/django.po +3 -0
  209. wagtail/contrib/simple_translation/locale/en/LC_MESSAGES/django.po +4 -4
  210. wagtail/contrib/simple_translation/locale/sl/LC_MESSAGES/django.mo +0 -0
  211. wagtail/contrib/simple_translation/locale/sl/LC_MESSAGES/django.po +5 -2
  212. wagtail/contrib/simple_translation/tests/test_views.py +51 -0
  213. wagtail/contrib/simple_translation/wagtail_hooks.py +16 -11
  214. wagtail/contrib/styleguide/locale/cy/LC_MESSAGES/django.mo +0 -0
  215. wagtail/contrib/styleguide/locale/cy/LC_MESSAGES/django.po +5 -1
  216. wagtail/contrib/styleguide/locale/en/LC_MESSAGES/django.po +1 -1
  217. wagtail/contrib/table_block/locale/cy/LC_MESSAGES/django.mo +0 -0
  218. wagtail/contrib/table_block/locale/cy/LC_MESSAGES/django.po +27 -1
  219. wagtail/contrib/table_block/locale/en/LC_MESSAGES/django.po +1 -1
  220. wagtail/contrib/typed_table_block/blocks.py +25 -0
  221. wagtail/contrib/typed_table_block/locale/cy/LC_MESSAGES/django.mo +0 -0
  222. wagtail/contrib/typed_table_block/locale/cy/LC_MESSAGES/django.po +12 -1
  223. wagtail/contrib/typed_table_block/locale/en/LC_MESSAGES/django.po +10 -10
  224. wagtail/contrib/typed_table_block/tests.py +24 -1
  225. wagtail/coreutils.py +5 -11
  226. wagtail/documents/admin_urls.py +2 -2
  227. wagtail/documents/locale/cy/LC_MESSAGES/django.mo +0 -0
  228. wagtail/documents/locale/cy/LC_MESSAGES/django.po +10 -0
  229. wagtail/documents/locale/en/LC_MESSAGES/django.po +61 -76
  230. wagtail/documents/migrations/0014_alter_document_file_size.py +18 -0
  231. wagtail/documents/models.py +1 -1
  232. wagtail/documents/rich_text/__init__.py +1 -3
  233. wagtail/documents/static/wagtaildocs/js/add-multiple.js +1 -1
  234. wagtail/documents/templates/wagtaildocs/bulk_actions/confirm_bulk_add_tags.html +5 -1
  235. wagtail/documents/templates/wagtaildocs/bulk_actions/confirm_bulk_add_to_collection.html +5 -1
  236. wagtail/documents/templates/wagtaildocs/bulk_actions/confirm_bulk_delete.html +11 -5
  237. wagtail/documents/templates/wagtaildocs/documents/add.html +13 -41
  238. wagtail/documents/templates/wagtaildocs/documents/edit.html +28 -56
  239. wagtail/documents/templates/wagtaildocs/documents/index.html +1 -4
  240. wagtail/documents/templates/wagtaildocs/homepage/site_summary_documents.html +2 -2
  241. wagtail/documents/templates/wagtaildocs/multiple/add.html +36 -41
  242. wagtail/documents/tests/test_admin_views.py +64 -6
  243. wagtail/documents/tests/test_site_summary.py +3 -3
  244. wagtail/documents/views/documents.py +103 -113
  245. wagtail/documents/views/multiple.py +19 -1
  246. wagtail/embeds/locale/en/LC_MESSAGES/django.po +1 -1
  247. wagtail/embeds/oembed_providers.py +9 -19
  248. wagtail/fields.py +43 -27
  249. wagtail/images/admin_urls.py +2 -2
  250. wagtail/images/blocks.py +230 -2
  251. wagtail/images/fields.py +17 -29
  252. wagtail/images/image_operations.py +1 -1
  253. wagtail/images/locale/cy/LC_MESSAGES/django.mo +0 -0
  254. wagtail/images/locale/cy/LC_MESSAGES/django.po +128 -1
  255. wagtail/images/locale/en/LC_MESSAGES/django.po +119 -129
  256. wagtail/images/migrations/0025_alter_image_file_alter_rendition_file.py +36 -43
  257. wagtail/images/migrations/0027_image_description.py +20 -0
  258. wagtail/images/models.py +120 -45
  259. wagtail/images/rich_text/__init__.py +1 -3
  260. wagtail/images/static/wagtailimages/js/add-multiple.js +1 -1
  261. wagtail/images/static/wagtailimages/js/image-block.js +1 -0
  262. wagtail/images/templates/wagtailimages/bulk_actions/confirm_bulk_add_tags.html +5 -1
  263. wagtail/images/templates/wagtailimages/bulk_actions/confirm_bulk_add_to_collection.html +5 -1
  264. wagtail/images/templates/wagtailimages/bulk_actions/confirm_bulk_delete.html +11 -5
  265. wagtail/images/templates/wagtailimages/chooser/results.html +2 -2
  266. wagtail/images/templates/wagtailimages/homepage/site_summary_images.html +2 -2
  267. wagtail/images/templates/wagtailimages/images/_file_field.html +2 -2
  268. wagtail/images/templates/wagtailimages/images/add.html +13 -31
  269. wagtail/images/templates/wagtailimages/images/edit.html +53 -82
  270. wagtail/images/templates/wagtailimages/images/index.html +1 -4
  271. wagtail/images/templates/wagtailimages/images/url_generator.html +1 -1
  272. wagtail/images/templates/wagtailimages/multiple/add.html +40 -47
  273. wagtail/images/templates/wagtailimages/widgets/image.html +5 -0
  274. wagtail/images/templates/wagtailimages/widgets/image_chooser.html +1 -1
  275. wagtail/images/tests/test_admin_views.py +70 -10
  276. wagtail/images/tests/test_blocks.py +367 -1
  277. wagtail/images/tests/test_image_operations.py +23 -0
  278. wagtail/images/tests/test_models.py +20 -0
  279. wagtail/images/tests/test_signal_handlers.py +99 -95
  280. wagtail/images/tests/test_site_summary.py +3 -3
  281. wagtail/images/tests/test_templatetags.py +11 -7
  282. wagtail/images/tests/tests.py +4 -0
  283. wagtail/images/views/images.py +103 -104
  284. wagtail/images/views/multiple.py +17 -1
  285. wagtail/locale/cy/LC_MESSAGES/django.mo +0 -0
  286. wagtail/locale/cy/LC_MESSAGES/django.po +3 -0
  287. wagtail/locale/en/LC_MESSAGES/django.po +137 -125
  288. wagtail/locale/sl/LC_MESSAGES/django.mo +0 -0
  289. wagtail/locale/sl/LC_MESSAGES/django.po +3 -0
  290. wagtail/locales/locale/en/LC_MESSAGES/django.po +8 -8
  291. wagtail/locales/views.py +4 -1
  292. wagtail/migrations/0089_log_entry_data_json_null_to_object.py +1 -7
  293. wagtail/models/__init__.py +122 -14
  294. wagtail/models/audit_log.py +1 -3
  295. wagtail/models/i18n.py +1 -2
  296. wagtail/project_template/Dockerfile +3 -3
  297. wagtail/project_template/requirements.txt +2 -2
  298. wagtail/query.py +17 -4
  299. wagtail/rich_text/__init__.py +2 -3
  300. wagtail/rich_text/pages.py +2 -4
  301. wagtail/rich_text/rewriters.py +2 -2
  302. wagtail/search/backends/database/mysql/query.py +3 -3
  303. wagtail/search/backends/database/sqlite/query.py +6 -6
  304. wagtail/search/locale/en/LC_MESSAGES/django.po +1 -1
  305. wagtail/sites/locale/en/LC_MESSAGES/django.po +6 -6
  306. wagtail/sites/views.py +0 -1
  307. wagtail/snippets/action_menu.py +14 -4
  308. wagtail/snippets/locale/cy/LC_MESSAGES/django.mo +0 -0
  309. wagtail/snippets/locale/cy/LC_MESSAGES/django.po +73 -1
  310. wagtail/snippets/locale/en/LC_MESSAGES/django.po +27 -33
  311. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser-telepath.js +1 -1
  312. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser.js +1 -1
  313. wagtail/snippets/templates/wagtailsnippets/bulk_actions/confirm_bulk_delete.html +5 -3
  314. wagtail/snippets/templates/wagtailsnippets/snippets/action_menu/locked.html +1 -1
  315. wagtail/snippets/templates/wagtailsnippets/snippets/action_menu/menu.html +1 -11
  316. wagtail/snippets/templates/wagtailsnippets/snippets/action_menu/menu_item.html +1 -6
  317. wagtail/snippets/templates/wagtailsnippets/snippets/action_menu/publish.html +1 -1
  318. wagtail/snippets/templates/wagtailsnippets/snippets/action_menu/save.html +1 -1
  319. wagtail/snippets/templates/wagtailsnippets/snippets/create.html +1 -18
  320. wagtail/snippets/templates/wagtailsnippets/snippets/edit.html +1 -14
  321. wagtail/snippets/templates/wagtailsnippets/snippets/index.html +2 -5
  322. wagtail/snippets/tests/test_preview.py +193 -61
  323. wagtail/snippets/tests/test_snippets.py +247 -38
  324. wagtail/snippets/tests/test_usage.py +5 -0
  325. wagtail/snippets/tests/test_viewset.py +25 -9
  326. wagtail/snippets/tests/test_workflows.py +232 -19
  327. wagtail/snippets/views/snippets.py +1 -10
  328. wagtail/test/numberformat.py +104 -0
  329. wagtail/test/settings.py +10 -0
  330. wagtail/test/settings_ui.py +2 -0
  331. wagtail/test/testapp/migrations/0010_alter_customimage_file_and_more.py +71 -78
  332. wagtail/test/testapp/migrations/0040_nocreatablesubpagetypespage_nosubpagetypespage.py +54 -0
  333. wagtail/test/testapp/migrations/0041_alter_jsonstreammodel_options.py +17 -0
  334. wagtail/test/testapp/migrations/0042_alter_customdocument_file_size_and_more.py +28 -0
  335. wagtail/test/testapp/migrations/0043_customimage_description.py +41 -0
  336. wagtail/test/testapp/migrations/0044_custompreviewsizesmodel_custompreviewsizespage.py +52 -0
  337. wagtail/test/testapp/migrations/0045_alter_streampage_body.py +52 -0
  338. wagtail/test/testapp/models.py +62 -4
  339. wagtail/test/testapp/rich_text.py +2 -2
  340. wagtail/test/testapp/templates/tests/form_page_landing.html +2 -1
  341. wagtail/test/testapp/urls.py +5 -0
  342. wagtail/test/testapp/views.py +5 -0
  343. wagtail/test/utils/page_tests.py +5 -5
  344. wagtail/test/utils/template_tests.py +2 -2
  345. wagtail/tests/test_blocks.py +15 -0
  346. wagtail/tests/test_page_permissions.py +109 -0
  347. wagtail/tests/test_revision_model.py +27 -0
  348. wagtail/tests/test_signals.py +21 -2
  349. wagtail/tests/test_tests.py +30 -0
  350. wagtail/users/locale/cy/LC_MESSAGES/django.mo +0 -0
  351. wagtail/users/locale/cy/LC_MESSAGES/django.po +118 -1
  352. wagtail/users/locale/dv/LC_MESSAGES/django.mo +0 -0
  353. wagtail/users/locale/dv/LC_MESSAGES/django.po +3 -0
  354. wagtail/users/locale/en/LC_MESSAGES/django.po +89 -113
  355. wagtail/users/locale/sl/LC_MESSAGES/django.mo +0 -0
  356. wagtail/users/locale/sl/LC_MESSAGES/django.po +3 -0
  357. wagtail/users/migrations/0014_userprofile_contrast.py +23 -0
  358. wagtail/users/models.py +11 -0
  359. wagtail/users/templates/wagtailusers/bulk_actions/confirm_bulk_assign_role.html +5 -1
  360. wagtail/users/templates/wagtailusers/bulk_actions/confirm_bulk_delete.html +5 -1
  361. wagtail/users/templates/wagtailusers/bulk_actions/confirm_bulk_set_active_state.html +5 -1
  362. wagtail/users/templates/wagtailusers/groups/create.html +19 -17
  363. wagtail/users/templates/wagtailusers/groups/edit.html +2 -40
  364. wagtail/users/templates/wagtailusers/groups/includes/formatted_permissions.html +1 -62
  365. wagtail/users/templates/wagtailusers/groups/includes/page_permissions_formset.html +0 -1
  366. wagtail/users/templates/wagtailusers/users/create.html +46 -50
  367. wagtail/users/templates/wagtailusers/users/edit.html +45 -62
  368. wagtail/users/templates/wagtailusers/users/index.html +1 -4
  369. wagtail/users/templatetags/wagtailusers_tags.py +1 -5
  370. wagtail/users/tests/test_admin_views.py +85 -66
  371. wagtail/users/views/groups.py +4 -1
  372. wagtail/users/views/users.py +2 -0
  373. {wagtail-6.2.2.dist-info → wagtail-6.3rc2.dist-info}/METADATA +6 -6
  374. {wagtail-6.2.2.dist-info → wagtail-6.3rc2.dist-info}/RECORD +378 -367
  375. wagtail/admin/static/wagtailadmin/js/preview-panel.js +0 -2
  376. wagtail/admin/static/wagtailadmin/js/preview-panel.js.LICENSE.txt +0 -11
  377. wagtail/admin/templates/wagtailadmin/generic/history_results.html +0 -1
  378. wagtail/admin/templates/wagtailadmin/page_privacy/ancestor_privacy.html +0 -3
  379. wagtail/admin/templates/wagtailadmin/pages/usage_results.html +0 -6
  380. wagtail/admin/templates/wagtailadmin/shared/workflow_history/index.html +0 -17
  381. wagtail/admin/templates/wagtailadmin/shared/workflow_history/results.html +0 -17
  382. wagtail/admin/templates/wagtailadmin/workflows/usage.html +0 -44
  383. {wagtail-6.2.2.dist-info → wagtail-6.3rc2.dist-info}/LICENSE +0 -0
  384. {wagtail-6.2.2.dist-info → wagtail-6.3rc2.dist-info}/WHEEL +0 -0
  385. {wagtail-6.2.2.dist-info → wagtail-6.3rc2.dist-info}/entry_points.txt +0 -0
  386. {wagtail-6.2.2.dist-info → wagtail-6.3rc2.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,8 @@
1
+ from collections import defaultdict
1
2
  from datetime import timedelta
2
3
 
3
4
  import django_filters
4
5
  from django.contrib.admin.utils import quote
5
- from django.core.paginator import Paginator
6
6
  from django.forms import CheckboxSelectMultiple
7
7
  from django.shortcuts import get_object_or_404
8
8
  from django.urls import reverse
@@ -165,6 +165,7 @@ class ActionColumn(Column):
165
165
 
166
166
  if (
167
167
  (url_name := self.url_names.get("revisions_unschedule"))
168
+ and instance.revision
168
169
  and instance.revision.approved_go_live_at
169
170
  and self.user_can_unschedule
170
171
  ):
@@ -198,9 +199,7 @@ class LogEntryUserColumn(UserColumn):
198
199
  class HistoryView(PermissionCheckedMixin, BaseObjectMixin, BaseListingView):
199
200
  any_permission_required = ["add", "change", "delete"]
200
201
  page_title = gettext_lazy("History")
201
- results_template_name = "wagtailadmin/generic/history_results.html"
202
202
  header_icon = "history"
203
- is_searchable = False
204
203
  paginate_by = 20
205
204
  filterset_class = HistoryFilterSet
206
205
  history_url_name = None
@@ -298,10 +297,13 @@ class HistoryView(PermissionCheckedMixin, BaseObjectMixin, BaseListingView):
298
297
  def user_can_unschedule(self):
299
298
  return self.user_has_permission("publish")
300
299
 
300
+ @cached_property
301
+ def verbose_name_plural(self):
302
+ return BaseLogEntry._meta.verbose_name_plural
303
+
301
304
  def get_context_data(self, *args, object_list=None, **kwargs):
302
305
  context = super().get_context_data(*args, object_list=object_list, **kwargs)
303
306
  context["object"] = self.object
304
- context["model_opts"] = BaseLogEntry._meta
305
307
  return context
306
308
 
307
309
  def get_base_queryset(self):
@@ -324,27 +326,77 @@ class HistoryView(PermissionCheckedMixin, BaseObjectMixin, BaseListingView):
324
326
  return kwargs
325
327
 
326
328
 
327
- class WorkflowHistoryView(BaseObjectMixin, WagtailAdminTemplateMixin, TemplateView):
328
- template_name = "wagtailadmin/shared/workflow_history/index.html"
329
- page_kwarg = "p"
330
- workflow_history_url_name = None
329
+ class WorkflowHistoryView(BaseObjectMixin, BaseListingView):
330
+ template_name = "wagtailadmin/shared/workflow_history/listing.html"
331
+ results_template_name = "wagtailadmin/shared/workflow_history/listing_results.html"
332
+ paginate_by = 20
333
+ index_url_name = None
334
+ edit_url_name = None
331
335
  workflow_history_detail_url_name = None
336
+ page_title = gettext_lazy("Workflow history")
337
+ context_object_name = "workflow_states"
332
338
 
333
339
  @cached_property
334
- def workflow_states(self):
335
- return WorkflowState.objects.for_instance(self.object).order_by("-created_at")
340
+ def index_url(self):
341
+ if self.index_url_name:
342
+ return reverse(self.index_url_name)
336
343
 
337
- def get_context_data(self, **kwargs):
338
- context = super().get_context_data(**kwargs)
344
+ @cached_property
345
+ def edit_url(self):
346
+ if self.edit_url_name:
347
+ return reverse(self.edit_url_name, args=(quote(self.object.pk),))
339
348
 
340
- paginator = Paginator(self.workflow_states, per_page=20)
341
- workflow_states = paginator.get_page(self.request.GET.get(self.page_kwarg))
349
+ @cached_property
350
+ def header_buttons(self):
351
+ buttons = []
352
+ if self.edit_url:
353
+ buttons.append(
354
+ HeaderButton(gettext("Edit"), url=self.edit_url, icon_name="edit")
355
+ )
356
+ return buttons
357
+
358
+ def get_page_subtitle(self):
359
+ return get_latest_str(self.object)
342
360
 
361
+ def get_breadcrumbs_items(self):
362
+ items = []
363
+ if self.index_url:
364
+ items.append(
365
+ {
366
+ "url": self.index_url,
367
+ "label": capfirst(self.model._meta.verbose_name_plural),
368
+ }
369
+ )
370
+
371
+ if self.edit_url:
372
+ items.append(
373
+ {
374
+ "url": self.edit_url,
375
+ "label": self.get_page_subtitle(),
376
+ }
377
+ )
378
+ items.append(
379
+ {
380
+ "url": "",
381
+ "label": self.get_page_title(),
382
+ "sublabel": self.get_page_subtitle(),
383
+ }
384
+ )
385
+
386
+ return self.breadcrumbs_items + items
387
+
388
+ def get_base_queryset(self):
389
+ return (
390
+ WorkflowState.objects.for_instance(self.object)
391
+ .select_related("workflow", "requested_by")
392
+ .order_by("-created_at")
393
+ )
394
+
395
+ def get_context_data(self, **kwargs):
396
+ context = super().get_context_data(**kwargs)
343
397
  context.update(
344
398
  {
345
399
  "object": self.object,
346
- "workflow_states": workflow_states,
347
- "workflow_history_url_name": self.workflow_history_url_name,
348
400
  "workflow_history_detail_url_name": self.workflow_history_detail_url_name,
349
401
  "model_opts": self.object._meta,
350
402
  }
@@ -356,36 +408,105 @@ class WorkflowHistoryDetailView(
356
408
  BaseObjectMixin, WagtailAdminTemplateMixin, TemplateView
357
409
  ):
358
410
  template_name = "wagtailadmin/shared/workflow_history/detail.html"
411
+ index_url_name = None
412
+ edit_url_name = None
359
413
  workflow_state_url_kwarg = "workflow_state_id"
360
414
  workflow_history_url_name = None
361
415
  page_title = gettext_lazy("Workflow progress")
362
416
  header_icon = "list-ul"
363
417
  object_icon = "doc-empty-inverse"
418
+ _show_breadcrumbs = True
419
+
420
+ @cached_property
421
+ def index_url(self):
422
+ if self.index_url_name:
423
+ return reverse(self.index_url_name)
424
+
425
+ @cached_property
426
+ def edit_url(self):
427
+ if self.edit_url_name:
428
+ return reverse(self.edit_url_name, args=(quote(self.object.pk),))
429
+
430
+ @cached_property
431
+ def workflow_history_url(self):
432
+ if self.workflow_history_url_name:
433
+ return reverse(
434
+ self.workflow_history_url_name, args=(quote(self.object.pk),)
435
+ )
436
+
437
+ def get_breadcrumbs_items(self):
438
+ items = []
439
+ if self.index_url:
440
+ items.append(
441
+ {
442
+ "url": self.index_url,
443
+ "label": capfirst(self.model._meta.verbose_name_plural),
444
+ }
445
+ )
446
+ if self.edit_url:
447
+ items.append(
448
+ {
449
+ "url": self.edit_url,
450
+ "label": self.get_page_subtitle(),
451
+ }
452
+ )
453
+ if self.workflow_history_url:
454
+ items.append(
455
+ {
456
+ "url": self.workflow_history_url,
457
+ "label": gettext("Workflow history"),
458
+ }
459
+ )
460
+ items.append(
461
+ {
462
+ "url": "",
463
+ "label": self.get_page_title(),
464
+ "sublabel": self.get_page_subtitle(),
465
+ }
466
+ )
467
+ return self.breadcrumbs_items + items
468
+
469
+ def get_page_subtitle(self):
470
+ return get_latest_str(self.object)
471
+
472
+ @cached_property
473
+ def header_buttons(self):
474
+ buttons = []
475
+ if self.edit_url:
476
+ buttons.append(
477
+ HeaderButton(
478
+ gettext("Edit / Review"),
479
+ url=self.edit_url,
480
+ icon_name="edit",
481
+ )
482
+ )
483
+ return buttons
364
484
 
365
485
  @cached_property
366
486
  def workflow_state(self):
367
487
  return get_object_or_404(
368
- WorkflowState.objects.for_instance(self.object).filter(
369
- id=self.kwargs[self.workflow_state_url_kwarg]
370
- ),
488
+ WorkflowState.objects.for_instance(self.object)
489
+ .filter(id=self.kwargs[self.workflow_state_url_kwarg])
490
+ .select_related("requested_by", "requested_by__wagtail_userprofile")
371
491
  )
372
492
 
373
493
  @cached_property
374
494
  def revisions(self):
375
495
  """
376
- Get QuerySet of all revisions that have existed during this workflow state.
377
- It's possible that the object is edited while the workflow is running,
496
+ Get QuerySet of all revisions that caused a task state change during this
497
+ workflow state. It's possible that a task is rejected and then resubmitted,
378
498
  so some tasks may be repeated. All tasks that have been completed no matter
379
499
  what revision needs to be displayed on this page.
380
500
  """
381
501
  return (
382
502
  Revision.objects.for_instance(self.object)
503
+ .select_related("user")
383
504
  .filter(
384
505
  id__in=TaskState.objects.filter(
385
506
  workflow_state=self.workflow_state
386
507
  ).values_list("revision_id", flat=True),
387
508
  )
388
- .order_by("-created_at")
509
+ .order_by("created_at")
389
510
  )
390
511
 
391
512
  @cached_property
@@ -395,14 +516,25 @@ class WorkflowHistoryDetailView(
395
516
  @cached_property
396
517
  def task_states_by_revision(self):
397
518
  """Get QuerySet of tasks completed for each revision."""
519
+ # Fetch task states for the revisions in one query instead of one query per revision
520
+ task_states = (
521
+ TaskState.objects.filter(
522
+ workflow_state=self.workflow_state,
523
+ revision_id__in=[revision.pk for revision in self.revisions],
524
+ )
525
+ .prefetch_related("task", "finished_by")
526
+ .specific()
527
+ )
528
+ task_states_by_revision_id = defaultdict(list)
529
+ for task_state in task_states:
530
+ task_states_by_revision_id[task_state.revision_id].append(task_state)
531
+
398
532
  task_states_by_revision_task = [
399
533
  (
400
534
  revision,
401
535
  {
402
536
  task_state.task: task_state
403
- for task_state in TaskState.objects.filter(
404
- workflow_state=self.workflow_state, revision=revision
405
- ).specific()
537
+ for task_state in task_states_by_revision_id[revision.pk]
406
538
  },
407
539
  )
408
540
  for revision in self.revisions
@@ -422,6 +554,7 @@ class WorkflowHistoryDetailView(
422
554
  """Generate timeline."""
423
555
  completed_task_states = (
424
556
  TaskState.objects.filter(workflow_state=self.workflow_state)
557
+ .select_related("finished_by", "task")
425
558
  .exclude(finished_at__isnull=True)
426
559
  .exclude(status=TaskState.STATUS_CANCELLED)
427
560
  )
@@ -476,7 +609,6 @@ class WorkflowHistoryDetailView(
476
609
  context.update(
477
610
  {
478
611
  "object": self.object,
479
- "object_icon": self.object_icon,
480
612
  "workflow_state": self.workflow_state,
481
613
  "tasks": self.tasks,
482
614
  "task_states_by_revision": self.task_states_by_revision,
@@ -8,7 +8,6 @@ from django.core.exceptions import (
8
8
  PermissionDenied,
9
9
  )
10
10
  from django.db import models, transaction
11
- from django.db.models import Q
12
11
  from django.db.models.constants import LOOKUP_SEP
13
12
  from django.db.models.functions import Cast
14
13
  from django.http import Http404, HttpResponseRedirect
@@ -29,7 +28,6 @@ from wagtail.actions.unpublish import UnpublishAction
29
28
  from wagtail.admin import messages
30
29
  from wagtail.admin.filters import WagtailFilterSet
31
30
  from wagtail.admin.forms.models import WagtailAdminModelForm
32
- from wagtail.admin.forms.search import SearchForm
33
31
  from wagtail.admin.panels import get_edit_handler
34
32
  from wagtail.admin.ui.components import Component, MediaContainer
35
33
  from wagtail.admin.ui.fields import display_class_registry
@@ -43,6 +41,7 @@ from wagtail.admin.ui.tables import (
43
41
  from wagtail.admin.utils import get_latest_str, get_valid_next_url_from_request
44
42
  from wagtail.admin.views.mixins import SpreadsheetExportMixin
45
43
  from wagtail.admin.widgets.button import (
44
+ Button,
46
45
  ButtonWithDropdown,
47
46
  HeaderButton,
48
47
  ListingButton,
@@ -51,8 +50,8 @@ from wagtail.log_actions import log
51
50
  from wagtail.log_actions import registry as log_registry
52
51
  from wagtail.models import DraftStateMixin, Locale, ReferenceIndex
53
52
  from wagtail.models.audit_log import ModelLogEntry
54
- from wagtail.search.backends import get_search_backend
55
53
  from wagtail.search.index import class_is_indexed
54
+ from wagtail.utils.deprecation import RemovedInWagtail70Warning
56
55
 
57
56
  from .base import BaseListingView, WagtailAdminTemplateMixin
58
57
  from .mixins import BeforeAfterHookMixin, HookResponseMixin, LocaleMixin, PanelMixin
@@ -74,57 +73,40 @@ class IndexView(
74
73
  inspect_url_name = None
75
74
  delete_url_name = None
76
75
  any_permission_required = ["add", "change", "delete", "view"]
77
- search_fields = None
78
- search_backend_name = "default"
79
- is_searchable = None
80
- search_kwarg = "q"
81
76
  columns = None # If not explicitly specified, will be derived from list_display
82
77
  list_display = ["__str__", UpdatedAtColumn()]
83
78
  list_filter = None
84
79
  show_other_searches = False
85
80
 
86
- def setup(self, request, *args, **kwargs):
87
- super().setup(request, *args, **kwargs)
88
-
89
- if not self.filterset_class:
90
- # Allow filterset_class to be dynamically constructed from list_filter
91
- self.filterset_class = self.get_filterset_class()
92
-
93
- self.setup_search()
94
-
95
- def setup_search(self):
96
- self.is_searchable = self.get_is_searchable()
97
- self.search_url = self.get_search_url()
98
- self.search_form = self.get_search_form()
99
- self.is_searching = False
100
- self.search_query = None
101
-
102
- if self.search_form and self.search_form.is_valid():
103
- self.search_query = self.search_form.cleaned_data[self.search_kwarg]
104
- self.is_searching = bool(self.search_query)
105
-
106
- def get_is_searchable(self):
107
- if self.model is None:
108
- return False
109
- if self.is_searchable is None:
110
- return class_is_indexed(self.model) or self.search_fields
111
- return self.is_searchable
112
-
113
81
  def get_search_url(self):
114
- if not self.is_searchable:
115
- return None
82
+ # This is only used by views that do not use breadcrumbs, thus uses the
83
+ # legacy header.html. The search in that header template accepts both
84
+ # the search_url (which really should be search_url_name) and the
85
+ # index_results_url. This means we can advise using the latter instead,
86
+ # without having to instruct how to set up breadcrumbs.
87
+ warnings.warn(
88
+ "`IndexView.get_search_url` is deprecated. "
89
+ "Use `IndexView.get_index_results_url` instead.",
90
+ RemovedInWagtail70Warning,
91
+ )
116
92
  return self.index_url_name
117
93
 
118
- def get_search_form(self):
119
- if self.model is None or not self.is_searchable:
120
- return None
94
+ @cached_property
95
+ def search_url(self):
96
+ return self.get_search_url()
121
97
 
122
- if self.is_searchable and self.search_kwarg in self.request.GET:
123
- return SearchForm(self.request.GET)
98
+ @cached_property
99
+ def is_searchable(self):
100
+ # Do not automatically enable search if the model is not indexed and
101
+ # search_fields is not defined.
102
+ if not (self.model and class_is_indexed(self.model)) and not self.search_fields:
103
+ return False
124
104
 
125
- return SearchForm()
105
+ # Require the results-only view to be set up before enabling search
106
+ return bool(self.index_results_url or self.search_url)
126
107
 
127
- def get_filterset_class(self):
108
+ @cached_property
109
+ def filterset_class(self):
128
110
  # Allow filterset_class to be dynamically constructed from list_filter.
129
111
 
130
112
  # If the model is translatable, ensure a ``WagtailFilterSet`` subclass
@@ -197,43 +179,6 @@ class IndexView(
197
179
 
198
180
  return queryset
199
181
 
200
- def get_queryset(self):
201
- queryset = super().get_queryset()
202
- queryset = self.search_queryset(queryset)
203
- return queryset
204
-
205
- def search_queryset(self, queryset):
206
- if not self.is_searching:
207
- return queryset
208
-
209
- if class_is_indexed(queryset.model) and self.search_backend_name:
210
- search_backend = get_search_backend(self.search_backend_name)
211
- if queryset.model.get_autocomplete_search_fields():
212
- return search_backend.autocomplete(
213
- self.search_query,
214
- queryset,
215
- fields=self.search_fields,
216
- order_by_relevance=(not self.is_explicitly_ordered),
217
- )
218
- else:
219
- # fall back on non-autocompleting search
220
- warnings.warn(
221
- f"{queryset.model} is defined as Indexable but does not specify "
222
- "any AutocompleteFields. Searches within the admin will only "
223
- "respond to complete words.",
224
- category=RuntimeWarning,
225
- )
226
- return search_backend.search(
227
- self.search_query,
228
- queryset,
229
- fields=self.search_fields,
230
- order_by_relevance=(not self.is_explicitly_ordered),
231
- )
232
- query = Q()
233
- for field in self.search_fields or []:
234
- query |= Q(**{field + "__icontains": self.search_query})
235
- return queryset.filter(query)
236
-
237
182
  def _get_title_column_class(self, column_class):
238
183
  if not issubclass(column_class, ButtonsColumnMixin):
239
184
 
@@ -356,13 +301,6 @@ class IndexView(
356
301
  return capfirst(self.model._meta.verbose_name_plural)
357
302
  return self.page_title
358
303
 
359
- def get_breadcrumbs_items(self):
360
- if not self.model:
361
- return self.breadcrumbs_items
362
- return self.breadcrumbs_items + [
363
- {"url": "", "label": capfirst(self.model._meta.verbose_name_plural)},
364
- ]
365
-
366
304
  @cached_property
367
305
  def header_buttons(self):
368
306
  buttons = []
@@ -453,6 +391,12 @@ class IndexView(
453
391
  )
454
392
  return _("Add")
455
393
 
394
+ @cached_property
395
+ def verbose_name_plural(self):
396
+ if self.model:
397
+ return self.model._meta.verbose_name_plural
398
+ return None
399
+
456
400
  def get_context_data(self, *args, **kwargs):
457
401
  context = super().get_context_data(*args, **kwargs)
458
402
 
@@ -461,11 +405,12 @@ class IndexView(
461
405
  context["add_url"] = context["header_action_url"] = self.add_url
462
406
  context["header_action_label"] = self.add_item_label
463
407
 
464
- context["is_searchable"] = self.is_searchable
465
- context["search_url"] = self.get_search_url()
466
- context["search_form"] = self.search_form
467
- context["is_searching"] = self.is_searching
468
- context["query_string"] = self.search_query
408
+ # RemovedInWagtail70Warning:
409
+ # Remove these in favor of using search_form and index_results_url
410
+ if self.is_searchable and not self.index_results_url:
411
+ context["is_searchable"] = self.is_searchable
412
+ context["search_url"] = self.search_url
413
+
469
414
  context["model_opts"] = self.model and self.model._meta
470
415
  return context
471
416
 
@@ -498,6 +443,7 @@ class CreateView(
498
443
  "The %(model_name)s could not be created due to errors."
499
444
  )
500
445
  submit_button_label = gettext_lazy("Create")
446
+ submit_button_active_label = gettext_lazy("Creating…")
501
447
  actions = ["create"]
502
448
 
503
449
  def setup(self, request, *args, **kwargs):
@@ -588,14 +534,20 @@ class CreateView(
588
534
  % {"model_name": self.model and self.model._meta.verbose_name}
589
535
  )
590
536
 
537
+ @cached_property
538
+ def has_unsaved_changes(self):
539
+ return self.form.is_bound
540
+
591
541
  def get_context_data(self, **kwargs):
592
542
  context = super().get_context_data(**kwargs)
593
543
  self.form = context.get("form")
594
544
  side_panels = self.get_side_panels()
595
545
  context["action_url"] = self.add_url
596
546
  context["submit_button_label"] = self.submit_button_label
547
+ context["submit_button_active_label"] = self.submit_button_active_label
597
548
  context["side_panels"] = side_panels
598
549
  context["media"] += side_panels.media
550
+ context["has_unsaved_changes"] = self.has_unsaved_changes
599
551
  return context
600
552
 
601
553
  def get_side_panels(self):
@@ -705,8 +657,10 @@ class EditView(
705
657
  form_class = None
706
658
  index_url_name = None
707
659
  edit_url_name = None
660
+ copy_url_name = None
708
661
  delete_url_name = None
709
662
  history_url_name = None
663
+ inspect_url_name = None
710
664
  usage_url_name = None
711
665
  page_title = gettext_lazy("Editing")
712
666
  context_object_name = None
@@ -716,6 +670,7 @@ class EditView(
716
670
  success_message = gettext_lazy("%(model_name)s '%(object)s' updated.")
717
671
  error_message = gettext_lazy("The %(model_name)s could not be saved due to errors.")
718
672
  submit_button_label = gettext_lazy("Save")
673
+ submit_button_active_label = gettext_lazy("Saving…")
719
674
  actions = ["edit"]
720
675
 
721
676
  def setup(self, request, *args, **kwargs):
@@ -779,6 +734,42 @@ class EditView(
779
734
  .first()
780
735
  )
781
736
 
737
+ @cached_property
738
+ def can_delete(self):
739
+ return self.user_has_permission_for_instance("delete", self.object)
740
+
741
+ @cached_property
742
+ def header_more_buttons(self):
743
+ buttons = []
744
+ if copy_url := self.get_copy_url():
745
+ buttons.append(
746
+ Button(
747
+ _("Copy"),
748
+ url=copy_url,
749
+ icon_name="copy",
750
+ priority=10,
751
+ )
752
+ )
753
+ if self.can_delete and (delete_url := self.get_delete_url()):
754
+ buttons.append(
755
+ Button(
756
+ self.delete_item_label,
757
+ url=delete_url,
758
+ icon_name="bin",
759
+ priority=20,
760
+ )
761
+ )
762
+ if inspect_url := self.get_inspect_url():
763
+ buttons.append(
764
+ Button(
765
+ _("Inspect"),
766
+ url=inspect_url,
767
+ icon_name="info-circle",
768
+ priority=30,
769
+ )
770
+ )
771
+ return buttons
772
+
782
773
  def get_edit_url(self):
783
774
  if not self.edit_url_name:
784
775
  raise ImproperlyConfigured(
@@ -787,6 +778,10 @@ class EditView(
787
778
  )
788
779
  return reverse(self.edit_url_name, args=(quote(self.object.pk),))
789
780
 
781
+ def get_copy_url(self):
782
+ if self.copy_url_name and self.user_has_permission("add"):
783
+ return reverse(self.copy_url_name, args=(quote(self.object.pk),))
784
+
790
785
  def get_delete_url(self):
791
786
  if self.delete_url_name:
792
787
  return reverse(self.delete_url_name, args=(quote(self.object.pk),))
@@ -795,6 +790,10 @@ class EditView(
795
790
  if self.history_url_name:
796
791
  return reverse(self.history_url_name, args=(quote(self.object.pk),))
797
792
 
793
+ def get_inspect_url(self):
794
+ if self.inspect_url_name:
795
+ return reverse(self.inspect_url_name, args=(quote(self.object.pk),))
796
+
798
797
  def get_usage_url(self):
799
798
  if self.usage_url_name:
800
799
  return reverse(self.usage_url_name, args=[quote(self.object.pk)])
@@ -895,6 +894,10 @@ class EditView(
895
894
  messages.validation_error(self.request, error_message, form)
896
895
  return super().form_invalid(form)
897
896
 
897
+ @cached_property
898
+ def has_unsaved_changes(self):
899
+ return self.form.is_bound
900
+
898
901
  def get_context_data(self, **kwargs):
899
902
  context = super().get_context_data(**kwargs)
900
903
  self.form = context.get("form")
@@ -904,9 +907,9 @@ class EditView(
904
907
  context["side_panels"] = side_panels
905
908
  context["media"] += side_panels.media
906
909
  context["submit_button_label"] = self.submit_button_label
907
- context["can_delete"] = self.user_has_permission_for_instance(
908
- "delete", self.object
909
- )
910
+ context["submit_button_active_label"] = self.submit_button_active_label
911
+ context["has_unsaved_changes"] = self.has_unsaved_changes
912
+ context["can_delete"] = self.can_delete
910
913
  if context["can_delete"]:
911
914
  context["delete_url"] = self.get_delete_url()
912
915
  context["delete_item_label"] = self.delete_item_label
@@ -1034,7 +1037,7 @@ class DeleteView(
1034
1037
  class InspectView(PermissionCheckedMixin, WagtailAdminTemplateMixin, TemplateView):
1035
1038
  any_permission_required = ["add", "change", "delete", "view"]
1036
1039
  template_name = "wagtailadmin/generic/inspect.html"
1037
- page_title = gettext_lazy("Inspecting")
1040
+ page_title = gettext_lazy("Inspect")
1038
1041
  model = None
1039
1042
  index_url_name = None
1040
1043
  edit_url_name = None
@@ -1071,12 +1074,25 @@ class InspectView(PermissionCheckedMixin, WagtailAdminTemplateMixin, TemplateVie
1071
1074
  items.append(
1072
1075
  {
1073
1076
  "url": "",
1074
- "label": _("Inspect"),
1077
+ "label": self.get_page_title(),
1075
1078
  "sublabel": object_str,
1076
1079
  }
1077
1080
  )
1078
1081
  return self.breadcrumbs_items + items
1079
1082
 
1083
+ @cached_property
1084
+ def header_more_buttons(self):
1085
+ buttons = []
1086
+ if edit_url := self.get_edit_url():
1087
+ buttons.append(
1088
+ Button(_("Edit"), url=edit_url, icon_name="edit", priority=10)
1089
+ )
1090
+ if delete_url := self.get_delete_url():
1091
+ buttons.append(
1092
+ Button(_("Delete"), url=delete_url, icon_name="bin", priority=20)
1093
+ )
1094
+ return buttons
1095
+
1080
1096
  def get_fields(self):
1081
1097
  fields = self.fields or [
1082
1098
  f.name
@@ -1146,8 +1162,6 @@ class InspectView(PermissionCheckedMixin, WagtailAdminTemplateMixin, TemplateVie
1146
1162
  context = super().get_context_data(**kwargs)
1147
1163
  context["object"] = self.object
1148
1164
  context["fields"] = self.get_fields_context()
1149
- context["edit_url"] = self.get_edit_url()
1150
- context["delete_url"] = self.get_delete_url()
1151
1165
  return context
1152
1166
 
1153
1167