wagtail 6.0.2__py3-none-any.whl → 6.1rc1__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 (355) hide show
  1. wagtail/__init__.py +1 -1
  2. wagtail/admin/checks.py +51 -0
  3. wagtail/admin/compare.py +1 -1
  4. wagtail/admin/filters.py +70 -1
  5. wagtail/admin/forms/account.py +1 -1
  6. wagtail/admin/forms/collections.py +15 -0
  7. wagtail/admin/forms/pages.py +49 -0
  8. wagtail/admin/locale/de/LC_MESSAGES/django.mo +0 -0
  9. wagtail/admin/locale/de/LC_MESSAGES/django.po +5 -5
  10. wagtail/admin/locale/en/LC_MESSAGES/django.po +474 -385
  11. wagtail/admin/locale/en/LC_MESSAGES/djangojs.po +3 -3
  12. wagtail/admin/locale/pt_PT/LC_MESSAGES/django.mo +0 -0
  13. wagtail/admin/locale/pt_PT/LC_MESSAGES/django.po +73 -2
  14. wagtail/admin/locale/ro/LC_MESSAGES/django.mo +0 -0
  15. wagtail/admin/locale/ro/LC_MESSAGES/django.po +3 -3
  16. wagtail/admin/panels/comment_panel.py +1 -1
  17. wagtail/admin/panels/field_panel.py +1 -1
  18. wagtail/admin/rich_text/converters/editor_html.py +3 -1
  19. wagtail/admin/rich_text/editors/draftail/__init__.py +28 -2
  20. wagtail/admin/static/wagtailadmin/css/core.css +1 -1
  21. wagtail/admin/static/wagtailadmin/css/panels/draftail.css +1 -1
  22. wagtail/admin/static/wagtailadmin/images/favicon.ico +0 -0
  23. wagtail/admin/static/wagtailadmin/js/bulk-actions.js +1 -1
  24. wagtail/admin/static/wagtailadmin/js/chooser-modal.js +1 -1
  25. wagtail/admin/static/wagtailadmin/js/chooser-widget-telepath.js +1 -1
  26. wagtail/admin/static/wagtailadmin/js/chooser-widget.js +1 -1
  27. wagtail/admin/static/wagtailadmin/js/comments.js +1 -1
  28. wagtail/admin/static/wagtailadmin/js/core.js +1 -1
  29. wagtail/admin/static/wagtailadmin/js/core.js.LICENSE.txt +1 -1
  30. wagtail/admin/static/wagtailadmin/js/date-time-chooser.js +1 -1
  31. wagtail/admin/static/wagtailadmin/js/draftail.js +1 -1
  32. wagtail/admin/static/wagtailadmin/js/expanding-formset.js +1 -1
  33. wagtail/admin/static/wagtailadmin/js/filtered-select.js +1 -1
  34. wagtail/admin/static/wagtailadmin/js/modal-workflow.js +1 -1
  35. wagtail/admin/static/wagtailadmin/js/page-chooser-modal.js +1 -1
  36. wagtail/admin/static/wagtailadmin/js/page-chooser-telepath.js +1 -1
  37. wagtail/admin/static/wagtailadmin/js/page-chooser.js +1 -1
  38. wagtail/admin/static/wagtailadmin/js/preview-panel.js +1 -1
  39. wagtail/admin/static/wagtailadmin/js/privacy-switch.js +1 -1
  40. wagtail/admin/static/wagtailadmin/js/sidebar.js +1 -1
  41. wagtail/admin/static/wagtailadmin/js/task-chooser-modal.js +1 -1
  42. wagtail/admin/static/wagtailadmin/js/task-chooser.js +1 -1
  43. wagtail/admin/static/wagtailadmin/js/telepath/blocks.js +1 -1
  44. wagtail/admin/static/wagtailadmin/js/telepath/telepath.js +1 -1
  45. wagtail/admin/static/wagtailadmin/js/telepath/widgets.js +1 -1
  46. wagtail/admin/static/wagtailadmin/js/userbar.js +1 -1
  47. wagtail/admin/static/wagtailadmin/js/vendor.js +1 -1
  48. wagtail/admin/static/wagtailadmin/js/vendor.js.LICENSE.txt +4 -4
  49. wagtail/admin/static/wagtailadmin/js/wagtailadmin.js +1 -1
  50. wagtail/admin/static/wagtailadmin/js/workflow-action.js +1 -1
  51. wagtail/admin/staticfiles.py +1 -0
  52. wagtail/admin/templates/wagtailadmin/base.html +1 -0
  53. wagtail/admin/templates/wagtailadmin/collection_privacy/set_privacy.html +3 -1
  54. wagtail/admin/templates/wagtailadmin/collections/index_results.html +10 -0
  55. wagtail/admin/templates/wagtailadmin/generic/base.html +1 -9
  56. wagtail/admin/templates/wagtailadmin/generic/form.html +3 -2
  57. wagtail/admin/templates/wagtailadmin/generic/history/action_cell.html +27 -0
  58. wagtail/admin/templates/wagtailadmin/home/workflow_objects_to_moderate.html +3 -3
  59. wagtail/admin/templates/wagtailadmin/icons/keyboard.svg +1 -0
  60. wagtail/admin/templates/wagtailadmin/page_privacy/set_privacy.html +3 -1
  61. wagtail/admin/templates/wagtailadmin/pages/_editor_js.html +0 -14
  62. wagtail/admin/templates/wagtailadmin/pages/action_menu/save_draft.html +3 -1
  63. wagtail/admin/templates/wagtailadmin/pages/choose_parent.html +17 -0
  64. wagtail/admin/templates/wagtailadmin/pages/explorable_index.html +8 -0
  65. wagtail/admin/templates/wagtailadmin/pages/history.html +1 -61
  66. wagtail/admin/templates/wagtailadmin/pages/index.html +1 -3
  67. wagtail/admin/templates/wagtailadmin/pages/listing/_locked_indicator.html +2 -2
  68. wagtail/admin/templates/wagtailadmin/pages/listing/_page_title_column_header.html +25 -27
  69. wagtail/admin/templates/wagtailadmin/pages/page_listing_header.html +2 -1
  70. wagtail/admin/templates/wagtailadmin/panels/multi_field_panel_child.html +1 -1
  71. wagtail/admin/templates/wagtailadmin/panels/publishing/schedule_publishing_panel.html +3 -1
  72. wagtail/admin/templates/wagtailadmin/panels/tabbed_interface.html +1 -1
  73. wagtail/admin/templates/wagtailadmin/shared/active_filters.html +2 -1
  74. wagtail/admin/templates/wagtailadmin/shared/breadcrumbs.html +8 -0
  75. wagtail/admin/templates/wagtailadmin/shared/forms/single_checkbox.html +1 -1
  76. wagtail/admin/templates/wagtailadmin/shared/headers/page_edit_header.html +1 -1
  77. wagtail/admin/templates/wagtailadmin/shared/headers/slim_header.html +21 -9
  78. wagtail/admin/templates/wagtailadmin/shared/human_readable_date.html +1 -1
  79. wagtail/admin/templates/wagtailadmin/shared/keyboard_shortcuts_dialog.html +29 -0
  80. wagtail/admin/templates/wagtailadmin/shared/side_panel_toggle.html +2 -1
  81. wagtail/admin/templates/wagtailadmin/skeleton.html +2 -1
  82. wagtail/admin/templates/wagtailadmin/tables/related_objects_cell.html +9 -0
  83. wagtail/admin/templates/wagtailadmin/tables/title_cell.html +9 -7
  84. wagtail/admin/templates/wagtailadmin/widgets/draftail_rich_text_area.html +1 -1
  85. wagtail/admin/templates/wagtailadmin/workflows/create.html +6 -23
  86. wagtail/admin/templates/wagtailadmin/workflows/create_task.html +6 -15
  87. wagtail/admin/templates/wagtailadmin/workflows/edit.html +6 -23
  88. wagtail/admin/templates/wagtailadmin/workflows/edit_task.html +6 -13
  89. wagtail/admin/templates/wagtailadmin/workflows/includes/task_usage_cell.html +4 -4
  90. wagtail/admin/templates/wagtailadmin/workflows/includes/workflow_tasks_cell.html +18 -0
  91. wagtail/admin/templates/wagtailadmin/workflows/includes/workflow_title_cell.html +7 -0
  92. wagtail/admin/templates/wagtailadmin/workflows/includes/workflow_used_by_cell.html +25 -0
  93. wagtail/admin/templates/wagtailadmin/workflows/index.html +0 -99
  94. wagtail/admin/templates/wagtailadmin/workflows/index_results.html +10 -0
  95. wagtail/admin/templates/wagtailadmin/workflows/task_index.html +0 -30
  96. wagtail/admin/templates/wagtailadmin/workflows/task_index_results.html +10 -0
  97. wagtail/admin/templates/wagtailadmin/workflows/usage.html +1 -1
  98. wagtail/admin/templatetags/wagtailadmin_tags.py +116 -39
  99. wagtail/admin/tests/pages/test_create_page.py +10 -4
  100. wagtail/admin/tests/pages/test_custom_listing.py +37 -0
  101. wagtail/admin/tests/pages/test_edit_page.py +6 -6
  102. wagtail/admin/tests/pages/test_explorer_view.py +19 -18
  103. wagtail/admin/tests/pages/test_move_page.py +1 -1
  104. wagtail/admin/tests/pages/test_page_usage.py +50 -2
  105. wagtail/admin/tests/pages/test_parent_page_chooser_view.py +119 -0
  106. wagtail/admin/tests/pages/test_preview.py +18 -4
  107. wagtail/admin/tests/test_account_management.py +20 -1
  108. wagtail/admin/tests/test_audit_log.py +172 -5
  109. wagtail/admin/tests/test_checks.py +92 -0
  110. wagtail/admin/tests/test_collections_views.py +19 -5
  111. wagtail/admin/tests/test_compare.py +6 -6
  112. wagtail/admin/tests/test_dashboard.py +404 -0
  113. wagtail/admin/tests/test_dbwhitelister.py +4 -5
  114. wagtail/admin/tests/test_edit_handlers.py +2 -2
  115. wagtail/admin/tests/test_keyboard_shortcuts.py +84 -0
  116. wagtail/admin/tests/test_page_chooser.py +31 -18
  117. wagtail/admin/tests/test_privacy.py +36 -2
  118. wagtail/admin/tests/test_rich_text.py +168 -23
  119. wagtail/admin/tests/test_templatetags.py +411 -43
  120. wagtail/admin/tests/test_views.py +4 -2
  121. wagtail/admin/tests/test_workflows.py +531 -9
  122. wagtail/admin/tests/tests.py +3 -1
  123. wagtail/admin/tests/ui/test_tables.py +48 -1
  124. wagtail/admin/tests/viewsets/test_model_viewset.py +126 -29
  125. wagtail/admin/ui/side_panels.py +3 -1
  126. wagtail/admin/ui/tables/__init__.py +13 -1
  127. wagtail/admin/ui/tables/pages.py +17 -6
  128. wagtail/admin/urls/__init__.py +8 -3
  129. wagtail/admin/urls/pages.py +5 -0
  130. wagtail/admin/urls/workflows.py +10 -0
  131. wagtail/admin/views/chooser.py +20 -24
  132. wagtail/admin/views/collections.py +17 -1
  133. wagtail/admin/views/generic/base.py +31 -4
  134. wagtail/admin/views/generic/history.py +220 -51
  135. wagtail/admin/views/generic/mixins.py +7 -4
  136. wagtail/admin/views/generic/models.py +54 -38
  137. wagtail/admin/views/generic/multiple_upload.py +17 -8
  138. wagtail/admin/views/generic/usage.py +17 -11
  139. wagtail/admin/views/home.py +15 -12
  140. wagtail/admin/views/mixins.py +30 -0
  141. wagtail/admin/views/pages/choose_parent.py +73 -0
  142. wagtail/admin/views/pages/history.py +54 -66
  143. wagtail/admin/views/pages/listing.py +187 -106
  144. wagtail/admin/views/pages/usage.py +6 -1
  145. wagtail/admin/views/pages/utils.py +70 -1
  146. wagtail/admin/views/workflows.py +150 -21
  147. wagtail/admin/viewsets/model.py +2 -2
  148. wagtail/admin/viewsets/pages.py +77 -0
  149. wagtail/admin/wagtail_hooks.py +40 -2
  150. wagtail/admin/widgets/button.py +10 -9
  151. wagtail/api/v2/filters.py +1 -1
  152. wagtail/api/v2/tests/test_pages.py +1 -1
  153. wagtail/blocks/base.py +18 -9
  154. wagtail/blocks/field_block.py +9 -7
  155. wagtail/blocks/list_block.py +16 -6
  156. wagtail/blocks/static_block.py +3 -0
  157. wagtail/blocks/stream_block.py +58 -23
  158. wagtail/blocks/struct_block.py +15 -9
  159. wagtail/contrib/forms/locale/en/LC_MESSAGES/django.po +39 -47
  160. wagtail/contrib/forms/models.py +5 -5
  161. wagtail/contrib/forms/templates/wagtailforms/list_submissions.html +44 -33
  162. wagtail/contrib/forms/templates/wagtailforms/submissions_index.html +2 -63
  163. wagtail/contrib/forms/tests/test_models.py +26 -0
  164. wagtail/contrib/forms/urls.py +6 -0
  165. wagtail/contrib/forms/views.py +52 -49
  166. wagtail/contrib/redirects/locale/ca/LC_MESSAGES/django.mo +0 -0
  167. wagtail/contrib/redirects/locale/ca/LC_MESSAGES/django.po +3 -3
  168. wagtail/contrib/redirects/locale/en/LC_MESSAGES/django.po +34 -42
  169. wagtail/contrib/redirects/signal_handlers.py +1 -1
  170. wagtail/contrib/redirects/templates/wagtailredirects/index.html +1 -36
  171. wagtail/contrib/redirects/templates/wagtailredirects/index_results.html +18 -0
  172. wagtail/contrib/redirects/templates/wagtailredirects/redirect_target_cell.html +8 -0
  173. wagtail/contrib/redirects/tests/test_import_command.py +1 -1
  174. wagtail/contrib/redirects/tests/test_redirects.py +79 -8
  175. wagtail/contrib/redirects/urls.py +2 -1
  176. wagtail/contrib/redirects/views.py +85 -55
  177. wagtail/contrib/search_promotions/admin_urls.py +2 -1
  178. wagtail/contrib/search_promotions/locale/en/LC_MESSAGES/django.po +41 -64
  179. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/index.html +1 -16
  180. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/index_results.html +11 -0
  181. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/list.html +0 -51
  182. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/results.html +3 -16
  183. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/search_promotion_column.html +15 -0
  184. wagtail/contrib/search_promotions/tests.py +122 -9
  185. wagtail/contrib/search_promotions/views.py +66 -65
  186. wagtail/contrib/settings/locale/en/LC_MESSAGES/django.po +3 -3
  187. wagtail/contrib/settings/registry.py +10 -5
  188. wagtail/contrib/settings/tests/generic/test_admin.py +9 -0
  189. wagtail/contrib/settings/tests/site_specific/test_admin.py +10 -1
  190. wagtail/contrib/settings/tests/site_specific/test_model.py +3 -3
  191. wagtail/contrib/settings/tests/site_specific/test_templates.py +1 -1
  192. wagtail/contrib/settings/views.py +3 -1
  193. wagtail/contrib/simple_translation/locale/en/LC_MESSAGES/django.po +1 -1
  194. wagtail/contrib/simple_translation/tests/test_wagtail_hooks.py +2 -2
  195. wagtail/contrib/styleguide/locale/en/LC_MESSAGES/django.po +7 -7
  196. wagtail/contrib/table_block/blocks.py +1 -1
  197. wagtail/contrib/table_block/locale/en/LC_MESSAGES/django.po +1 -1
  198. wagtail/contrib/table_block/static/table_block/js/table.js +1 -1
  199. wagtail/contrib/typed_table_block/locale/en/LC_MESSAGES/django.po +1 -1
  200. wagtail/contrib/typed_table_block/static/typed_table_block/js/typed_table_block.js +1 -1
  201. wagtail/coreutils.py +3 -2
  202. wagtail/documents/admin_urls.py +2 -2
  203. wagtail/documents/locale/en/LC_MESSAGES/django.po +22 -22
  204. wagtail/documents/migrations/0013_delete_uploadeddocument.py +16 -0
  205. wagtail/documents/models.py +1 -20
  206. wagtail/documents/rich_text/__init__.py +11 -7
  207. wagtail/documents/static/wagtaildocs/js/document-chooser-modal.js +1 -1
  208. wagtail/documents/static/wagtaildocs/js/document-chooser-telepath.js +1 -1
  209. wagtail/documents/static/wagtaildocs/js/document-chooser.js +1 -1
  210. wagtail/documents/templates/wagtaildocs/documents/index.html +0 -16
  211. wagtail/documents/tests/test_admin_views.py +155 -23
  212. wagtail/documents/tests/test_collection_privacy.py +55 -1
  213. wagtail/documents/tests/test_rich_text.py +14 -0
  214. wagtail/documents/views/documents.py +25 -22
  215. wagtail/documents/views/multiple.py +6 -7
  216. wagtail/documents/views/serve.py +16 -1
  217. wagtail/documents/wagtail_hooks.py +20 -15
  218. wagtail/embeds/blocks.py +5 -0
  219. wagtail/embeds/locale/en/LC_MESSAGES/django.po +2 -2
  220. wagtail/embeds/rich_text/__init__.py +1 -1
  221. wagtail/embeds/tests/test_rich_text.py +14 -0
  222. wagtail/embeds/wagtail_hooks.py +4 -14
  223. wagtail/fields.py +3 -48
  224. wagtail/images/admin_urls.py +2 -2
  225. wagtail/images/check_files/wagtail.jpg +0 -0
  226. wagtail/images/check_files/wagtail.png +0 -0
  227. wagtail/images/fields.py +2 -0
  228. wagtail/images/image_operations.py +1 -1
  229. wagtail/images/locale/en/LC_MESSAGES/django.po +33 -45
  230. wagtail/images/locale/pt_PT/LC_MESSAGES/django.mo +0 -0
  231. wagtail/images/locale/pt_PT/LC_MESSAGES/django.po +4 -0
  232. wagtail/images/migrations/0026_delete_uploadedimage.py +16 -0
  233. wagtail/images/models.py +49 -43
  234. wagtail/images/rich_text/__init__.py +18 -8
  235. wagtail/images/static/wagtailimages/js/image-chooser-modal.js +1 -1
  236. wagtail/images/static/wagtailimages/js/image-chooser-telepath.js +1 -1
  237. wagtail/images/static/wagtailimages/js/image-chooser.js +1 -1
  238. wagtail/images/templates/wagtailimages/images/image_listing_header.html +6 -0
  239. wagtail/images/templates/wagtailimages/images/index.html +11 -51
  240. wagtail/images/tests/test_admin_views.py +119 -62
  241. wagtail/images/tests/test_image_operations.py +10 -0
  242. wagtail/images/tests/test_models.py +35 -33
  243. wagtail/images/tests/test_rich_text.py +14 -0
  244. wagtail/images/tests/utils.py +1 -1
  245. wagtail/images/views/images.py +35 -64
  246. wagtail/images/views/multiple.py +6 -7
  247. wagtail/images/wagtail_hooks.py +4 -14
  248. wagtail/locale/en/LC_MESSAGES/django.po +150 -136
  249. wagtail/locales/locale/en/LC_MESSAGES/django.po +1 -1
  250. wagtail/locales/tests.py +18 -3
  251. wagtail/locales/views.py +0 -1
  252. wagtail/management/commands/rebuild_references_index.py +3 -1
  253. wagtail/migrations/0092_alter_collectionviewrestriction_password_and_more.py +33 -0
  254. wagtail/migrations/0093_uploadedfile.py +53 -0
  255. wagtail/models/__init__.py +147 -32
  256. wagtail/models/i18n.py +1 -1
  257. wagtail/models/{collections.py → media.py} +33 -2
  258. wagtail/models/reference_index.py +1 -1
  259. wagtail/models/view_restrictions.py +10 -3
  260. wagtail/project_template/project_name/settings/base.py +6 -0
  261. wagtail/project_template/requirements.txt +1 -1
  262. wagtail/rich_text/__init__.py +25 -8
  263. wagtail/rich_text/pages.py +19 -8
  264. wagtail/rich_text/rewriters.py +140 -68
  265. wagtail/search/backends/database/mysql/mysql.py +3 -3
  266. wagtail/search/backends/database/postgres/postgres.py +3 -3
  267. wagtail/search/backends/database/sqlite/sqlite.py +2 -2
  268. wagtail/search/backends/elasticsearch7.py +4 -0
  269. wagtail/search/locale/en/LC_MESSAGES/django.po +3 -3
  270. wagtail/search/tests/test_postgres_backend.py +50 -0
  271. wagtail/sites/locale/en/LC_MESSAGES/django.po +8 -8
  272. wagtail/sites/tests.py +35 -9
  273. wagtail/sites/views.py +3 -1
  274. wagtail/snippets/locale/de/LC_MESSAGES/django.mo +0 -0
  275. wagtail/snippets/locale/de/LC_MESSAGES/django.po +5 -6
  276. wagtail/snippets/locale/en/LC_MESSAGES/django.po +16 -56
  277. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser-telepath.js +1 -1
  278. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser.js +1 -1
  279. wagtail/snippets/templates/wagtailsnippets/snippets/action_menu/publish.html +3 -1
  280. wagtail/snippets/templates/wagtailsnippets/snippets/action_menu/save.html +3 -1
  281. wagtail/snippets/templates/wagtailsnippets/snippets/create.html +1 -1
  282. wagtail/snippets/templates/wagtailsnippets/snippets/edit.html +1 -1
  283. wagtail/snippets/tests/test_preview.py +13 -2
  284. wagtail/snippets/tests/test_snippets.py +41 -16
  285. wagtail/snippets/tests/test_viewset.py +63 -18
  286. wagtail/snippets/tests/test_workflows.py +12 -0
  287. wagtail/snippets/views/snippets.py +1 -40
  288. wagtail/templatetags/wagtailcore_tags.py +1 -1
  289. wagtail/test/demosite/models.py +1 -1
  290. wagtail/test/middleware.py +14 -1
  291. wagtail/test/testapp/fixtures/test.json +20 -0
  292. wagtail/test/testapp/migrations/0001_squashed_0073_revisablechildmodel_secret_text.py +8 -8
  293. wagtail/test/testapp/migrations/0023_snippetchoosermodel_full_featured.py +1 -0
  294. wagtail/test/testapp/migrations/0034_custompermissionmodel.py +44 -0
  295. wagtail/test/testapp/migrations/0035_modelwithcustommanager.py +30 -0
  296. wagtail/test/testapp/migrations/0036_complexdefaultstreampage.py +28 -0
  297. wagtail/test/testapp/models.py +79 -2
  298. wagtail/test/testapp/templates/tests/custom_docs_password_required.html +10 -0
  299. wagtail/test/testapp/templates/tests/custom_page_password_required.html +10 -0
  300. wagtail/test/testapp/views.py +24 -2
  301. wagtail/test/testapp/wagtail_hooks.py +19 -0
  302. wagtail/test/utils/wagtail_tests.py +2 -2
  303. wagtail/tests/test_blocks.py +262 -1
  304. wagtail/tests/test_migrations.py +1 -1
  305. wagtail/tests/test_page_model.py +77 -0
  306. wagtail/tests/test_page_privacy.py +18 -1
  307. wagtail/tests/test_rich_text.py +95 -5
  308. wagtail/tests/test_streamfield.py +43 -0
  309. wagtail/tests/test_utils.py +8 -2
  310. wagtail/tests/test_views.py +52 -1
  311. wagtail/tests/test_whitelist.py +7 -7
  312. wagtail/users/forms.py +3 -1
  313. wagtail/users/locale/en/LC_MESSAGES/django.po +124 -96
  314. wagtail/users/migrations/0013_userprofile_density.py +23 -0
  315. wagtail/users/models.py +14 -3
  316. wagtail/users/templates/wagtailusers/groups/create.html +1 -7
  317. wagtail/users/templates/wagtailusers/groups/edit.html +1 -13
  318. wagtail/users/templates/wagtailusers/groups/includes/formatted_permissions.html +46 -2
  319. wagtail/users/templates/wagtailusers/groups/includes/group_form_js.html +0 -2
  320. wagtail/users/templates/wagtailusers/users/create.html +1 -9
  321. wagtail/users/templates/wagtailusers/users/edit.html +1 -9
  322. wagtail/users/templates/wagtailusers/users/index.html +2 -5
  323. wagtail/users/templates/wagtailusers/users/index_results.html +3 -13
  324. wagtail/users/templates/wagtailusers/users/user_cell.html +9 -0
  325. wagtail/users/templatetags/wagtailusers_tags.py +107 -20
  326. wagtail/users/tests/test_admin_views.py +669 -90
  327. wagtail/users/views/groups.py +58 -61
  328. wagtail/users/views/users.py +211 -92
  329. wagtail/users/wagtail_hooks.py +6 -38
  330. wagtail/users/widgets.py +3 -5
  331. wagtail/utils/text.py +1 -1
  332. wagtail/views.py +5 -9
  333. wagtail/whitelist.py +1 -1
  334. {wagtail-6.0.2.dist-info → wagtail-6.1rc1.dist-info}/METADATA +4 -5
  335. {wagtail-6.0.2.dist-info → wagtail-6.1rc1.dist-info}/RECORD +339 -320
  336. wagtail/admin/static/wagtailadmin/js/page-editor.js +0 -1
  337. wagtail/admin/static/wagtailadmin/js/vendor/mousetrap.min.js +0 -1
  338. wagtail/admin/static/wagtailadmin/js/vendor/urlify.js +0 -1
  339. wagtail/admin/static/wagtailadmin/js/vendor/xregexp.min.js +0 -1
  340. wagtail/admin/templates/wagtailadmin/collections/index.html +0 -34
  341. wagtail/admin/templates/wagtailadmin/pages/revisions/_actions.html +0 -22
  342. wagtail/admin/templates/wagtailadmin/shared/page_breadcrumbs.html +0 -55
  343. wagtail/admin/tests/pages/test_dashboard.py +0 -172
  344. wagtail/contrib/redirects/templates/wagtailredirects/results.html +0 -23
  345. wagtail/documents/templates/wagtaildocs/documents/list.html +0 -2
  346. wagtail/search/tests/test_postgres_stemming.py +0 -40
  347. wagtail/sites/templates/wagtailsites/create.html +0 -6
  348. wagtail/sites/templates/wagtailsites/edit.html +0 -6
  349. wagtail/snippets/templates/wagtailsnippets/snippets/revisions/_actions.html +0 -36
  350. wagtail/users/templates/wagtailusers/users/list.html +0 -62
  351. wagtail/users/urls/users.py +0 -12
  352. {wagtail-6.0.2.dist-info → wagtail-6.1rc1.dist-info}/LICENSE +0 -0
  353. {wagtail-6.0.2.dist-info → wagtail-6.1rc1.dist-info}/WHEEL +0 -0
  354. {wagtail-6.0.2.dist-info → wagtail-6.1rc1.dist-info}/entry_points.txt +0 -0
  355. {wagtail-6.0.2.dist-info → wagtail-6.1rc1.dist-info}/top_level.txt +0 -0
