wagtail 7.2.1__py3-none-any.whl → 7.3rc1__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 (312) hide show
  1. wagtail/__init__.py +1 -1
  2. wagtail/actions/copy_for_translation.py +4 -2
  3. wagtail/admin/action_menu.py +4 -1
  4. wagtail/admin/api/actions/convert_alias.py +2 -2
  5. wagtail/admin/api/actions/copy.py +2 -2
  6. wagtail/admin/api/actions/copy_for_translation.py +2 -2
  7. wagtail/admin/api/actions/create_alias.py +2 -2
  8. wagtail/admin/api/actions/delete.py +1 -1
  9. wagtail/admin/api/actions/move.py +1 -1
  10. wagtail/admin/api/actions/publish.py +2 -2
  11. wagtail/admin/api/actions/revert_to_page_revision.py +2 -2
  12. wagtail/admin/api/actions/unpublish.py +1 -1
  13. wagtail/admin/api/filters.py +2 -2
  14. wagtail/admin/compare.py +22 -0
  15. wagtail/admin/forms/account.py +52 -1
  16. wagtail/admin/forms/comments.py +53 -0
  17. wagtail/admin/forms/models.py +36 -0
  18. wagtail/admin/forms/pages.py +7 -0
  19. wagtail/admin/forms/workflows.py +5 -2
  20. wagtail/admin/icons.py +4 -3
  21. wagtail/admin/locale/ar/LC_MESSAGES/django.mo +0 -0
  22. wagtail/admin/locale/ar/LC_MESSAGES/django.po +35 -0
  23. wagtail/admin/locale/en/LC_MESSAGES/django.po +262 -234
  24. wagtail/admin/locale/en/LC_MESSAGES/djangojs.po +72 -43
  25. wagtail/admin/locale/it/LC_MESSAGES/django.mo +0 -0
  26. wagtail/admin/locale/it/LC_MESSAGES/django.po +566 -6
  27. wagtail/admin/locale/it/LC_MESSAGES/djangojs.mo +0 -0
  28. wagtail/admin/locale/it/LC_MESSAGES/djangojs.po +40 -2
  29. wagtail/admin/locale/nl/LC_MESSAGES/django.mo +0 -0
  30. wagtail/admin/locale/nl/LC_MESSAGES/django.po +2 -2
  31. wagtail/admin/locale/sv/LC_MESSAGES/django.mo +0 -0
  32. wagtail/admin/locale/sv/LC_MESSAGES/django.po +330 -15
  33. wagtail/admin/locale/sv/LC_MESSAGES/djangojs.mo +0 -0
  34. wagtail/admin/locale/sv/LC_MESSAGES/djangojs.po +3 -2
  35. wagtail/admin/panels/comment_panel.py +1 -51
  36. wagtail/admin/panels/title_field_panel.py +3 -1
  37. wagtail/admin/static/wagtailadmin/css/core.css +1 -1
  38. wagtail/admin/static/wagtailadmin/js/bulk-actions.js +1 -1
  39. wagtail/admin/static/wagtailadmin/js/comments.js +1 -1
  40. wagtail/admin/static/wagtailadmin/js/core.js +1 -1
  41. wagtail/admin/static/wagtailadmin/js/core.js.LICENSE.txt +1 -1
  42. wagtail/admin/static/wagtailadmin/js/draftail.js +1 -1
  43. wagtail/admin/static/wagtailadmin/js/privacy-switch.js +1 -1
  44. wagtail/admin/static/wagtailadmin/js/sidebar.js +1 -1
  45. wagtail/admin/static/wagtailadmin/js/telepath/blocks.js +1 -1
  46. wagtail/admin/static/wagtailadmin/js/userbar.js +1 -1
  47. wagtail/admin/static/wagtailadmin/js/userbar.js.LICENSE.txt +1 -1
  48. wagtail/admin/static/wagtailadmin/js/vendor.js +1 -1
  49. wagtail/admin/static/wagtailadmin/js/wagtailadmin.js +1 -1
  50. wagtail/admin/templates/wagtailadmin/base.html +1 -1
  51. wagtail/admin/templates/wagtailadmin/generic/edit_partials.html +100 -0
  52. wagtail/admin/templates/wagtailadmin/generic/form.html +26 -5
  53. wagtail/admin/templates/wagtailadmin/generic/includes/_loaded_revision_inputs.html +3 -0
  54. wagtail/admin/templates/wagtailadmin/generic/listing_results.html +1 -17
  55. wagtail/admin/templates/wagtailadmin/pages/create.html +14 -4
  56. wagtail/admin/templates/wagtailadmin/pages/edit.html +16 -3
  57. wagtail/admin/templates/wagtailadmin/pages/edit_partials.html +31 -0
  58. wagtail/admin/templates/wagtailadmin/pages/index_results.html +1 -9
  59. wagtail/admin/templates/wagtailadmin/shared/autosave/indicator.html +36 -0
  60. wagtail/admin/templates/wagtailadmin/shared/breadcrumbs.html +1 -1
  61. wagtail/admin/templates/wagtailadmin/shared/editing_sessions/list.html +2 -2
  62. wagtail/admin/templates/wagtailadmin/shared/editing_sessions/module.html +19 -3
  63. wagtail/admin/templates/wagtailadmin/shared/headers/_actions.html +5 -0
  64. wagtail/admin/templates/wagtailadmin/shared/headers/_history_icon_link.html +2 -2
  65. wagtail/admin/templates/wagtailadmin/shared/headers/slim_header.html +8 -9
  66. wagtail/admin/templates/wagtailadmin/shared/listing/filter_partials.html +19 -0
  67. wagtail/admin/templates/wagtailadmin/shared/side_panel_toggle.html +3 -3
  68. wagtail/admin/templates/wagtailadmin/shared/side_panels/includes/side_panel.html +3 -0
  69. wagtail/admin/templates/wagtailadmin/shared/side_panels/includes/status/privacy.html +18 -6
  70. wagtail/admin/templates/wagtailadmin/shared/side_panels/includes/status/usage.html +11 -4
  71. wagtail/admin/templates/wagtailadmin/shared/side_panels/includes/status/workflow.html +22 -1
  72. wagtail/admin/templates/wagtailadmin/shared/side_panels/preview.html +1 -0
  73. wagtail/admin/templates/wagtailadmin/shared/side_panels.html +1 -2
  74. wagtail/admin/templates/wagtailadmin/shared/unsaved_changes_warning.html +20 -20
  75. wagtail/admin/templates/wagtailadmin/userbar/base.html +2 -0
  76. wagtail/admin/templates/wagtailadmin/userbar/item_accessibility.html +1 -1
  77. wagtail/admin/templatetags/wagtailadmin_tags.py +6 -14
  78. wagtail/admin/tests/pages/test_create_page.py +298 -1
  79. wagtail/admin/tests/pages/test_custom_listing.py +48 -2
  80. wagtail/admin/tests/pages/test_edit_page.py +721 -7
  81. wagtail/admin/tests/pages/test_explorer_view.py +9 -5
  82. wagtail/admin/tests/pages/test_page_search.py +15 -0
  83. wagtail/admin/tests/pages/test_revisions.py +4 -0
  84. wagtail/admin/tests/test_account_management.py +88 -2
  85. wagtail/admin/tests/test_collections_views.py +15 -15
  86. wagtail/admin/tests/test_compare.py +34 -0
  87. wagtail/admin/tests/test_editing_sessions.py +230 -8
  88. wagtail/admin/tests/test_forms.py +192 -1
  89. wagtail/admin/tests/test_icon_sprite.py +22 -2
  90. wagtail/admin/tests/test_page_chooser.py +13 -13
  91. wagtail/admin/tests/test_reports_views.py +4 -1
  92. wagtail/admin/tests/test_userbar.py +69 -5
  93. wagtail/admin/tests/test_views_generic.py +5 -5
  94. wagtail/admin/tests/test_workflows.py +14 -12
  95. wagtail/admin/tests/viewsets/test_model_viewset.py +13 -0
  96. wagtail/admin/ui/autosave.py +5 -0
  97. wagtail/admin/ui/editing_sessions.py +3 -0
  98. wagtail/admin/ui/side_panels.py +19 -20
  99. wagtail/admin/userbar.py +48 -27
  100. wagtail/admin/views/bulk_action/dispatcher.py +2 -2
  101. wagtail/admin/views/chooser.py +6 -6
  102. wagtail/admin/views/editing_sessions.py +20 -7
  103. wagtail/admin/views/generic/__init__.py +1 -0
  104. wagtail/admin/views/generic/chooser.py +5 -5
  105. wagtail/admin/views/generic/mixins.py +143 -26
  106. wagtail/admin/views/generic/models.py +157 -27
  107. wagtail/admin/views/generic/ordering.py +1 -1
  108. wagtail/admin/views/generic/preview.py +4 -4
  109. wagtail/admin/views/home.py +3 -1
  110. wagtail/admin/views/pages/create.py +77 -29
  111. wagtail/admin/views/pages/edit.py +160 -34
  112. wagtail/admin/views/pages/preview.py +4 -4
  113. wagtail/admin/views/pages/revisions.py +3 -0
  114. wagtail/admin/views/pages/search.py +4 -4
  115. wagtail/admin/views/pages/usage.py +2 -2
  116. wagtail/admin/views/tags.py +2 -2
  117. wagtail/admin/views/workflows.py +73 -54
  118. wagtail/admin/viewsets/model.py +34 -8
  119. wagtail/admin/widgets/button.py +11 -4
  120. wagtail/admin/widgets/chooser.py +6 -4
  121. wagtail/admin/widgets/slug.py +1 -1
  122. wagtail/api/v2/filters.py +27 -21
  123. wagtail/api/v2/pagination.py +4 -4
  124. wagtail/api/v2/views.py +7 -7
  125. wagtail/blocks/list_block.py +0 -8
  126. wagtail/blocks/migrations/migrate_operation.py +8 -1
  127. wagtail/blocks/stream_block.py +2 -10
  128. wagtail/blocks/struct_block.py +192 -12
  129. wagtail/compat.py +2 -2
  130. wagtail/contrib/forms/locale/en/LC_MESSAGES/django.po +1 -1
  131. wagtail/contrib/forms/locale/it/LC_MESSAGES/django.mo +0 -0
  132. wagtail/contrib/forms/locale/it/LC_MESSAGES/django.po +40 -2
  133. wagtail/contrib/frontend_cache/backends/azure.py +6 -6
  134. wagtail/contrib/frontend_cache/backends/cloudfront.py +2 -2
  135. wagtail/contrib/frontend_cache/utils.py +1 -1
  136. wagtail/contrib/redirects/forms.py +1 -1
  137. wagtail/contrib/redirects/locale/en/LC_MESSAGES/django.po +14 -14
  138. wagtail/contrib/redirects/locale/it/LC_MESSAGES/django.mo +0 -0
  139. wagtail/contrib/redirects/locale/it/LC_MESSAGES/django.po +15 -2
  140. wagtail/contrib/redirects/locale/sv/LC_MESSAGES/django.mo +0 -0
  141. wagtail/contrib/redirects/locale/sv/LC_MESSAGES/django.po +16 -3
  142. wagtail/contrib/redirects/middleware.py +11 -7
  143. wagtail/contrib/redirects/models.py +17 -1
  144. wagtail/contrib/redirects/tests/test_import_admin_views.py +3 -3
  145. wagtail/contrib/redirects/tests/test_redirects.py +56 -8
  146. wagtail/contrib/search_promotions/locale/en/LC_MESSAGES/django.po +6 -6
  147. wagtail/contrib/search_promotions/locale/it/LC_MESSAGES/django.mo +0 -0
  148. wagtail/contrib/search_promotions/locale/it/LC_MESSAGES/django.po +43 -2
  149. wagtail/contrib/search_promotions/tests.py +1 -1
  150. wagtail/contrib/search_promotions/views/settings.py +24 -25
  151. wagtail/contrib/settings/jinja2tags.py +2 -2
  152. wagtail/contrib/settings/locale/en/LC_MESSAGES/django.po +2 -2
  153. wagtail/contrib/settings/locale/it/LC_MESSAGES/django.mo +0 -0
  154. wagtail/contrib/settings/locale/it/LC_MESSAGES/django.po +6 -2
  155. wagtail/contrib/settings/locale/sv/LC_MESSAGES/django.mo +0 -0
  156. wagtail/contrib/settings/locale/sv/LC_MESSAGES/django.po +3 -2
  157. wagtail/contrib/settings/tests/generic/test_admin.py +118 -2
  158. wagtail/contrib/settings/tests/site_specific/test_admin.py +78 -15
  159. wagtail/contrib/settings/tests/site_specific/test_model.py +2 -2
  160. wagtail/contrib/settings/views.py +7 -0
  161. wagtail/contrib/simple_translation/locale/en/LC_MESSAGES/django.po +1 -1
  162. wagtail/contrib/simple_translation/locale/it/LC_MESSAGES/django.mo +0 -0
  163. wagtail/contrib/simple_translation/locale/it/LC_MESSAGES/django.po +5 -2
  164. wagtail/contrib/styleguide/locale/en/LC_MESSAGES/django.po +7 -7
  165. wagtail/contrib/styleguide/tests.py +2 -0
  166. wagtail/contrib/styleguide/views.py +4 -5
  167. wagtail/contrib/table_block/locale/ar/LC_MESSAGES/django.mo +0 -0
  168. wagtail/contrib/table_block/locale/ar/LC_MESSAGES/django.po +5 -1
  169. wagtail/contrib/table_block/locale/en/LC_MESSAGES/django.po +1 -1
  170. wagtail/contrib/table_block/locale/it/LC_MESSAGES/django.mo +0 -0
  171. wagtail/contrib/table_block/locale/it/LC_MESSAGES/django.po +5 -2
  172. wagtail/contrib/typed_table_block/blocks.py +37 -0
  173. wagtail/contrib/typed_table_block/locale/en/LC_MESSAGES/django.po +10 -10
  174. wagtail/contrib/typed_table_block/locale/sv/LC_MESSAGES/django.mo +0 -0
  175. wagtail/contrib/typed_table_block/locale/sv/LC_MESSAGES/django.po +4 -3
  176. wagtail/contrib/typed_table_block/tests.py +108 -0
  177. wagtail/coreutils.py +4 -4
  178. wagtail/documents/__init__.py +4 -4
  179. wagtail/documents/locale/en/LC_MESSAGES/django.po +15 -15
  180. wagtail/documents/locale/it/LC_MESSAGES/django.mo +0 -0
  181. wagtail/documents/locale/it/LC_MESSAGES/django.po +32 -2
  182. wagtail/documents/locale/sv/LC_MESSAGES/django.mo +0 -0
  183. wagtail/documents/locale/sv/LC_MESSAGES/django.po +32 -4
  184. wagtail/documents/models.py +1 -1
  185. wagtail/documents/tests/test_admin_views.py +19 -4
  186. wagtail/documents/tests/test_search.py +0 -2
  187. wagtail/documents/tests/test_views.py +6 -4
  188. wagtail/documents/views/bulk_actions/add_tags.py +1 -1
  189. wagtail/embeds/finders/facebook.py +3 -3
  190. wagtail/embeds/finders/instagram.py +3 -3
  191. wagtail/embeds/finders/oembed.py +7 -2
  192. wagtail/embeds/locale/en/LC_MESSAGES/django.po +1 -1
  193. wagtail/embeds/oembed_providers.py +8 -0
  194. wagtail/embeds/tests/test_embeds.py +35 -0
  195. wagtail/images/__init__.py +4 -4
  196. wagtail/images/admin_urls.py +3 -3
  197. wagtail/images/blocks.py +1 -1
  198. wagtail/images/formats.py +2 -2
  199. wagtail/images/image_operations.py +2 -2
  200. wagtail/images/locale/en/LC_MESSAGES/django.po +44 -40
  201. wagtail/images/locale/it/LC_MESSAGES/django.mo +0 -0
  202. wagtail/images/locale/it/LC_MESSAGES/django.po +50 -4
  203. wagtail/images/locale/nl/LC_MESSAGES/django.mo +0 -0
  204. wagtail/images/locale/nl/LC_MESSAGES/django.po +3 -3
  205. wagtail/images/locale/sv/LC_MESSAGES/django.mo +0 -0
  206. wagtail/images/locale/sv/LC_MESSAGES/django.po +49 -6
  207. wagtail/images/models.py +11 -10
  208. wagtail/images/templates/wagtailimages/images/index_results.html +1 -1
  209. wagtail/images/templates/wagtailimages/images/url_generator.html +17 -38
  210. wagtail/images/templates/wagtailimages/images/url_generator_output.html +28 -0
  211. wagtail/images/templatetags/wagtailimages_tags.py +4 -4
  212. wagtail/images/tests/test_admin_views.py +432 -59
  213. wagtail/images/tests/test_image_operations.py +2 -2
  214. wagtail/images/tests/test_models.py +0 -2
  215. wagtail/images/views/bulk_actions/add_tags.py +1 -1
  216. wagtail/images/views/images.py +72 -39
  217. wagtail/locale/en/LC_MESSAGES/django.po +103 -105
  218. wagtail/locale/it/LC_MESSAGES/django.mo +0 -0
  219. wagtail/locale/it/LC_MESSAGES/django.po +105 -2
  220. wagtail/locale/sv/LC_MESSAGES/django.mo +0 -0
  221. wagtail/locale/sv/LC_MESSAGES/django.po +95 -12
  222. wagtail/locales/locale/en/LC_MESSAGES/django.po +1 -1
  223. wagtail/locales/locale/it/LC_MESSAGES/django.mo +0 -0
  224. wagtail/locales/locale/it/LC_MESSAGES/django.po +13 -2
  225. wagtail/locales/locale/sv/LC_MESSAGES/django.mo +0 -0
  226. wagtail/locales/locale/sv/LC_MESSAGES/django.po +4 -3
  227. wagtail/locales/tests.py +5 -5
  228. wagtail/models/i18n.py +4 -6
  229. wagtail/models/media.py +18 -0
  230. wagtail/models/pages.py +65 -11
  231. wagtail/models/reference_index.py +15 -0
  232. wagtail/models/revisions.py +40 -12
  233. wagtail/models/workflows.py +0 -3
  234. wagtail/permission_policies/base.py +2 -2
  235. wagtail/permission_policies/collections.py +2 -2
  236. wagtail/project_template/requirements.txt +2 -2
  237. wagtail/search/locale/en/LC_MESSAGES/django.po +1 -1
  238. wagtail/signal_handlers.py +1 -0
  239. wagtail/sites/locale/en/LC_MESSAGES/django.po +1 -1
  240. wagtail/sites/locale/sv/LC_MESSAGES/django.mo +0 -0
  241. wagtail/sites/locale/sv/LC_MESSAGES/django.po +3 -2
  242. wagtail/sites/tests.py +7 -7
  243. wagtail/snippets/action_menu.py +2 -1
  244. wagtail/snippets/locale/en/LC_MESSAGES/django.po +23 -13
  245. wagtail/snippets/locale/sv/LC_MESSAGES/django.mo +0 -0
  246. wagtail/snippets/locale/sv/LC_MESSAGES/django.po +12 -1
  247. wagtail/snippets/tests/test_chooser_block.py +197 -0
  248. wagtail/snippets/tests/test_chooser_panel.py +149 -0
  249. wagtail/snippets/tests/test_chooser_views.py +375 -0
  250. wagtail/snippets/tests/test_chooser_widget.py +22 -0
  251. wagtail/snippets/tests/test_compare_revisions_view.py +167 -0
  252. wagtail/snippets/tests/test_copy_view.py +38 -0
  253. wagtail/snippets/tests/test_create_view.py +1159 -0
  254. wagtail/snippets/tests/test_delete_view.py +225 -0
  255. wagtail/snippets/tests/test_edit_view.py +2882 -0
  256. wagtail/snippets/tests/test_history_view.py +182 -0
  257. wagtail/snippets/tests/test_index_view.py +105 -0
  258. wagtail/snippets/tests/test_list_view.py +786 -0
  259. wagtail/snippets/tests/test_locking.py +28 -0
  260. wagtail/snippets/tests/test_permissions.py +167 -0
  261. wagtail/snippets/tests/test_preview.py +3 -3
  262. wagtail/snippets/tests/test_revert_view.py +343 -0
  263. wagtail/snippets/tests/test_snippet_models.py +112 -0
  264. wagtail/snippets/tests/test_unpublish_view.py +228 -0
  265. wagtail/snippets/tests/test_unschedule_view.py +151 -0
  266. wagtail/snippets/tests/test_viewset.py +38 -5
  267. wagtail/snippets/views/snippets.py +78 -30
  268. wagtail/snippets/widgets.py +2 -2
  269. wagtail/templatetags/wagtailcore_tags.py +2 -2
  270. wagtail/test/dummy_external_storage.py +1 -1
  271. wagtail/test/testapp/fixtures/test.json +22 -0
  272. wagtail/test/testapp/fixtures/test_empty.json +2 -0
  273. wagtail/test/testapp/fixtures/test_explorable_pages.json +10 -0
  274. wagtail/test/testapp/fixtures/test_specific.json +9 -0
  275. wagtail/test/testapp/migrations/0059_nopromotepage.py +25 -0
  276. wagtail/test/testapp/models.py +7 -0
  277. wagtail/test/testapp/views.py +5 -0
  278. wagtail/test/utils/page_tests.py +7 -7
  279. wagtail/test/utils/wagtail_factories/builder.py +2 -2
  280. wagtail/tests/streamfield_migrations/test_migrations.py +35 -0
  281. wagtail/tests/test_blocks.py +321 -61
  282. wagtail/tests/test_collection_model.py +12 -0
  283. wagtail/tests/test_page_model.py +190 -1
  284. wagtail/tests/test_page_privacy.py +5 -0
  285. wagtail/tests/test_reference_index.py +42 -0
  286. wagtail/tests/test_revision_model.py +118 -1
  287. wagtail/tests/test_utils.py +2 -2
  288. wagtail/users/locale/en/LC_MESSAGES/django.po +14 -14
  289. wagtail/users/locale/id_ID/LC_MESSAGES/django.mo +0 -0
  290. wagtail/users/locale/id_ID/LC_MESSAGES/django.po +6 -2
  291. wagtail/users/locale/it/LC_MESSAGES/django.mo +0 -0
  292. wagtail/users/locale/it/LC_MESSAGES/django.po +14 -2
  293. wagtail/users/locale/sv/LC_MESSAGES/django.mo +0 -0
  294. wagtail/users/locale/sv/LC_MESSAGES/django.po +48 -17
  295. wagtail/users/tests/test_admin_views.py +39 -25
  296. wagtail/users/utils.py +4 -1
  297. wagtail/users/views/groups.py +19 -5
  298. wagtail/users/wagtail_hooks.py +1 -1
  299. wagtail/utils/loading.py +2 -2
  300. wagtail-7.3rc1.data/data/AGENTS.md +21 -0
  301. wagtail-7.3rc1.data/data/CHANGELOG.txt +4941 -0
  302. wagtail-7.3rc1.data/data/CODE_OF_CONDUCT.md +71 -0
  303. wagtail-7.3rc1.data/data/CONTRIBUTORS.md +999 -0
  304. wagtail-7.3rc1.data/data/SPONSORS.md +55 -0
  305. {wagtail-7.2.1.dist-info → wagtail-7.3rc1.dist-info}/METADATA +6 -6
  306. {wagtail-7.2.1.dist-info → wagtail-7.3rc1.dist-info}/RECORD +310 -280
  307. {wagtail-7.2.1.dist-info → wagtail-7.3rc1.dist-info}/WHEEL +1 -1
  308. wagtail/images/static/wagtailimages/js/image-url-generator.js +0 -1
  309. wagtail/snippets/tests/test_snippets.py +0 -6377
  310. {wagtail-7.2.1.dist-info → wagtail-7.3rc1.dist-info}/entry_points.txt +0 -0
  311. {wagtail-7.2.1.dist-info → wagtail-7.3rc1.dist-info}/licenses/LICENSE +0 -0
  312. {wagtail-7.2.1.dist-info → wagtail-7.3rc1.dist-info}/top_level.txt +0 -0
