wagtail 6.0.1__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 (512) 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/ca/LC_MESSAGES/django.mo +0 -0
  9. wagtail/admin/locale/ca/LC_MESSAGES/django.po +122 -0
  10. wagtail/admin/locale/de/LC_MESSAGES/django.mo +0 -0
  11. wagtail/admin/locale/de/LC_MESSAGES/django.po +5 -5
  12. wagtail/admin/locale/en/LC_MESSAGES/django.po +474 -385
  13. wagtail/admin/locale/en/LC_MESSAGES/djangojs.po +3 -3
  14. wagtail/admin/locale/es/LC_MESSAGES/django.mo +0 -0
  15. wagtail/admin/locale/es/LC_MESSAGES/django.po +6 -6
  16. wagtail/admin/locale/fr/LC_MESSAGES/django.mo +0 -0
  17. wagtail/admin/locale/fr/LC_MESSAGES/django.po +70 -3
  18. wagtail/admin/locale/he_IL/LC_MESSAGES/django.mo +0 -0
  19. wagtail/admin/locale/he_IL/LC_MESSAGES/django.po +2 -6
  20. wagtail/admin/locale/he_IL/LC_MESSAGES/djangojs.mo +0 -0
  21. wagtail/admin/locale/he_IL/LC_MESSAGES/djangojs.po +2 -2
  22. wagtail/admin/locale/hr_HR/LC_MESSAGES/django.mo +0 -0
  23. wagtail/admin/locale/hr_HR/LC_MESSAGES/django.po +4 -0
  24. wagtail/admin/locale/hu/LC_MESSAGES/django.mo +0 -0
  25. wagtail/admin/locale/hu/LC_MESSAGES/django.po +142 -2
  26. wagtail/admin/locale/it/LC_MESSAGES/django.mo +0 -0
  27. wagtail/admin/locale/it/LC_MESSAGES/django.po +80 -8
  28. wagtail/admin/locale/it/LC_MESSAGES/djangojs.mo +0 -0
  29. wagtail/admin/locale/it/LC_MESSAGES/djangojs.po +14 -2
  30. wagtail/admin/locale/lv/LC_MESSAGES/django.mo +0 -0
  31. wagtail/admin/locale/lv/LC_MESSAGES/django.po +154 -1
  32. wagtail/admin/locale/pt_PT/LC_MESSAGES/django.mo +0 -0
  33. wagtail/admin/locale/pt_PT/LC_MESSAGES/django.po +73 -2
  34. wagtail/admin/locale/ro/LC_MESSAGES/django.mo +0 -0
  35. wagtail/admin/locale/ro/LC_MESSAGES/django.po +3 -3
  36. wagtail/admin/locale/sl/LC_MESSAGES/django.mo +0 -0
  37. wagtail/admin/locale/sl/LC_MESSAGES/django.po +145 -2
  38. wagtail/admin/locale/sv/LC_MESSAGES/django.mo +0 -0
  39. wagtail/admin/locale/sv/LC_MESSAGES/django.po +77 -3
  40. wagtail/admin/locale/zh_Hant/LC_MESSAGES/django.mo +0 -0
  41. wagtail/admin/locale/zh_Hant/LC_MESSAGES/django.po +17 -1
  42. wagtail/admin/panels/comment_panel.py +1 -1
  43. wagtail/admin/panels/field_panel.py +1 -1
  44. wagtail/admin/rich_text/converters/editor_html.py +3 -1
  45. wagtail/admin/rich_text/editors/draftail/__init__.py +28 -2
  46. wagtail/admin/static/wagtailadmin/css/core.css +1 -1
  47. wagtail/admin/static/wagtailadmin/css/panels/draftail.css +1 -1
  48. wagtail/admin/static/wagtailadmin/images/favicon.ico +0 -0
  49. wagtail/admin/static/wagtailadmin/js/bulk-actions.js +1 -1
  50. wagtail/admin/static/wagtailadmin/js/chooser-modal.js +1 -1
  51. wagtail/admin/static/wagtailadmin/js/chooser-widget-telepath.js +1 -1
  52. wagtail/admin/static/wagtailadmin/js/chooser-widget.js +1 -1
  53. wagtail/admin/static/wagtailadmin/js/comments.js +1 -1
  54. wagtail/admin/static/wagtailadmin/js/core.js +1 -1
  55. wagtail/admin/static/wagtailadmin/js/core.js.LICENSE.txt +1 -1
  56. wagtail/admin/static/wagtailadmin/js/date-time-chooser.js +1 -1
  57. wagtail/admin/static/wagtailadmin/js/draftail.js +1 -1
  58. wagtail/admin/static/wagtailadmin/js/expanding-formset.js +1 -1
  59. wagtail/admin/static/wagtailadmin/js/filtered-select.js +1 -1
  60. wagtail/admin/static/wagtailadmin/js/modal-workflow.js +1 -1
  61. wagtail/admin/static/wagtailadmin/js/page-chooser-modal.js +1 -1
  62. wagtail/admin/static/wagtailadmin/js/page-chooser-telepath.js +1 -1
  63. wagtail/admin/static/wagtailadmin/js/page-chooser.js +1 -1
  64. wagtail/admin/static/wagtailadmin/js/preview-panel.js +1 -1
  65. wagtail/admin/static/wagtailadmin/js/privacy-switch.js +1 -1
  66. wagtail/admin/static/wagtailadmin/js/sidebar.js +1 -1
  67. wagtail/admin/static/wagtailadmin/js/task-chooser-modal.js +1 -1
  68. wagtail/admin/static/wagtailadmin/js/task-chooser.js +1 -1
  69. wagtail/admin/static/wagtailadmin/js/telepath/blocks.js +1 -1
  70. wagtail/admin/static/wagtailadmin/js/telepath/telepath.js +1 -1
  71. wagtail/admin/static/wagtailadmin/js/telepath/widgets.js +1 -1
  72. wagtail/admin/static/wagtailadmin/js/userbar.js +1 -1
  73. wagtail/admin/static/wagtailadmin/js/vendor.js +1 -1
  74. wagtail/admin/static/wagtailadmin/js/vendor.js.LICENSE.txt +4 -4
  75. wagtail/admin/static/wagtailadmin/js/wagtailadmin.js +1 -1
  76. wagtail/admin/static/wagtailadmin/js/workflow-action.js +1 -1
  77. wagtail/admin/staticfiles.py +1 -0
  78. wagtail/admin/templates/wagtailadmin/admin_base.html +1 -0
  79. wagtail/admin/templates/wagtailadmin/base.html +1 -0
  80. wagtail/admin/templates/wagtailadmin/collection_privacy/set_privacy.html +3 -1
  81. wagtail/admin/templates/wagtailadmin/collections/edit.html +0 -1
  82. wagtail/admin/templates/wagtailadmin/collections/index_results.html +10 -0
  83. wagtail/admin/templates/wagtailadmin/generic/base.html +1 -9
  84. wagtail/admin/templates/wagtailadmin/generic/form.html +4 -2
  85. wagtail/admin/templates/wagtailadmin/generic/history/action_cell.html +27 -0
  86. wagtail/admin/templates/wagtailadmin/generic/index_results.html +8 -0
  87. wagtail/admin/templates/wagtailadmin/home/workflow_objects_to_moderate.html +3 -4
  88. wagtail/admin/templates/wagtailadmin/icons/keyboard.svg +1 -0
  89. wagtail/admin/templates/wagtailadmin/page_privacy/set_privacy.html +3 -1
  90. wagtail/admin/templates/wagtailadmin/pages/_editor_js.html +0 -15
  91. wagtail/admin/templates/wagtailadmin/pages/action_menu/save_draft.html +3 -1
  92. wagtail/admin/templates/wagtailadmin/pages/choose_parent.html +17 -0
  93. wagtail/admin/templates/wagtailadmin/pages/explorable_index.html +8 -0
  94. wagtail/admin/templates/wagtailadmin/pages/history.html +1 -61
  95. wagtail/admin/templates/wagtailadmin/pages/index.html +1 -5
  96. wagtail/admin/templates/wagtailadmin/pages/listing/_locked_indicator.html +2 -2
  97. wagtail/admin/templates/wagtailadmin/pages/listing/_page_title_column_header.html +25 -27
  98. wagtail/admin/templates/wagtailadmin/pages/page_listing_header.html +2 -1
  99. wagtail/admin/templates/wagtailadmin/panels/multi_field_panel_child.html +1 -1
  100. wagtail/admin/templates/wagtailadmin/panels/publishing/schedule_publishing_panel.html +3 -1
  101. wagtail/admin/templates/wagtailadmin/panels/tabbed_interface.html +1 -1
  102. wagtail/admin/templates/wagtailadmin/shared/active_filters.html +2 -1
  103. wagtail/admin/templates/wagtailadmin/shared/breadcrumbs.html +8 -0
  104. wagtail/admin/templates/wagtailadmin/shared/forms/single_checkbox.html +1 -1
  105. wagtail/admin/templates/wagtailadmin/shared/headers/page_edit_header.html +1 -1
  106. wagtail/admin/templates/wagtailadmin/shared/headers/slim_header.html +21 -9
  107. wagtail/admin/templates/wagtailadmin/shared/human_readable_date.html +1 -1
  108. wagtail/admin/templates/wagtailadmin/shared/keyboard_shortcuts_dialog.html +29 -0
  109. wagtail/admin/templates/wagtailadmin/shared/side_panel_toggle.html +2 -1
  110. wagtail/admin/templates/wagtailadmin/skeleton.html +2 -1
  111. wagtail/admin/templates/wagtailadmin/tables/related_objects_cell.html +9 -0
  112. wagtail/admin/templates/wagtailadmin/tables/title_cell.html +9 -7
  113. wagtail/admin/templates/wagtailadmin/widgets/draftail_rich_text_area.html +1 -1
  114. wagtail/admin/templates/wagtailadmin/workflows/create.html +6 -23
  115. wagtail/admin/templates/wagtailadmin/workflows/create_task.html +6 -15
  116. wagtail/admin/templates/wagtailadmin/workflows/edit.html +6 -23
  117. wagtail/admin/templates/wagtailadmin/workflows/edit_task.html +6 -13
  118. wagtail/admin/templates/wagtailadmin/workflows/includes/task_usage_cell.html +4 -4
  119. wagtail/admin/templates/wagtailadmin/workflows/includes/workflow_tasks_cell.html +18 -0
  120. wagtail/admin/templates/wagtailadmin/workflows/includes/workflow_title_cell.html +7 -0
  121. wagtail/admin/templates/wagtailadmin/workflows/includes/workflow_used_by_cell.html +25 -0
  122. wagtail/admin/templates/wagtailadmin/workflows/index.html +0 -99
  123. wagtail/admin/templates/wagtailadmin/workflows/index_results.html +10 -0
  124. wagtail/admin/templates/wagtailadmin/workflows/task_index.html +0 -30
  125. wagtail/admin/templates/wagtailadmin/workflows/task_index_results.html +10 -0
  126. wagtail/admin/templates/wagtailadmin/workflows/usage.html +1 -1
  127. wagtail/admin/templatetags/wagtailadmin_tags.py +116 -39
  128. wagtail/admin/tests/api/test_pages.py +26 -10
  129. wagtail/admin/tests/pages/test_create_page.py +10 -4
  130. wagtail/admin/tests/pages/test_custom_listing.py +37 -0
  131. wagtail/admin/tests/pages/test_edit_page.py +6 -6
  132. wagtail/admin/tests/pages/test_explorer_view.py +19 -18
  133. wagtail/admin/tests/pages/test_move_page.py +1 -1
  134. wagtail/admin/tests/pages/test_page_usage.py +50 -2
  135. wagtail/admin/tests/pages/test_parent_page_chooser_view.py +119 -0
  136. wagtail/admin/tests/pages/test_preview.py +18 -4
  137. wagtail/admin/tests/test_account_management.py +20 -1
  138. wagtail/admin/tests/test_audit_log.py +172 -5
  139. wagtail/admin/tests/test_checks.py +92 -0
  140. wagtail/admin/tests/test_collections_views.py +19 -5
  141. wagtail/admin/tests/test_compare.py +6 -6
  142. wagtail/admin/tests/test_dashboard.py +404 -0
  143. wagtail/admin/tests/test_dbwhitelister.py +4 -5
  144. wagtail/admin/tests/test_edit_handlers.py +2 -2
  145. wagtail/admin/tests/test_keyboard_shortcuts.py +84 -0
  146. wagtail/admin/tests/test_page_chooser.py +31 -18
  147. wagtail/admin/tests/test_privacy.py +36 -2
  148. wagtail/admin/tests/test_rich_text.py +168 -23
  149. wagtail/admin/tests/test_templatetags.py +411 -43
  150. wagtail/admin/tests/test_views.py +4 -2
  151. wagtail/admin/tests/test_workflows.py +531 -9
  152. wagtail/admin/tests/tests.py +3 -1
  153. wagtail/admin/tests/ui/test_tables.py +48 -1
  154. wagtail/admin/tests/viewsets/test_model_viewset.py +130 -23
  155. wagtail/admin/ui/side_panels.py +3 -1
  156. wagtail/admin/ui/tables/__init__.py +13 -1
  157. wagtail/admin/ui/tables/pages.py +17 -6
  158. wagtail/admin/urls/__init__.py +8 -3
  159. wagtail/admin/urls/pages.py +5 -0
  160. wagtail/admin/urls/workflows.py +10 -0
  161. wagtail/admin/views/chooser.py +20 -24
  162. wagtail/admin/views/collections.py +17 -1
  163. wagtail/admin/views/generic/base.py +34 -4
  164. wagtail/admin/views/generic/history.py +220 -51
  165. wagtail/admin/views/generic/mixins.py +7 -4
  166. wagtail/admin/views/generic/models.py +54 -47
  167. wagtail/admin/views/generic/multiple_upload.py +17 -8
  168. wagtail/admin/views/generic/usage.py +17 -11
  169. wagtail/admin/views/home.py +15 -12
  170. wagtail/admin/views/mixins.py +30 -0
  171. wagtail/admin/views/pages/choose_parent.py +73 -0
  172. wagtail/admin/views/pages/history.py +54 -66
  173. wagtail/admin/views/pages/listing.py +187 -106
  174. wagtail/admin/views/pages/usage.py +6 -1
  175. wagtail/admin/views/pages/utils.py +70 -1
  176. wagtail/admin/views/workflows.py +150 -21
  177. wagtail/admin/viewsets/model.py +2 -2
  178. wagtail/admin/viewsets/pages.py +77 -0
  179. wagtail/admin/wagtail_hooks.py +40 -2
  180. wagtail/admin/widgets/button.py +10 -10
  181. wagtail/api/v2/filters.py +1 -1
  182. wagtail/api/v2/tests/test_pages.py +1 -1
  183. wagtail/blocks/base.py +18 -9
  184. wagtail/blocks/field_block.py +9 -7
  185. wagtail/blocks/list_block.py +16 -6
  186. wagtail/blocks/static_block.py +3 -0
  187. wagtail/blocks/stream_block.py +58 -23
  188. wagtail/blocks/struct_block.py +15 -9
  189. wagtail/contrib/forms/locale/en/LC_MESSAGES/django.po +39 -47
  190. wagtail/contrib/forms/locale/he_IL/LC_MESSAGES/django.mo +0 -0
  191. wagtail/contrib/forms/locale/he_IL/LC_MESSAGES/django.po +2 -2
  192. wagtail/contrib/forms/models.py +5 -5
  193. wagtail/contrib/forms/templates/wagtailforms/list_submissions.html +44 -33
  194. wagtail/contrib/forms/templates/wagtailforms/submissions_index.html +2 -63
  195. wagtail/contrib/forms/tests/test_models.py +26 -0
  196. wagtail/contrib/forms/urls.py +6 -0
  197. wagtail/contrib/forms/views.py +52 -49
  198. wagtail/contrib/redirects/locale/ca/LC_MESSAGES/django.mo +0 -0
  199. wagtail/contrib/redirects/locale/ca/LC_MESSAGES/django.po +3 -3
  200. wagtail/contrib/redirects/locale/en/LC_MESSAGES/django.po +34 -42
  201. wagtail/contrib/redirects/locale/fr/LC_MESSAGES/django.po +2 -2
  202. wagtail/contrib/redirects/locale/he_IL/LC_MESSAGES/django.mo +0 -0
  203. wagtail/contrib/redirects/locale/he_IL/LC_MESSAGES/django.po +2 -2
  204. wagtail/contrib/redirects/signal_handlers.py +1 -1
  205. wagtail/contrib/redirects/templates/wagtailredirects/index.html +1 -36
  206. wagtail/contrib/redirects/templates/wagtailredirects/index_results.html +18 -0
  207. wagtail/contrib/redirects/templates/wagtailredirects/redirect_target_cell.html +8 -0
  208. wagtail/contrib/redirects/tests/test_import_command.py +1 -1
  209. wagtail/contrib/redirects/tests/test_redirects.py +79 -8
  210. wagtail/contrib/redirects/urls.py +2 -1
  211. wagtail/contrib/redirects/views.py +85 -55
  212. wagtail/contrib/search_promotions/admin_urls.py +2 -1
  213. wagtail/contrib/search_promotions/locale/en/LC_MESSAGES/django.po +41 -64
  214. wagtail/contrib/search_promotions/locale/fr/LC_MESSAGES/django.po +2 -2
  215. wagtail/contrib/search_promotions/locale/he_IL/LC_MESSAGES/django.mo +0 -0
  216. wagtail/contrib/search_promotions/locale/he_IL/LC_MESSAGES/django.po +2 -2
  217. wagtail/contrib/search_promotions/locale/hr_HR/LC_MESSAGES/django.mo +0 -0
  218. wagtail/contrib/search_promotions/locale/hr_HR/LC_MESSAGES/django.po +41 -2
  219. wagtail/contrib/search_promotions/locale/it/LC_MESSAGES/django.mo +0 -0
  220. wagtail/contrib/search_promotions/locale/it/LC_MESSAGES/django.po +9 -3
  221. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/index.html +1 -16
  222. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/index_results.html +11 -0
  223. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/list.html +0 -51
  224. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/results.html +3 -16
  225. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/search_promotion_column.html +15 -0
  226. wagtail/contrib/search_promotions/tests.py +122 -9
  227. wagtail/contrib/search_promotions/views.py +66 -65
  228. wagtail/contrib/settings/locale/en/LC_MESSAGES/django.po +3 -3
  229. wagtail/contrib/settings/locale/he_IL/LC_MESSAGES/django.mo +0 -0
  230. wagtail/contrib/settings/locale/he_IL/LC_MESSAGES/django.po +2 -2
  231. wagtail/contrib/settings/locale/tr/LC_MESSAGES/django.mo +0 -0
  232. wagtail/contrib/settings/locale/tr/LC_MESSAGES/django.po +6 -2
  233. wagtail/contrib/settings/registry.py +10 -5
  234. wagtail/contrib/settings/tests/generic/test_admin.py +9 -0
  235. wagtail/contrib/settings/tests/site_specific/test_admin.py +10 -1
  236. wagtail/contrib/settings/tests/site_specific/test_model.py +3 -3
  237. wagtail/contrib/settings/tests/site_specific/test_templates.py +1 -1
  238. wagtail/contrib/settings/views.py +3 -1
  239. wagtail/contrib/simple_translation/locale/en/LC_MESSAGES/django.po +1 -1
  240. wagtail/contrib/simple_translation/tests/test_wagtail_hooks.py +2 -2
  241. wagtail/contrib/styleguide/locale/en/LC_MESSAGES/django.po +7 -7
  242. wagtail/contrib/styleguide/locale/he_IL/LC_MESSAGES/django.mo +0 -0
  243. wagtail/contrib/styleguide/locale/he_IL/LC_MESSAGES/django.po +2 -2
  244. wagtail/contrib/table_block/blocks.py +2 -2
  245. wagtail/contrib/table_block/locale/ca/LC_MESSAGES/django.mo +0 -0
  246. wagtail/contrib/table_block/locale/ca/LC_MESSAGES/django.po +27 -2
  247. wagtail/contrib/table_block/locale/en/LC_MESSAGES/django.po +1 -1
  248. wagtail/contrib/table_block/locale/hu/LC_MESSAGES/django.mo +0 -0
  249. wagtail/contrib/table_block/locale/hu/LC_MESSAGES/django.po +27 -2
  250. wagtail/contrib/table_block/locale/it/LC_MESSAGES/django.mo +0 -0
  251. wagtail/contrib/table_block/locale/it/LC_MESSAGES/django.po +27 -2
  252. wagtail/contrib/table_block/static/table_block/js/table.js +1 -1
  253. wagtail/contrib/table_block/tests.py +6 -0
  254. wagtail/contrib/typed_table_block/locale/ca/LC_MESSAGES/django.mo +0 -0
  255. wagtail/contrib/typed_table_block/locale/ca/LC_MESSAGES/django.po +12 -2
  256. wagtail/contrib/typed_table_block/locale/en/LC_MESSAGES/django.po +1 -1
  257. wagtail/contrib/typed_table_block/locale/hu/LC_MESSAGES/django.mo +0 -0
  258. wagtail/contrib/typed_table_block/locale/hu/LC_MESSAGES/django.po +12 -2
  259. wagtail/contrib/typed_table_block/locale/it/LC_MESSAGES/django.mo +0 -0
  260. wagtail/contrib/typed_table_block/locale/it/LC_MESSAGES/django.po +12 -2
  261. wagtail/contrib/typed_table_block/static/typed_table_block/js/typed_table_block.js +1 -1
  262. wagtail/coreutils.py +3 -2
  263. wagtail/documents/admin_urls.py +2 -2
  264. wagtail/documents/locale/en/LC_MESSAGES/django.po +22 -22
  265. wagtail/documents/locale/fr/LC_MESSAGES/django.po +2 -2
  266. wagtail/documents/locale/he_IL/LC_MESSAGES/django.mo +0 -0
  267. wagtail/documents/locale/he_IL/LC_MESSAGES/django.po +2 -2
  268. wagtail/documents/locale/hr_HR/LC_MESSAGES/django.mo +0 -0
  269. wagtail/documents/locale/hr_HR/LC_MESSAGES/django.po +19 -2
  270. wagtail/documents/locale/hu/LC_MESSAGES/django.mo +0 -0
  271. wagtail/documents/locale/hu/LC_MESSAGES/django.po +16 -2
  272. wagtail/documents/locale/it/LC_MESSAGES/django.mo +0 -0
  273. wagtail/documents/locale/it/LC_MESSAGES/django.po +19 -2
  274. wagtail/documents/migrations/0013_delete_uploadeddocument.py +16 -0
  275. wagtail/documents/models.py +1 -20
  276. wagtail/documents/rich_text/__init__.py +11 -7
  277. wagtail/documents/static/wagtaildocs/js/document-chooser-modal.js +1 -1
  278. wagtail/documents/static/wagtaildocs/js/document-chooser-telepath.js +1 -1
  279. wagtail/documents/static/wagtaildocs/js/document-chooser.js +1 -1
  280. wagtail/documents/templates/wagtaildocs/documents/index.html +0 -16
  281. wagtail/documents/tests/test_admin_views.py +155 -23
  282. wagtail/documents/tests/test_collection_privacy.py +55 -1
  283. wagtail/documents/tests/test_rich_text.py +14 -0
  284. wagtail/documents/views/documents.py +25 -22
  285. wagtail/documents/views/multiple.py +6 -7
  286. wagtail/documents/views/serve.py +16 -1
  287. wagtail/documents/wagtail_hooks.py +20 -15
  288. wagtail/embeds/blocks.py +5 -0
  289. wagtail/embeds/locale/en/LC_MESSAGES/django.po +2 -2
  290. wagtail/embeds/locale/fr/LC_MESSAGES/django.po +2 -2
  291. wagtail/embeds/locale/he_IL/LC_MESSAGES/django.mo +0 -0
  292. wagtail/embeds/locale/he_IL/LC_MESSAGES/django.po +2 -2
  293. wagtail/embeds/rich_text/__init__.py +1 -1
  294. wagtail/embeds/tests/test_rich_text.py +14 -0
  295. wagtail/embeds/wagtail_hooks.py +4 -14
  296. wagtail/fields.py +3 -48
  297. wagtail/images/admin_urls.py +2 -2
  298. wagtail/images/check_files/wagtail.jpg +0 -0
  299. wagtail/images/check_files/wagtail.png +0 -0
  300. wagtail/images/fields.py +2 -0
  301. wagtail/images/image_operations.py +1 -1
  302. wagtail/images/locale/ca/LC_MESSAGES/django.mo +0 -0
  303. wagtail/images/locale/ca/LC_MESSAGES/django.po +12 -0
  304. wagtail/images/locale/en/LC_MESSAGES/django.po +33 -45
  305. wagtail/images/locale/fr/LC_MESSAGES/django.po +2 -2
  306. wagtail/images/locale/he_IL/LC_MESSAGES/django.mo +0 -0
  307. wagtail/images/locale/he_IL/LC_MESSAGES/django.po +2 -2
  308. wagtail/images/locale/hu/LC_MESSAGES/django.mo +0 -0
  309. wagtail/images/locale/hu/LC_MESSAGES/django.po +28 -2
  310. wagtail/images/locale/it/LC_MESSAGES/django.mo +0 -0
  311. wagtail/images/locale/it/LC_MESSAGES/django.po +14 -2
  312. wagtail/images/locale/pt_PT/LC_MESSAGES/django.mo +0 -0
  313. wagtail/images/locale/pt_PT/LC_MESSAGES/django.po +4 -0
  314. wagtail/images/migrations/0026_delete_uploadedimage.py +16 -0
  315. wagtail/images/models.py +49 -43
  316. wagtail/images/rich_text/__init__.py +18 -8
  317. wagtail/images/static/wagtailimages/js/image-chooser-modal.js +1 -1
  318. wagtail/images/static/wagtailimages/js/image-chooser-telepath.js +1 -1
  319. wagtail/images/static/wagtailimages/js/image-chooser.js +1 -1
  320. wagtail/images/templates/wagtailimages/images/image_listing_header.html +6 -0
  321. wagtail/images/templates/wagtailimages/images/index.html +11 -51
  322. wagtail/images/tests/test_admin_views.py +119 -62
  323. wagtail/images/tests/test_image_operations.py +10 -0
  324. wagtail/images/tests/test_models.py +35 -33
  325. wagtail/images/tests/test_rich_text.py +14 -0
  326. wagtail/images/tests/utils.py +1 -1
  327. wagtail/images/views/images.py +35 -64
  328. wagtail/images/views/multiple.py +6 -7
  329. wagtail/images/wagtail_hooks.py +4 -14
  330. wagtail/locale/en/LC_MESSAGES/django.po +150 -136
  331. wagtail/locale/es/LC_MESSAGES/django.mo +0 -0
  332. wagtail/locale/es/LC_MESSAGES/django.po +3 -2
  333. wagtail/locale/fr/LC_MESSAGES/django.po +2 -2
  334. wagtail/locale/he_IL/LC_MESSAGES/django.mo +0 -0
  335. wagtail/locale/he_IL/LC_MESSAGES/django.po +2 -2
  336. wagtail/locale/it/LC_MESSAGES/django.mo +0 -0
  337. wagtail/locale/it/LC_MESSAGES/django.po +5 -5
  338. wagtail/locale/sl/LC_MESSAGES/django.mo +0 -0
  339. wagtail/locale/sl/LC_MESSAGES/django.po +27 -2
  340. wagtail/locales/locale/ar/LC_MESSAGES/django.po +1 -1
  341. wagtail/locales/locale/be/LC_MESSAGES/django.po +1 -1
  342. wagtail/locales/locale/bg/LC_MESSAGES/django.po +1 -1
  343. wagtail/locales/locale/ca/LC_MESSAGES/django.po +1 -1
  344. wagtail/locales/locale/cs/LC_MESSAGES/django.po +1 -1
  345. wagtail/locales/locale/cy/LC_MESSAGES/django.po +1 -1
  346. wagtail/locales/locale/da/LC_MESSAGES/django.po +1 -1
  347. wagtail/locales/locale/de/LC_MESSAGES/django.po +1 -1
  348. wagtail/locales/locale/el/LC_MESSAGES/django.po +1 -1
  349. wagtail/locales/locale/en/LC_MESSAGES/django.po +1 -1
  350. wagtail/locales/locale/es/LC_MESSAGES/django.po +1 -1
  351. wagtail/locales/locale/et/LC_MESSAGES/django.po +2 -2
  352. wagtail/locales/locale/fa/LC_MESSAGES/django.po +1 -1
  353. wagtail/locales/locale/fi/LC_MESSAGES/django.po +1 -1
  354. wagtail/locales/locale/fr/LC_MESSAGES/django.po +1 -1
  355. wagtail/locales/locale/gl/LC_MESSAGES/django.po +1 -1
  356. wagtail/locales/locale/he_IL/LC_MESSAGES/django.mo +0 -0
  357. wagtail/locales/locale/he_IL/LC_MESSAGES/django.po +3 -3
  358. wagtail/locales/locale/hr_HR/LC_MESSAGES/django.po +1 -1
  359. wagtail/locales/locale/hu/LC_MESSAGES/django.po +1 -1
  360. wagtail/locales/locale/id_ID/LC_MESSAGES/django.po +1 -1
  361. wagtail/locales/locale/is_IS/LC_MESSAGES/django.po +1 -1
  362. wagtail/locales/locale/it/LC_MESSAGES/django.po +1 -1
  363. wagtail/locales/locale/ja/LC_MESSAGES/django.po +1 -1
  364. wagtail/locales/locale/ko/LC_MESSAGES/django.po +1 -1
  365. wagtail/locales/locale/lt/LC_MESSAGES/django.po +1 -1
  366. wagtail/locales/locale/lv/LC_MESSAGES/django.po +1 -1
  367. wagtail/locales/locale/mi/LC_MESSAGES/django.po +1 -1
  368. wagtail/locales/locale/mn/LC_MESSAGES/django.po +1 -1
  369. wagtail/locales/locale/my/LC_MESSAGES/django.po +1 -1
  370. wagtail/locales/locale/nb/LC_MESSAGES/django.po +1 -1
  371. wagtail/locales/locale/nl/LC_MESSAGES/django.po +1 -1
  372. wagtail/locales/locale/pl/LC_MESSAGES/django.po +1 -1
  373. wagtail/locales/locale/pt_BR/LC_MESSAGES/django.po +1 -1
  374. wagtail/locales/locale/pt_PT/LC_MESSAGES/django.po +1 -1
  375. wagtail/locales/locale/ro/LC_MESSAGES/django.po +1 -1
  376. wagtail/locales/locale/ru/LC_MESSAGES/django.po +1 -1
  377. wagtail/locales/locale/sk_SK/LC_MESSAGES/django.po +1 -1
  378. wagtail/locales/locale/sl/LC_MESSAGES/django.po +1 -1
  379. wagtail/locales/locale/sv/LC_MESSAGES/django.po +1 -1
  380. wagtail/locales/locale/tet/LC_MESSAGES/django.po +1 -1
  381. wagtail/locales/locale/th/LC_MESSAGES/django.po +1 -1
  382. wagtail/locales/locale/tr/LC_MESSAGES/django.po +1 -1
  383. wagtail/locales/locale/tr_TR/LC_MESSAGES/django.po +1 -1
  384. wagtail/locales/locale/uk/LC_MESSAGES/django.po +1 -1
  385. wagtail/locales/locale/vi/LC_MESSAGES/django.po +1 -1
  386. wagtail/locales/locale/zh/LC_MESSAGES/django.po +1 -1
  387. wagtail/locales/locale/zh_Hans/LC_MESSAGES/django.po +1 -1
  388. wagtail/locales/locale/zh_Hant/LC_MESSAGES/django.po +1 -1
  389. wagtail/locales/tests.py +18 -3
  390. wagtail/locales/views.py +0 -1
  391. wagtail/management/commands/rebuild_references_index.py +3 -1
  392. wagtail/migrations/0092_alter_collectionviewrestriction_password_and_more.py +33 -0
  393. wagtail/migrations/0093_uploadedfile.py +53 -0
  394. wagtail/models/__init__.py +147 -32
  395. wagtail/models/i18n.py +1 -1
  396. wagtail/models/{collections.py → media.py} +33 -2
  397. wagtail/models/reference_index.py +1 -1
  398. wagtail/models/view_restrictions.py +10 -3
  399. wagtail/project_template/project_name/settings/base.py +6 -0
  400. wagtail/project_template/requirements.txt +1 -1
  401. wagtail/rich_text/__init__.py +25 -8
  402. wagtail/rich_text/pages.py +19 -8
  403. wagtail/rich_text/rewriters.py +140 -68
  404. wagtail/search/backends/database/mysql/mysql.py +3 -3
  405. wagtail/search/backends/database/postgres/postgres.py +3 -3
  406. wagtail/search/backends/database/sqlite/sqlite.py +2 -2
  407. wagtail/search/backends/elasticsearch7.py +4 -0
  408. wagtail/search/locale/en/LC_MESSAGES/django.po +3 -3
  409. wagtail/search/tests/test_postgres_backend.py +50 -0
  410. wagtail/sites/locale/en/LC_MESSAGES/django.po +8 -8
  411. wagtail/sites/locale/he_IL/LC_MESSAGES/django.mo +0 -0
  412. wagtail/sites/locale/he_IL/LC_MESSAGES/django.po +2 -2
  413. wagtail/sites/locale/ro/LC_MESSAGES/django.mo +0 -0
  414. wagtail/sites/locale/ro/LC_MESSAGES/django.po +3 -2
  415. wagtail/sites/tests.py +35 -9
  416. wagtail/sites/views.py +3 -1
  417. wagtail/snippets/locale/de/LC_MESSAGES/django.mo +0 -0
  418. wagtail/snippets/locale/de/LC_MESSAGES/django.po +7 -8
  419. wagtail/snippets/locale/en/LC_MESSAGES/django.po +16 -56
  420. wagtail/snippets/locale/fr/LC_MESSAGES/django.po +2 -2
  421. wagtail/snippets/locale/he_IL/LC_MESSAGES/django.mo +0 -0
  422. wagtail/snippets/locale/he_IL/LC_MESSAGES/django.po +2 -2
  423. wagtail/snippets/locale/hr_HR/LC_MESSAGES/django.mo +0 -0
  424. wagtail/snippets/locale/hr_HR/LC_MESSAGES/django.po +6 -2
  425. wagtail/snippets/locale/lv/LC_MESSAGES/django.mo +0 -0
  426. wagtail/snippets/locale/lv/LC_MESSAGES/django.po +12 -0
  427. wagtail/snippets/locale/zh_Hant/LC_MESSAGES/django.mo +0 -0
  428. wagtail/snippets/locale/zh_Hant/LC_MESSAGES/django.po +4 -0
  429. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser-telepath.js +1 -1
  430. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser.js +1 -1
  431. wagtail/snippets/templates/wagtailsnippets/snippets/action_menu/publish.html +3 -1
  432. wagtail/snippets/templates/wagtailsnippets/snippets/action_menu/save.html +3 -1
  433. wagtail/snippets/templates/wagtailsnippets/snippets/create.html +2 -3
  434. wagtail/snippets/templates/wagtailsnippets/snippets/edit.html +2 -3
  435. wagtail/snippets/tests/test_preview.py +13 -2
  436. wagtail/snippets/tests/test_snippets.py +41 -16
  437. wagtail/snippets/tests/test_viewset.py +95 -18
  438. wagtail/snippets/tests/test_workflows.py +12 -0
  439. wagtail/snippets/views/snippets.py +1 -40
  440. wagtail/templatetags/wagtailcore_tags.py +1 -1
  441. wagtail/test/demosite/models.py +1 -1
  442. wagtail/test/middleware.py +14 -1
  443. wagtail/test/testapp/fixtures/test.json +20 -0
  444. wagtail/test/testapp/migrations/0001_squashed_0073_revisablechildmodel_secret_text.py +8 -8
  445. wagtail/test/testapp/migrations/0023_snippetchoosermodel_full_featured.py +1 -0
  446. wagtail/test/testapp/migrations/0034_custompermissionmodel.py +44 -0
  447. wagtail/test/testapp/migrations/0035_modelwithcustommanager.py +30 -0
  448. wagtail/test/testapp/migrations/0036_complexdefaultstreampage.py +28 -0
  449. wagtail/test/testapp/models.py +79 -2
  450. wagtail/test/testapp/templates/tests/custom_docs_password_required.html +10 -0
  451. wagtail/test/testapp/templates/tests/custom_page_password_required.html +10 -0
  452. wagtail/test/testapp/views.py +24 -2
  453. wagtail/test/testapp/wagtail_hooks.py +19 -0
  454. wagtail/test/utils/wagtail_tests.py +2 -2
  455. wagtail/tests/test_blocks.py +262 -1
  456. wagtail/tests/test_migrations.py +1 -1
  457. wagtail/tests/test_page_model.py +77 -0
  458. wagtail/tests/test_page_privacy.py +18 -1
  459. wagtail/tests/test_rich_text.py +95 -5
  460. wagtail/tests/test_streamfield.py +43 -0
  461. wagtail/tests/test_utils.py +8 -2
  462. wagtail/tests/test_views.py +52 -1
  463. wagtail/tests/test_whitelist.py +7 -7
  464. wagtail/users/forms.py +3 -1
  465. wagtail/users/locale/en/LC_MESSAGES/django.po +124 -96
  466. wagtail/users/locale/fr/LC_MESSAGES/django.po +2 -2
  467. wagtail/users/locale/he_IL/LC_MESSAGES/django.mo +0 -0
  468. wagtail/users/locale/he_IL/LC_MESSAGES/django.po +2 -2
  469. wagtail/users/locale/hr_HR/LC_MESSAGES/django.mo +0 -0
  470. wagtail/users/locale/hr_HR/LC_MESSAGES/django.po +13 -2
  471. wagtail/users/migrations/0013_userprofile_density.py +23 -0
  472. wagtail/users/models.py +14 -3
  473. wagtail/users/templates/wagtailusers/groups/create.html +1 -7
  474. wagtail/users/templates/wagtailusers/groups/edit.html +1 -13
  475. wagtail/users/templates/wagtailusers/groups/includes/formatted_permissions.html +46 -2
  476. wagtail/users/templates/wagtailusers/groups/includes/group_form_js.html +0 -3
  477. wagtail/users/templates/wagtailusers/users/create.html +1 -14
  478. wagtail/users/templates/wagtailusers/users/edit.html +1 -14
  479. wagtail/users/templates/wagtailusers/users/index.html +2 -5
  480. wagtail/users/templates/wagtailusers/users/index_results.html +3 -13
  481. wagtail/users/templates/wagtailusers/users/user_cell.html +9 -0
  482. wagtail/users/templatetags/wagtailusers_tags.py +107 -20
  483. wagtail/users/tests/test_admin_views.py +669 -90
  484. wagtail/users/views/groups.py +58 -61
  485. wagtail/users/views/users.py +211 -92
  486. wagtail/users/wagtail_hooks.py +6 -38
  487. wagtail/users/widgets.py +3 -5
  488. wagtail/utils/text.py +1 -1
  489. wagtail/views.py +5 -9
  490. wagtail/whitelist.py +1 -1
  491. {wagtail-6.0.1.dist-info → wagtail-6.1rc1.dist-info}/METADATA +5 -6
  492. {wagtail-6.0.1.dist-info → wagtail-6.1rc1.dist-info}/RECORD +496 -477
  493. wagtail/admin/static/wagtailadmin/js/page-editor.js +0 -1
  494. wagtail/admin/static/wagtailadmin/js/vendor/mousetrap.min.js +0 -1
  495. wagtail/admin/static/wagtailadmin/js/vendor/urlify.js +0 -1
  496. wagtail/admin/static/wagtailadmin/js/vendor/xregexp.min.js +0 -1
  497. wagtail/admin/templates/wagtailadmin/collections/index.html +0 -34
  498. wagtail/admin/templates/wagtailadmin/pages/revisions/_actions.html +0 -22
  499. wagtail/admin/templates/wagtailadmin/shared/page_breadcrumbs.html +0 -55
  500. wagtail/admin/tests/pages/test_dashboard.py +0 -172
  501. wagtail/contrib/redirects/templates/wagtailredirects/results.html +0 -23
  502. wagtail/documents/templates/wagtaildocs/documents/list.html +0 -2
  503. wagtail/search/tests/test_postgres_stemming.py +0 -40
  504. wagtail/sites/templates/wagtailsites/create.html +0 -7
  505. wagtail/sites/templates/wagtailsites/edit.html +0 -7
  506. wagtail/snippets/templates/wagtailsnippets/snippets/revisions/_actions.html +0 -36
  507. wagtail/users/templates/wagtailusers/users/list.html +0 -62
  508. wagtail/users/urls/users.py +0 -12
  509. {wagtail-6.0.1.dist-info → wagtail-6.1rc1.dist-info}/LICENSE +0 -0
  510. {wagtail-6.0.1.dist-info → wagtail-6.1rc1.dist-info}/WHEEL +0 -0
  511. {wagtail-6.0.1.dist-info → wagtail-6.1rc1.dist-info}/entry_points.txt +0 -0
  512. {wagtail-6.0.1.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
@@ -22,7 +22,6 @@ from wagtail.test.testapp.models import (
22
22
  SearchTestModel,
23
23
  VariousOnDeleteModel,
24
24
  )
25
- from wagtail.test.testapp.views import FCToyAlt1ViewSet
26
25
  from wagtail.test.utils.template_tests import AdminTemplateTestUtils
27
26
  from wagtail.test.utils.wagtail_tests import WagtailTestUtils
28
27
  from wagtail.utils.deprecation import RemovedInWagtail70Warning
@@ -994,7 +993,7 @@ class TestHistoryView(WagtailTestUtils, TestCase):
994
993
  )