@@ -115,6 +115,28 @@ class TestWorkflowsIndexView(AdminTemplateTestUtils, WagtailTestUtils, TestCase)
115
115
  moderators.user_set.add(self.moderator)
116
116
  moderators.permissions.add(Permission.objects.get(codename="add_workflow"))
117
117
 
118
+ def create_workflows(self):
119
+ home_page = Page.objects.get(depth=2)
120
+ workflows = [
121
+ Workflow.objects.create(name=f"test_workflow_{i}", active=True)
122
+ for i in range(5)
123
+ ]
124
+ task = SimpleTask.objects.create(name="test_task")
125
+ workflow_tasks = [
126
+ WorkflowTask(workflow=workflow, task=task) for workflow in workflows
127
+ ]
128
+ WorkflowTask.objects.bulk_create(workflow_tasks)
129
+ workflow_pages = [
130
+ WorkflowPage(
131
+ workflow=workflow,
132
+ page=home_page.add_child(
133
+ instance=SimplePage(title="Simple", content="Very simple")
134
+ ),
135
+ )
136
+ for workflow in workflows
137
+ ]
138
+ WorkflowPage.objects.bulk_create(workflow_pages)
139
+
118
140
  def get(self, params={}):
119
141
  return self.client.get(reverse("wagtailadmin_workflows:index"), params)
