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
@@ -2,7 +2,13 @@ from django.template import Context, Template
2
2
  from django.test import RequestFactory, TestCase
3
3
  from django.utils.html import format_html
4
4
 
5
- from wagtail.admin.ui.tables import BaseColumn, Column, Table, TitleColumn
5
+ from wagtail.admin.ui.tables import (
6
+ BaseColumn,
7
+ Column,
8
+ RelatedObjectsColumn,
9
+ Table,
10
+ TitleColumn,
11
+ )
6
12
  from wagtail.models import Page, Site
7
13
 
8
14
 
@@ -322,3 +328,44 @@ class TestTable(TestCase):
322
328
  </table>
323
329
  """,
324
330
  )
331
+
332
+
333
+ class TestRelatedObjectsColumn(TestCase):
334
+ def setUp(self):
335
+ self.rf = RequestFactory()
336
+
337
+ def render_component(self, obj):
338
+ request = self.rf.get("/")
339
+ template = Template("{% load wagtailadmin_tags %}{% component obj %}")
340
+ return template.render(Context({"request": request, "obj": obj}))
341
+
342
+ def test_table_render(self):
343
+ table = Table(
344
+ [
345
+ Column("title"),
346
+ RelatedObjectsColumn("sites_rooted_here"),
347
+ ],
348
+ Page.objects.all(),
349
+ )
350
+
351
+ html = self.render_component(table)
352
+ self.assertHTMLEqual(
353
+ html,
354
+ """
355
+ <table class="listing">
356
+ <thead>
357
+ <tr><th>Title</th><th>Sites rooted here</th></tr>
358
+ </thead>
359
+ <tbody>
360
+ <tr>
361
+ <td>Root</td>
362
+ <td></td>
363
+ </tr>
364
+ <tr>
365
+ <td>Welcome to your new Wagtail site!</td>
366
+ <td><ul><li>localhost [default]</li></ul></td>
367
+ </tr>
368
+ </tbody>
369
+ </table>
370
+ """,
371
+ )
@@ -6,7 +6,7 @@ from django.contrib.admin.utils import quote
6
6
  from django.contrib.auth import get_permission_codename
7
7
  from django.contrib.auth.models import Permission
8
8
  from django.contrib.contenttypes.models import ContentType
9
- from django.test import RequestFactory, TestCase
9
+ from django.test import TestCase
10
10
  from django.urls import NoReverseMatch, reverse
11
11
  from django.utils.formats import date_format, localize
12
12
  from django.utils.html import escape
@@ -14,7 +14,6 @@ from django.utils.timezone import make_aware
14
14
  from openpyxl import load_workbook
15
15
 
16
16
  from wagtail.admin.admin_url_finder import AdminURLFinder
17
- from wagtail.admin.staticfiles import versioned_static
18
17
  from wagtail.log_actions import log
19
18
  from wagtail.models import ModelLogEntry
20
19
  from wagtail.test.testapp.models import (
@@ -23,7 +22,6 @@ from wagtail.test.testapp.models import (
23
22
  SearchTestModel,
24
23
  VariousOnDeleteModel,
25
24
  )
26
- from wagtail.test.testapp.views import FCToyAlt1ViewSet
27
25
  from wagtail.test.utils.template_tests import AdminTemplateTestUtils
28
26
  from wagtail.test.utils.wagtail_tests import WagtailTestUtils
29
27
  from wagtail.utils.deprecation import RemovedInWagtail70Warning
@@ -995,7 +993,7 @@ class TestHistoryView(WagtailTestUtils, TestCase):
995
993
  )
996
994
  response = self.client.get(self.url)
997
995
  soup = self.get_soup(response.content)
998
- rows = soup.select("tbody tr")
996
+ rows = soup.select("#listing-results tbody tr")
999
997
  self.assertEqual(response.status_code, 200)
1000
998
  self.assertEqual(len(rows), 2)
1001
999
 
@@ -1012,28 +1010,109 @@ class TestHistoryView(WagtailTestUtils, TestCase):
1012
1010
  for rendered_row, expected_row in zip(rendered_rows, expected):
1013
1011
  self.assertSequenceEqual(rendered_row, expected_row)
1014
1012
 
1015
- def test_filters(self):
1016
- response = self.client.get(self.url, {"action": "wagtail.edit"})
1013
+ def test_action_filter(self):
1014
+ response = self.client.get(self.url)
1015
+ self.assertEqual(response.status_code, 200)
1016
+
1017
+ # Should only show the created and edited options for the filter
1017
1018
  soup = self.get_soup(response.content)
1018
- rows = soup.select("tbody tr")
1019
+ options = soup.select('input[name="action"][type="checkbox"]')
1020
+ self.assertEqual(len(options), 2)
1021
+ self.assertEqual(
1022
+ {option.attrs.get("value") for option in options},
1023
+ {"wagtail.create", "wagtail.edit"},
1024
+ )
1025
+ # Should not show the heading when not searching
1026
+ heading = soup.select_one('h2[role="alert"]')
1027
+ self.assertIsNone(heading)
1028
+
1029
+ # Should only show the edited log
1030
+ response = self.client.get(self.url, {"action": "wagtail.edit"})
1019
1031
  self.assertEqual(response.status_code, 200)
1032
+ soup = self.get_soup(response.content)
1033
+ rows = soup.select("#listing-results tbody tr")
1020
1034
  self.assertEqual(len(rows), 1)
1021
1035
  self.assertEqual(rows[0].select_one("td").text.strip(), "Edited")
1022
1036
 
1037
+ # Should only show the created log
1023
1038
  response = self.client.get(self.url, {"action": "wagtail.create"})
1039
+ self.assertEqual(response.status_code, 200)
1024
1040
  soup = self.get_soup(response.content)
1025
- rows = soup.select("tbody tr")
1041
+ rows = soup.select("#listing-results tbody tr")
1026
1042
  heading = soup.select_one('h2[role="alert"]')
1027
- self.assertEqual(response.status_code, 200)
1028
1043
  self.assertEqual(heading.string.strip(), "There is 1 match")
1029
1044
  self.assertEqual(len(rows), 1)
1030
1045
  self.assertEqual(rows[0].select_one("td").text.strip(), "Created")
1031
1046
 
1047
+ # Should display the heading when there are results
1048
+ heading = soup.select_one('h2[role="alert"]')
1049
+ self.assertEqual(heading.string.strip(), "There is 1 match")
1050
+
1051
+ response = self.client.get(
1052
+ self.url,
1053
+ {"action": ["wagtail.create", "wagtail.edit"]},
1054
+ )
1055
+ self.assertEqual(response.status_code, 200)
1056
+ self.assertEqual(len(response.context["object_list"]), 2)
1057
+
1058
+ def test_user_filter(self):
1059
+ # A user who absolutely has no permissions to the model
1060
+ self.create_user("no_power")
1061
+
1062
+ # A user who has permissions to the model,
1063
+ # but only has logs for a different object
1064
+ has_power = self.create_superuser("has_power")
1065
+ other_obj = FeatureCompleteToy.objects.create(name="Woody")
1066
+ log(
1067
+ instance=other_obj,
1068
+ action="wagtail.create",
1069
+ content_changed=True,
1070
+ user=has_power,
1071
+ )
1072
+
1073
+ # A user who does not have permissions to the model,
1074
+ # but has logs for the object (e.g. maybe they used to have permissions)
1075
+ previously_has_power = self.create_user("previously_has_power")
1076
+ log(
1077
+ instance=self.object,
1078
+ action="wagtail.edit",
1079
+ content_changed=True,
1080
+ user=previously_has_power,
1081
+ )
1082
+
1083
+ response = self.client.get(self.url)
1084
+ self.assertEqual(response.status_code, 200)
1085
+ soup = self.get_soup(response.content)
1086
+
1087
+ # Should only show the current user and the previously_has_power user
1088
+ options = soup.select('input[name="user"][type="checkbox"]')
1089
+ self.assertEqual(len(options), 2)
1090
+ self.assertEqual(
1091
+ {option.attrs.get("value") for option in options},
1092
+ {str(self.user.pk), str(previously_has_power.pk)},
1093
+ )
1094
+
1095
+ response = self.client.get(self.url, {"user": str(self.user.pk)})
1096
+ self.assertEqual(response.status_code, 200)
1097
+ self.assertEqual(len(response.context["object_list"]), 2)
1098
+
1099
+ response = self.client.get(self.url, {"user": str(previously_has_power.pk)})
1100
+ self.assertEqual(response.status_code, 200)
1101
+ self.assertEqual(len(response.context["object_list"]), 1)
1102
+
1103
+ # Should allow filtering by multiple users
1104
+ response = self.client.get(
1105
+ self.url,
1106
+ {"user": [str(self.user.pk), str(previously_has_power.pk)]},
1107
+ )
1108
+ self.assertEqual(response.status_code, 200)
1109
+ self.assertEqual(len(response.context["object_list"]), 3)
1110
+
1032
1111
  def test_filtered_no_results(self):
1033
- response = self.client.get(self.url, {"timestamp_before": "2020-01-01"})
1112
+ response = self.client.get(self.url, {"timestamp_to": "2020-01-01"})
1034
1113
  soup = self.get_soup(response.content)
1035
1114
  results = soup.select_one("#listing-results")
1036
- table = soup.select_one("table")
1115
+ table = soup.select_one("#listing-results table")
1037
1116
  p = results.select_one("p")
1038
1117
  self.assertEqual(response.status_code, 200)
1039
1118
  self.assertIsNotNone(results)
@@ -1046,12 +1125,25 @@ class TestHistoryView(WagtailTestUtils, TestCase):
1046
1125
  response = self.client.get(self.url)
1047
1126
  soup = self.get_soup(response.content)
1048
1127
  results = soup.select_one("#listing-results")
1049
- table = soup.select_one("table")
1128
+ table = soup.select_one("#listing-results table")
1050
1129
  self.assertEqual(response.status_code, 200)
1051
1130
  self.assertIsNotNone(results)
1052
1131
  self.assertEqual(results.text.strip(), "There are no log entries to display.")
1053
1132
  self.assertIsNone(table)
1054
1133
 
1134
+ # Should hide the action and user filters as there are no choices for
1135
+ # these filters (since the choices are based on the current queryset)
1136
+ action_inputs = soup.select('input[name="action"]')
1137
+ self.assertEqual(len(action_inputs), 0)
1138
+ user_inputs = soup.select('input[name="user"]')
1139
+ self.assertEqual(len(user_inputs), 0)
1140
+
1141
+ # Should still render the timestamp filter as it is always available
1142
+ timestamp_before_input = soup.select_one('input[name="timestamp_to"]')
1143
+ self.assertIsNotNone(timestamp_before_input)
1144
+ timestamp_after_input = soup.select_one('input[name="timestamp_from"]')
1145
+ self.assertIsNotNone(timestamp_after_input)
1146
+
1055
1147
  def test_edit_view_links_to_history_view(self):
1056
1148
  edit_url = reverse("feature_complete_toy:edit", args=(quote(self.object.pk),))
1057
1149
  response = self.client.get(edit_url)
@@ -1060,6 +1152,19 @@ class TestHistoryView(WagtailTestUtils, TestCase):
1060
1152
  history_link = header.find("a", attrs={"href": self.url})
1061
1153
  self.assertIsNotNone(history_link)
1062
1154
 
1155
+ def test_deleted_user(self):
1156
+ to_be_deleted = self.create_user("to_be_deleted")
1157
+ user_id = to_be_deleted.pk
1158
+ log(
1159
+ instance=self.object,
1160
+ action="wagtail.edit",
1161
+ content_changed=True,
1162
+ user=to_be_deleted,
1163
+ )
1164
+ to_be_deleted.delete()
1165
+ response = self.client.get(self.url)
1166
+ self.assertContains(response, f"user {user_id} (deleted)")
1167
+
1063
1168
 
1064
1169
  class TestUsageView(WagtailTestUtils, TestCase):
1065
1170
  @classmethod
@@ -1085,7 +1190,7 @@ class TestUsageView(WagtailTestUtils, TestCase):
1085
1190
  h1 = soup.select_one("h1")
1086
1191
  self.assertEqual(h1.text.strip(), f"Usage: {self.object}")
1087
1192
 
1088
- tds = soup.select("tbody tr td")
1193
+ tds = soup.select("#listing-results tbody tr td")
1089
1194
  self.assertEqual(len(tds), 3)
1090
1195
  self.assertEqual(tds[0].text.strip(), str(self.tbx))
1091
1196
  self.assertEqual(tds[1].text.strip(), "Various on delete model")
@@ -1133,7 +1238,7 @@ class TestUsageView(WagtailTestUtils, TestCase):
1133
1238
  h1 = soup.select_one("h1")
1134
1239
  self.assertEqual(h1.text.strip(), f"Usage: {self.object}")
1135
1240
 
1136
- tds = soup.select("tbody tr td")
1241
+ tds = soup.select("#listing-results tbody tr td")
1137
1242
  self.assertEqual(len(tds), 3)
1138
1243
  self.assertEqual(tds[0].text.strip(), "(Private various on delete model)")
1139
1244
  self.assertEqual(tds[1].text.strip(), "Various on delete model")
@@ -1155,7 +1260,7 @@ class TestUsageView(WagtailTestUtils, TestCase):
1155
1260
  h1 = soup.select_one("h1")
1156
1261
  self.assertEqual(h1.text.strip(), f"Usage: {self.object}")
1157
1262
 
1158
- tds = soup.select("tbody tr td")
1263
+ tds = soup.select("#listing-results tbody tr td")
1159
1264
  self.assertEqual(len(tds), 3)
1160
1265
  self.assertEqual(tds[0].text.strip(), str(self.tbx))
1161
1266
  self.assertEqual(tds[1].text.strip(), "Various on delete model")
@@ -1181,7 +1286,7 @@ class TestUsageView(WagtailTestUtils, TestCase):
1181
1286
  response = self.client.get(self.url)
1182
1287
  soup = self.get_soup(response.content)
1183
1288
  results = soup.select_one("#listing-results")
1184
- table = soup.select_one("table")
1289
+ table = soup.select_one("#listing-results table")
1185
1290
  self.assertEqual(response.status_code, 200)
1186
1291
  self.assertIsNotNone(results)
1187
1292
  self.assertEqual(results.text.strip(), "There are no results.")
@@ -1426,14 +1531,11 @@ class TestCopyView(WagtailTestUtils, TestCase):
1426
1531
  self.assertRedirects(response, reverse("wagtailadmin_home"))
1427
1532
 
1428
1533
  def test_form_is_prefilled(self):
1429
- request = RequestFactory().get(self.url)
1430
- request.user = self.user
1431
- view = FCToyAlt1ViewSet().copy_view_class()
1432
- view.setup(request)
1433
- view.model = self.object.__class__
1434
- view.kwargs = {"pk": self.object.pk}
1435
-
1436
- self.assertEqual(view.get_form_kwargs()["instance"], self.object)
1534
+ response = self.client.get(self.url)
1535
+ self.assertEqual(response.status_code, 200)
1536
+ soup = self.get_soup(response.content)
1537
+ name_input = soup.select_one('input[name="name"]')
1538
+ self.assertEqual(name_input.attrs.get("value"), "Test Toy")
1437
1539
 
1438
1540
 
1439
1541
  class TestEditHandler(WagtailTestUtils, TestCase):
@@ -1469,11 +1571,6 @@ class TestEditHandler(WagtailTestUtils, TestCase):
1469
1571
  self.assertIsNotNone(rendered_heading)
1470
1572
  self.assertEqual(rendered_heading.text.strip(), expected_heading)
1471
1573
 
1472
- # Ensure modal-workflow.js is included, as it's needed by choosers
1473
- modal_workflow_js = versioned_static("wagtailadmin/js/modal-workflow.js")
1474
- modal_workflow_script = soup.select_one(f'script[src="{modal_workflow_js}"]')
1475
- self.assertIsNotNone(modal_workflow_script)
1476
-
1477
1574
 
1478
1575
  class TestDefaultMessages(WagtailTestUtils, TestCase):
1479
1576
  def setUp(self):
@@ -15,6 +15,7 @@ class BaseSidePanel(Component):
15
15
  icon_name = ""
16
16
  has_counter = True
17
17
  counter_classname = ""
18
+ keyboard_shortcut = None
18
19
 
19
20
  def __init__(self, panel):
20
21
  self.panel = panel
@@ -229,7 +230,7 @@ class StatusSidePanel(BaseSidePanel):
229
230
  context["last_updated_info"] = self.last_updated_info
230
231
  context.update(self.get_scheduled_publishing_context(parent_context))
231
232
  context.update(self.get_lock_context(parent_context))
232
- if self.object.pk:
233
+ if self.object.pk and self.usage_url:
233
234
  context.update(self.get_usage_context())
234
235
  return context
235
236
 
@@ -338,6 +339,7 @@ class PreviewSidePanel(BaseSidePanel):
338
339
  aria_label = gettext_lazy("Toggle preview")
339
340
  icon_name = "mobile-alt"
340
341
  has_counter = False
342
+ keyboard_shortcut = "mod+p"
341
343
 
342
344
  name = "preview"
343
345
  title = gettext_lazy("Preview")
@@ -149,7 +149,10 @@ class Column(BaseColumn):
149
149
  if callable(self.accessor):
150
150
  return self.accessor(instance)
151
151
  else:
152
- return multigetattr(instance, self.accessor)
152
+ try:
153
+ return multigetattr(instance, self.accessor)
154
+ except AttributeError:
155
+ return None
153
156
 
154
157
  def get_cell_context_data(self, instance, parent_context):
155
158
  context = super().get_cell_context_data(instance, parent_context)
@@ -393,6 +396,15 @@ class DownloadColumn(Column):
393
396
  return context
394
397
 
395
398
 
399
+ class RelatedObjectsColumn(Column):
400
+ """Outputs a list of objects related to the object through a one-to-many relationship"""
401
+
402
+ cell_template_name = "wagtailadmin/tables/related_objects_cell.html"
403
+
404
+ def get_value(self, instance):
405
+ return getattr(instance, self.accessor).all()
406
+
407
+
396
408
  class Table(Component):
397
409
  template_name = "wagtailadmin/tables/table.html"
398
410
  classname = "listing"
@@ -11,14 +11,24 @@ class PageTitleColumn(BaseColumn):
11
11
 
12
12
  def get_header_context_data(self, parent_context):
13
13
  context = super().get_header_context_data(parent_context)
14
+ parent_page = parent_context.get("parent_page")
15
+
14
16
  context["items_count"] = parent_context.get("items_count")
15
17
  context["page_obj"] = parent_context.get("page_obj")
16
- context["parent_page"] = parent_context.get("parent_page")
17
- context["is_searching"] = parent_context.get("is_searching")
18
- context["is_filtering"] = parent_context.get("is_filtering")
19
- context["is_searching_whole_tree"] = parent_context.get(
20
- "is_searching_whole_tree"
21
- )
18
+ context["parent_page"] = parent_page
19
+
20
+ if parent_page and (
21
+ parent_context.get("is_searching") or parent_context.get("is_filtering")
22
+ ):
23
+ # Results are switchable between searching the whole tree and searching just this parent.
24
+ # Add extra signposting to show which scope we're in, and provide a link to switch scope.
25
+ if parent_context.get("is_searching_whole_tree"):
26
+ context["result_scope"] = "whole_tree"
27
+ else:
28
+ context["result_scope"] = "parent"
29
+ else:
30
+ # No signposting needed
31
+ context["result_scope"] = None
22
32
 
23
33
  # If results are not paginated e.g. when using the OrderingColumn,
24
34
  # all items are displayed on the page
@@ -27,6 +37,7 @@ class PageTitleColumn(BaseColumn):
27
37
  if context["page_obj"]:
28
38
  context["start_index"] = context["page_obj"].start_index()
29
39
  context["end_index"] = context["page_obj"].end_index()
40
+
30
41
  return context
31
42
 
32
43
  def get_cell_context_data(self, instance, parent_context):
@@ -28,15 +28,19 @@ urlpatterns = [
28
28
  path("api/", include(api_urls)),
29
29
  path("failwhale/", home.error_test, name="wagtailadmin_error_test"),
30
30
  # TODO: Move into wagtailadmin_pages namespace
31
- path("pages/", listing.IndexView.as_view(), name="wagtailadmin_explore_root"),
31
+ path(
32
+ "pages/",
33
+ listing.ExplorableIndexView.as_view(),
34
+ name="wagtailadmin_explore_root",
35
+ ),
32
36
  path(
33
37
  "pages/<int:parent_page_id>/",
34
- listing.IndexView.as_view(),
38
+ listing.ExplorableIndexView.as_view(),
35
39
  name="wagtailadmin_explore",
36
40
  ),
37
41
  path(
38
42
  "pages/<int:parent_page_id>/results/",
39
- listing.IndexView.as_view(results_only=True),
43
+ listing.ExplorableIndexView.as_view(results_only=True),
40
44
  name="wagtailadmin_explore_results",
41
45
  ),
42
46
  # bulk actions
@@ -127,6 +131,7 @@ def get_sprite_hash():
127
131
  global sprite_hash
128
132
  if not sprite_hash:
129
133
  content = str(home.sprite(None).content, "utf-8")
134
+ # SECRET_KEY is used to prevent exposing the Wagtail version
130
135
  sprite_hash = hashlib.sha1(
131
136
  (content + settings.SECRET_KEY).encode("utf-8")
132
137
  ).hexdigest()[:8]
@@ -121,4 +121,9 @@ urlpatterns = [
121
121
  name="workflow_history_detail",
122
122
  ),
123
123
  path("<int:page_id>/history/", history.PageHistoryView.as_view(), name="history"),
124
+ path(
125
+ "<int:page_id>/history/results/",
126
+ history.PageHistoryView.as_view(results_only=True),
127
+ name="history_results",
128
+ ),
124
129
  ]
@@ -5,6 +5,11 @@ from wagtail.admin.views import workflows
5
5
  app_name = "wagtailadmin_workflows"
6
6
  urlpatterns = [
7
7
  path("list/", workflows.Index.as_view(), name="index"),
8
+ path(
9
+ "list/results/",
10
+ workflows.Index.as_view(results_only=True),
11
+ name="index_results",
12
+ ),
8
13
  path("add/", workflows.Create.as_view(), name="add"),
9
14
  path("enable/<int:pk>/", workflows.enable_workflow, name="enable"),
10
15
  path("disable/<int:pk>/", workflows.Disable.as_view(), name="disable"),
@@ -23,6 +28,11 @@ urlpatterns = [
23
28
  ),
24
29
  path("tasks/select_type/", workflows.select_task_type, name="select_task_type"),
25
30
  path("tasks/index/", workflows.TaskIndex.as_view(), name="task_index"),
31
+ path(
32
+ "tasks/index/results/",
33
+ workflows.TaskIndex.as_view(results_only=True),
34
+ name="task_index_results",
35
+ ),
26
36
  path("tasks/edit/<int:pk>/", workflows.EditTask.as_view(), name="edit_task"),
27
37
  path(
28
38
  "tasks/disable/<int:pk>/", workflows.DisableTask.as_view(), name="disable_task"
@@ -7,7 +7,6 @@ from django.http import Http404
7
7
  from django.shortcuts import get_object_or_404
8
8
  from django.template.response import TemplateResponse
9
9
  from django.urls.base import reverse
10
- from django.utils.http import urlencode
11
10
  from django.utils.translation import gettext_lazy as _
12
11
  from django.views.generic.base import View
13
12
 
@@ -93,7 +92,7 @@ def can_choose_page(
93
92
 
94
93
  if user_perm == "move_to":
95
94
  return page_to_move.permissions_for_user(user).can_move_to(page)
96
- if user_perm == "copy_to":
95
+ if user_perm in {"add_subpage", "copy_to"}:
97
96
  return page.permissions_for_user(user).can_add_subpage()
98
97
 
99
98
  return True
@@ -286,6 +285,11 @@ class BrowseView(View):
286
285
  selected_locale = None
287
286
  locale_options = []
288
287
  if self.i18n_enabled:
288
+ # Ensure query parameters (e.g. `page_type`, `user_perms`, etc.) are
289
+ # preserved when switching locales, but reset the pagination as the
290
+ # number of pages might be different.
291
+ new_params = request.GET.copy()
292
+ new_params.pop("p", None)
289
293
  if self.parent_page.is_root():
290
294
  # 'locale' is the current value of the "Locale" selector in the UI
291
295
  if request.GET.get("locale"):
@@ -298,25 +302,20 @@ class BrowseView(View):
298
302
 
299
303
  # we are at the Root level, so get the locales from the current pages
300
304
  choose_url = reverse("wagtailadmin_choose_page")
301
- locale_options = [
302
- {
303
- "locale": locale,
304
- "url": choose_url
305
- + "?"
306
- + urlencode(
307
- {
308
- "page_type": page_type_string,
309
- "locale": locale.language_code,
310
- }
311
- ),
312
- }
313
- for locale in Locale.objects.filter(
314
- pk__in=pages.values_list("locale_id")
315
- ).exclude(pk=active_locale_id)
316
- ]
305
+ for locale in Locale.objects.filter(
306
+ pk__in=pages.values_list("locale_id")
307
+ ).exclude(pk=active_locale_id):
308
+ new_params["locale"] = locale.language_code
309
+ locale_options.append(
310
+ {
311
+ "locale": locale,
312
+ "url": choose_url + "?" + new_params.urlencode(),
313
+ }
314
+ )
317
315
  else:
318
316
  # We have a parent page (that is not the root page). Use its locale as the selected localer
319
317
  selected_locale = self.parent_page.locale
318
+ new_params.pop("locale", None)
320
319
  # and get the locales based on its available translations
321
320
  locales_and_parent_pages = {
322
321
  item["locale"]: item["pk"]
@@ -332,17 +331,14 @@ class BrowseView(View):
332
331
  "wagtailadmin_choose_page_child",
333
332
  args=[locales_and_parent_pages[locale.pk]],
334
333
  )
335
-
336
334
  locale_options.append(
337
335
  {
338
336
  "locale": locale,
339
- "url": choose_child_url
340
- + "?"
341
- + urlencode({"page_type": page_type_string}),
337
+ "url": choose_child_url + "?" + new_params.urlencode(),
342
338
  }
343
339
  )
344
340
 
345
- # finally, filter the browseable pages on the selected locale
341
+ # finally, filter the browsable pages on the selected locale
346
342
  if selected_locale:
347
343
  pages = pages.filter(locale=selected_locale)
348
344
 
@@ -355,7 +351,7 @@ class BrowseView(View):
355
351
  except InvalidPage:
356
352
  raise Http404
357
353
 
358
- # Annotate each page with can_choose/can_decend flags
354
+ # Annotate each page with can_choose/can_descend flags
359
355
  for page in pages:
360
356
  page.can_choose = can_choose_page(
361
357
  page,
@@ -5,6 +5,7 @@ from django.utils.translation import gettext_lazy
5
5
  from wagtail import hooks
6
6
  from wagtail.admin import messages
7
7
  from wagtail.admin.forms.collections import CollectionForm
8
+ from wagtail.admin.ui.tables import TitleColumn
8
9
  from wagtail.admin.views.generic import CreateView, DeleteView, EditView, IndexView
9
10
  from wagtail.models import Collection
10
11
  from wagtail.permissions import collection_permission_policy
@@ -14,18 +15,31 @@ class Index(IndexView):
14
15
  permission_policy = collection_permission_policy
15
16
  model = Collection
16
17
  context_object_name = "collections"
17
- template_name = "wagtailadmin/collections/index.html"
18
+ results_template_name = "wagtailadmin/collections/index_results.html"
18
19
  add_url_name = "wagtailadmin_collections:add"
19
20
  index_url_name = "wagtailadmin_collections:index"
20
21
  page_title = gettext_lazy("Collections")
21
22
  add_item_label = gettext_lazy("Add a collection")
22
23
  header_icon = "folder-open-1"
24
+ columns = [
25
+ TitleColumn(
26
+ "name",
27
+ label=gettext_lazy("Name"),
28
+ url_name="wagtailadmin_collections:edit",
29
+ id_accessor="0",
30
+ accessor="1",
31
+ )
32
+ ]
33
+ _show_breadcrumbs = True
23
34
 
24
35
  def get_queryset(self):
25
36
  return self.permission_policy.instances_user_has_any_permission_for(
26
37
  self.request.user, ["add", "change", "delete"]
27
38
  ).exclude(depth=1)
28
39
 
40
+ def get_table(self, object_list):
41
+ return super().get_table(object_list.get_indented_choices())
42
+
29
43
 
30
44
  class Create(CreateView):
31
45
  permission_policy = collection_permission_policy
@@ -37,6 +51,7 @@ class Create(CreateView):
37
51
  edit_url_name = "wagtailadmin_collections:edit"
38
52
  index_url_name = "wagtailadmin_collections:index"
39
53
  header_icon = "folder-open-1"
54
+ _show_breadcrumbs = True
40
55
 
41
56
  def get_form(self, form_class=None):
42
57
  form = super().get_form(form_class)
@@ -67,6 +82,7 @@ class Edit(EditView):
67
82
  delete_url_name = "wagtailadmin_collections:delete"
68
83
  context_object_name = "collection"
69
84
  header_icon = "folder-open-1"
85
+ _show_breadcrumbs = True
70
86
 
71
87
  def _user_may_move_collection(self, user, instance):
72
88
  """