995
994
  response = self.client.get(self.url)
996
995
  soup = self.get_soup(response.content)
997
- rows = soup.select("tbody tr")
996
+ rows = soup.select("#listing-results tbody tr")
998
997
  self.assertEqual(response.status_code, 200)
999
998
  self.assertEqual(len(rows), 2)
1000
999
 
@@ -1011,28 +1010,109 @@ class TestHistoryView(WagtailTestUtils, TestCase):
1011
1010
  for rendered_row, expected_row in zip(rendered_rows, expected):
1012
1011
  self.assertSequenceEqual(rendered_row, expected_row)
1013
1012
 
1014
- def test_filters(self):
1015
- 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
1016
1018
  soup = self.get_soup(response.content)
1017
- 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"})
1018
1031
  self.assertEqual(response.status_code, 200)
1032
+ soup = self.get_soup(response.content)
1033
+ rows = soup.select("#listing-results tbody tr")
1019
1034
  self.assertEqual(len(rows), 1)
1020
1035
  self.assertEqual(rows[0].select_one("td").text.strip(), "Edited")
1021
1036
 
1037
+ # Should only show the created log
1022
1038
  response = self.client.get(self.url, {"action": "wagtail.create"})
1039
+ self.assertEqual(response.status_code, 200)
1023
1040
  soup = self.get_soup(response.content)
