wagtail 6.3.2__py3-none-any.whl → 6.4rc1__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 (296) hide show
  1. wagtail/__init__.py +1 -1
  2. wagtail/actions/publish_revision.py +4 -5
  3. wagtail/admin/auth.py +0 -2
  4. wagtail/admin/checks.py +1 -1
  5. wagtail/admin/filters.py +3 -1
  6. wagtail/admin/forms/account.py +21 -11
  7. wagtail/admin/forms/collections.py +2 -9
  8. wagtail/admin/forms/formsets.py +32 -0
  9. wagtail/admin/forms/pages.py +5 -1
  10. wagtail/admin/forms/workflows.py +2 -13
  11. wagtail/admin/locale/ar/LC_MESSAGES/django.mo +0 -0
  12. wagtail/admin/locale/ar/LC_MESSAGES/django.po +68 -1
  13. wagtail/admin/locale/ar/LC_MESSAGES/djangojs.mo +0 -0
  14. wagtail/admin/locale/ar/LC_MESSAGES/djangojs.po +5 -1
  15. wagtail/admin/locale/en/LC_MESSAGES/django.po +312 -356
  16. wagtail/admin/locale/en/LC_MESSAGES/djangojs.po +21 -16
  17. wagtail/admin/menu.py +0 -13
  18. wagtail/admin/panels/base.py +2 -2
  19. wagtail/admin/panels/group.py +4 -1
  20. wagtail/admin/panels/inline_panel.py +5 -2
  21. wagtail/admin/panels/model_utils.py +36 -0
  22. wagtail/admin/panels/page_utils.py +2 -40
  23. wagtail/admin/panels/signal_handlers.py +0 -2
  24. wagtail/admin/static/wagtailadmin/css/core.css +1 -1
  25. wagtail/admin/static/wagtailadmin/css/panels/draftail.css +1 -1
  26. wagtail/admin/static/wagtailadmin/css/panels/streamfield.css +1 -1
  27. wagtail/admin/static/wagtailadmin/js/comments.js +1 -1
  28. wagtail/admin/static/wagtailadmin/js/core.js +1 -1
  29. wagtail/admin/static/wagtailadmin/js/core.js.LICENSE.txt +1 -8
  30. wagtail/admin/static/wagtailadmin/js/draftail.js +1 -1
  31. wagtail/admin/static/wagtailadmin/js/modal-workflow.js +1 -1
  32. wagtail/admin/static/wagtailadmin/js/page-chooser-modal.js +1 -1
  33. wagtail/admin/static/wagtailadmin/js/privacy-switch.js +1 -1
  34. wagtail/admin/static/wagtailadmin/js/sidebar.js +1 -1
  35. wagtail/admin/static/wagtailadmin/js/telepath/blocks.js +1 -1
  36. wagtail/admin/static/wagtailadmin/js/userbar.js +1 -1
  37. wagtail/admin/static/wagtailadmin/js/userbar.js.LICENSE.txt +1 -1
  38. wagtail/admin/static/wagtailadmin/js/vendor.js +1 -1
  39. wagtail/admin/static/wagtailadmin/js/vendor.js.LICENSE.txt +7 -0
  40. wagtail/admin/templates/wagtailadmin/404.html +4 -0
  41. wagtail/admin/templates/wagtailadmin/chooser/browse.html +2 -1
  42. wagtail/admin/templates/wagtailadmin/chooser/tables/parent_page_cell.html +1 -1
  43. wagtail/admin/templates/wagtailadmin/collections/_privacy_switch.html +8 -1
  44. wagtail/admin/templates/wagtailadmin/generic/confirm_delete.html +15 -9
  45. wagtail/admin/templates/wagtailadmin/generic/confirm_unpublish.html +21 -25
  46. wagtail/admin/templates/wagtailadmin/generic/form.html +1 -1
  47. wagtail/admin/templates/wagtailadmin/generic/preview_error.html +3 -0
  48. wagtail/admin/templates/wagtailadmin/generic/revisions/compare.html +63 -76
  49. wagtail/admin/templates/wagtailadmin/pages/_editor_js.html +0 -2
  50. wagtail/admin/templates/wagtailadmin/pages/edit.html +1 -5
  51. wagtail/admin/templates/wagtailadmin/panels/inline_panel_child.html +1 -0
  52. wagtail/admin/templates/wagtailadmin/permissions/includes/collection_member_permissions_form.html +1 -1
  53. wagtail/admin/templates/wagtailadmin/permissions/includes/collection_member_permissions_formset.html +6 -22
  54. wagtail/admin/templates/wagtailadmin/shared/formatted_field.html +2 -2
  55. wagtail/admin/templates/wagtailadmin/shared/header.html +2 -2
  56. wagtail/admin/templates/wagtailadmin/shared/page_status_tag_new.html +32 -39
  57. wagtail/admin/templates/wagtailadmin/shared/revisions/confirm_unschedule.html +13 -17
  58. wagtail/admin/templates/wagtailadmin/shared/side_panels/includes/status/privacy.html +15 -3
  59. wagtail/admin/templates/wagtailadmin/shared/side_panels/preview.html +1 -1
  60. wagtail/admin/templates/wagtailadmin/skeleton.html +4 -2
  61. wagtail/admin/templates/wagtailadmin/workflows/create.html +1 -1
  62. wagtail/admin/templates/wagtailadmin/workflows/edit.html +1 -1
  63. wagtail/admin/templates/wagtailadmin/workflows/includes/workflow_pages_form.html +1 -1
  64. wagtail/admin/templates/wagtailadmin/workflows/includes/workflow_pages_formset.html +6 -23
  65. wagtail/admin/templatetags/wagtailadmin_tags.py +12 -0
  66. wagtail/admin/templatetags/wagtailuserbar.py +2 -3
  67. wagtail/admin/tests/pages/test_create_page.py +110 -1
  68. wagtail/admin/tests/pages/test_edit_page.py +3 -2
  69. wagtail/admin/tests/pages/test_explorer_view.py +18 -0
  70. wagtail/admin/tests/pages/test_page_usage.py +24 -20
  71. wagtail/admin/tests/pages/test_preview.py +69 -1
  72. wagtail/admin/tests/pages/test_revisions.py +40 -6
  73. wagtail/admin/tests/test_account_management.py +39 -1
  74. wagtail/admin/tests/test_audit_log.py +4 -2
  75. wagtail/admin/tests/test_block_preview.py +224 -0
  76. wagtail/admin/tests/test_edit_handlers.py +23 -6
  77. wagtail/admin/tests/test_page_chooser.py +50 -3
  78. wagtail/admin/tests/test_privacy.py +49 -26
  79. wagtail/admin/tests/test_site_summary.py +15 -10
  80. wagtail/admin/tests/test_templatetags.py +19 -0
  81. wagtail/admin/tests/test_userbar.py +82 -1
  82. wagtail/admin/tests/test_views_generic.py +27 -12
  83. wagtail/admin/tests/test_workflows.py +69 -0
  84. wagtail/admin/tests/tests.py +23 -4
  85. wagtail/admin/tests/ui/test_sidebar.py +1 -1
  86. wagtail/admin/tests/viewsets/test_model_viewset.py +15 -13
  87. wagtail/admin/ui/side_panels.py +7 -4
  88. wagtail/admin/urls/__init__.py +6 -0
  89. wagtail/admin/urls/pages.py +1 -1
  90. wagtail/admin/userbar.py +21 -1
  91. wagtail/admin/views/account.py +5 -0
  92. wagtail/admin/views/chooser.py +5 -1
  93. wagtail/admin/views/collections.py +0 -2
  94. wagtail/admin/views/generic/base.py +20 -10
  95. wagtail/admin/views/generic/history.py +0 -1
  96. wagtail/admin/views/generic/models.py +79 -21
  97. wagtail/admin/views/generic/preview.py +50 -1
  98. wagtail/admin/views/mixins.py +4 -2
  99. wagtail/admin/views/pages/bulk_actions/delete.py +11 -23
  100. wagtail/admin/views/pages/bulk_actions/page_bulk_action.py +17 -0
  101. wagtail/admin/views/pages/bulk_actions/publish.py +11 -31
  102. wagtail/admin/views/pages/bulk_actions/unpublish.py +11 -31
  103. wagtail/admin/views/pages/create.py +1 -0
  104. wagtail/admin/views/pages/edit.py +38 -30
  105. wagtail/admin/views/pages/revisions.py +43 -114
  106. wagtail/admin/views/pages/utils.py +0 -1
  107. wagtail/admin/views/tags.py +6 -2
  108. wagtail/admin/views/workflows.py +8 -6
  109. wagtail/admin/viewsets/model.py +0 -4
  110. wagtail/admin/viewsets/pages.py +0 -1
  111. wagtail/admin/widgets/tags.py +1 -0
  112. wagtail/api/v2/tests/test_documents.py +4 -2
  113. wagtail/api/v2/tests/test_images.py +4 -2
  114. wagtail/api/v2/tests/test_pages.py +8 -4
  115. wagtail/blocks/base.py +59 -1
  116. wagtail/blocks/field_block.py +6 -0
  117. wagtail/blocks/list_block.py +4 -0
  118. wagtail/blocks/static_block.py +3 -0
  119. wagtail/blocks/stream_block.py +5 -1
  120. wagtail/blocks/struct_block.py +6 -0
  121. wagtail/compat.py +16 -0
  122. wagtail/contrib/forms/forms.py +27 -7
  123. wagtail/contrib/forms/locale/en/LC_MESSAGES/django.po +2 -2
  124. wagtail/contrib/forms/tests/test_models.py +7 -5
  125. wagtail/contrib/forms/tests/test_views.py +75 -0
  126. wagtail/contrib/frontend_cache/tasks.py +83 -0
  127. wagtail/contrib/frontend_cache/tests.py +47 -32
  128. wagtail/contrib/frontend_cache/utils.py +2 -70
  129. wagtail/contrib/redirects/base_formats.py +2 -2
  130. wagtail/contrib/redirects/locale/ar/LC_MESSAGES/django.mo +0 -0
  131. wagtail/contrib/redirects/locale/ar/LC_MESSAGES/django.po +3 -0
  132. wagtail/contrib/redirects/locale/en/LC_MESSAGES/django.po +24 -37
  133. wagtail/contrib/redirects/templates/wagtailredirects/add.html +1 -24
  134. wagtail/contrib/redirects/templates/wagtailredirects/confirm_delete.html +3 -13
  135. wagtail/contrib/redirects/tests/test_redirects.py +122 -110
  136. wagtail/contrib/redirects/tests/test_signal_handlers.py +75 -69
  137. wagtail/contrib/redirects/urls.py +2 -2
  138. wagtail/contrib/redirects/views.py +35 -73
  139. wagtail/contrib/search_promotions/admin_urls.py +10 -3
  140. wagtail/contrib/search_promotions/forms.py +55 -26
  141. wagtail/contrib/search_promotions/locale/en/LC_MESSAGES/django.po +44 -54
  142. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/add.html +21 -31
  143. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/confirm_delete.html +3 -12
  144. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/edit.html +11 -34
  145. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/includes/searchpromotion_form.html +1 -0
  146. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/includes/searchpromotions_formset.js +2 -1
  147. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/index.html +0 -1
  148. wagtail/contrib/search_promotions/tests.py +814 -13
  149. wagtail/contrib/search_promotions/views/__init__.py +1 -0
  150. wagtail/contrib/search_promotions/views/reports.py +56 -0
  151. wagtail/contrib/search_promotions/views/settings.py +258 -0
  152. wagtail/contrib/search_promotions/wagtail_hooks.py +12 -1
  153. wagtail/contrib/settings/locale/ar/LC_MESSAGES/django.mo +0 -0
  154. wagtail/contrib/settings/locale/ar/LC_MESSAGES/django.po +6 -1
  155. wagtail/contrib/settings/locale/en/LC_MESSAGES/django.po +3 -3
  156. wagtail/contrib/settings/templates/wagtailsettings/edit.html +1 -5
  157. wagtail/contrib/settings/tests/generic/test_admin.py +2 -5
  158. wagtail/contrib/settings/tests/generic/test_register.py +1 -1
  159. wagtail/contrib/settings/tests/site_specific/test_admin.py +2 -5
  160. wagtail/contrib/settings/tests/site_specific/test_register.py +1 -1
  161. wagtail/contrib/settings/views.py +9 -23
  162. wagtail/contrib/simple_translation/locale/en/LC_MESSAGES/django.po +1 -1
  163. wagtail/contrib/styleguide/locale/en/LC_MESSAGES/django.po +1 -1
  164. wagtail/contrib/table_block/locale/en/LC_MESSAGES/django.po +1 -1
  165. wagtail/contrib/table_block/tests.py +4 -1
  166. wagtail/contrib/typed_table_block/blocks.py +3 -0
  167. wagtail/contrib/typed_table_block/locale/en/LC_MESSAGES/django.po +10 -10
  168. wagtail/contrib/typed_table_block/static/typed_table_block/js/typed_table_block.js +1 -1
  169. wagtail/contrib/typed_table_block/tests.py +33 -0
  170. wagtail/documents/locale/en/LC_MESSAGES/django.po +26 -26
  171. wagtail/documents/migrations/0011_add_choose_permissions.py +1 -0
  172. wagtail/documents/models.py +1 -0
  173. wagtail/documents/signal_handlers.py +6 -2
  174. wagtail/documents/static/wagtaildocs/js/add-multiple.js +1 -1
  175. wagtail/documents/templates/wagtaildocs/documents/edit.html +1 -3
  176. wagtail/documents/templates/wagtaildocs/multiple/add.html +7 -1
  177. wagtail/documents/tests/test_admin_views.py +74 -33
  178. wagtail/documents/tests/test_views.py +21 -12
  179. wagtail/documents/views/chooser.py +1 -0
  180. wagtail/documents/views/documents.py +1 -2
  181. wagtail/documents/views/multiple.py +0 -1
  182. wagtail/documents/views/serve.py +9 -2
  183. wagtail/documents/wagtail_hooks.py +6 -1
  184. wagtail/embeds/locale/en/LC_MESSAGES/django.po +1 -1
  185. wagtail/embeds/oembed_providers.py +0 -64
  186. wagtail/fields.py +3 -0
  187. wagtail/images/apps.py +2 -1
  188. wagtail/images/blocks.py +6 -2
  189. wagtail/images/forms.py +40 -3
  190. wagtail/images/locale/ar/LC_MESSAGES/django.mo +0 -0
  191. wagtail/images/locale/ar/LC_MESSAGES/django.po +4 -0
  192. wagtail/images/locale/en/LC_MESSAGES/django.po +49 -49
  193. wagtail/images/migrations/0023_add_choose_permissions.py +1 -0
  194. wagtail/images/rich_text/contentstate.py +1 -0
  195. wagtail/images/rich_text/editor_html.py +1 -0
  196. wagtail/images/signal_handlers.py +17 -10
  197. wagtail/images/static/wagtailimages/js/add-multiple.js +1 -1
  198. wagtail/images/static/wagtailimages/js/image-block.js +1 -1
  199. wagtail/images/static/wagtailimages/js/image-chooser-telepath.js +1 -1
  200. wagtail/images/static/wagtailimages/js/image-chooser.js +1 -1
  201. wagtail/images/static/wagtailimages/js/image-url-generator.js +1 -1
  202. wagtail/images/static/wagtailimages/js/vendor/jquery.fileupload-image.js +1 -1
  203. wagtail/images/tasks.py +18 -0
  204. wagtail/images/templates/wagtailimages/images/edit.html +1 -3
  205. wagtail/images/templates/wagtailimages/images/url_generator.html +1 -1
  206. wagtail/images/templates/wagtailimages/multiple/add.html +7 -2
  207. wagtail/images/templates/wagtailimages/widgets/image_chooser.html +1 -1
  208. wagtail/images/tests/test_admin_views.py +53 -29
  209. wagtail/images/tests/test_blocks.py +3 -2
  210. wagtail/images/tests/test_models.py +12 -10
  211. wagtail/images/tests/tests.py +10 -0
  212. wagtail/images/views/chooser.py +1 -0
  213. wagtail/images/views/images.py +1 -3
  214. wagtail/images/views/multiple.py +0 -1
  215. wagtail/images/views/serve.py +18 -2
  216. wagtail/images/widgets.py +3 -0
  217. wagtail/locale/en/LC_MESSAGES/django.po +228 -216
  218. wagtail/locales/locale/en/LC_MESSAGES/django.po +1 -1
  219. wagtail/management/commands/publish_scheduled.py +1 -1
  220. wagtail/migrations/0087_alter_grouppagepermission_unique_together_and_more.py +16 -8
  221. wagtail/models/__init__.py +300 -119
  222. wagtail/models/i18n.py +2 -2
  223. wagtail/models/panels.py +37 -0
  224. wagtail/models/sites.py +7 -6
  225. wagtail/permission_policies/pages.py +2 -2
  226. wagtail/project_template/project_name/settings/base.py +4 -0
  227. wagtail/project_template/requirements.txt +1 -1
  228. wagtail/query.py +145 -0
  229. wagtail/search/backends/database/mysql/mysql.py +25 -17
  230. wagtail/search/backends/database/postgres/postgres.py +44 -83
  231. wagtail/search/backends/database/sqlite/sqlite.py +25 -17
  232. wagtail/search/backends/elasticsearch7.py +4 -0
  233. wagtail/search/locale/en/LC_MESSAGES/django.po +1 -1
  234. wagtail/search/query.py +8 -2
  235. wagtail/search/signal_handlers.py +6 -9
  236. wagtail/search/tasks.py +10 -0
  237. wagtail/search/tests/test_elasticsearch7_backend.py +21 -0
  238. wagtail/search/tests/test_index_functions.py +10 -6
  239. wagtail/search/tests/test_postgres_backend.py +0 -14
  240. wagtail/signal_handlers.py +5 -20
  241. wagtail/sites/locale/en/LC_MESSAGES/django.po +1 -1
  242. wagtail/snippets/locale/en/LC_MESSAGES/django.po +3 -13
  243. wagtail/snippets/tests/test_preview.py +5 -0
  244. wagtail/snippets/tests/test_snippets.py +100 -45
  245. wagtail/snippets/tests/test_usage.py +29 -24
  246. wagtail/snippets/tests/test_viewset.py +1 -1
  247. wagtail/snippets/views/snippets.py +0 -12
  248. wagtail/tasks.py +41 -0
  249. wagtail/templates/wagtailcore/shared/block_preview.html +29 -0
  250. wagtail/test/earlypage/__init__.py +0 -0
  251. wagtail/test/earlypage/migrations/0001_initial.py +37 -0
  252. wagtail/test/earlypage/migrations/__init__.py +0 -0
  253. wagtail/test/earlypage/models.py +14 -0
  254. wagtail/test/settings.py +3 -0
  255. wagtail/test/testapp/fixtures/test.json +7 -0
  256. wagtail/test/testapp/fixtures/test_specific.json +6 -3
  257. wagtail/test/testapp/models.py +58 -44
  258. wagtail/test/testapp/templates/tests/custom_block_preview.html +16 -0
  259. wagtail/test/testapp/templates/tests/static_block_preview.html +5 -0
  260. wagtail/test/testapp/wagtail_hooks.py +9 -0
  261. wagtail/tests/test_blocks.py +189 -2
  262. wagtail/tests/test_hooks.py +166 -1
  263. wagtail/tests/test_management_commands.py +54 -13
  264. wagtail/tests/test_page_allowed_http_methods.py +32 -0
  265. wagtail/tests/test_page_model.py +68 -0
  266. wagtail/tests/test_page_privacy.py +10 -0
  267. wagtail/tests/test_page_queryset.py +79 -0
  268. wagtail/tests/test_reference_index.py +84 -75
  269. wagtail/tests/test_streamfield.py +30 -0
  270. wagtail/tests/test_utils.py +61 -0
  271. wagtail/users/forms.py +2 -9
  272. wagtail/users/locale/en/LC_MESSAGES/django.po +17 -17
  273. wagtail/users/templates/wagtailusers/groups/create.html +0 -5
  274. wagtail/users/templates/wagtailusers/groups/includes/page_permissions_form.html +1 -1
  275. wagtail/users/templates/wagtailusers/groups/includes/page_permissions_formset.html +6 -6
  276. wagtail/users/tests/test_admin_views.py +96 -4
  277. wagtail/users/tests/test_utils.py +76 -0
  278. wagtail/users/utils.py +43 -11
  279. wagtail/utils/setup.py +2 -2
  280. wagtail/utils/templates.py +26 -0
  281. wagtail/utils/widgets.py +1 -0
  282. wagtail/views.py +9 -1
  283. wagtail/wagtail_hooks.py +67 -29
  284. {wagtail-6.3.2.dist-info → wagtail-6.4rc1.dist-info}/METADATA +2 -2
  285. {wagtail-6.3.2.dist-info → wagtail-6.4rc1.dist-info}/RECORD +289 -276
  286. wagtail/admin/static/wagtailadmin/js/expanding-formset.js +0 -1
  287. wagtail/admin/static/wagtailadmin/js/vendor/rangy-core.js +0 -1
  288. wagtail/admin/static/wagtailadmin/js/vendor/uuidv4.min.js +0 -1
  289. wagtail/contrib/search_promotions/views.py +0 -323
  290. wagtail/images/static/wagtailimages/js/vendor/canvas-to-blob.min.js +0 -1
  291. wagtail/users/static/wagtailusers/js/group-form.js +0 -1
  292. wagtail/users/templates/wagtailusers/groups/includes/group_form_js.html +0 -3
  293. {wagtail-6.3.2.dist-info → wagtail-6.4rc1.dist-info}/LICENSE +0 -0
  294. {wagtail-6.3.2.dist-info → wagtail-6.4rc1.dist-info}/WHEEL +0 -0
  295. {wagtail-6.3.2.dist-info → wagtail-6.4rc1.dist-info}/entry_points.txt +0 -0
  296. {wagtail-6.3.2.dist-info → wagtail-6.4rc1.dist-info}/top_level.txt +0 -0
