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
@@ -153,13 +153,13 @@ class TestRichTextFieldComparison(TestFieldComparison):
153
153
  SimplePage._meta.get_field("content"),
154
154
  SimplePage(content="Original content"),
155
155
  SimplePage(
156
- content='<script type="text/javascript">doSomethingBad();</script>'
156
+ content='Do something good. <script type="text/javascript">doSomethingBad();</script>'
157
157
  ),
158
158
  )
159
159
 
160
160
  self.assertEqual(
161
161
  comparison.htmldiff(),
162
- '<span class="deletion">Original content</span><span class="addition">doSomethingBad();</span>',
162
+ '<span class="deletion">Original content</span><span class="addition">Do something good.</span>',
163
163
  )
164
164
  self.assertIsInstance(comparison.htmldiff(), SafeString)
165
165
 
@@ -491,7 +491,7 @@ class TestStreamFieldComparison(TestCase):
491
491
 
492
492
  self.assertEqual(
493
493
  comparison.htmldiff(),
494
- '<div class="comparison__child-object">I really like <span class="deletion">Wagtail &lt;3</span><span class="addition">evil code &gt;_&lt; doSomethingBad();</span></div>',
494
+ '<div class="comparison__child-object">I really like <span class="deletion">Wagtail &lt;3</span><span class="addition">evil code &gt;_&lt;</span></div>',
495
495
  )
496
496
  self.assertIsInstance(comparison.htmldiff(), SafeString)
497
497
 
@@ -525,7 +525,7 @@ class TestStreamFieldComparison(TestCase):
525
525
 
526
526
  self.assertEqual(
527
527
  comparison.htmldiff(),
528
- '<div class="comparison__child-object">Original and unchanged content</div>\n<div class="comparison__child-object addition">I really like evil code &gt;_&lt; doSomethingBad();</div>',
528
+ '<div class="comparison__child-object">Original and unchanged content</div>\n<div class="comparison__child-object addition">I really like evil code &gt;_&lt;</div>',
529
529
  )
530
530
  self.assertIsInstance(comparison.htmldiff(), SafeString)
531
531
 
@@ -559,7 +559,7 @@ class TestStreamFieldComparison(TestCase):
559
559
 
560
560
  self.assertEqual(
561
561
  comparison.htmldiff(),
562
- '<div class="comparison__child-object">Original and unchanged content</div>\n<div class="comparison__child-object deletion">I really like evil code &gt;_&lt; doSomethingBad();</div>',
562
+ '<div class="comparison__child-object">Original and unchanged content</div>\n<div class="comparison__child-object deletion">I really like evil code &gt;_&lt;</div>',
563
563
  )
564
564
  self.assertIsInstance(comparison.htmldiff(), SafeString)
565
565
 
@@ -1228,7 +1228,7 @@ class TestChildRelationComparison(TestCase):
1228
1228
  def test_has_changed(self):
1229
1229
  # Father Christmas renamed to Santa Claus. And Father Ted added.
1230
1230
  # Father Christmas should be mapped to Father Ted because they
1231
- # are most alike. Santa claus should be displayed as "new"
1231
+ # are most alike. Santa Claus should be displayed as "new"
1232
1232
  event_page = EventPage(title="Event page", slug="event")