1024
- rows = soup.select("tbody tr")
1041
+ rows = soup.select("#listing-results tbody tr")
1025
1042
  heading = soup.select_one('h2[role="alert"]')
1026
- self.assertEqual(response.status_code, 200)
1027
1043
  self.assertEqual(heading.string.strip(), "There is 1 match")
1028
1044
  self.assertEqual(len(rows), 1)
1029
1045
  self.assertEqual(rows[0].select_one("td").text.strip(), "Created")
1030
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
+
1031
1111
  def test_filtered_no_results(self):
1032
- response = self.client.get(self.url, {"timestamp_before": "2020-01-01"})
1112
+ response = self.client.get(self.url, {"timestamp_to": "2020-01-01"})
1033
1113
  soup = self.get_soup(response.content)
1034
1114
  results = soup.select_one("#listing-results")
1035
- table = soup.select_one("table")
1115
+ table = soup.select_one("#listing-results table")
1036
1116
  p = results.select_one("p")
1037
1117
  self.assertEqual(response.status_code, 200)
1038
1118
  self.assertIsNotNone(results)
@@ -1045,12 +1125,25 @@ class TestHistoryView(WagtailTestUtils, TestCase):
1045
1125
  response = self.client.get(self.url)
1046
1126
  soup = self.get_soup(response.content)