120
142
 
@@ -122,7 +144,10 @@ class TestWorkflowsIndexView(AdminTemplateTestUtils, WagtailTestUtils, TestCase)
122
144
  response = self.get()
123
145
  self.assertEqual(response.status_code, 200)
124
146
  self.assertTemplateUsed(response, "wagtailadmin/workflows/index.html")
125
- self.assertBreadcrumbsNotRendered(response.content)
147
+ self.assertBreadcrumbsItemsRendered(
148
+ [{"url": "", "label": "Workflows"}],
149
+ response.content,
150
+ )
126
151
 
127
152
  # Initially there should be no workflows listed
128
153
  self.assertContains(response, "There are no enabled workflows.")
@@ -136,6 +161,15 @@ class TestWorkflowsIndexView(AdminTemplateTestUtils, WagtailTestUtils, TestCase)
136
161
  self.assertNotContains(response, "There are no enabled workflows.")
137
162
  self.assertContains(response, "test_workflow")
138
163
 
164
+ def test_num_queries(self):
165
+ self.create_workflows()
166
+ self.get()
167
+ with self.assertNumQueries(23):
168
+ self.get()
169
+ self.create_workflows()
170
+ with self.assertNumQueries(33):
171
+ self.get()
172
+
139
173
  def test_deactivated(self):