@@ -93,12 +93,16 @@ class TestPageExplorer(WagtailTestUtils, TestCase):
93
93
  count=3,
94
94
  )
95
95
 
96
+ # Should render bulk actions markup
96
97
  bulk_actions_js = versioned_static("wagtailadmin/js/bulk-actions.js")
97
- self.assertContains(
98
- response,
99
- f'<script defer src="{bulk_actions_js}"></script>',
100
- html=True,
101
- )
98
+ soup = self.get_soup(response.content)
99
+ script = soup.select_one(f"script[src='{bulk_actions_js}']")
100
+ self.assertIsNotNone(script)
101
+ bulk_actions = soup.select("[data-bulk-action-button]")
102
+ self.assertTrue(bulk_actions)
103
+ # 'next' parameter is constructed client-side later based on filters state
104
+ for action in bulk_actions:
105
+ self.assertNotIn("next=", action["href"])
102
106
 
103
107
  def test_explore_results(self):
104
108
  explore_results_url = reverse(
@@ -6,6 +6,7 @@ from django.test import TransactionTestCase
6
6
  from django.urls import reverse
7
7
  from django.utils.http import urlencode
8
8
 
9
+ from wagtail.admin.staticfiles import versioned_static
9
10
  from wagtail.models import Page
10
11
  from wagtail.test.testapp.models import EventIndex, SimplePage, SingleEventPage
11
12
  from wagtail.test.utils import WagtailTestUtils
@@ -338,3 +339,17 @@ class TestPageSearch(WagtailTestUtils, TransactionTestCase):
338
339
  response,
339
340
  f"{url}?q=&amp;content_type=tests.eventindex",
340
341
  )