1047
1127
  results = soup.select_one("#listing-results")
1048
- table = soup.select_one("table")
1128
+ table = soup.select_one("#listing-results table")
1049
1129
  self.assertEqual(response.status_code, 200)
1050
1130
  self.assertIsNotNone(results)
1051
1131
  self.assertEqual(results.text.strip(), "There are no log entries to display.")
1052
1132
  self.assertIsNone(table)
1053
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
+
1054
1147
  def test_edit_view_links_to_history_view(self):
1055
1148
  edit_url = reverse("feature_complete_toy:edit", args=(quote(self.object.pk),))
1056
1149
  response = self.client.get(edit_url)
@@ -1059,6 +1152,19 @@ class TestHistoryView(WagtailTestUtils, TestCase):
1059
1152
  history_link = header.find("a", attrs={"href": self.url})
1060
1153
  self.assertIsNotNone(history_link)
1061
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
+
1062
1168
 
1063
1169
  class TestUsageView(WagtailTestUtils, TestCase):
1064
1170
  @classmethod
@@ -1084,7 +1190,7 @@ class TestUsageView(WagtailTestUtils, TestCase):
1084
1190
  h1 = soup.select_one("h1")
1085
1191
  self.assertEqual(h1.text.strip(), f"Usage: {self.object}")