140
174
  Workflow.objects.create(name="test_workflow", active=False)
141
175
 
@@ -147,11 +181,30 @@ class TestWorkflowsIndexView(AdminTemplateTestUtils, WagtailTestUtils, TestCase)
147
181
  self.assertContains(
148
182
  response, '<span class="w-status">Disabled</span>', html=True
149
183
  )
184
+ # Should display the "Show disabled" option as a filter
185
+ soup = self.get_soup(response.content)
186
+ active_filter = soup.select_one('[data-w-active-filter-id="id_show_disabled"]')
187
+ self.assertIsNotNone(active_filter)
188
+ self.assertEqual(
189
+ active_filter.get_text(separator=" ", strip=True),
190
+ "Show disabled: Yes",
191
+ )
192
+ show_disabled_yes = soup.select_one('input[name="show_disabled"][value="true"]')
193
+ self.assertIsNotNone(show_disabled_yes)
194
+ self.assertTrue(show_disabled_yes.has_attr("checked"))
150
195
 
151
196
  # If we set 'show_disabled' to 'False', the workflow should not be displayed
152
197
  response = self.get(params={})
153
198
  self.assertEqual(response.status_code, 200)
154
199
  self.assertContains(response, "There are no enabled workflows.")
200
+ # Should not display any active filters,
201
+ # and the "Show disabled" option should be set to "No" by default
202
+ soup = self.get_soup(response.content)
203
+ active_filter = soup.select_one('[data-w-active-filter-id="id_show_disabled"]')
204
+ self.assertIsNone(active_filter)
205
+ show_disabled_no = soup.select_one('input[name="show_disabled"][value="false"]')
206
+ self.assertIsNotNone(show_disabled_no)
207
+ self.assertTrue(show_disabled_no.has_attr("checked"))
155
208
 