@@ -479,7 +479,7 @@ class CreateView(
479
479
  {
480
480
  "url": "",
481
481
  "label": _("New: %(model_name)s")
482
- % {"model_name": capfirst(self.model._meta.verbose_name)},
482
+ % {"model_name": self.get_page_subtitle()},
483
483
  }
484
484
  )
485
485
  return self.breadcrumbs_items + items
@@ -677,6 +677,16 @@ class EditView(
677
677
  super().setup(request, *args, **kwargs)
678
678
  self.action = self.get_action(request)
679
679
 
680
+ @cached_property
681
+ def object_pk(self):
682
+ # Must be a cached_property to prevent this from being re-run on the unquoted
683
+ # pk written back by get_object, which would result in it being unquoted again.
684
+ try:
685
+ quoted_pk = self.kwargs[self.pk_url_kwarg]
686
+ except KeyError:
687
+ quoted_pk = self.args[0]
688
+ return unquote(str(quoted_pk))
689
+
680
690
  def get_action(self, request):
681
691
  for action in self.get_available_actions():
682
692
  if request.POST.get(f"action-{action}"):
@@ -687,9 +697,9 @@ class EditView(
687
697
  return self.actions
688
698
 
689
699
  def get_object(self, queryset=None):
690
- if self.pk_url_kwarg not in self.kwargs:
691
- self.kwargs[self.pk_url_kwarg] = self.args[0]
692
- self.kwargs[self.pk_url_kwarg] = unquote(str(self.kwargs[self.pk_url_kwarg]))
700
+ # SingleObjectMixin.get_object looks for the unquoted pk in self.kwargs,
701
+ # so we need to write it back there.
702
+ self.kwargs[self.pk_url_kwarg] = self.object_pk
693
703
  return super().get_object(queryset)
694
704
 
695
705
  def get_page_subtitle(self):
@@ -947,9 +957,15 @@ class DeleteView(
947
957
  # If the object has already been loaded, return it to avoid another query
948
958
  if getattr(self, "object", None):
949
959
  return self.object
950
- if self.pk_url_kwarg not in self.kwargs:
951
- self.kwargs[self.pk_url_kwarg] = self.args[0]
952
- self.kwargs[self.pk_url_kwarg] = unquote(str(self.kwargs[self.pk_url_kwarg]))
960
+
961
+ # SingleObjectMixin.get_object looks for the unquoted pk in self.kwargs,
962
+ # so we need to write it back there.
963
+ try:
964
+ quoted_pk = self.kwargs[self.pk_url_kwarg]
965
+ except KeyError:
966
+ quoted_pk = self.args[0]
967
+ self.kwargs[self.pk_url_kwarg] = unquote(str(quoted_pk))
968
+
953
969
  return super().get_object(queryset)
954
970
 
955
971
  def get_usage(self):
@@ -1167,13 +1183,47 @@ class InspectView(PermissionCheckedMixin, WagtailAdminTemplateMixin, TemplateVie
1167
1183
 
1168
1184
  class RevisionsCompareView(WagtailAdminTemplateMixin, TemplateView):
1169
1185
  edit_handler = None
1186
+ index_url_name = None
1170
1187
  edit_url_name = None
1171
1188
  history_url_name = None
1172
1189
  edit_label = gettext_lazy("Edit")
1173
1190
  history_label = gettext_lazy("History")
1191
+ page_title = gettext_lazy("Compare")
1174
1192
  template_name = "wagtailadmin/generic/revisions/compare.html"
1193
+ _show_breadcrumbs = True
1175
1194
  model = None
1176
1195
 
1196
+ def get_breadcrumbs_items(self):
1197
+ items = []
1198
+ if (index_url := self.get_index_url()) and self.model:
1199
+ items.append(
1200
+ {
1201
+ "url": index_url,
1202
+ "label": capfirst(self.model._meta.verbose_name_plural),
1203
+ }
1204
+ )
1205
+ if edit_url := self.get_edit_url():
1206
+ items.append({"url": edit_url, "label": self.get_page_subtitle()})
1207
+ if history_url := self.get_history_url():
1208
+ items.append({"url": history_url, "label": self.history_label})
1209
+ items.append(
1210
+ {
1211
+ "url": "",
1212
+ "label": self.get_page_title(),
1213
+ "sublabel": self.get_page_subtitle(),
1214
+ }
1215
+ )
1216
+ return self.breadcrumbs_items + items
1217
+
1218
+ @cached_property
1219
+ def header_buttons(self):
1220
+ buttons = []
1221
+ if edit_url := self.get_edit_url():
1222
+ buttons.append(
1223
+ HeaderButton(self.edit_label, url=edit_url, icon_name="edit")
1224
+ )
1225
+ return buttons
1226
+
1177
1227
  def setup(self, request, pk, revision_id_a, revision_id_b, *args, **kwargs):
1178
1228
  super().setup(request, *args, **kwargs)
1179
1229
  self.pk = pk
@@ -1192,6 +1242,10 @@ class RevisionsCompareView(WagtailAdminTemplateMixin, TemplateView):
1192
1242
  def get_page_subtitle(self):
1193
1243
  return str(self.object)
1194
1244
 
1245
+ def get_index_url(self):
1246
+ if self.index_url_name:
1247
+ return reverse(self.index_url_name)
1248
+
1195
1249
  def get_history_url(self):
1196
1250
  if self.history_url_name:
1197
1251
  return reverse(self.history_url_name, args=(quote(self.object.pk),))
@@ -1253,10 +1307,6 @@ class RevisionsCompareView(WagtailAdminTemplateMixin, TemplateView):
1253
1307
  context.update(
1254
1308
  {
1255
1309
  "object": self.object,
1256
- "history_label": self.history_label,
1257
- "edit_label": self.edit_label,
1258
- "history_url": self.get_history_url(),
1259
- "edit_url": self.get_edit_url(),
1260
1310
  "revision_a": revision_a,
1261
1311
  "revision_a_heading": revision_a_heading,
1262
1312
  "revision_b": revision_b,
@@ -1274,6 +1324,7 @@ class UnpublishView(HookResponseMixin, WagtailAdminTemplateMixin, TemplateView):
1274
1324
  edit_url_name = None
1275
1325
  unpublish_url_name = None
1276
1326
  usage_url_name = None
1327
+ page_title = gettext_lazy("Unpublish")
1277
1328
  success_message = gettext_lazy("'%(object)s' unpublished.")
1278
1329
  template_name = "wagtailadmin/generic/confirm_unpublish.html"
1279
1330
 
@@ -1294,12 +1345,15 @@ class UnpublishView(HookResponseMixin, WagtailAdminTemplateMixin, TemplateView):
1294
1345
  def get_usage(self):
1295
1346
  return ReferenceIndex.get_grouped_references_to(self.object)
1296
1347
 
1348
+ def get_breadcrumbs_items(self):
1349
+ return []
1350
+
1297
1351
  def get_objects_to_unpublish(self):
1298
1352
  # Hook to allow child classes to have more objects to unpublish (e.g. page descendants)
1299
1353
  return [self.object]
1300
1354
 
1301
- def get_object_display_title(self):
1302
- return str(self.object)
1355
+ def get_page_subtitle(self):
1356
+ return get_latest_str(self.object)
1303
1357
 
1304
1358
  def get_success_message(self):
1305
1359
  if self.success_message is None:
@@ -1365,7 +1419,6 @@ class UnpublishView(HookResponseMixin, WagtailAdminTemplateMixin, TemplateView):
1365
1419
  context = super().get_context_data(**kwargs)
1366
1420
  context["model_opts"] = self.object._meta
1367
1421
  context["object"] = self.object
1368
- context["object_display_title"] = self.get_object_display_title()
1369
1422
  context["unpublish_url"] = self.get_unpublish_url()
1370
1423
  context["next_url"] = self.get_next_url()
1371
1424
  context["usage_url"] = self.get_usage_url()
@@ -1384,6 +1437,7 @@ class RevisionsUnscheduleView(WagtailAdminTemplateMixin, TemplateView):
1384
1437
  'Version %(revision_id)s of "%(object)s" unscheduled.'
1385
1438
  )
1386
1439
  template_name = "wagtailadmin/shared/revisions/confirm_unschedule.html"
1440
+ page_title = gettext_lazy("Unschedule")
1387
1441
 
1388
1442
  def setup(self, request, pk, revision_id, *args, **kwargs):
1389
1443
  super().setup(request, *args, **kwargs)
@@ -1397,6 +1451,9 @@ class RevisionsUnscheduleView(WagtailAdminTemplateMixin, TemplateView):
1397
1451
  raise Http404
1398
1452
  return get_object_or_404(self.model, pk=unquote(str(self.pk)))
1399
1453
 
1454
+ def get_breadcrumbs_items(self):
1455
+ return []
1456
+
1400
1457
  def get_revision(self):
1401
1458
  return get_object_or_404(self.object.revisions, id=self.revision_id)
1402
1459
 
@@ -1407,7 +1464,7 @@ class RevisionsUnscheduleView(WagtailAdminTemplateMixin, TemplateView):
1407
1464
  )
1408
1465
 
1409
1466
  def get_object_display_title(self):
1410
- return str(self.object)
1467
+ return get_latest_str(self.object)
1411
1468
 
1412
1469
  def get_success_message(self):
1413
1470
  if self.success_message is None:
@@ -1437,10 +1494,13 @@ class RevisionsUnscheduleView(WagtailAdminTemplateMixin, TemplateView):
1437
1494
  return reverse(self.history_url_name, args=(quote(self.object.pk),))
1438
1495
 
1439
1496
  def get_page_subtitle(self):
1440
- return _('revision %(revision_id)s of "%(object)s"') % {
1441
- "revision_id": self.revision.id,
1442
- "object": self.get_object_display_title(),
1443
- }
1497
+ return capfirst(
1498
+ _('revision %(revision_id)s of "%(object)s"')
1499
+ % {
1500
+ "revision_id": self.revision.id,
1501
+ "object": self.get_object_display_title(),
1502
+ }
1503
+ )
1444
1504
 
1445
1505
  def get_context_data(self, **kwargs):
1446
1506
  context = super().get_context_data(**kwargs)
@@ -1448,8 +1508,6 @@ class RevisionsUnscheduleView(WagtailAdminTemplateMixin, TemplateView):
1448
1508
  {
1449
1509
  "object": self.object,
1450
1510
  "revision": self.revision,
1451
- "subtitle": self.get_page_subtitle(),
1452
- "object_display_title": self.get_object_display_title(),
1453
1511
  "revisions_unschedule_url": self.get_revisions_unschedule_url(),
1454
1512
  "next_url": self.get_next_url(),
1455
1513
  }
@@ -7,9 +7,12 @@ from django.http.request import QueryDict
7
7
  from django.shortcuts import get_object_or_404
8
8
  from django.template.response import TemplateResponse
9
9
  from django.utils.decorators import method_decorator
10
- from django.views.generic import View
10
+ from django.utils.functional import cached_property
11
+ from django.utils.translation import gettext
12
+ from django.views.generic import TemplateView, View
11
13
 
12
14
  from wagtail.admin.panels import get_edit_handler
15
+ from wagtail.blocks.base import Block
13
16
  from wagtail.models import PreviewableMixin, RevisionMixin
14
17
  from wagtail.utils.decorators import xframe_options_sameorigin_override
15
18
 
@@ -163,3 +166,49 @@ class PreviewRevision(View):
163
166
  raise PermissionDenied
164
167
 
165
168
  return self.revision_object.make_preview_request(request, preview_mode)
169
+
170
+
171
+ @method_decorator(xframe_options_sameorigin_override, name="get")
172
+ class StreamFieldBlockPreview(TemplateView):
173
+ http_method_names = ("get",)
174
+
175
+ @cached_property
176
+ def block_id(self):
177
+ return self.request.GET.get("id")
178
+
179
+ @cached_property
180
+ def block_def(self) -> Block:
181
+ if not (block := Block.definition_registry.get(self.block_id)):
182
+ raise Http404
183
+ return block
184
+
185
+ @cached_property
186
+ def block_value(self):
187
+ return self.block_def.get_preview_value()
188
+
189
+ @cached_property
190
+ def page_title(self):
191
+ return gettext("Preview for %(block_label)s (%(block_type)s)") % {
192
+ "block_label": self.block_def.label,
193
+ "block_type": self.block_def.__class__.__name__,
194
+ }
195
+
196
+ @cached_property
197
+ def base_context(self):
198
+ # Do NOT use the name `block` in the context, as it will conflict with
199
+ # the current block inside a template {% block %} tag.
200
+ return {
201
+ "request": self.request,
202
+ "block_def": self.block_def,
203
+ "block_class": self.block_def.__class__,
204
+ "bound_block": self.block_def.bind(self.block_value),
205
+ "page_title": self.page_title,
206
+ }
207
+
208
+ def get_template_names(self):
209
+ return self.block_def.get_preview_template(self.block_value, self.base_context)
210
+
211
+ def get_context_data(self, **kwargs):
212
+ context = super().get_context_data(**kwargs)
213
+ context.update(self.base_context)
214
+ return self.block_def.get_preview_context(self.block_value, context)
@@ -14,8 +14,6 @@ from django.utils.formats import get_format
14
14
  from django.utils.functional import cached_property
15
15
  from django.utils.text import capfirst
16
16
  from django.utils.translation import gettext as _
17
- from openpyxl import Workbook
18
- from openpyxl.cell import WriteOnlyCell
19
17
 
20
18
  from wagtail.admin.widgets.button import Button
21
19
  from wagtail.coreutils import multigetattr
@@ -208,6 +206,8 @@ class SpreadsheetExportMixin:
208
206
 
209
207
  def generate_xlsx_row(self, worksheet, row_dict, date_format=None):
210
208
  """Generate cells to append to the worksheet"""
209
+ from openpyxl.cell import WriteOnlyCell
210
+
211
211
  for field, value in row_dict.items():
212
212
  cell = WriteOnlyCell(
213
213
  worksheet, self.preprocess_field_value(field, value, self.FORMAT_XLSX)
@@ -246,6 +246,8 @@ class SpreadsheetExportMixin:
246
246
 
247
247
  def write_xlsx(self, queryset, output):
248
248
  """Write an xlsx workbook from a queryset"""
249
+ from openpyxl import Workbook
250
+
249
251
  workbook = Workbook(write_only=True, iso_dates=True)
250
252
 
251
253
  worksheet = workbook.create_sheet(title="Sheet1")
@@ -31,27 +31,15 @@ class DeleteBulkAction(PageBulkAction):
31
31
  return num_parent_objects, num_child_objects
32
32
 
33
33
  def get_success_message(self, num_parent_objects, num_child_objects):
34
- if num_parent_objects == 1:
35
- if num_child_objects == 0:
36
- success_message = _("1 page has been deleted")
37
- else:
38
- success_message = ngettext(
39
- "1 page and %(num_child_objects)d child page have been deleted",
40
- "1 page and %(num_child_objects)d child pages have been deleted",
41
- num_child_objects,
42
- ) % {"num_child_objects": num_child_objects}
34
+ if num_child_objects > 0:
35
+ # Translators: This forms a message such as "1 page and 3 child pages have been deleted"
36
+ return _("%(parent_pages)s and %(child_pages)s have been deleted") % {
37
+ "parent_pages": self.get_parent_page_text(num_parent_objects),
38
+ "child_pages": self.get_child_page_text(num_child_objects),
39
+ }
43
40
  else:
44
- if num_child_objects == 0:
45
- success_message = _(
46
- "%(num_parent_objects)d pages have been deleted"
47
- ) % {"num_parent_objects": num_parent_objects}
48
- else:
49
- success_message = ngettext(
50
- "%(num_parent_objects)d pages and %(num_child_objects)d child page have been deleted",
51
- "%(num_parent_objects)d pages and %(num_child_objects)d child pages have been deleted",
52
- num_child_objects,
53
- ) % {
54
- "num_child_objects": num_child_objects,
55
- "num_parent_objects": num_parent_objects,
56
- }
57
- return success_message
41
+ return ngettext(
42
+ "%(num_parent_objects)d page has been deleted",
43
+ "%(num_parent_objects)d pages have been deleted",
44
+ num_parent_objects,
45
+ ) % {"num_parent_objects": num_parent_objects}
@@ -1,4 +1,5 @@
1
1
  from django import forms
2
+ from django.utils.translation import ngettext
2
3
 
3
4
  from wagtail.admin.views.bulk_action import BulkAction
4
5
  from wagtail.admin.views.pages.search import page_filter_search
@@ -55,3 +56,19 @@ class PageBulkAction(BulkAction):
55
56
 
56
57
  def get_execution_context(self):
57
58
  return {"user": self.request.user}
59
+
60
+ def get_parent_page_text(self, num_parent_objects):
61
+ # Translators: This appears within a message such as "2 pages and 3 child pages have been published"
62
+ return ngettext(
63
+ "%(num_parent_objects)d page",
64
+ "%(num_parent_objects)d pages",
65
+ num_parent_objects,
66
+ ) % {"num_parent_objects": num_parent_objects}
67
+
68
+ def get_child_page_text(self, num_child_objects):
69
+ # Translators: This appears within a message such as "2 pages and 3 child pages have been published"
70
+ return ngettext(
71
+ "%(num_child_objects)d child page",
72
+ "%(num_child_objects)d child pages",
73
+ num_child_objects,
74
+ ) % {"num_child_objects": num_child_objects}
@@ -71,35 +71,15 @@ class PublishBulkAction(PageBulkAction):
71
71
 
72
72
  def get_success_message(self, num_parent_objects, num_child_objects):
73
73
  include_descendants = self.cleaned_form.cleaned_data["include_descendants"]
74
- if num_parent_objects == 1:
75
- if include_descendants:
76
- if num_child_objects == 0:
77
- success_message = _("1 page has been published")
78
- else:
79
- success_message = ngettext(
80
- "1 page and %(num_child_objects)d child page have been published",
81
- "1 page and %(num_child_objects)d child pages have been published",
82
- num_child_objects,
83
- ) % {"num_child_objects": num_child_objects}
84
- else:
85
- success_message = _("1 page has been published")
74
+ if include_descendants and num_child_objects > 0:
75
+ # Translators: This forms a message such as "1 page and 3 child pages have been published"
76
+ return _("%(parent_pages)s and %(child_pages)s have been published") % {
77
+ "parent_pages": self.get_parent_page_text(num_parent_objects),
78
+ "child_pages": self.get_child_page_text(num_child_objects),
79
+ }
86
80
  else:
87
- if include_descendants:
88
- if num_child_objects == 0:
89
- success_message = _(
90
- "%(num_parent_objects)d pages have been published"
91
- ) % {"num_parent_objects": num_parent_objects}
92
- else:
93
- success_message = ngettext(
94
- "%(num_parent_objects)d pages and %(num_child_objects)d child page have been published",
95
- "%(num_parent_objects)d pages and %(num_child_objects)d child pages have been published",
96
- num_child_objects,
97
- ) % {
98
- "num_child_objects": num_child_objects,
99
- "num_parent_objects": num_parent_objects,
100
- }
101
- else:
102
- success_message = _(
103
- "%(num_parent_objects)d pages have been published"
104
- ) % {"num_parent_objects": num_parent_objects}
105
- return success_message
81
+ return ngettext(
82
+ "%(num_parent_objects)d page has been published",
83
+ "%(num_parent_objects)d pages have been published",
84
+ num_parent_objects,
85
+ ) % {"num_parent_objects": num_parent_objects}
@@ -65,35 +65,15 @@ class UnpublishBulkAction(PageBulkAction):
65
65
 
66
66
  def get_success_message(self, num_parent_objects, num_child_objects):
67
67
  include_descendants = self.cleaned_form.cleaned_data["include_descendants"]
68
- if num_parent_objects == 1:
69
- if include_descendants:
70
- if num_child_objects == 0:
71
- success_message = _("1 page has been unpublished")
72
- else:
73
- success_message = ngettext(
74
- "1 page and %(num_child_objects)d child page have been unpublished",
75
- "1 page and %(num_child_objects)d child pages have been unpublished",
76
- num_child_objects,
77
- ) % {"num_child_objects": num_child_objects}
78
- else:
79
- success_message = _("1 page has been unpublished")
68
+ if include_descendants and num_child_objects > 0:
69
+ # Translators: This forms a message such as "1 page and 3 child pages have been unpublished"
70
+ return _("%(parent_pages)s and %(child_pages)s have been unpublished") % {
71
+ "parent_pages": self.get_parent_page_text(num_parent_objects),
72
+ "child_pages": self.get_child_page_text(num_child_objects),
73
+ }
80
74
  else:
81
- if include_descendants:
82
- if num_child_objects == 0:
83
- success_message = _(
84
- "%(num_parent_objects)d pages have been unpublished"
85
- ) % {"num_parent_objects": num_parent_objects}
86
- else:
87
- success_message = ngettext(
88
- "%(num_parent_objects)d pages and %(num_child_objects)d child page have been unpublished",
89
- "%(num_parent_objects)d pages and %(num_child_objects)d child pages have been unpublished",
90
- num_child_objects,
91
- ) % {
92
- "num_child_objects": num_child_objects,
93
- "num_parent_objects": num_parent_objects,
94
- }
95
- else:
96
- success_message = _(
97
- "%(num_parent_objects)d pages have been unpublished"
98
- ) % {"num_parent_objects": num_parent_objects}
99
- return success_message
75
+ return ngettext(
76
+ "%(num_parent_objects)d page has been unpublished",
77
+ "%(num_parent_objects)d pages have been unpublished",
78
+ num_parent_objects,
79
+ ) % {"num_parent_objects": num_parent_objects}
@@ -358,6 +358,7 @@ class CreateView(WagtailAdminTemplateMixin, HookResponseMixin, View):
358
358
  show_schedule_publishing_toggle=self.form.show_schedule_publishing_toggle,
359
359
  locale=self.locale,
360
360
  translations=self.translations,
361
+ parent_page=self.parent_page,
361
362
  ),
362
363
  ]
363
364
  if self.page.is_previewable():
@@ -290,9 +290,7 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
290
290
  reply.log_delete(page_revision=revision, user=self.request.user)
291
291
 
292
292
  def get_edit_message_button(self):
293
- return messages.button(
294
- reverse("wagtailadmin_pages:edit", args=(self.page.id,)), _("Edit")
295
- )
293
+ return messages.button(self.get_edit_url(), _("Edit"))
296
294
 
297
295
  def get_view_draft_message_button(self):
298
296
  return messages.button(
@@ -320,7 +318,13 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
320
318
  else:
321
319
  return self.page
322
320
 
323
- def dispatch(self, request, page_id):
321
+ def get_object(self):
322
+ return self.real_page_record.get_latest_revision_as_object()
323
+
324
+ def get_edit_url(self):
325
+ return reverse("wagtailadmin_pages:edit", args=(self.page.id,))
326
+
327
+ def dispatch(self, request, page_id, **kwargs):
324
328
  self.real_page_record = get_object_or_404(
325
329
  Page.objects.prefetch_workflow_states(), id=page_id
326
330
  )
@@ -340,7 +344,14 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
340
344
  "back to a branch where the model class is still present."
341
345
  )
342
346
 
343
- self.page = self.real_page_record.get_latest_revision_as_object()
347
+ self.revision_id = kwargs.get("revision_id")
348
+ self.is_reverting = bool(self.revision_id)
349
+ self.previous_revision = None
350
+ if self.is_reverting:
351
+ self.previous_revision = get_object_or_404(
352
+ self.real_page_record.revisions, id=self.revision_id
353
+ )
354
+ self.page = self.get_object()
344
355
  self.parent = self.page.get_parent()
345
356
  self.scheduled_page = self.real_page_record.get_scheduled_revision_as_object()
346
357
 
@@ -393,9 +404,9 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
393
404
 
394
405
  self.errors_debug = None
395
406
 
396
- return super().dispatch(request)
407
+ return super().dispatch(request, page_id, **kwargs)
397
408
 
398
- def get(self, request):
409
+ def get(self, request, *args, **kwargs):
399
410
  if self.lock:
400
411
  lock_message = self.lock.get_message(self.request.user)
401
412
  if lock_message:
@@ -454,7 +465,7 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
454
465
  ],
455
466
  )
456
467
 
457
- def post(self, request):
468
+ def post(self, request, *args, **kwargs):
458
469
  # Don't allow POST requests if the page is an alias
459
470
  if self.page.alias_of_id:
460
471
  # Return 405 "Method Not Allowed" response
@@ -491,14 +502,6 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
491
502
  return self.workflow_action in available_action_names
492
503
 
493
504
  def form_valid(self, form):
494
- self.is_reverting = bool(self.request.POST.get("revision"))
495
- # If a revision ID was passed in the form, get that revision so its
496
- # date can be referenced in notification messages
497
- if self.is_reverting:
498
- self.previous_revision = get_object_or_404(
499
- self.page.revisions, id=self.request.POST.get("revision")
500
- )
501
-
502
505
  self.has_content_changes = self.form.has_changed()
503
506
 
504
507
  if self.request.POST.get("action-publish") and self.page_perms.can_publish():
@@ -533,7 +536,7 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
533
536
  revision = self.page.save_revision(
534
537
  user=self.request.user,
535
538
  log_action=True, # Always log the new revision on edit
536
- previous_revision=(self.previous_revision if self.is_reverting else None),
539
+ previous_revision=self.previous_revision,
537
540
  )
538
541
 
539
542
  self.add_save_confirmation_message()
@@ -558,7 +561,7 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
558
561
  revision = self.page.save_revision(
559
562
  user=self.request.user,
560
563
  log_action=True, # Always log the new revision on edit
561
- previous_revision=(self.previous_revision if self.is_reverting else None),
564
+ previous_revision=self.previous_revision,
562
565
  )
563
566
 
564
567
  # store submitted go_live_at for messaging below
@@ -572,7 +575,7 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
572
575
  revision,
573
576
  user=self.request.user,
574
577
  changed=self.has_content_changes,
575
- previous_revision=(self.previous_revision if self.is_reverting else None),
578
+ previous_revision=self.previous_revision,
576
579
  )