1086
1192
 
1087
- tds = soup.select("tbody tr td")
1193
+ tds = soup.select("#listing-results tbody tr td")
1088
1194
  self.assertEqual(len(tds), 3)
1089
1195
  self.assertEqual(tds[0].text.strip(), str(self.tbx))
1090
1196
  self.assertEqual(tds[1].text.strip(), "Various on delete model")
@@ -1132,7 +1238,7 @@ class TestUsageView(WagtailTestUtils, TestCase):
1132
1238
  h1 = soup.select_one("h1")
1133
1239
  self.assertEqual(h1.text.strip(), f"Usage: {self.object}")
1134
1240
 
1135
- tds = soup.select("tbody tr td")
1241
+ tds = soup.select("#listing-results tbody tr td")
1136
1242
  self.assertEqual(len(tds), 3)
1137
1243
  self.assertEqual(tds[0].text.strip(), "(Private various on delete model)")
1138
1244
  self.assertEqual(tds[1].text.strip(), "Various on delete model")
@@ -1154,7 +1260,7 @@ class TestUsageView(WagtailTestUtils, TestCase):
1154
1260
  h1 = soup.select_one("h1")
1155
1261
  self.assertEqual(h1.text.strip(), f"Usage: {self.object}")
1156
1262
 
1157
- tds = soup.select("tbody tr td")
1263
+ tds = soup.select("#listing-results tbody tr td")
1158
1264
  self.assertEqual(len(tds), 3)