342
+
343
+ def test_bulk_action_rendered(self):
344
+ response = self.get()
345
+ self.assertEqual(response.status_code, 200)
346
+ # Should render bulk actions markup
347
+ bulk_actions_js = versioned_static("wagtailadmin/js/bulk-actions.js")
348
+ soup = self.get_soup(response.content)
349
+ script = soup.select_one(f"script[src='{bulk_actions_js}']")
350
+ self.assertIsNotNone(script)
351
+ bulk_actions = soup.select("[data-bulk-action-button]")
352
+ self.assertTrue(bulk_actions)
353
+ # 'next' parameter is constructed client-side later based on filters state
354
+ for action in bulk_actions:
355
+ self.assertNotIn("next=", action["href"])
@@ -130,6 +130,10 @@ class TestRevisions(WagtailTestUtils, TestCase):
130
130
  args=(self.christmas_event.id, self.last_christmas_revision.id),
131
131
  ),
132
132
  )
133
+ # Autosave should be disabled
134
+ self.assertNotIn("w-autosave", form["data-controller"].split())
135
+ self.assertNotIn("w-autosave", form["data-action"])
136
+ self.assertIsNone(form.attrs.get("data-w-autosave-interval-value"))
133
137
 
134
138
  # Buttons should be relabelled
135
139
  self.assertContains(response, "Replace current draft")