577
580
  action.execute(skip_permission_checks=True)
578
581
 
@@ -654,7 +657,7 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
654
657
  revision = self.page.save_revision(
655
658
  user=self.request.user,
656
659
  log_action=True, # Always log the new revision on edit
657
- previous_revision=(self.previous_revision if self.is_reverting else None),
660
+ previous_revision=self.previous_revision,
658
661
  )
659
662
 
660
663
  if self.has_content_changes and "comments" in self.form.formsets:
@@ -701,7 +704,7 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
701
704
  revision = self.page.save_revision(
702
705
  user=self.request.user,
703
706
  log_action=True, # Always log the new revision on edit
704
- previous_revision=(self.previous_revision if self.is_reverting else None),
707
+ previous_revision=self.previous_revision,
705
708
  )
706
709
 
707
710
  if self.has_content_changes and "comments" in self.form.formsets:
@@ -783,7 +786,7 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
783
786
  revision = self.page.save_revision(
784
787
  user=self.request.user,
785
788
  log_action=True, # Always log the new revision on edit
786
- previous_revision=(self.previous_revision if self.is_reverting else None),
789
+ previous_revision=self.previous_revision,
787
790
  )
788
791
 
789
792
  if self.has_content_changes and "comments" in self.form.formsets:
@@ -810,7 +813,7 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
810
813
  return redirect("wagtailadmin_explore", self.page.get_parent().id)