1159
1265
  self.assertEqual(tds[0].text.strip(), str(self.tbx))
1160
1266
  self.assertEqual(tds[1].text.strip(), "Various on delete model")
@@ -1180,7 +1286,7 @@ class TestUsageView(WagtailTestUtils, TestCase):
1180
1286
  response = self.client.get(self.url)
1181
1287
  soup = self.get_soup(response.content)
1182
1288
  results = soup.select_one("#listing-results")
1183
- table = soup.select_one("table")
1289
+ table = soup.select_one("#listing-results table")
1184
1290
  self.assertEqual(response.status_code, 200)
1185
1291
  self.assertIsNotNone(results)
1186
1292
  self.assertEqual(results.text.strip(), "There are no results.")
@@ -1425,14 +1531,11 @@ class TestCopyView(WagtailTestUtils, TestCase):
1425
1531
  self.assertRedirects(response, reverse("wagtailadmin_home"))
1426
1532
 
1427
1533
  def test_form_is_prefilled(self):
1428
- request = RequestFactory().get(self.url)
1429
- request.user = self.user
1430
- view = FCToyAlt1ViewSet().copy_view_class()
1431
- view.setup(request)
1432
- view.model = self.object.__class__
1433
- view.kwargs = {"pk": self.object.pk}
1434
-
1435
- 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")
1436
1539
 
1437
1540
 
1438
1541
  class TestEditHandler(WagtailTestUtils, TestCase):
@@ -1448,6 +1551,10 @@ class TestEditHandler(WagtailTestUtils, TestCase):
1448
1551
  response = self.client.get(self.url)
1449
1552
  self.assertEqual(response.status_code, 200)
1450
1553
  self.assertTemplateUsed(response, "wagtailadmin/shared/panel.html")
1554
+ # Many features from the Panels API are powered by client-side JS in
1555
+ # _editor_js.html. We need to make sure that this template is included
1556
+ # in the response for now.
1557
+ self.assertTemplateUsed(response, "wagtailadmin/pages/_editor_js.html", count=1)
1451
1558
 
1452
1559
  soup = self.get_soup(response.content)
1453
1560
 
@@ -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
  """