1233
1233
  event_page.speakers.add(
1234
1234
  EventPageSpeaker(
@@ -0,0 +1,404 @@
1
+ from django.contrib.auth import get_user_model
2
+ from django.contrib.auth.models import Group, Permission
3
+ from django.contrib.contenttypes.models import ContentType
4
+ from django.test import TestCase
5
+ from django.urls import reverse
6
+ from django.utils import timezone
7
+ from freezegun import freeze_time
8
+
9
+ from wagtail.admin.views.home import (
10
+ LockedPagesPanel,
11
+ RecentEditsPanel,
12
+ UserObjectsInWorkflowModerationPanel,
13
+ WorkflowObjectsToModeratePanel,
14
+ )
15
+ from wagtail.coreutils import get_dummy_request
16
+ from wagtail.models import GroupPagePermission, Page, Workflow, WorkflowContentType
17
+ from wagtail.test.testapp.models import FullFeaturedSnippet, SimplePage
18
+ from wagtail.test.utils import WagtailTestUtils
19
+
20
+
21
+ class TestRecentEditsPanel(WagtailTestUtils, TestCase):
22
+ def setUp(self):
23
+ # Find root page
24
+ self.root_page = Page.objects.get(id=2)
25
+
26
+ # Add child page
27
+ child_page = SimplePage(
28
+ title="Hello world!",
29
+ slug="hello-world",
30
+ content="Some content here",
31
+ )
32
+ self.root_page.add_child(instance=child_page)
33
+ self.revision = child_page.save_revision()
34
+ self.revision.publish()
35
+ self.child_page = SimplePage.objects.get(id=child_page.id)
36
+
37
+ self.user_alice = self.create_superuser(username="alice", password="password")
38
+ self.create_superuser(username="bob", password="password")
39
+
40
+ def change_something(self, title):
41
+ post_data = {"title": title, "content": "Some content", "slug": "hello-world"}
42
+ response = self.client.post(
43
+ reverse("wagtailadmin_pages:edit", args=(self.child_page.id,)), post_data
44
+ )
45
+
46
+ # Should be redirected to edit page
47
+ self.assertRedirects(
48
+ response, reverse("wagtailadmin_pages:edit", args=(self.child_page.id,))
49
+ )
50
+
51
+ # The page should have "has_unpublished_changes" flag set
52
+ child_page_new = SimplePage.objects.get(id=self.child_page.id)
53
+ self.assertTrue(child_page_new.has_unpublished_changes)
54
+
55
+ def go_to_dashboard_response(self):
56
+ response = self.client.get(reverse("wagtailadmin_home"))
57
+ self.assertEqual(response.status_code, 200)
58
+ return response
59
+
60
+ def test_your_recent_edits(self):
61
+ # Login as Bob
62
+ self.login(username="bob", password="password")
63
+
64
+ # Bob hasn't edited anything yet
65
+ response = self.client.get(reverse("wagtailadmin_home"))
66
+ self.assertNotIn("Your most recent edits", response.content.decode("utf-8"))
67
+
68
+ # Login as Alice
69
+ self.client.logout()
70
+ self.login(username="alice", password="password")
71
+
72
+ # Alice changes something
73
+ self.change_something("Alice's edit")
74
+
75
+ # Edit should show up on dashboard
76
+ response = self.go_to_dashboard_response()
77
+ self.assertIn("Your most recent edits", response.content.decode("utf-8"))
78
+
79
+ # Bob changes something
80
+ self.login(username="bob", password="password")
81
+ self.change_something("Bob's edit")
82
+
83
+ # Edit shows up on Bobs dashboard
84
+ response = self.go_to_dashboard_response()
85
+ self.assertIn("Your most recent edits", response.content.decode("utf-8"))
86
+
87
+ # Login as Alice again
88
+ self.client.logout()
89
+ self.login(username="alice", password="password")
90
+
91
+ # Alice's dashboard should still list that first edit
92
+ response = self.go_to_dashboard_response()
93
+ self.assertIn("Your most recent edits", response.content.decode("utf-8"))
94
+
95
+ def test_missing_page_record(self):
96
+ # Ensure that the panel still renders when one of the page IDs returned from querying
97
+ # PageLogEntry has no corresponding Page object. This can happen if a page is deleted,
98
+ # because PageLogEntry records are kept on deletion.
99
+
100
+ self.login(username="alice", password="password")
101
+ self.change_something("Alice's edit")
102
+ self.child_page.delete()
103
+ response = self.client.get(reverse("wagtailadmin_home"))
104
+ self.assertEqual(response.status_code, 200)
105
+
106
+ def test_panel(self):
107
+ """Test if the panel actually returns expected pages"""
108
+ self.login(username="bob", password="password")
109
+ # change a page
110
+
111
+ edit_timestamp = timezone.now()
112
+ with freeze_time(edit_timestamp):
113
+ self.change_something("Bob's edit")
114
+
115
+ # set a user to 'mock' a request
116
+ self.client.user = get_user_model().objects.get(email="bob@example.com")
117
+ # get the panel to get the last edits
118
+ panel = RecentEditsPanel()
119
+ ctx = panel.get_context_data({"request": self.client})
120
+
121
+ page = Page.objects.get(pk=self.child_page.id).specific
122
+
123
+ # check the timestamp matches the edit
124
+ self.assertEqual(ctx["last_edits"][0][0], edit_timestamp)
125
+ # check if the page in this list is the specific page
126
+ self.assertEqual(ctx["last_edits"][0][1], page)
127
+
128
+ def test_copying_does_not_count_as_an_edit(self):
129
+ self.login(username="bob", password="password")
130
+ # change a page
131
+ self.change_something("Bob was ere")
132
+
133
+ # copy the page
134
+ post_data = {
135
+ "new_title": "Goodbye world!",
136
+ "new_slug": "goodbye-world",
137
+ "new_parent_page": str(self.root_page.id),
138
+ "copy_subpages": False,
139
+ "alias": False,
140
+ }
141
+ self.client.post(
142
+ reverse("wagtailadmin_pages:copy", args=(self.child_page.id,)), post_data
143
+ )
144
+ # check that page has been copied
145
+ self.assertTrue(Page.objects.get(title="Goodbye world!"))
146
+
147
+ response = self.client.get(reverse("wagtailadmin_home"))
148
+ self.assertEqual(response.status_code, 200)
149
+ self.assertContains(response, "Your most recent edits")
150
+ self.assertContains(response, "Bob was ere")
151
+ self.assertNotContains(response, "Goodbye world!")
152
+
153
+
154
+ class TestRecentEditsQueryCount(WagtailTestUtils, TestCase):
155
+ fixtures = ["test.json"]
156
+
157
+ def setUp(self):
158
+ self.bob = self.create_superuser(username="bob", password="password")
159
+ self.dummy_request = get_dummy_request()
160
+ self.dummy_request.user = self.bob
161
+ workflow = Workflow.objects.first()
162
+ workflow_pages = {5, 6}
163
+ locked_pages = {6, 9}
164
+ scheduled_pages = {9, 12}
165
+ # make a bunch of page edits (all to EventPages, so that calls to specific() don't add
166
+ # an unpredictable number of queries)
167
+ pages_to_edit = list(
168
+ Page.objects.filter(id__in=[4, 5, 6, 9, 12, 13]).order_by("pk").specific()
169
+ )
170
+ for page in pages_to_edit:
171
+ revision = page.save_revision(user=self.bob, log_action=True)
172
+ if page.pk in workflow_pages:
173
+ workflow.start(page, self.bob)
174
+ if page.pk in locked_pages:
175
+ page.locked = True
176
+ page.locked_by = self.bob
177
+ page.locked_at = timezone.now()
178
+ page.save()
179
+ if page.pk in scheduled_pages:
180
+ revision.approved_go_live_at = timezone.now()
181
+ revision.save()
182
+
183
+ def test_panel_query_count(self):
184
+ panel = RecentEditsPanel()
185
+ parent_context = {"request": self.dummy_request}
186
+ # Warm up the cache
187
+ html = panel.render_html(parent_context)
188
+
189
+ with self.assertNumQueries(5):
190
+ # Rendering RecentEditsPanel should not generate N+1 queries -
191
+ # i.e. any number less than 6 would be reasonable here
192
+ html = panel.render_html(parent_context)
193
+ # check that the panel is still actually returning results
194
+ self.assertIn("Ameristralia Day", html)
195
+ soup = self.get_soup(html)
196
+ self.assertEqual(len(soup.select('svg use[href="#icon-lock"]')), 2)
197
+ expected_statuses = [
198
+ "live + draft",
199
+ "live + scheduled",
200
+ "live + scheduled",
201
+ "in moderation",
202
+ "in moderation",
203
+ ]
204
+ statuses = [
205
+ "".join(e.find_all(string=True, recursive=False)).strip()
206
+ for e in soup.select(".w-status")
207
+ ]
208
+ self.assertEqual(statuses, expected_statuses)
209
+
210
+
211
+ class TestLockedPagesQueryCount(WagtailTestUtils, TestCase):
212
+ fixtures = ["test.json"]
213
+
214
+ def setUp(self):
215
+ self.bob = self.create_superuser(username="bob", password="password")
216
+ self.dummy_request = get_dummy_request()
217
+ self.dummy_request.user = self.bob
218
+
219
+ pages = Page.objects.filter(pk__in=[9, 12, 13]).order_by("pk")
220
+ for i, page in enumerate(pages):
221
+ page.locked = True
222
+ page.locked_by = self.bob
223
+ page.locked_at = timezone.now() + timezone.timedelta(hours=i)
224
+ page.save()
225
+
226
+ def test_panel_query_count(self):
227
+ panel = LockedPagesPanel()
228
+ parent_context = {"request": self.dummy_request, "csrf_token": "dummy"}
229
+ # Warm up the cache
230
+ html = panel.render_html(parent_context)
231
+
232
+ with self.assertNumQueries(1):
233
+ html = panel.render_html(parent_context)
234
+ soup = self.get_soup(html)
235
+ # Should be sorted descending by locked_at
236
+ expected_titles = [
237
+ "Saint Patrick (single event)",
238
+ "Steal underpants",
239
+ "Ameristralia Day",
240
+ ]
241
+ titles = [e.get_text(strip=True) for e in soup.select(".title-wrapper a")]
242
+ self.assertEqual(titles, expected_titles)
243
+
244
+
245
+ class UserObjectsInWorkflowModerationQueryCount(WagtailTestUtils, TestCase):
246
+ fixtures = ["test.json"]
247
+
248
+ def setUp(self):
249
+ self.superuser = self.create_superuser(username="admin", password="password")
250
+ self.bob = self.create_user(username="bob", password="password")
251
+ self.someone_else = self.create_user(
252
+ username="someoneelse", password="password"
253
+ )
254
+ editors = Group.objects.get(name="Editors")
255
+ editors.user_set.add(self.bob, self.someone_else)
256
+
257
+ workflow = Workflow.objects.first()
258
+ WorkflowContentType.objects.create(
259
+ workflow=workflow,
260
+ content_type=ContentType.objects.get_for_model(FullFeaturedSnippet),
261
+ )
262
+ GroupPagePermission.objects.create(
263
+ group=editors, page=Page.get_first_root_node(), permission_type="change"
264
+ )
265
+ editors.permissions.add(
266
+ Permission.objects.get(codename="change_fullfeaturedsnippet")
267
+ )
268
+
269
+ # Pages owned by bob, but workflow started by someone else
270
+ Page.objects.filter(id__in=[9, 12]).update(owner=self.bob)
271
+ for page in Page.objects.filter(id__in=[9, 12]).specific():
272
+ page.save_revision()
273
+ workflow.start(page, self.someone_else)
274
+ # Lock it to test the lock indicator
275
+ page.locked = True
276
+ page.locked_by = self.superuser
277
+ page.locked_at = timezone.now()
278
+ page.save()
279
+
280
+ # Page workflow started by bob
281
+ for page in Page.objects.filter(id__in=[4, 13]).specific():
282
+ page.save_revision()
283
+ workflow.start(page, self.bob)
284
+
285
+ # Snippet workflow started by bob
286
+ for i in range(1, 3):
287
+ obj = FullFeaturedSnippet.objects.create(text=f"Some obj {i}")
288
+ obj.save_revision()
289
+ workflow.start(obj, self.bob)
290
+
291
+ self.dummy_request = get_dummy_request()
292
+ self.dummy_request.user = self.bob
293
+
294
+ def test_panel_query_count(self):
295
+ panel = UserObjectsInWorkflowModerationPanel()
296
+ parent_context = {"request": self.dummy_request}
297
+ # Warm up the cache
298
+ html = panel.render_html(parent_context)
299
+
300
+ with self.assertNumQueries(4):
301
+ html = panel.render_html(parent_context)
302
+
303
+ soup = self.get_soup(html)
304
+ self.assertEqual(len(soup.select('svg use[href="#icon-lock"]')), 2)
305
+ expected_titles = [
306
+ "Some obj 2",
307
+ "Some obj 1",
308
+ "Saint Patrick (single event)",
309
+ "Christmas",
310
+ "Steal underpants",
311
+ "Ameristralia Day",
312
+ ]
313
+ titles = [e.get_text(strip=True) for e in soup.select(".title-wrapper a")]
314
+ self.assertEqual(titles, expected_titles)
315
+
316
+
317
+ class WorkflowObjectsToModerateQueryCount(WagtailTestUtils, TestCase):
318
+ fixtures = ["test.json"]
319
+
320
+ def setUp(self):
321
+ self.superuser = self.create_superuser(username="admin", password="password")
322
+ self.bob = self.create_user(username="bob", password="password")
323
+ self.moderator = self.create_user(username="moderator", password="password")
324
+
325
+ editors = Group.objects.get(name="Editors")
326
+ moderators = Group.objects.get(name="Moderators")
327
+
328
+ editors.user_set.add(self.bob)
329
+ moderators.user_set.add(self.moderator)
330
+
331
+ root = Page.get_first_root_node()
332
+ GroupPagePermission.objects.create(
333
+ group=editors, page=root, permission_type="change"
334
+ )
335
+ GroupPagePermission.objects.create(
336
+ group=moderators, page=root, permission_type="change"
337
+ )
338
+ GroupPagePermission.objects.create(
339
+ group=moderators, page=root, permission_type="publish"
340
+ )
341
+
342
+ editors.permissions.add(
343
+ Permission.objects.get(codename="change_fullfeaturedsnippet")
344
+ )
345
+ moderators.permissions.add(
346
+ *Permission.objects.filter(
347
+ codename__in=[
348
+ "change_fullfeaturedsnippet",
349
+ "publish_fullfeaturedsnippet",
350
+ ]
351
+ ),
352
+ )
353
+
354
+ workflow = Workflow.objects.first()
355
+ WorkflowContentType.objects.create(
356
+ workflow=workflow,
357
+ content_type=ContentType.objects.get_for_model(FullFeaturedSnippet),
358
+ )
359
+
360
+ # Pages workflow started by bob and locked by moderator
361
+ for page in Page.objects.filter(id__in=[9, 12]).specific():
362
+ page.save_revision()
363
+ workflow.start(page, self.bob)
364
+ # Lock it to test the lock indicator
365
+ page.locked = True
366
+ page.locked_by = self.moderator
367
+ page.locked_at = timezone.now()
368
+ page.save()
369
+
370
+ # Page workflow started by bob
371
+ for page in Page.objects.filter(id__in=[4, 13]).specific():
372
+ page.save_revision()
373
+ workflow.start(page, self.bob)
374
+
375
+ # Snippet workflow started by bob
376
+ for i in range(1, 3):
377
+ obj = FullFeaturedSnippet.objects.create(text=f"Some obj {i}")
378
+ obj.save_revision()
379
+ workflow.start(obj, self.bob)
380
+
381
+ self.dummy_request = get_dummy_request()
382
+ self.dummy_request.user = self.moderator
383
+
384
+ def test_panel_query_count(self):
385
+ panel = WorkflowObjectsToModeratePanel()
386
+ parent_context = {"request": self.dummy_request, "csrf_token": "dummy"}
387
+ # Warm up the cache
388
+ html = panel.render_html(parent_context)
389
+
390
+ with self.assertNumQueries(13):
391
+ html = panel.render_html(parent_context)
392
+
393
+ soup = self.get_soup(html)
394
+ self.assertEqual(len(soup.select('svg use[href="#icon-lock"]')), 2)
395
+ expected_titles = [
396
+ "Some obj 2",
397
+ "Some obj 1",
398
+ "Saint Patrick (single event)",
399
+ "Christmas",
400
+ "Steal underpants",
401
+ "Ameristralia Day",
402
+ ]
403
+ titles = [e.get_text(strip=True) for e in soup.select(".title-wrapper a")]
404
+ self.assertEqual(titles, expected_titles)
@@ -9,7 +9,7 @@ class TestDbWhitelisterMethods(WagtailTestUtils, TestCase):
9
9
  self.whitelister = EditorHTMLConverter().whitelister
10
10
 
11
11
  def test_clean_tag_node_div(self):
12
- soup = self.get_soup("<div>foo</div>", "html5lib")
12
+ soup = self.get_soup("<div>foo</div>")
13
13
  tag = soup.div
14
14
  self.assertEqual(tag.name, "div")
15
15
  self.whitelister.clean_tag_node(soup, tag)
@@ -18,7 +18,6 @@ class TestDbWhitelisterMethods(WagtailTestUtils, TestCase):
18
18
  def test_clean_tag_node_with_data_embedtype(self):
19
19
  soup = self.get_soup(
20
20
  '<p><a data-embedtype="image" data-id=1 data-format="left" data-alt="bar" irrelevant="baz">foo</a></p>',
21
- "html5lib",
22
21
  )
23
22
  tag = soup.p
24
23
  self.whitelister.clean_tag_node(soup, tag)
@@ -29,14 +28,13 @@ class TestDbWhitelisterMethods(WagtailTestUtils, TestCase):
29
28
  def test_clean_tag_node_with_data_linktype(self):
30
29
  soup = self.get_soup(
31
30
  '<a data-linktype="document" data-id="1" irrelevant="baz">foo</a>',
32
- "html5lib",
33
31
  )
34
32
  tag = soup.a
35
33
  self.whitelister.clean_tag_node(soup, tag)
36
34
  self.assertEqual(str(tag), '<a id="1" linktype="document">foo</a>')
37
35
 
38
36
  def test_clean_tag_node(self):
39
- soup = self.get_soup('<a irrelevant="baz">foo</a>', "html5lib")
37
+ soup = self.get_soup('<a irrelevant="baz">foo</a>')
40
38
  tag = soup.a
41
39
  self.whitelister.clean_tag_node(soup, tag)
42
40
  self.assertEqual(str(tag), "<a>foo</a>")
@@ -52,7 +50,8 @@ class TestDbWhitelister(WagtailTestUtils, TestCase):
52
50
  (necessary because we can't guarantee the order that attributes are output in)
53
51
  """
54
52
  self.assertEqual(
55
- self.get_soup(str1, "html5lib"), self.get_soup(str2, "html5lib")
53
+ self.get_soup(str1),
54
+ self.get_soup(str2),
56
55
  )
57
56
 
58
57
  def test_page_link_is_rewritten(self):
@@ -189,7 +189,7 @@ class TestGetFormForModel(TestCase):
189
189
  self.assertIn("speakers", form.formsets)
190
190
  self.assertNotIn("related_links", form.formsets)
191
191
 
192
- def test_get_form_for_model_with_widget_overides_by_class(self):
192
+ def test_get_form_for_model_with_widget_overrides_by_class(self):
193
193
  EventPageForm = get_form_for_model(
194
194
  EventPage,
195
195
  form_class=WagtailAdminPageForm,
@@ -201,7 +201,7 @@ class TestGetFormForModel(TestCase):
201
201
  self.assertEqual(type(form.fields["date_from"]), forms.DateField)
202
202
  self.assertEqual(type(form.fields["date_from"].widget), forms.PasswordInput)
203
203
 
204
- def test_get_form_for_model_with_widget_overides_by_instance(self):
204
+ def test_get_form_for_model_with_widget_overrides_by_instance(self):
205
205
  EventPageForm = get_form_for_model(
206
206
  EventPage,
207
207
  form_class=WagtailAdminPageForm,
@@ -0,0 +1,84 @@
1
+ import json
2
+ import re
3
+
4
+ from django.test import TestCase
5
+ from django.test.client import Client
6
+ from django.urls import reverse
7
+
8
+ from wagtail.test.utils import WagtailTestUtils
9
+
10
+
11
+ class TestKeyboardShortcutsDialog(WagtailTestUtils, TestCase):
12
+ def setUp(self):
13
+ self.login()
14
+
15
+ def test_keyboard_shortcuts_trigger_in_sidebar(self):
16
+ response = self.client.get(reverse("wagtailadmin_home"))
17
+ self.assertEqual(response.status_code, 200)
18
+
19
+ sidebar_data = (
20
+ self.get_soup(response.content)
21
+ .select_one("#wagtail-sidebar-props")
22
+ .contents[0]
23
+ )
24
+
25
+ self.assertIn(
26
+ json.dumps(
27
+ {
28
+ "role": "button",
29
+ "data-a11y-dialog-show": "keyboard-shortcuts-dialog",
30
+ "data-action": "w-action#noop:prevent:stop",
31
+ "data-controller": "w-action",
32
+ }
33
+ ),
34
+ sidebar_data,
35
+ )
36
+
37
+ def test_keyboard_shortcuts_dialog(self):
38
+ response = self.client.get(reverse("wagtailadmin_home"))
39
+
40
+ self.assertEqual(response.status_code, 200)
41
+ self.assertTemplateUsed(
42
+ response, "wagtailadmin/shared/keyboard_shortcuts_dialog.html"
43
+ )
44
+
45
+ soup = self.get_soup(response.content)
46
+
47
+ # Check that the keyboard shortcuts dialog is present
48
+ shortcuts_dialog = soup.select_one("#keyboard-shortcuts-dialog")
49
+ self.assertIsNotNone(shortcuts_dialog)
50
+
51
+ # Check that the keyboard shortcuts dialog has basic accessible content
52
+ self.assertIn(
53
+ "All keyboard shortcuts", shortcuts_dialog.find("caption").prettify()
54
+ )
55
+ self.assertIn("Keyboard shortcut", shortcuts_dialog.find("thead").prettify())
56
+
57
+
58
+ class TestMacKeyboardShortcutsDialog(WagtailTestUtils, TestCase):
59
+ def setUp(self):
60
+ # Creates a client with a Mac user agent
61
+ self.client = Client(
62
+ headers={
63
+ "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"
64
+ }
65
+ )
66
+ self.login()
67
+
68
+ def test_mac_useragent_and_behavior(self):
69
+ response = self.client.get(reverse("wagtailadmin_home"))
70
+
71
+ # Check that the user agent is a Mac
72
+ user_agent = response.context["request"].headers.get("User-Agent", "")
73
+ is_mac = re.search(r"Mac|iPod|iPhone|iPad", user_agent)
74
+
75
+ # Add assertions based on expected Mac behavior
76
+ self.assertTrue(is_mac)
77
+
78
+ # Check that the keyboard shortcuts dialog has Mac-specific content
79
+ soup = self.get_soup(response.content)
80
+ shortcuts_dialog = soup.select_one("#keyboard-shortcuts-dialog")
81
+ all_shortcuts = shortcuts_dialog.select("kbd")
82
+ for shortcut in all_shortcuts:
83
+ # All shortcuts should have the ⌘ symbol
84
+ self.assertIn("⌘", shortcut.prettify())