156
209
  def test_permissions(self):
157
210
  self.login(user=self.editor)
@@ -169,6 +222,75 @@ class TestWorkflowsIndexView(AdminTemplateTestUtils, WagtailTestUtils, TestCase)
169
222
  response = self.get()
170
223
  self.assertEqual(response.status_code, 200)
171
224
 
225
+ def test_ordering(self):
226
+ workflows = sorted(
227
+ [
228
+ # Mix up the creation order to ensure we're not ordering by PK
229
+ Workflow.objects.create(name="workflow_1"),
230
+ Workflow.objects.create(name="workflow_3"),
231
+ Workflow.objects.create(name="workflow_2"),
232
+ ],
233
+ key=lambda workflow: workflow.name,
234
+ )
235
+
236
+ response = self.get()
237
+ self.assertEqual(response.status_code, 200)
238
+ self.assertSequenceEqual(response.context["object_list"], workflows)
239
+ self.assertEqual(response.context["object_list"].query.order_by, ("name",))
240
+
241
+ response = self.get(params={"ordering": "name"})
242
+ self.assertEqual(response.status_code, 200)
243
+ self.assertSequenceEqual(response.context["object_list"], workflows)
244
+ self.assertEqual(response.context["object_list"].query.order_by, ("name",))
245
+
246
+ response = self.get(params={"ordering": "-name"})
247
+ self.assertEqual(response.status_code, 200)
248
+ self.assertSequenceEqual(response.context["object_list"], workflows[::-1])
249
+ self.assertEqual(response.context["object_list"].query.order_by, ("-name",))
250
+
251
+ def test_search(self):
252
+ Workflow.objects.create(name="foo workflow")
253
+ Workflow.objects.create(name="bar workflow")
254
+ Workflow.objects.create(name="bar world workflow")
255
+
256
+ response = self.get(params={"q": "bAr"})
257
+ self.assertEqual(response.status_code, 200)
258
+ self.assertContains(response, "bar workflow")
259
+ self.assertContains(response, "bar world workflow")
260
+ self.assertNotContains(response, "foo workflow")
261
+
262
+ def test_search_results(self):
263
+ Workflow.objects.create(name="foo workflow")
264
+ Workflow.objects.create(name="bar workflow")
265
+ Workflow.objects.create(name="bar world workflow")
266
+
267
+ response = self.client.get(
268
+ reverse("wagtailadmin_workflows:index_results"),
269
+ {"q": "AR"},
270
+ )
271
+ self.assertEqual(response.status_code, 200)
272
+ self.assertBreadcrumbsNotRendered(response.content)
273
+ self.assertContains(response, "bar workflow")
274
+ self.assertContains(response, "bar world workflow")
275
+ self.assertNotContains(response, "foo workflow")
276
+
277
+ def test_pagination(self):
278
+ Workflow.objects.bulk_create(
279
+ [Workflow(name=f"workflow_{i}") for i in range(1, 50)]
280
+ )
281
+
282
+ url = reverse("wagtailadmin_workflows:index")
283
+
284
+ response = self.get({"p": 2})
285
+ self.assertEqual(response.status_code, 200)
286
+ self.assertEqual(len(response.context["object_list"]), 20)
287
+ self.assertContains(response, url + "?p=1")
288
+ self.assertNotContains(response, url + "?p=2")
289
+ self.assertContains(response, url + "?p=3")
290
+
291
+ response = self.get({"p": 4})
292
+ self.assertEqual(response.status_code, 404)
293
+
172
294
 
173
295
  class TestWorkflowPermissions(WagtailTestUtils, TestCase):
174
296
  def setUp(self):