@@ -1,3 +1,4 @@
1
+ import io
1
2
  import unittest
2
3
  import zoneinfo
3
4
 
@@ -10,7 +11,9 @@ from django.core import mail
10
11
  from django.core.files.uploadedfile import SimpleUploadedFile
11
12
  from django.test import RequestFactory, TestCase, override_settings
12
13
  from django.urls import reverse
14
+ from PIL import Image
13
15
 
16
+ from wagtail.admin.forms.account import AvatarPreferencesForm
14
17
  from wagtail.admin.localization import (
15
18
  WAGTAILADMIN_PROVIDED_LANGUAGES,
16
19
  get_available_admin_languages,
@@ -733,6 +736,23 @@ class TestAccountUploadAvatar(WagtailTestUtils, TestCase, TestAccountSectionUtil
733
736
  self.avatar = get_test_image_file()
734
737
  self.other_avatar = get_test_image_file()
735
738
 
739
+ def create_image_file(self, size=(800, 800), color="red", name="test.png"):
740
+ img = Image.new("RGB", size, color=color)
741
+ img_byte_arr = io.BytesIO()
742
+
743
+ ext = name.split(".")[-1].upper()
744
+ if ext == "JPG":
745
+ ext = "JPEG"
746
+
747
+ img.save(img_byte_arr, format=ext)
748
+ img_byte_arr.seek(0)
749
+
750
+ content_type = f"image/{ext.lower()}" if ext != "JPEG" else "image/jpeg"
751
+
752
+ return SimpleUploadedFile(
753
+ name=name, content=img_byte_arr.read(), content_type=content_type
754
+ )
755
+
736
756
  def test_account_view(self):
737
757
  """
738
758
  This tests that the account view renders a "Upload a profile picture" field
@@ -789,6 +809,7 @@ class TestAccountUploadAvatar(WagtailTestUtils, TestCase, TestAccountSectionUtil
789
809
  """
790
810
  profile = UserProfile.get_for_user(self.user)
791
811
  profile.avatar = self.avatar
812
+ old_url = profile.avatar.url
792
813
  profile.save()
793
814
 
794
815
  # Upload a new avatar
@@ -798,7 +819,8 @@ class TestAccountUploadAvatar(WagtailTestUtils, TestCase, TestAccountSectionUtil
798
819
 
799
820
  # Check the avatar was changed
800
821
  profile.refresh_from_db()
801
- self.assertIn("test.png", profile.avatar.url)
822
+ self.assertEqual("/media/test.png", old_url)
823
+ self.assertTrue(profile.avatar.url.endswith(".png"))
802
824
 
803
825
  def test_clear_removes_current_avatar(self):
804
826
  """
@@ -815,7 +837,71 @@ class TestAccountUploadAvatar(WagtailTestUtils, TestCase, TestAccountSectionUtil
815
837
 
816
838
  # Check the avatar was changed
817
839
  profile.refresh_from_db()
818
- self.assertIn("test.png", profile.avatar.url)
840
+ self.assertTrue(profile.avatar)
841
+
842
+ def test_avatar_resize_large_image(self):
843
+ """Ensure that large uploaded images are resized to a maximum of 400x400."""
844
+ uploaded_file = self.create_image_file(size=(800, 800), name="large_image.jpg")
845
+ form = AvatarPreferencesForm(files={"avatar": uploaded_file})
846
+ self.assertTrue(form.is_valid())
847
+
848
+ avatar_file = form.clean_avatar()
849
+ resized_image = Image.open(avatar_file)
850
+ self.assertLessEqual(resized_image.width, 400)
851
+ self.assertLessEqual(resized_image.height, 400)
852
+
853
+ def test_avatar_no_resize_small_image(self):
854
+ uploaded_file = self.create_image_file(size=(300, 300), name="small_image.jpg")
855
+ form = AvatarPreferencesForm(files={"avatar": uploaded_file})
856
+ self.assertTrue(form.is_valid())
857
+
858
+ avatar_file = form.clean_avatar()
859
+ image = Image.open(avatar_file)
860
+ self.assertEqual(image.size, (300, 300))
861
+
862
+ def test_avatar_resize_width_greater_than_height(self):
863
+ uploaded_file = self.create_image_file(size=(500, 300), name="wide_image.jpg")
864
+ form = AvatarPreferencesForm(files={"avatar": uploaded_file})
865
+ self.assertTrue(form.is_valid())
866
+
867
+ avatar_file = form.clean_avatar()
868
+ resized_image = Image.open(avatar_file)
869
+ self.assertEqual(resized_image.size, (400, 240))
870
+
871
+ original_ratio = 500 / 300
872
+ new_ratio = resized_image.width / resized_image.height
873
+ self.assertAlmostEqual(original_ratio, new_ratio, places=2)
874
+
875
+ def test_avatar_resize_height_greater_than_width(self):
876
+ uploaded_file = self.create_image_file(size=(300, 500), name="tall_image.jpg")
877
+ form = AvatarPreferencesForm(files={"avatar": uploaded_file})
878
+ self.assertTrue(form.is_valid())
879
+
880
+ avatar_file = form.clean_avatar()
881
+ resized_image = Image.open(avatar_file)
882
+ self.assertEqual(resized_image.size, (240, 400))
883
+
884
+ original_ratio = 300 / 500
885
+ new_ratio = resized_image.width / resized_image.height
886
+ self.assertAlmostEqual(original_ratio, new_ratio, places=2)
887
+
888
+ def test_avatar_preserves_original_format(self):
889
+ # Test JPG format
890
+ jpg_file = self.create_image_file(size=(500, 500), name="test.jpg")
891
+ form = AvatarPreferencesForm(files={"avatar": jpg_file})
892
+ self.assertTrue(form.is_valid())
893
+ avatar_file = form.clean_avatar()
894
+ self.assertTrue(avatar_file.name.endswith(".jpg"))
895
+
896
+ # Test PNG format
897
+ png_file = self.create_image_file(size=(500, 500), name="test.png")
898
+ form = AvatarPreferencesForm(files={"avatar": png_file})
899
+ self.assertTrue(form.is_valid())
900
+ avatar_file = form.clean_avatar()
901
+ self.assertTrue(avatar_file.name.endswith(".png"))
902
+
903
+ resized_image = Image.open(avatar_file)
904
+ self.assertIsNotNone(resized_image.format)
819
905
 
820
906
 
821
907
  class TestAccountManagementForNonModerator(WagtailTestUtils, TestCase):
@@ -55,7 +55,7 @@ class TestCollectionsIndexViewAsSuperuser(
55
55
  def setUp(self):
56
56
  self.login()
57
57
 
58
- def get(self, params={}):
58
+ def get(self, params=None):
59
59
  return self.client.get(reverse("wagtailadmin_collections:index"), params)
60
60
 
61
61
  def test_simple(self):
@@ -118,7 +118,7 @@ class TestCollectionsIndexView(CollectionInstanceTestUtils, WagtailTestUtils, Te
118
118
  super().setUp()
119
119
  self.login(self.marketing_user, password="password")
120
120
 
121
- def get(self, params={}):
121
+ def get(self, params=None):
122
122
  return self.client.get(reverse("wagtailadmin_collections:index"), params)
123
123
 
124
124
  def test_marketing_user_no_permissions(self):
@@ -203,10 +203,10 @@ class TestAddCollectionAsSuperuser(AdminTemplateTestUtils, WagtailTestUtils, Tes
203
203
  self.login()
204
204
  self.root_collection = Collection.get_first_root_node()
205
205
 
206
- def get(self, params={}):
206
+ def get(self, params=None):
207
207
  return self.client.get(reverse("wagtailadmin_collections:add"), params)
208
208
 
209
- def post(self, post_data={}):
209
+ def post(self, post_data=None):
210
210
  return self.client.post(reverse("wagtailadmin_collections:add"), post_data)
211
211
 
212
212
  def test_get(self):
@@ -245,10 +245,10 @@ class TestAddCollection(CollectionInstanceTestUtils, WagtailTestUtils, TestCase)
245
245
  super().setUp()
246
246
  self.login(self.marketing_user, password="password")
247
247
 
248
- def get(self, params={}):
248
+ def get(self, params=None):
249
249
  return self.client.get(reverse("wagtailadmin_collections:add"), params)
250
250
 
251
- def post(self, post_data={}):
251
+ def post(self, post_data=None):
252
252
  return self.client.post(reverse("wagtailadmin_collections:add"), post_data)
253
253
 
254
254
  def test_marketing_user_no_permissions(self):
@@ -316,7 +316,7 @@ class TestEditCollectionAsSuperuser(AdminTemplateTestUtils, WagtailTestUtils, Te
316
316
  self.l2 = self.l1.add_child(name="Level 2")
317
317
  self.l3 = self.l2.add_child(name="Level 3")
318
318
 
319
- def get(self, params={}, collection_id=None):
319
+ def get(self, params=None, collection_id=None):
320
320
  return self.client.get(
321
321
  reverse(
322
322
  "wagtailadmin_collections:edit",
@@ -325,7 +325,7 @@ class TestEditCollectionAsSuperuser(AdminTemplateTestUtils, WagtailTestUtils, Te
325
325
  params,
326
326
  )
327
327
 
328
- def post(self, post_data={}, collection_id=None):
328
+ def post(self, post_data=None, collection_id=None):
329
329
  return self.client.post(
330
330
  reverse(
331
331
  "wagtailadmin_collections:edit",
@@ -408,12 +408,12 @@ class TestEditCollection(CollectionInstanceTestUtils, WagtailTestUtils, TestCase
408
408
  )
409
409
  self.login(self.marketing_user, password="password")
410
410
 
411
- def get(self, collection_id, params={}):
411
+ def get(self, collection_id, params=None):
412
412
  return self.client.get(
413
413
  reverse("wagtailadmin_collections:edit", args=(collection_id,)), params
414
414
  )
415
415
 
416
- def post(self, collection_id, post_data={}):
416
+ def post(self, collection_id, post_data=None):
417
417
  return self.client.post(
418
418
  reverse("wagtailadmin_collections:edit", args=(collection_id,)), post_data
419
419
  )
@@ -598,7 +598,7 @@ class TestDeleteCollectionAsSuperuser(
598
598
  self.root_collection = Collection.get_first_root_node()
599
599
  self.collection = self.root_collection.add_child(name="Holiday snaps")
600
600
 
601
- def get(self, params={}, collection_id=None):
601
+ def get(self, params=None, collection_id=None):
602
602
  return self.client.get(
603
603
  reverse(
604
604
  "wagtailadmin_collections:delete",
@@ -607,7 +607,7 @@ class TestDeleteCollectionAsSuperuser(
607
607
  params,
608
608
  )
609
609
 
610
- def post(self, post_data={}, collection_id=None):
610
+ def post(self, post_data=None, collection_id=None):
611
611
  return self.client.post(
612
612
  reverse(
613
613
  "wagtailadmin_collections:delete",
@@ -703,12 +703,12 @@ class TestDeleteCollection(CollectionInstanceTestUtils, WagtailTestUtils, TestCa
703
703
  )
704
704
  self.login(self.marketing_user, password="password")
705
705
 
706
- def get(self, collection_id, params={}):
706
+ def get(self, collection_id, params=None):
707
707
  return self.client.get(
708
708
  reverse("wagtailadmin_collections:delete", args=(collection_id,)), params
709
709
  )
710
710
 
711
- def post(self, collection_id, post_data={}):
711
+ def post(self, collection_id, post_data=None):
712
712
  return self.client.post(
713
713
  reverse("wagtailadmin_collections:delete", args=(collection_id,)), post_data
714
714
  )
@@ -775,7 +775,7 @@ class TestSetCollectionPrivacy(CollectionInstanceTestUtils, WagtailTestUtils, Te
775
775
  super().setUp()
776
776
  self.login()
777
777
 
778
- def get(self, collection_id, params={}):
778
+ def get(self, collection_id, params=None):
779
779
  return self.client.get(
780
780
  reverse("wagtailadmin_collections:set_privacy", args=(collection_id,)),
781
781
  params,
@@ -921,6 +921,40 @@ class TestStreamFieldComparison(TestCase):
921
921
  self.assertTrue(comparison.has_changed())
922
922
 
923
923
 
924
+ class TestStreamBlockComparison(TestCase):
925
+ comparison_class = compare.StreamBlockComparison
926
+
927
+ def test_streamblock_comparison_htmlvalue(self):
928
+ field = StreamPage._meta.get_field("body")
929
+ stream_block = field.stream_block.child_blocks["books"]
930
+
931
+ stream_value = StreamValue(
932
+ stream_block,
933
+ [
934
+ ("title", "Test Title", "1"),
935
+ ("author", "Test Author", "2"),
936
+ ],
937
+ )
938
+
939
+ comparison = self.comparison_class(
940
+ stream_block,
941
+ True,
942
+ True,
943
+ stream_value,
944
+ stream_value,
945
+ )
946
+
947
+ result = comparison.htmlvalue(stream_value)
948
+
949
+ self.assertIsInstance(result, SafeString)
950
+ self.assertIn("<dl>", result)
951
+ self.assertIn("</dl>", result)
952
+ self.assertIn("<dt>Title</dt>", result)
953
+ self.assertIn("<dt>Author</dt>", result)
954
+ self.assertIn("<dd>Test Title</dd>", result)
955
+ self.assertIn("<dd>Test Author</dd>", result)
956
+
957
+
924
958
  class TestChoiceFieldComparison(TestCase):
925
959
  comparison_class = compare.ChoiceFieldComparison
926
960
 
@@ -282,12 +282,16 @@ class TestPingView(WagtailTestUtils, TestCase):
282
282
  self.assertIn("Vic Otheruser saved a new version", session_text)
283
283
  self.assertNotIn("Currently viewing", session_text)
284
284
  dialog_title = soup.select_one(
285
- 'template[data-w-teleport-target-value="#title-text-w-overwrite-changes-dialog"]'
285
+ "template"
286
+ '[data-w-teleport-target-value="#title-text-w-overwrite-changes-dialog"]'
287
+ '[data-w-teleport-mode-value="textContent"]'
286
288
  )
287
289
  self.assertIsNotNone(dialog_title)
288
290
  self.assertIn("Vic Otheruser saved a new version", dialog_title.string)
289
291
  dialog_subtitle = soup.select_one(
290
- 'template[data-w-teleport-target-value="#subtitle-w-overwrite-changes-dialog"]'
292
+ "template"
293
+ '[data-w-teleport-target-value="#subtitle-w-overwrite-changes-dialog"]'
294
+ '[data-w-teleport-mode-value="textContent"]'
291
295
  )
292
296
  self.assertIsNotNone(dialog_subtitle)
293
297
  self.assertIn(
@@ -328,6 +332,179 @@ class TestPingView(WagtailTestUtils, TestCase):
328
332
  ],
329
333
  )
330
334
 
335
+ soup = self.get_soup(response_json["html"])
336
+ rendered_sessions = soup.select("ol.w-editing-sessions__list li")
337
+ self.assertEqual(len(rendered_sessions), 1)
338
+ session_text = rendered_sessions[0].text
339
+ self.assertIn("Vic Otheruser saved a new version", session_text)
340
+ self.assertNotIn("Currently viewing", session_text)
341
+ dialog_title = soup.select_one(
342
+ "template"
343
+ '[data-w-teleport-target-value="#title-text-w-overwrite-changes-dialog"]'
344
+ '[data-w-teleport-mode-value="textContent"]'
345
+ )
346
+ self.assertIsNotNone(dialog_title)
347
+ self.assertIn(
348
+ "Vic Otheruser saved a new version",
349
+ dialog_title.string,
350
+ )
351
+ dialog_subtitle = soup.select_one(
352
+ "template"
353
+ '[data-w-teleport-target-value="#subtitle-w-overwrite-changes-dialog"]'
354
+ '[data-w-teleport-mode-value="textContent"]'
355
+ )
356
+ self.assertIsNotNone(dialog_subtitle)
357
+ self.assertIn(
358
+ "Proceeding will overwrite the changes made by Vic Otheruser. "
359
+ "Refreshing the page will show you the new changes, but you will lose any of your unsaved changes.",
360
+ dialog_subtitle.string,
361
+ )
362
+
363
+ self.session.refresh_from_db()
364
+ self.assertEqual(self.session.last_seen_at, TIMESTAMP_NOW)
365
+ self.assertFalse(self.session.is_editing)
366
+
367
+ @freeze_time(TIMESTAMP_NOW)
368
+ def test_ping_with_overwritten_revision(self):
369
+ # Simulate other user creating a new revision to be used for autosave
370
+ with freeze_time(TIMESTAMP_1):
371
+ loaded_revision = self.page.save_revision(user=self.other_user)
372
+
373
+ loaded_timestamp = loaded_revision.created_at.isoformat()
374
+ response = self.client.post(
375
+ reverse(
376
+ "wagtailadmin_editing_sessions:ping",
377
+ args=("wagtailcore", "page", self.page.id, self.session.id),
378
+ ),
379
+ {
380
+ "revision_id": self.original_revision.id,
381
+ "revision_created_at": loaded_timestamp,
382
+ },
383
+ )
384
+ self.assertEqual(response.status_code, 200)
385
+ response_json = response.json()
386
+ self.assertEqual(response_json["session_id"], self.session.id)
387
+
388
+ # no revisions have been saved since the original revision
389
+ self.assertEqual(
390
+ response_json["other_sessions"],
391
+ [
392
+ {
393
+ "session_id": self.other_session.id,
394
+ "user": "Vic Otheruser",
395
+ "last_seen_at": TIMESTAMP_2.isoformat(),
396
+ "is_editing": False,
397
+ "revision_id": None,
398
+ },
399
+ ],
400
+ )
401
+
402
+ soup = self.get_soup(response_json["html"])
403
+ rendered_sessions = soup.select("ol.w-editing-sessions__list li")
404
+ self.assertEqual(len(rendered_sessions), 1)
405
+ session_text = rendered_sessions[0].text
406
+ self.assertIn("Vic Otheruser", session_text)
407
+ self.assertIn("Currently viewing", session_text)
408
+ self.assertNotIn("saved a new version", session_text)
409
+
410
+ self.session.refresh_from_db()
411
+ self.assertEqual(self.session.last_seen_at, TIMESTAMP_NOW)
412
+ self.assertFalse(self.session.is_editing)
413
+
414
+ # Simulate other user doing an autosave by overwriting the same revision
415
+ # that we have loaded
416
+ with freeze_time(TIMESTAMP_3):
417
+ self.page.save_revision(
418
+ user=self.other_user,
419
+ overwrite_revision=loaded_revision,
420
+ )
421
+
422
+ response = self.client.post(
423
+ reverse(
424
+ "wagtailadmin_editing_sessions:ping",
425
+ args=("wagtailcore", "page", self.page.id, self.session.id),
426
+ ),
427
+ {
428
+ "revision_id": self.original_revision.id,
429
+ "revision_created_at": loaded_timestamp,
430
+ },
431
+ )
432
+ self.assertEqual(response.status_code, 200)
433
+ response_json = response.json()
434
+ self.assertEqual(response_json["session_id"], self.session.id)
435
+
436
+ # the overwritten revision should be indicated in the response
437
+ # (and last_seen_at should reflect it), even though it has the same ID
438
+ # as the loaded revision
439
+ self.assertEqual(
440
+ response_json["other_sessions"],
441
+ [
442
+ {
443
+ "session_id": self.other_session.id,
444
+ "user": "Vic Otheruser",
445
+ "last_seen_at": TIMESTAMP_3.isoformat(),
446
+ "is_editing": False,
447
+ "revision_id": loaded_revision.id,
448
+ },
449
+ ],
450
+ )
451
+
452
+ soup = self.get_soup(response_json["html"])
453
+ rendered_sessions = soup.select("ol.w-editing-sessions__list li")
454
+ self.assertEqual(len(rendered_sessions), 1)
455
+ session_text = rendered_sessions[0].text
456
+ self.assertIn("Vic Otheruser saved a new version", session_text)
457
+ self.assertNotIn("Currently viewing", session_text)
458
+ dialog_title = soup.select_one(
459
+ 'template[data-w-teleport-target-value="#title-text-w-overwrite-changes-dialog"]'
460
+ )
461
+ self.assertIsNotNone(dialog_title)
462
+ self.assertIn("Vic Otheruser saved a new version", dialog_title.string)
463
+ dialog_subtitle = soup.select_one(
464
+ 'template[data-w-teleport-target-value="#subtitle-w-overwrite-changes-dialog"]'
465
+ )
466
+ self.assertIsNotNone(dialog_subtitle)
467
+ self.assertIn(
468
+ "Proceeding will overwrite the changes made by Vic Otheruser. "
469
+ "Refreshing the page will show you the new changes, but you will lose any of your unsaved changes.",
470
+ dialog_subtitle.string,
471
+ )
472
+
473
+ self.session.refresh_from_db()
474
+ self.assertEqual(self.session.last_seen_at, TIMESTAMP_NOW)
475
+ self.assertFalse(self.session.is_editing)
476
+
477
+ self.other_session.delete()
478
+
479
+ response = self.client.post(
480
+ reverse(
481
+ "wagtailadmin_editing_sessions:ping",
482
+ args=("wagtailcore", "page", self.page.id, self.session.id),
483
+ ),
484
+ {
485
+ "revision_id": self.original_revision.id,
486
+ "revision_created_at": loaded_timestamp,
487
+ },
488
+ )
489
+ self.assertEqual(response.status_code, 200)
490
+ response_json = response.json()
491
+ self.assertEqual(response_json["session_id"], self.session.id)
492
+
493
+ # the overwritten revision should still appear as an "other session" in
494
+ # the response, even though the editing session record has been deleted
495
+ self.assertEqual(
496
+ response_json["other_sessions"],
497
+ [
498
+ {
499
+ "session_id": None,
500
+ "user": "Vic Otheruser",
501
+ "last_seen_at": TIMESTAMP_3.isoformat(),
502
+ "is_editing": False,
503
+ "revision_id": loaded_revision.id,
504
+ },
505
+ ],
506
+ )
507
+
331
508
  soup = self.get_soup(response_json["html"])
332
509
  rendered_sessions = soup.select("ol.w-editing-sessions__list li")
333
510
  self.assertEqual(len(rendered_sessions), 1)
@@ -417,7 +594,9 @@ class TestPingView(WagtailTestUtils, TestCase):
417
594
  self.assertIn("Gordon Thirduser saved a new version", session_text)
418
595
  self.assertNotIn("Currently viewing", session_text)
419
596
  dialog_title = soup.select_one(
420
- 'template[data-w-teleport-target-value="#title-text-w-overwrite-changes-dialog"]'
597
+ "template"
598
+ '[data-w-teleport-target-value="#title-text-w-overwrite-changes-dialog"]'
599
+ '[data-w-teleport-mode-value="textContent"]'
421
600
  )
422
601
  self.assertIsNotNone(dialog_title)
423
602
  self.assertIn(
@@ -425,7 +604,9 @@ class TestPingView(WagtailTestUtils, TestCase):
425
604
  dialog_title.string,
426
605
  )
427
606
  dialog_subtitle = soup.select_one(
428
- 'template[data-w-teleport-target-value="#subtitle-w-overwrite-changes-dialog"]'
607
+ "template"
608
+ '[data-w-teleport-target-value="#subtitle-w-overwrite-changes-dialog"]'
609
+ '[data-w-teleport-mode-value="textContent"]'
429
610
  )
430
611
  self.assertIsNotNone(dialog_subtitle)
431
612
  self.assertIn(
@@ -487,7 +668,9 @@ class TestPingView(WagtailTestUtils, TestCase):
487
668
  self.assertIn("System saved a new version", session_text)
488
669
  self.assertNotIn("Currently viewing", session_text)
489
670
  dialog_title = soup.select_one(
490
- 'template[data-w-teleport-target-value="#title-text-w-overwrite-changes-dialog"]'
671
+ "template"
672
+ '[data-w-teleport-target-value="#title-text-w-overwrite-changes-dialog"]'
673
+ '[data-w-teleport-mode-value="textContent"]'
491
674
  )
492
675
  self.assertIsNotNone(dialog_title)
493
676
  self.assertIn(
@@ -495,7 +678,9 @@ class TestPingView(WagtailTestUtils, TestCase):
495
678
  dialog_title.string,
496
679
  )
497
680
  dialog_subtitle = soup.select_one(
498
- 'template[data-w-teleport-target-value="#subtitle-w-overwrite-changes-dialog"]'
681
+ "template"
682
+ '[data-w-teleport-target-value="#subtitle-w-overwrite-changes-dialog"]'
683
+ '[data-w-teleport-mode-value="textContent"]'
499
684
  )
500
685
  self.assertIsNotNone(dialog_subtitle)
501
686
  self.assertIn(
@@ -730,11 +915,15 @@ class TestPingView(WagtailTestUtils, TestCase):
730
915
  self.assertIn("You have unsaved changes in another window", session_text)
731
916
  self.assertNotIn("Currently viewing", session_text)
732
917
  dialog_title = soup.select_one(
733
- 'template[data-w-teleport-target-value="#title-text-w-overwrite-changes-dialog"]'
918
+ "template"
919
+ '[data-w-teleport-target-value="#title-text-w-overwrite-changes-dialog"]'
920
+ '[data-w-teleport-mode-value="textContent"]'
734
921
  )
735
922
  self.assertIsNone(dialog_title)
736
923
  dialog_subtitle = soup.select_one(
737
- 'template[data-w-teleport-target-value="#subtitle-w-overwrite-changes-dialog"]'
924
+ "template"
925
+ '[data-w-teleport-target-value="#subtitle-w-overwrite-changes-dialog"]'
926
+ '[data-w-teleport-mode-value="textContent"]'
738
927
  )
739
928
  self.assertIsNone(dialog_subtitle)
740
929
  other_session_text = rendered_sessions[1].text
@@ -937,6 +1126,32 @@ class TestPingView(WagtailTestUtils, TestCase):
937
1126
  ],
938
1127
  )
939
1128
 
1129
+ # The rendered HTML should use special messaging if the newer revision
1130
+ # is made by the same user ("in another window").
1131
+ soup = self.get_soup(response_json["html"])
1132
+ rendered_sessions = soup.select("ol.w-editing-sessions__list li")
1133
+ self.assertEqual(len(rendered_sessions), 2)
1134
+ session_text = rendered_sessions[0].text
1135
+ self.assertIn("You saved a new version in another window", session_text)
1136
+ self.assertNotIn("Currently viewing", session_text)
1137
+ dialog_title = soup.select_one(
1138
+ 'template[data-w-teleport-target-value="#title-text-w-overwrite-changes-dialog"]'
1139
+ )
1140
+ self.assertIsNotNone(dialog_title)
1141
+ self.assertIn(
1142
+ "You saved a new version in another window", dialog_title.text.strip()
1143
+ )
1144
+ dialog_subtitle = soup.select_one(
1145
+ 'template[data-w-teleport-target-value="#subtitle-w-overwrite-changes-dialog"]'
1146
+ )
1147
+ self.assertIsNotNone(dialog_subtitle)
1148
+ self.assertIn(
1149
+ "Proceeding will overwrite the changes you made in that window. "
1150
+ "Refreshing the page will show you the new changes, but you will "
1151
+ "lose any of your unsaved changes in the current window.",
1152
+ dialog_subtitle.text.strip(),
1153
+ )
1154
+
940
1155
  @freeze_time(TIMESTAMP_NOW)
941
1156
  def test_user_must_have_edit_permission_on_page(self):
942
1157
  # make user a member of Editors
@@ -1331,6 +1546,13 @@ class TestModuleInEditView(WagtailTestUtils, TestCase):
1331
1546
  revision_input.get("value"),
1332
1547
  str(self.object.latest_revision.id),
1333
1548
  )
1549
+ revision_created_at = soup.select_one('input[name="revision_created_at"]')
1550
+ self.assertIsNotNone(revision_created_at)
1551
+ self.assertEqual(revision_created_at.get("type"), "hidden")
1552
+ self.assertEqual(
1553
+ revision_created_at.get("value"),
1554
+ self.object.latest_revision.created_at.isoformat(),
1555
+ )
1334
1556
 
1335
1557
  @freeze_time(TIMESTAMP_NOW)
1336
1558
  def test_edit_view_with_default_interval(self):