811
814
 
812
815
  def redirect_and_remain(self):
813
- target_url = reverse("wagtailadmin_pages:edit", args=[self.page.id])
816
+ target_url = self.get_edit_url()
814
817
  if self.next_url:
815
818
  # Ensure the 'next' url is passed through again if present
816
819
  target_url += "?next=%s" % quote(self.next_url)
@@ -868,6 +871,7 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
868
871
  scheduled_object=self.scheduled_page,
869
872
  locale=self.locale,
870
873
  translations=self.translations,
874
+ parent_page=self.page.get_parent(),
871
875
  ),
872
876
  ]
873
877
  if self.page.is_previewable():
@@ -904,19 +908,22 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
904
908
  self.page.latest_revision_id,
905
909
  )
906
910
 
907
- def get_context_data(self, **kwargs):
908
- context = super().get_context_data(**kwargs)
909
- user_perms = self.page.permissions_for_user(self.request.user)
910
- bound_panel = self.edit_handler.get_bound_panel(
911
- instance=self.page, request=self.request, form=self.form
912
- )
913
- action_menu = PageActionMenu(
911
+ def get_action_menu(self):
912
+ return PageActionMenu(
914
913
  self.request,
915
914
  view="edit",
916
915
  page=self.page,
917
916
  lock=self.lock,
918
917
  locked_for_user=self.locked_for_user,
919
918
  )
919
+
920
+ def get_context_data(self, **kwargs):
921
+ context = super().get_context_data(**kwargs)
922
+ user_perms = self.page.permissions_for_user(self.request.user)
923
+ bound_panel = self.edit_handler.get_bound_panel(
924
+ instance=self.page, request=self.request, form=self.form
925
+ )
926
+ action_menu = self.get_action_menu()
920
927
  side_panels = self.get_side_panels()
921
928
 
922
929
  media = MediaContainer([bound_panel, self.form, action_menu, side_panels]).media
@@ -932,6 +939,7 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
932
939
  "side_panels": side_panels,
933
940
  "form": self.form,
934
941
  "next": self.next_url,
942
+ "action_url": self.get_edit_url(),
935
943
  "history_url": self.get_history_url(),
936
944
  "has_unsaved_changes": self.has_unsaved_changes,
937
945
  "page_locked": self.locked_for_user,