@@ -258,7 +380,13 @@ class TestWorkflowsCreateView(AdminTemplateTestUtils, WagtailTestUtils, TestCase
258
380
  response = self.get()
259
381
  self.assertEqual(response.status_code, 200)
260
382
  self.assertTemplateUsed(response, "wagtailadmin/workflows/create.html")
261
- self.assertBreadcrumbsNotRendered(response.content)
383
+ self.assertBreadcrumbsItemsRendered(
384
+ [
385
+ {"label": "Workflows", "url": "/admin/workflows/list/"},
386
+ {"label": "New: Workflow", "url": ""},
387
+ ],
388
+ response.content,
389
+ )
262
390
 
263
391
  def test_post(self):
264
392
  response = self.post(
@@ -431,7 +559,7 @@ class TestWorkflowsCreateView(AdminTemplateTestUtils, WagtailTestUtils, TestCase
431
559
  class TestWorkflowsEditView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
432
560
  def setUp(self):
433
561
  delete_existing_workflows()
434
- self.login()
562
+ self.user = self.login()
435
563
  self.workflow = Workflow.objects.create(name="workflow_to_edit")
436
564
  self.task_1 = SimpleTask.objects.create(name="first_task")
437
565
  self.task_2 = SimpleTask.objects.create(name="second_task")
@@ -481,7 +609,13 @@ class TestWorkflowsEditView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
481
609
  response = self.get()
482
610
  self.assertEqual(response.status_code, 200)
483
611
  self.assertTemplateUsed(response, "wagtailadmin/workflows/edit.html")
484
- self.assertBreadcrumbsNotRendered(response.content)
612
+ self.assertBreadcrumbsItemsRendered(
613
+ [
614
+ {"url": "/admin/workflows/list/", "label": "Workflows"},
615
+ {"url": "", "label": str(self.workflow)},
616
+ ],
617
+ response.content,
618
+ )
485
619
 
486
620
  # Check that the list of pages has the page to which this workflow is assigned
487
621
  self.assertContains(response, self.page.title)
@@ -660,6 +794,52 @@ class TestWorkflowsEditView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
660
794
  link = WorkflowContentType.objects.get(content_type=self.snippet_content_type)
661
795
  self.assertEqual(link.workflow, other_workflow)
662
796
 
797
+ def test_render_enable_button_if_workflow_disabled(self):
798
+ self.workflow.active = False
799
+ self.workflow.save()
800
+ response = self.get()
801
+ soup = self.get_soup(response.content)
802
+ enable_url = reverse("wagtailadmin_workflows:enable", args=(self.workflow.pk,))
803
+ enable_button = soup.find("button", {"data-w-action-url-value": enable_url})
804
+ self.assertIsNotNone(enable_button)
805
+
806
+ def test_render_enable_button_if_workflow_disabled_minimal_permissions(self):
807
+ self.user.is_superuser = False
808
+ self.user.save()
809
+ self.user.user_permissions.add(
810
+ Permission.objects.get(
811
+ content_type__app_label="wagtailadmin",
812
+ codename="access_admin",
813
+ ),
814
+ Permission.objects.get(codename="add_workflow"),
815
+ Permission.objects.get(codename="change_workflow"),
816
+ )
817
+ self.workflow.active = False
818
+ self.workflow.save()
819
+ response = self.get()
820
+ soup = self.get_soup(response.content)
821
+ enable_url = reverse("wagtailadmin_workflows:enable", args=(self.workflow.pk,))
822
+ enable_button = soup.find("button", {"data-w-action-url-value": enable_url})
823
+ self.assertIsNotNone(enable_button)
824
+
825
+ def test_render_enable_button_if_workflow_disabled_no_permissions(self):
826
+ self.user.is_superuser = False
827
+ self.user.save()
828
+ self.user.user_permissions.add(
829
+ Permission.objects.get(
830
+ content_type__app_label="wagtailadmin",
831
+ codename="access_admin",
832
+ ),
833
+ Permission.objects.get(codename="change_workflow"),
834
+ )
835
+ self.workflow.active = False
836
+ self.workflow.save()
837
+ response = self.get()
838
+ soup = self.get_soup(response.content)
839
+ enable_url = reverse("wagtailadmin_workflows:enable", args=(self.workflow.pk,))
840
+ enable_button = soup.find("button", {"data-w-action-url-value": enable_url})
841
+ self.assertIsNone(enable_button)
842
+
663
843
  def test_pages_and_content_types_ignored_if_workflow_disabled(self):
664
844
  self.workflow.active = False
665
845
  self.workflow.save()
@@ -793,7 +973,10 @@ class TestTaskIndexView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
793
973
  response = self.get()
794
974
  self.assertEqual(response.status_code, 200)
795
975
  self.assertTemplateUsed(response, "wagtailadmin/workflows/task_index.html")
796
- self.assertBreadcrumbsNotRendered(response.content)
976
+ self.assertBreadcrumbsItemsRendered(
977
+ [{"url": "", "label": "Tasks"}],
978
+ response.content,
979
+ )
797
980
 
798
981
  # Initially there should be no tasks listed
799
982
  self.assertContains(response, "There are no enabled tasks")
@@ -818,6 +1001,17 @@ class TestTaskIndexView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
818
1001
  self.assertContains(
819
1002
  response, '<span class="w-status">Disabled</span>', html=True
820
1003
  )
1004
+ # Should display the "Show disabled" option as a filter
1005
+ soup = self.get_soup(response.content)
1006
+ active_filter = soup.select_one('[data-w-active-filter-id="id_show_disabled"]')
1007
+ self.assertIsNotNone(active_filter)
1008
+ self.assertEqual(
1009
+ active_filter.get_text(separator=" ", strip=True),
1010
+ "Show disabled: Yes",
1011
+ )
1012
+ show_disabled_yes = soup.select_one('input[name="show_disabled"][value="true"]')
1013
+ self.assertIsNotNone(show_disabled_yes)
1014
+ self.assertTrue(show_disabled_yes.has_attr("checked"))
821
1015
 
822
1016
  # The listing should not contain task if show_disabled query parameter is 'False'
823
1017
  response = self.get(params={})
@@ -825,6 +1019,15 @@ class TestTaskIndexView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
825
1019
  self.assertContains(response, "There are no enabled tasks")
826
1020
  self.assertNotContains(response, "test_task")
827
1021
 
1022
+ # Should not display any active filters,
1023
+ # and the "Show disabled" option should be set to "No" by default
1024
+ soup = self.get_soup(response.content)
1025
+ active_filter = soup.select_one('[data-w-active-filter-id="id_show_disabled"]')
1026
+ self.assertIsNone(active_filter)
1027
+ show_disabled_no = soup.select_one('input[name="show_disabled"][value="false"]')
1028
+ self.assertIsNotNone(show_disabled_no)
1029
+ self.assertTrue(show_disabled_no.has_attr("checked"))
1030
+
828
1031
  def test_permissions(self):
829
1032
  self.login(user=self.editor)
830
1033
  response = self.get()
@@ -841,6 +1044,185 @@ class TestTaskIndexView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
841
1044
  response = self.get()
842
1045
  self.assertEqual(response.status_code, 200)
843
1046
 
1047
+ def test_ordering(self):
1048
+ tasks = sorted(
1049
+ [
1050
+ # Mix up the creation order to ensure we're not ordering by PK
1051
+ Task.objects.create(name="task_1"),
1052
+ Task.objects.create(name="task_3"),
1053
+ Task.objects.create(name="task_2"),
1054
+ ],
1055
+ key=lambda task: task.name,
1056
+ )
1057
+
1058
+ response = self.get()
1059
+ self.assertEqual(response.status_code, 200)
1060
+ self.assertSequenceEqual(response.context["object_list"], tasks)
1061
+ self.assertEqual(response.context["object_list"].query.order_by, ("name",))
1062
+
1063
+ response = self.get(params={"ordering": "name"})
1064
+ self.assertEqual(response.status_code, 200)
1065
+ self.assertSequenceEqual(response.context["object_list"], tasks)
1066
+ self.assertEqual(response.context["object_list"].query.order_by, ("name",))
1067
+
1068
+ response = self.get(params={"ordering": "-name"})
1069
+ self.assertEqual(response.status_code, 200)
1070
+ self.assertSequenceEqual(response.context["object_list"], tasks[::-1])
1071
+ self.assertEqual(response.context["object_list"].query.order_by, ("-name",))
1072
+
1073
+ def test_search(self):
1074
+ Task.objects.create(name="foo task")
1075
+ Task.objects.create(name="bar task")
1076
+ Task.objects.create(name="bar world task")
1077
+
1078
+ response = self.get(params={"q": "bAr"})
1079
+ self.assertEqual(response.status_code, 200)
1080
+ self.assertContains(response, "bar task")
1081
+ self.assertContains(response, "bar world task")
1082
+ self.assertNotContains(response, "foo task")
1083
+
1084
+ def test_search_results(self):
1085
+ Task.objects.create(name="foo task")
1086
+ Task.objects.create(name="bar task")
1087
+ Task.objects.create(name="bar world task")
1088
+
1089
+ response = self.client.get(
1090
+ reverse("wagtailadmin_workflows:task_index_results"),
1091
+ {"q": "AR"},
1092
+ )
1093
+ self.assertEqual(response.status_code, 200)
1094
+ self.assertBreadcrumbsNotRendered(response.content)
1095
+ self.assertContains(response, "bar task")
1096
+ self.assertContains(response, "bar world task")
1097
+ self.assertNotContains(response, "foo task")
1098
+
1099
+ def test_task_type_filter(self):
1100
+ SimpleTask.objects.create(name="easy task")
1101
+ SimpleTask.objects.create(name="medium task")
1102
+ GroupApprovalTask.objects.create(name="complex task")
1103
+
1104
+ simple_ct = ContentType.objects.get_for_model(SimpleTask).pk
1105
+ group_approval_ct = ContentType.objects.get_for_model(GroupApprovalTask).pk
1106
+
1107
+ response = self.get(params={"content_type": [simple_ct]})
1108
+ self.assertEqual(response.status_code, 200)
1109
+ self.assertContains(response, "easy task")
1110
+ self.assertContains(response, "medium task")
1111
+ self.assertNotContains(response, "complex task")
1112
+
1113
+ # Should display the active filter
1114
+ soup = self.get_soup(response.content)
1115
+ active_filter = soup.select_one('[data-w-active-filter-id="id_content_type"]')
1116
+ self.assertIsNotNone(active_filter)
1117
+ self.assertEqual(
1118
+ active_filter.get_text(separator=" ", strip=True),
1119
+ "Type: Simple task",
1120
+ )
1121
+ simple_ct_box = soup.select_one(
1122
+ f'input[name="content_type"][value="{simple_ct}"]'
1123
+ )
1124
+ self.assertIsNotNone(simple_ct_box)
1125
+ self.assertTrue(simple_ct_box.has_attr("checked"))
1126
+ group_approval_ct_box = soup.select_one(
1127
+ f'input[name="content_type"][value="{group_approval_ct}"]'
1128
+ )
1129
+ self.assertIsNotNone(group_approval_ct_box)
1130
+ self.assertFalse(group_approval_ct_box.has_attr("checked"))
1131
+
1132
+ # Should allow multiple content types to be selected
1133
+ response = self.get(params={"content_type": [simple_ct, group_approval_ct]})
1134
+ self.assertEqual(response.status_code, 200)
1135
+ self.assertContains(response, "easy task")
1136
+ self.assertContains(response, "medium task")
1137
+ self.assertContains(response, "complex task")
1138
+
1139
+ # Should display the active filters
1140
+ soup = self.get_soup(response.content)
1141
+ active_filters = soup.select('[data-w-active-filter-id="id_content_type"]')
1142
+ self.assertCountEqual(
1143
+ [filter.get_text(separator=" ", strip=True) for filter in active_filters],
1144
+ {"Type: Simple task", "Type: Group approval task"},
1145
+ )
1146
+ simple_ct_box = soup.select_one(
1147
+ f'input[name="content_type"][value="{simple_ct}"]'
1148
+ )
1149
+ self.assertIsNotNone(simple_ct_box)
1150
+ self.assertTrue(simple_ct_box.has_attr("checked"))
1151
+ group_approval_ct_box = soup.select_one(
1152
+ f'input[name="content_type"][value="{group_approval_ct}"]'
1153
+ )
1154
+ self.assertIsNotNone(group_approval_ct_box)
1155
+ self.assertTrue(group_approval_ct_box.has_attr("checked"))
1156
+
1157
+ def test_task_type_filter_hidden_if_single_task_type(self):
1158
+ SimpleTask.objects.create(name="easy task")
1159
+ SimpleTask.objects.create(name="medium task")
1160
+ GroupApprovalTask.objects.create(name="complex task")
1161
+
1162
+ simple_ct = ContentType.objects.get_for_model(SimpleTask).pk
1163
+
1164
+ with mock.patch(
1165
+ "wagtail.admin.views.workflows.get_task_types"
1166
+ ) as get_task_types:
1167
+ get_task_types.return_value = [SimpleTask]
1168
+ response = self.get({"content_type": [simple_ct]})
1169
+
1170
+ # Should not be filtered
1171
+ self.assertContains(response, "easy task")
1172
+ self.assertContains(response, "medium task")
1173
+ self.assertContains(response, "complex task")
1174
+
1175
+ # Should not display the content type filter
1176
+ soup = self.get_soup(response.content)
1177
+ active_filters = soup.select_one(".w-active_filters")
1178
+ self.assertIsNone(active_filters)
1179
+ content_type_filter = soup.select_one('input[name="content_type"]')
1180
+ self.assertIsNone(content_type_filter)
1181
+
1182
+ def test_pagination(self):
1183
+ Task.objects.bulk_create([Task(name=f"task_{i}") for i in range(1, 120)])
1184
+
1185
+ url = reverse("wagtailadmin_workflows:task_index")
1186
+
1187
+ response = self.get({"p": 2})
1188
+ self.assertEqual(response.status_code, 200)
1189
+ self.assertEqual(len(response.context["object_list"]), 50)
1190
+ self.assertContains(response, url + "?p=1")
1191
+ self.assertNotContains(response, url + "?p=2")
1192
+ self.assertContains(response, url + "?p=3")
1193
+
1194
+ response = self.get({"p": 4})
1195
+ self.assertEqual(response.status_code, 404)
1196
+
1197
+ def test_num_queries(self):
1198
+ workflows = [Workflow.objects.create(name=f"workflow_{i}") for i in range(7)]
1199
+ tasks = [Task.objects.create(name=f"task_{i}") for i in range(20)]
1200
+ WorkflowTask.objects.bulk_create(
1201
+ [
1202
+ WorkflowTask(workflow=workflow, task=task, sort_order=0)
1203
+ for workflow in workflows
1204
+ for task in tasks
1205
+ ]
1206
+ )
1207
+ self.get()
1208
+
1209
+ with self.assertNumQueries(12):
1210
+ response = self.get()
1211
+ self.assertContains(response, "+2 more", count=20)
1212
+
1213
+ tasks = [Task.objects.create(name=f"task_{i}") for i in range(21, 41)]
1214
+ WorkflowTask.objects.bulk_create(
1215
+ [
1216
+ WorkflowTask(workflow=workflow, task=task, sort_order=0)
1217
+ for workflow in workflows
1218
+ for task in tasks
1219
+ ]
1220
+ )
1221
+
1222
+ with self.assertNumQueries(12):
1223
+ response = self.get()
1224
+ self.assertContains(response, "+2 more", count=40)
1225
+
844
1226
 
845
1227
  class TestCreateTaskView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
846
1228
  def setUp(self):
@@ -888,7 +1270,13 @@ class TestCreateTaskView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
888
1270
  response = self.get()
889
1271
  self.assertEqual(response.status_code, 200)
890
1272
  self.assertTemplateUsed(response, "wagtailadmin/workflows/create_task.html")
891
- self.assertBreadcrumbsNotRendered(response.content)
1273
+ self.assertBreadcrumbsItemsRendered(
1274
+ [
1275
+ {"label": "Tasks", "url": "/admin/workflows/tasks/index/"},
1276
+ {"label": "New: Simple task", "url": ""},
1277
+ ],
1278
+ response.content,
1279
+ )
892
1280
 
893
1281
  def test_get_with_non_task_model(self):
894
1282
  response = self.get(
@@ -972,7 +1360,7 @@ class TestSelectTaskTypeView(WagtailTestUtils, TestCase):
972
1360
  class TestEditTaskView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
973
1361
  def setUp(self):
974
1362
  delete_existing_workflows()
975
- self.login()
1363
+ self.user = self.login()
976
1364
  self.task = GroupApprovalTask.objects.create(name="test_task")
977
1365
 
978
1366
  self.editor = self.create_user(
@@ -1006,7 +1394,13 @@ class TestEditTaskView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
1006
1394
  response = self.get()
1007
1395
  self.assertEqual(response.status_code, 200)
1008
1396
  self.assertTemplateUsed(response, "wagtailadmin/workflows/edit_task.html")
1009
- self.assertBreadcrumbsNotRendered(response.content)
1397
+ self.assertBreadcrumbsItemsRendered(
1398
+ [
1399
+ {"url": "/admin/workflows/tasks/index/", "label": "Tasks"},
1400
+ {"url": "", "label": str(self.task)},
1401
+ ],
1402
+ response.content,
1403
+ )
1010
1404
 
1011
1405
  def test_post(self):
1012
1406
  self.assertEqual(self.task.groups.count(), 0)
@@ -1045,6 +1439,52 @@ class TestEditTaskView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
1045
1439
  response = self.get()
1046
1440
  self.assertEqual(response.status_code, 200)
1047
1441
 
1442
+ def test_render_enable_button_if_task_disabled(self):
1443
+ self.task.active = False
1444
+ self.task.save()
1445
+ response = self.get()
1446
+ soup = self.get_soup(response.content)
1447
+ enable_url = reverse("wagtailadmin_workflows:enable_task", args=(self.task.pk,))
1448
+ enable_button = soup.find("button", {"data-w-action-url-value": enable_url})
1449
+ self.assertIsNotNone(enable_button)
1450
+
1451
+ def test_render_enable_button_if_task_disabled_minimal_permissions(self):
1452
+ self.user.is_superuser = False
1453
+ self.user.save()
1454
+ self.user.user_permissions.add(
1455
+ Permission.objects.get(
1456
+ content_type__app_label="wagtailadmin",
1457
+ codename="access_admin",
1458
+ ),
1459
+ Permission.objects.get(codename="add_task"),
1460
+ Permission.objects.get(codename="change_task"),
1461
+ )
1462
+ self.task.active = False
1463
+ self.task.save()
1464
+ response = self.get()
1465
+ soup = self.get_soup(response.content)
1466
+ enable_url = reverse("wagtailadmin_workflows:enable_task", args=(self.task.pk,))
1467
+ enable_button = soup.find("button", {"data-w-action-url-value": enable_url})
1468
+ self.assertIsNotNone(enable_button)
1469
+
1470
+ def test_render_enable_button_if_task_disabled_no_permissions(self):
1471
+ self.user.is_superuser = False
1472
+ self.user.save()
1473
+ self.user.user_permissions.add(
1474
+ Permission.objects.get(
1475
+ content_type__app_label="wagtailadmin",
1476
+ codename="access_admin",
1477
+ ),
1478
+ Permission.objects.get(codename="change_task"),
1479
+ )
1480
+ self.task.active = False
1481
+ self.task.save()
1482
+ response = self.get()
1483
+ soup = self.get_soup(response.content)
1484
+ enable_url = reverse("wagtailadmin_workflows:enable_task", args=(self.task.pk,))
1485
+ enable_button = soup.find("button", {"data-w-action-url-value": enable_url})
1486
+ self.assertIsNone(enable_button)
1487
+
1048
1488
  def test_admin_url_finder(self):
1049
1489
  editor_url_finder = AdminURLFinder(self.editor)
1050
1490
  self.assertIsNone(editor_url_finder.get_edit_url(self.task))
@@ -3029,6 +3469,47 @@ class TestDisableViews(AdminTemplateTestUtils, BasePageWorkflowTests):
3029
3469
  self.workflow.refresh_from_db()
3030
3470
  self.assertIs(self.workflow.active, True)
3031
3471
 
3472
+ def test_enable_workflow_minimal_permissions(self):
3473
+ self.superuser.is_superuser = False
3474
+ self.superuser.save()
3475
+ self.superuser.user_permissions.add(
3476
+ Permission.objects.get(
3477
+ content_type__app_label="wagtailadmin",
3478
+ codename="access_admin",
3479
+ ),
3480
+ Permission.objects.get(codename="add_workflow"),
3481
+ )
3482
+ self.login(self.superuser)
3483
+ self.workflow.active = False
3484
+ self.workflow.save()
3485
+
3486
+ response = self.client.post(
3487
+ reverse("wagtailadmin_workflows:enable", args=(self.workflow.pk,))
3488
+ )
3489
+ self.assertEqual(response.status_code, 302)
3490
+ self.workflow.refresh_from_db()
3491
+ self.assertIs(self.workflow.active, True)
3492
+
3493
+ def test_enable_workflow_no_permissions(self):
3494
+ self.superuser.is_superuser = False
3495
+ self.superuser.save()
3496
+ self.superuser.user_permissions.add(
3497
+ Permission.objects.get(
3498
+ content_type__app_label="wagtailadmin",
3499
+ codename="access_admin",
3500
+ ),
3501
+ )
3502
+ self.login(self.superuser)
3503
+ self.workflow.active = False
3504
+ self.workflow.save()
3505
+
3506
+ response = self.client.post(
3507
+ reverse("wagtailadmin_workflows:enable", args=(self.workflow.pk,))
3508
+ )
3509
+ self.assertRedirects(response, reverse("wagtailadmin_home"))
3510
+ self.workflow.refresh_from_db()
3511
+ self.assertIs(self.workflow.active, False)
3512
+
3032
3513
  def test_enable_task(self):
3033
3514
  self.login(self.superuser)
3034
3515
  self.task_1.active = False
@@ -3041,6 +3522,47 @@ class TestDisableViews(AdminTemplateTestUtils, BasePageWorkflowTests):
3041
3522
  self.task_1.refresh_from_db()
3042
3523
  self.assertIs(self.task_1.active, True)
3043
3524
 
3525
+ def test_enable_task_minimal_permissions(self):
3526
+ self.superuser.is_superuser = False
3527
+ self.superuser.save()
3528
+ self.superuser.user_permissions.add(
3529
+ Permission.objects.get(
3530
+ content_type__app_label="wagtailadmin",
3531
+ codename="access_admin",
3532
+ ),
3533
+ Permission.objects.get(codename="add_task"),
3534
+ )
3535
+ self.login(self.superuser)
3536
+ self.task_1.active = False
3537
+ self.task_1.save()
3538
+
3539
+ response = self.client.post(
3540
+ reverse("wagtailadmin_workflows:enable_task", args=(self.task_1.pk,))
3541
+ )
3542
+ self.assertEqual(response.status_code, 302)
3543
+ self.task_1.refresh_from_db()
3544
+ self.assertIs(self.task_1.active, True)
3545
+
3546
+ def test_enable_task_no_permissions(self):
3547
+ self.superuser.is_superuser = False
3548
+ self.superuser.save()
3549
+ self.superuser.user_permissions.add(
3550
+ Permission.objects.get(
3551
+ content_type__app_label="wagtailadmin",
3552
+ codename="access_admin",
3553
+ ),
3554
+ )
3555
+ self.login(self.superuser)
3556
+ self.task_1.active = False
3557
+ self.task_1.save()
3558
+
3559
+ response = self.client.post(
3560
+ reverse("wagtailadmin_workflows:enable_task", args=(self.task_1.pk,))
3561
+ )
3562
+ self.assertRedirects(response, reverse("wagtailadmin_home"))
3563
+ self.task_1.refresh_from_db()
3564
+ self.assertIs(self.task_1.active, False)
3565
+
3044
3566
 
3045
3567
  class TestDisableViewsWithSnippetWorkflows(TestDisableViews, BaseSnippetWorkflowTests):
3046
3568
  pass
@@ -3721,7 +4243,7 @@ class TestWorkflowStateEmailNotifier(BasePageWorkflowTests):
3721
4243
  notifier.get_recipient_users(workflow_state), {self.submitter}
3722
4244
  )
3723
4245
 
3724
- def test_workflowstate_email_notifier_get_recipient_users__with_trigerring_user(
4246
+ def test_workflowstate_email_notifier_get_recipient_users__with_triggering_user(
3725
4247
  self
3726
4248
  ):
3727
4249
  self.workflow.start(self.object, user=self.submitter)
@@ -517,7 +517,9 @@ class TestAdminURLAppendSlash(WagtailTestUtils, TestCase):
517
517
 
518
518
  # Check that correct page is returned after CommonMiddleware redirect
519
519
  self.assertEqual(response.status_code, 200)
520
- self.assertTemplateUsed(response, "wagtailadmin/pages/index.html")
520
+ self.assertTemplateUsed(
521
+ response, "wagtailadmin/pages/explorable_index.html"
522
+ )
521
523
  self.assertEqual(Page.objects.get(id=1), response.context["parent_page"])
522
524
  self.assertIn(self.root_page, response.context["pages"])
523
525