wagtail 6.1.3__py3-none-any.whl → 6.2rc1__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 (257) hide show
  1. wagtail/__init__.py +1 -1
  2. wagtail/actions/copy_for_translation.py +15 -1
  3. wagtail/admin/checks.py +20 -30
  4. wagtail/admin/forms/pages.py +10 -0
  5. wagtail/admin/icons.py +43 -0
  6. wagtail/admin/locale/en/LC_MESSAGES/django.po +405 -295
  7. wagtail/admin/locale/en/LC_MESSAGES/djangojs.po +21 -3
  8. wagtail/admin/locale/sl/LC_MESSAGES/django.mo +0 -0
  9. wagtail/admin/locale/sl/LC_MESSAGES/django.po +30 -0
  10. wagtail/admin/menu.py +2 -2
  11. wagtail/admin/migrations/0004_editingsession.py +57 -0
  12. wagtail/admin/migrations/0005_editingsession_is_editing.py +18 -0
  13. wagtail/admin/models.py +36 -3
  14. wagtail/admin/rich_text/editors/draftail/__init__.py +2 -20
  15. wagtail/admin/static/wagtailadmin/css/core.css +1 -1
  16. wagtail/admin/static/wagtailadmin/js/bulk-actions.js +1 -1
  17. wagtail/admin/static/wagtailadmin/js/chooser-modal.js +1 -1
  18. wagtail/admin/static/wagtailadmin/js/chooser-widget-telepath.js +1 -1
  19. wagtail/admin/static/wagtailadmin/js/chooser-widget.js +1 -1
  20. wagtail/admin/static/wagtailadmin/js/comments.js +1 -1
  21. wagtail/admin/static/wagtailadmin/js/core.js +1 -1
  22. wagtail/admin/static/wagtailadmin/js/date-time-chooser.js +1 -1
  23. wagtail/admin/static/wagtailadmin/js/draftail.js +1 -1
  24. wagtail/admin/static/wagtailadmin/js/expanding-formset.js +1 -1
  25. wagtail/admin/static/wagtailadmin/js/filtered-select.js +1 -1
  26. wagtail/admin/static/wagtailadmin/js/modal-workflow.js +1 -1
  27. wagtail/admin/static/wagtailadmin/js/page-chooser-modal.js +1 -1
  28. wagtail/admin/static/wagtailadmin/js/page-chooser-telepath.js +1 -1
  29. wagtail/admin/static/wagtailadmin/js/page-chooser.js +1 -1
  30. wagtail/admin/static/wagtailadmin/js/preview-panel.js +2 -1
  31. wagtail/admin/static/wagtailadmin/js/preview-panel.js.LICENSE.txt +11 -0
  32. wagtail/admin/static/wagtailadmin/js/privacy-switch.js +1 -1
  33. wagtail/admin/static/wagtailadmin/js/sidebar.js +1 -1
  34. wagtail/admin/static/wagtailadmin/js/task-chooser-modal.js +1 -1
  35. wagtail/admin/static/wagtailadmin/js/task-chooser.js +1 -1
  36. wagtail/admin/static/wagtailadmin/js/telepath/blocks.js +1 -1
  37. wagtail/admin/static/wagtailadmin/js/telepath/widgets.js +1 -1
  38. wagtail/admin/static/wagtailadmin/js/userbar.js +2 -1
  39. wagtail/admin/static/wagtailadmin/js/userbar.js.LICENSE.txt +11 -0
  40. wagtail/admin/static/wagtailadmin/js/vendor.js +1 -1
  41. wagtail/admin/static/wagtailadmin/js/vendor.js.LICENSE.txt +0 -12
  42. wagtail/admin/static/wagtailadmin/js/wagtailadmin.js +1 -1
  43. wagtail/admin/static/wagtailadmin/js/workflow-action.js +1 -1
  44. wagtail/admin/templates/wagtailadmin/collection_privacy/ancestor_privacy.html +2 -6
  45. wagtail/admin/templates/wagtailadmin/generic/index_results.html +1 -17
  46. wagtail/admin/templates/wagtailadmin/generic/listing_results.html +20 -1
  47. wagtail/admin/templates/wagtailadmin/home/workflow_objects_to_moderate.html +2 -11
  48. wagtail/admin/templates/wagtailadmin/page_privacy/ancestor_privacy.html +2 -6
  49. wagtail/admin/templates/wagtailadmin/page_privacy/no_privacy.html +2 -0
  50. wagtail/admin/templates/wagtailadmin/pages/_editor_js.html +0 -1
  51. wagtail/admin/templates/wagtailadmin/pages/action_menu/menu.html +1 -1
  52. wagtail/admin/templates/wagtailadmin/reports/aging_pages_results.html +54 -0
  53. wagtail/admin/templates/wagtailadmin/reports/base_page_report.html +1 -17
  54. wagtail/admin/templates/wagtailadmin/reports/base_page_report_results.html +10 -0
  55. wagtail/admin/templates/wagtailadmin/reports/base_report.html +1 -40
  56. wagtail/admin/templates/wagtailadmin/reports/base_report_results.html +1 -0
  57. wagtail/admin/templates/wagtailadmin/reports/listing/_list_page_report.html +21 -27
  58. wagtail/admin/templates/wagtailadmin/reports/listing/_list_page_types_usage.html +48 -54
  59. wagtail/admin/templates/wagtailadmin/reports/{locked_pages.html → locked_pages_results.html} +3 -3
  60. wagtail/admin/templates/wagtailadmin/reports/page_types_usage_results.html +10 -0
  61. wagtail/admin/templates/wagtailadmin/reports/site_history_results.html +53 -0
  62. wagtail/admin/templates/wagtailadmin/reports/workflow_results.html +74 -0
  63. wagtail/admin/templates/wagtailadmin/reports/workflow_tasks_results.html +56 -0
  64. wagtail/admin/templates/wagtailadmin/shared/_workflow_init.html +8 -44
  65. wagtail/admin/templates/wagtailadmin/shared/avatar.html +11 -1
  66. wagtail/admin/templates/wagtailadmin/shared/dialog/dialog.html +5 -4
  67. wagtail/admin/templates/wagtailadmin/shared/dropdown/dropdown_button.html +2 -1
  68. wagtail/admin/templates/wagtailadmin/shared/editing_sessions/list.html +132 -0
  69. wagtail/admin/templates/wagtailadmin/shared/editing_sessions/module.html +44 -0
  70. wagtail/admin/templates/wagtailadmin/shared/headers/slim_header.html +7 -1
  71. wagtail/admin/templates/wagtailadmin/shared/page_status_tag_new.html +1 -1
  72. wagtail/admin/templates/wagtailadmin/shared/side_panels/checks.html +32 -16
  73. wagtail/admin/templates/wagtailadmin/skeleton.html +1 -1
  74. wagtail/admin/templates/wagtailadmin/userbar/item_accessibility.html +9 -11
  75. wagtail/admin/templatetags/wagtailadmin_tags.py +13 -2
  76. wagtail/admin/tests/formats/en/__init__.py +0 -0
  77. wagtail/admin/tests/formats/en/formats.py +1 -0
  78. wagtail/admin/tests/pages/test_create_page.py +47 -0
  79. wagtail/admin/tests/pages/test_edit_page.py +10 -8
  80. wagtail/admin/tests/pages/test_parent_page_chooser_view.py +45 -1
  81. wagtail/admin/tests/test_checks.py +53 -3
  82. wagtail/admin/tests/test_collections_views.py +62 -1
  83. wagtail/admin/tests/test_edit_handlers.py +37 -0
  84. wagtail/admin/tests/test_editing_sessions.py +1336 -0
  85. wagtail/admin/tests/test_icon_sprite.py +12 -21
  86. wagtail/admin/tests/test_page_chooser.py +309 -7
  87. wagtail/admin/tests/test_privacy.py +82 -0
  88. wagtail/admin/tests/test_reports_views.py +464 -70
  89. wagtail/admin/tests/test_userbar.py +93 -6
  90. wagtail/admin/tests/test_workflows.py +223 -33
  91. wagtail/admin/tests/viewsets/test_model_viewset.py +151 -2
  92. wagtail/admin/ui/editing_sessions.py +57 -0
  93. wagtail/admin/urls/__init__.py +9 -15
  94. wagtail/admin/urls/editing_sessions.py +17 -0
  95. wagtail/admin/urls/reports.py +33 -1
  96. wagtail/admin/userbar.py +77 -20
  97. wagtail/admin/views/chooser.py +49 -22
  98. wagtail/admin/views/collections.py +0 -11
  99. wagtail/admin/views/editing_sessions.py +193 -0
  100. wagtail/admin/views/generic/__init__.py +1 -0
  101. wagtail/admin/views/generic/history.py +9 -3
  102. wagtail/admin/views/generic/mixins.py +44 -3
  103. wagtail/admin/views/generic/models.py +46 -72
  104. wagtail/admin/views/generic/permissions.py +20 -10
  105. wagtail/admin/views/home.py +2 -31
  106. wagtail/admin/views/page_privacy.py +20 -5
  107. wagtail/admin/views/pages/choose_parent.py +62 -0
  108. wagtail/admin/views/pages/edit.py +28 -0
  109. wagtail/admin/views/reports/aging_pages.py +6 -10
  110. wagtail/admin/views/reports/audit_logging.py +13 -42
  111. wagtail/admin/views/reports/base.py +31 -4
  112. wagtail/admin/views/reports/locked_pages.py +5 -8
  113. wagtail/admin/views/reports/page_types_usage.py +6 -10
  114. wagtail/admin/views/reports/workflows.py +36 -12
  115. wagtail/admin/viewsets/base.py +8 -3
  116. wagtail/admin/viewsets/chooser.py +1 -1
  117. wagtail/admin/viewsets/model.py +26 -1
  118. wagtail/admin/wagtail_hooks.py +2 -1
  119. wagtail/api/v2/filters.py +6 -0
  120. wagtail/api/v2/tests/test_documents.py +1 -1
  121. wagtail/api/v2/tests/test_images.py +1 -1
  122. wagtail/api/v2/tests/test_pages.py +11 -1
  123. wagtail/api/v2/utils.py +2 -2
  124. wagtail/blocks/base.py +35 -12
  125. wagtail/blocks/definition_lookup.py +85 -0
  126. wagtail/blocks/list_block.py +12 -0
  127. wagtail/blocks/migrations/migrate_operation.py +2 -0
  128. wagtail/blocks/stream_block.py +19 -0
  129. wagtail/blocks/struct_block.py +19 -0
  130. wagtail/contrib/forms/locale/en/LC_MESSAGES/django.po +1 -1
  131. wagtail/contrib/frontend_cache/backends/__init__.py +5 -0
  132. wagtail/contrib/frontend_cache/backends/azure.py +179 -0
  133. wagtail/contrib/frontend_cache/backends/base.py +28 -0
  134. wagtail/contrib/frontend_cache/backends/cloudflare.py +114 -0
  135. wagtail/contrib/frontend_cache/backends/cloudfront.py +99 -0
  136. wagtail/contrib/frontend_cache/backends/http.py +64 -0
  137. wagtail/contrib/frontend_cache/tests.py +59 -17
  138. wagtail/contrib/frontend_cache/utils.py +26 -8
  139. wagtail/contrib/redirects/filters.py +15 -1
  140. wagtail/contrib/redirects/locale/en/LC_MESSAGES/django.po +37 -72
  141. wagtail/contrib/redirects/models.py +6 -5
  142. wagtail/contrib/redirects/templates/wagtailredirects/edit.html +1 -38
  143. wagtail/contrib/redirects/tests/test_redirects.py +141 -1
  144. wagtail/contrib/redirects/urls.py +1 -2
  145. wagtail/contrib/redirects/views.py +39 -80
  146. wagtail/contrib/routable_page/models.py +6 -4
  147. wagtail/contrib/routable_page/tests.py +11 -0
  148. wagtail/contrib/search_promotions/locale/en/LC_MESSAGES/django.po +1 -1
  149. wagtail/contrib/settings/locale/en/LC_MESSAGES/django.po +4 -4
  150. wagtail/contrib/simple_translation/locale/en/LC_MESSAGES/django.po +5 -1
  151. wagtail/contrib/simple_translation/models.py +2 -1
  152. wagtail/contrib/styleguide/locale/en/LC_MESSAGES/django.po +7 -7
  153. wagtail/contrib/table_block/locale/en/LC_MESSAGES/django.po +1 -1
  154. wagtail/contrib/table_block/static/table_block/js/table.js +1 -1
  155. wagtail/contrib/typed_table_block/blocks.py +19 -0
  156. wagtail/contrib/typed_table_block/locale/en/LC_MESSAGES/django.po +10 -10
  157. wagtail/contrib/typed_table_block/static/typed_table_block/js/typed_table_block.js +1 -1
  158. wagtail/contrib/typed_table_block/tests.py +38 -0
  159. wagtail/coreutils.py +1 -1
  160. wagtail/documents/__init__.py +1 -1
  161. wagtail/documents/locale/en/LC_MESSAGES/django.po +8 -8
  162. wagtail/documents/locale/sl/LC_MESSAGES/django.mo +0 -0
  163. wagtail/documents/locale/sl/LC_MESSAGES/django.po +20 -0
  164. wagtail/documents/models.py +5 -1
  165. wagtail/documents/static/wagtaildocs/js/document-chooser-modal.js +1 -1
  166. wagtail/documents/static/wagtaildocs/js/document-chooser-telepath.js +1 -1
  167. wagtail/documents/static/wagtaildocs/js/document-chooser.js +1 -1
  168. wagtail/documents/tests/test_models.py +5 -1
  169. wagtail/embeds/apps.py +2 -0
  170. wagtail/embeds/embeds.py +12 -14
  171. wagtail/embeds/finders/__init__.py +2 -0
  172. wagtail/embeds/finders/facebook.py +17 -33
  173. wagtail/embeds/finders/instagram.py +19 -16
  174. wagtail/embeds/locale/en/LC_MESSAGES/django.po +1 -1
  175. wagtail/embeds/signal_handlers.py +13 -0
  176. wagtail/embeds/tests/test_embeds.py +7 -7
  177. wagtail/fields.py +58 -14
  178. wagtail/images/__init__.py +1 -1
  179. wagtail/images/locale/en/LC_MESSAGES/django.po +34 -34
  180. wagtail/images/locale/sl/LC_MESSAGES/django.mo +0 -0
  181. wagtail/images/locale/sl/LC_MESSAGES/django.po +20 -0
  182. wagtail/images/models.py +2 -0
  183. wagtail/images/static/wagtailimages/js/image-chooser-modal.js +1 -1
  184. wagtail/images/static/wagtailimages/js/image-chooser-telepath.js +1 -1
  185. wagtail/images/static/wagtailimages/js/image-chooser.js +1 -1
  186. wagtail/images/templates/wagtailimages/images/edit.html +4 -4
  187. wagtail/images/tests/test_admin_views.py +26 -2
  188. wagtail/images/views/chooser.py +6 -1
  189. wagtail/locale/en/LC_MESSAGES/django.po +84 -80
  190. wagtail/locales/locale/en/LC_MESSAGES/django.po +2 -2
  191. wagtail/locales/tests.py +16 -0
  192. wagtail/locales/wagtail_hooks.py +0 -9
  193. wagtail/migrations/0094_alter_page_locale.py +19 -0
  194. wagtail/models/__init__.py +11 -5
  195. wagtail/models/i18n.py +6 -1
  196. wagtail/project_template/requirements.txt +1 -1
  197. wagtail/search/locale/en/LC_MESSAGES/django.po +1 -1
  198. wagtail/signals.py +4 -0
  199. wagtail/sites/locale/en/LC_MESSAGES/django.po +2 -2
  200. wagtail/sites/tests.py +15 -0
  201. wagtail/sites/wagtail_hooks.py +0 -9
  202. wagtail/snippets/locale/en/LC_MESSAGES/django.po +9 -9
  203. wagtail/snippets/permissions.py +5 -3
  204. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser-telepath.js +1 -1
  205. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser.js +1 -1
  206. wagtail/snippets/templates/wagtailsnippets/snippets/action_menu/menu.html +1 -1
  207. wagtail/snippets/tests/test_snippets.py +78 -12
  208. wagtail/snippets/tests/test_viewset.py +22 -0
  209. wagtail/snippets/views/snippets.py +19 -14
  210. wagtail/snippets/wagtail_hooks.py +2 -10
  211. wagtail/templatetags/wagtailcore_tags.py +3 -0
  212. wagtail/test/dummy_external_storage.py +1 -1
  213. wagtail/test/i18n/migrations/0003_alter_clusterabletestmodel_locale_and_more.py +40 -0
  214. wagtail/test/routablepage/models.py +4 -0
  215. wagtail/test/snippets/migrations/0012_alter_translatablesnippet_locale.py +20 -0
  216. wagtail/test/testapp/migrations/0038_sociallink.py +52 -0
  217. wagtail/test/testapp/migrations/0039_alter_eventcategory_locale_and_more.py +45 -0
  218. wagtail/test/testapp/models.py +24 -0
  219. wagtail/test/testapp/views.py +1 -0
  220. wagtail/test/testapp/wagtail_hooks.py +9 -0
  221. wagtail/test/urls_multilang.py +6 -1
  222. wagtail/test/urls_multilang_non_root.py +11 -0
  223. wagtail/tests/streamfield_migrations/test_migrations.py +53 -12
  224. wagtail/tests/test_audit_log.py +72 -2
  225. wagtail/tests/test_blocks.py +103 -0
  226. wagtail/tests/test_signals.py +49 -2
  227. wagtail/tests/test_streamfield.py +153 -0
  228. wagtail/tests/test_utils.py +14 -0
  229. wagtail/tests/tests.py +5 -0
  230. wagtail/users/apps.py +1 -0
  231. wagtail/users/forms.py +7 -0
  232. wagtail/users/locale/en/LC_MESSAGES/django.po +55 -50
  233. wagtail/users/models.py +1 -0
  234. wagtail/users/templates/wagtailusers/groups/includes/formatted_permissions.html +3 -2
  235. wagtail/users/templatetags/wagtailusers_tags.py +9 -0
  236. wagtail/users/tests/__init__.py +7 -1
  237. wagtail/users/tests/test_admin_views.py +117 -32
  238. wagtail/users/views/groups.py +4 -0
  239. wagtail/users/views/users.py +58 -14
  240. wagtail/users/wagtail_hooks.py +7 -123
  241. wagtail/utils/utils.py +1 -0
  242. wagtail/utils/version.py +5 -2
  243. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/METADATA +3 -3
  244. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/RECORD +248 -222
  245. wagtail/admin/templates/wagtailadmin/reports/aging_pages.html +0 -58
  246. wagtail/admin/templates/wagtailadmin/reports/page_types_usage.html +0 -18
  247. wagtail/admin/templates/wagtailadmin/reports/site_history.html +0 -57
  248. wagtail/admin/templates/wagtailadmin/reports/workflow.html +0 -81
  249. wagtail/admin/templates/wagtailadmin/reports/workflow_tasks.html +0 -63
  250. wagtail/contrib/frontend_cache/backends.py +0 -400
  251. wagtail/contrib/redirects/templates/wagtailredirects/list.html +0 -43
  252. wagtail/contrib/redirects/templates/wagtailredirects/reports/redirects_report.html +0 -14
  253. wagtail/contrib/redirects/tests/test_reports_view.py +0 -82
  254. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/LICENSE +0 -0
  255. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/WHEEL +0 -0
  256. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/entry_points.txt +0 -0
  257. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/top_level.txt +0 -0
@@ -7,7 +7,7 @@ from wagtail.models import Page, PageViewRestriction
7
7
 
8
8
 
9
9
  def set_privacy(request, page_id):
10
- page = get_object_or_404(Page, id=page_id)
10
+ page = get_object_or_404(Page, id=page_id).specific_deferred
11
11
  page_perms = page.permissions_for_user(request.user)
12
12
  if not page_perms.can_set_view_restrictions():
13
13
  raise PermissionDenied
@@ -16,13 +16,17 @@ def set_privacy(request, page_id):
16
16
  restrictions = page.get_view_restrictions().order_by("page__depth")
17
17
  if restrictions:
18
18
  restriction = restrictions[0]
19
- restriction_exists_on_ancestor = restriction.page != page
19
+ restriction_exists_on_ancestor = restriction.page.id != page.id
20
20
  else:
21
21
  restriction = None
22
22
  restriction_exists_on_ancestor = False
23
23
 
24
24
  if request.method == "POST":
25
- form = PageViewRestrictionForm(request.POST, instance=restriction)
25
+ form = PageViewRestrictionForm(
26
+ request.POST,
27
+ instance=restriction,
28
+ private_page_options=page.private_page_options,
29
+ )
26
30
  if form.is_valid() and not restriction_exists_on_ancestor:
27
31
  if form.cleaned_data["restriction_type"] == PageViewRestriction.NONE:
28
32
  # remove any existing restriction
@@ -49,10 +53,15 @@ def set_privacy(request, page_id):
49
53
  else: # request is a GET
50
54
  if not restriction_exists_on_ancestor:
51
55
  if restriction:
52
- form = PageViewRestrictionForm(instance=restriction)
56
+ form = PageViewRestrictionForm(
57
+ instance=restriction, private_page_options=page.private_page_options
58
+ )
53
59
  else:
54
60
  # no current view restrictions on this page
55
- form = PageViewRestrictionForm(initial={"restriction_type": "none"})
61
+ form = PageViewRestrictionForm(
62
+ initial={"restriction_type": "none"},
63
+ private_page_options=page.private_page_options,
64
+ )
56
65
 
57
66
  if restriction_exists_on_ancestor:
58
67
  # display a message indicating that there is a restriction at ancestor level -
@@ -65,6 +74,12 @@ def set_privacy(request, page_id):
65
74
  "page_with_restriction": restriction.page,
66
75
  },
67
76
  )
77
+ elif len(page.private_page_options) == 0:
78
+ return render_modal_workflow(
79
+ request,
80
+ "wagtailadmin/page_privacy/no_privacy.html",
81
+ None,
82
+ )
68
83
  else:
69
84
  # no restriction set at ancestor level - can set restrictions here
70
85
  return render_modal_workflow(
@@ -1,4 +1,5 @@
1
1
  from django.contrib.admin.utils import quote
2
+ from django.contrib.contenttypes.models import ContentType
2
3
  from django.shortcuts import redirect
3
4
  from django.urls import reverse
4
5
  from django.utils.functional import cached_property
@@ -10,6 +11,7 @@ from django.views.generic import FormView
10
11
  from wagtail.admin.forms.pages import ParentChooserForm
11
12
  from wagtail.admin.views.generic.base import WagtailAdminTemplateMixin
12
13
  from wagtail.models import Page
14
+ from wagtail.permissions import page_permission_policy
13
15
 
14
16
 
15
17
  class ChooseParentView(WagtailAdminTemplateMixin, FormView):
@@ -18,6 +20,66 @@ class ChooseParentView(WagtailAdminTemplateMixin, FormView):
18
20
  index_url_name = None
19
21
  page_title = gettext_lazy("Choose parent")
20
22
 
23
+ def get_valid_parent_pages(self, user):
24
+ """
25
+ Identifies possible parent pages for the current user by first looking
26
+ at allowed_parent_page_models() on self.model to limit options to the
27
+ correct type of page, then checking permissions on those individual
28
+ pages to make sure we have permission to add a subpage to it.
29
+ """
30
+ # Get queryset of pages where this page type can be added
31
+ allowed_parent_page_content_types = list(
32
+ ContentType.objects.get_for_models(
33
+ *self.model.allowed_parent_page_models()
34
+ ).values()
35
+ )
36
+ allowed_parent_pages = Page.objects.filter(
37
+ content_type__in=allowed_parent_page_content_types
38
+ )
39
+
40
+ # Get queryset of pages where the user has permission to add subpages
41
+ if user.is_superuser:
42
+ pages_where_user_can_add = Page.objects.all()
43
+ else:
44
+ pages_where_user_can_add = Page.objects.none()
45
+
46
+ perms = {
47
+ perm
48
+ for perm in page_permission_policy.get_cached_permissions_for_user(user)
49
+ if perm.permission.codename == "add_page"
50
+ }
51
+
52
+ for perm in perms:
53
+ # user has add permission on any subpage of perm.page
54
+ # (including perm.page itself)
55
+ pages_where_user_can_add |= Page.objects.descendant_of(
56
+ perm.page, inclusive=True
57
+ )
58
+
59
+ # Combine them
60
+ return allowed_parent_pages & pages_where_user_can_add
61
+
62
+ def get(self, request, *args, **kwargs):
63
+ parents = self.get_valid_parent_pages(request.user)
64
+
65
+ # Only fetch the IDs for the first two parents to check if there's only
66
+ # one valid parent, and if so, redirect to the add page view with that
67
+ # parent pre-selected
68
+ parent_ids = parents.values_list("pk", flat=True)[:2]
69
+ if len(parent_ids) == 1:
70
+ parent_id = quote(parent_ids[0])
71
+ model_opts = self.model._meta
72
+ return redirect(
73
+ "wagtailadmin_pages:add",
74
+ model_opts.app_label,
75
+ model_opts.model_name,
76
+ parent_id,
77
+ )
78
+
79
+ # The page can be added in multiple places, so proceed with rendering
80
+ # the view's form so that the parent can be specified
81
+ return super().get(request, *args, **kwargs)
82
+
21
83
  def get_form(self):
22
84
  if self.request.method == "POST":
23
85
  return ParentChooserForm(self.model, self.request.user, self.request.POST)
@@ -17,7 +17,9 @@ from wagtail.actions.publish_page_revision import PublishPageRevisionAction
17
17
  from wagtail.admin import messages
18
18
  from wagtail.admin.action_menu import PageActionMenu
19
19
  from wagtail.admin.mail import send_notification
20
+ from wagtail.admin.models import EditingSession
20
21
  from wagtail.admin.ui.components import MediaContainer
22
+ from wagtail.admin.ui.editing_sessions import EditingSessionsModule
21
23
  from wagtail.admin.ui.side_panels import (
22
24
  ChecksSidePanel,
23
25
  CommentsSidePanel,
@@ -36,6 +38,7 @@ from wagtail.models import (
36
38
  Page,
37
39
  PageSubscription,
38
40
  WorkflowState,
41
+ get_default_page_content_type,
39
42
  )
40
43
  from wagtail.utils.timestamps import render_timestamp
41
44
 
@@ -878,6 +881,30 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
878
881
  side_panels.append(CommentsSidePanel(self.page, self.request))
879
882
  return MediaContainer(side_panels)
880
883
 
884
+ def get_editing_sessions(self):
885
+ EditingSession.cleanup()
886
+ content_type = get_default_page_content_type()
887
+ session = EditingSession.objects.create(
888
+ user=self.request.user,
889
+ content_type=content_type,
890
+ object_id=self.page.pk,
891
+ last_seen_at=timezone.now(),
892
+ )
893
+ return EditingSessionsModule(
894
+ session,
895
+ reverse(
896
+ "wagtailadmin_editing_sessions:ping",
897
+ args=("wagtailcore", "page", self.page.pk, session.id),
898
+ ),
899
+ reverse(
900
+ "wagtailadmin_editing_sessions:release",
901
+ args=(session.id,),
902
+ ),
903
+ [],
904
+ content_type,
905
+ self.page.latest_revision_id,
906
+ )
907
+
881
908
  def get_context_data(self, **kwargs):
882
909
  context = super().get_context_data(**kwargs)
883
910
  user_perms = self.page.permissions_for_user(self.request.user)
@@ -925,6 +952,7 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
925
952
  and user_perms.can_unlock(),
926
953
  "locale": self.locale,
927
954
  "media": media,
955
+ "editing_sessions": self.get_editing_sessions(),
928
956
  }
929
957
  )
930
958
 
@@ -1,6 +1,6 @@
1
1
  import django_filters
2
2
  from django.contrib.auth import get_user_model
3
- from django.core.exceptions import PermissionDenied, ValidationError
3
+ from django.core.exceptions import ValidationError
4
4
  from django.db.models import OuterRef, Subquery
5
5
  from django.utils.translation import gettext_lazy as _
6
6
 
@@ -29,10 +29,12 @@ class AgingPagesReportFilterSet(WagtailFilterSet):
29
29
 
30
30
 
31
31
  class AgingPagesView(PageReportView):
32
- template_name = "wagtailadmin/reports/aging_pages.html"
33
- title = _("Aging pages")
32
+ results_template_name = "wagtailadmin/reports/aging_pages_results.html"
33
+ page_title = _("Aging pages")
34
34
  header_icon = "time"
35
35
  filterset_class = AgingPagesReportFilterSet
36
+ index_url_name = "wagtailadmin_reports:aging_pages"
37
+ index_results_url_name = "wagtailadmin_reports:aging_pages_results"
36
38
  export_headings = {
37
39
  "status_string": _("Status"),
38
40
  "last_published_at": _("Last published at"),
@@ -46,6 +48,7 @@ class AgingPagesView(PageReportView):
46
48
  "last_published_by_user",
47
49
  "content_type",
48
50
  ]
51
+ any_permission_required = ["add", "change", "publish"]
49
52
 
50
53
  def __init__(self, **kwargs):
51
54
  super().__init__(**kwargs)
@@ -110,10 +113,3 @@ class AgingPagesView(PageReportView):
110
113
  )
111
114
 
112
115
  return super().get_queryset()
113
-
114
- def dispatch(self, request, *args, **kwargs):
115
- if not page_permission_policy.user_has_any_permission(
116
- request.user, ["add", "change", "publish"]
117
- ):
118
- raise PermissionDenied
119
- return super().dispatch(request, *args, **kwargs)
@@ -2,7 +2,6 @@ import datetime
2
2
  from collections import defaultdict
3
3
 
4
4
  import django_filters
5
- from django import forms
6
5
  from django.contrib.auth import get_user_model
7
6
  from django.contrib.contenttypes.models import ContentType
8
7
  from django.db.models import IntegerField, Value
@@ -12,9 +11,8 @@ from django.utils.translation import gettext_lazy as _
12
11
  from wagtail.admin.admin_url_finder import AdminURLFinder
13
12
  from wagtail.admin.filters import (
14
13
  ContentTypeFilter,
15
- DateRangePickerWidget,
16
- WagtailFilterSet,
17
14
  )
15
+ from wagtail.admin.views.pages.history import PageHistoryFilterSet
18
16
  from wagtail.coreutils import get_content_type_label
19
17
  from wagtail.log_actions import registry as log_action_registry
20
18
  from wagtail.models import PageLogEntry
@@ -52,62 +50,35 @@ def get_actions_for_filter(user):
52
50
  ]
53
51
 
54
52
 
55
- class SiteHistoryReportFilterSet(WagtailFilterSet):
56
- action = django_filters.ChoiceFilter(
57
- label=_("Action"),
58
- # choices are set dynamically in __init__()
59
- )
60
- hide_commenting_actions = django_filters.BooleanFilter(
61
- label=_("Hide commenting actions"),
62
- method="filter_hide_commenting_actions",
63
- widget=forms.CheckboxInput,
64
- )
65
- timestamp = django_filters.DateFromToRangeFilter(
66
- label=_("Date"), widget=DateRangePickerWidget
67
- )
53
+ class SiteHistoryReportFilterSet(PageHistoryFilterSet):
68
54
  label = django_filters.CharFilter(label=_("Name"), lookup_expr="icontains")
69
- user = django_filters.ModelChoiceFilter(
70
- label=_("User"),
71
- field_name="user",
72
- queryset=lambda request: get_users_for_filter(request.user),
73
- )
74
55
  object_type = ContentTypeFilter(
75
56
  label=_("Type"),
76
57
  method="filter_object_type",
77
58
  queryset=lambda request: get_content_types_for_filter(request.user),
78
59
  )
79
60
 
80
- def filter_hide_commenting_actions(self, queryset, name, value):
81
- if value:
82
- queryset = queryset.exclude(action__startswith="wagtail.comments")
83
- return queryset
84
-
85
61
  def filter_object_type(self, queryset, name, value):
86
62
  return queryset.filter_on_content_type(value)
87
63
 
64
+ def get_action_choices(self):
65
+ return get_actions_for_filter(self.request.user)
66
+
67
+ def get_user_choices(self):
68
+ return get_users_for_filter(self.request.user)
69
+
88
70
  class Meta:
89
71
  model = PageLogEntry
90
- fields = [
91
- "object_type",
92
- "label",
93
- "action",
94
- "user",
95
- "timestamp",
96
- "hide_commenting_actions",
97
- ]
98
-
99
- def __init__(self, *args, **kwargs):
100
- super().__init__(*args, **kwargs)
101
- self.filters["action"].extra["choices"] = get_actions_for_filter(
102
- self.request.user
103
- )
72
+ fields = []
104
73
 
105
74
 
106
75
  class LogEntriesView(ReportView):
107
- template_name = "wagtailadmin/reports/site_history.html"
108
- title = _("Site history")
76
+ results_template_name = "wagtailadmin/reports/site_history_results.html"
77
+ page_title = _("Site history")
109
78
  header_icon = "history"
110
79
  filterset_class = SiteHistoryReportFilterSet
80
+ index_url_name = "wagtailadmin_reports:site_history"
81
+ index_results_url_name = "wagtailadmin_reports:site_history_results"
111
82
 
112
83
  export_headings = {
113
84
  "object_id": _("ID"),
@@ -1,13 +1,38 @@
1
+ from warnings import warn
2
+
1
3
  from django.utils.translation import gettext_lazy as _
2
4
 
3
- from wagtail.admin.views.generic.base import BaseListingView
5
+ from wagtail.admin.views.generic import BaseListingView, PermissionCheckedMixin
4
6
  from wagtail.admin.views.mixins import SpreadsheetExportMixin
7
+ from wagtail.permissions import page_permission_policy
8
+ from wagtail.utils.deprecation import RemovedInWagtail70Warning
5
9
 
6
10
 
7
- class ReportView(SpreadsheetExportMixin, BaseListingView):
11
+ class ReportView(SpreadsheetExportMixin, PermissionCheckedMixin, BaseListingView):
8
12
  template_name = "wagtailadmin/reports/base_report.html"
13
+ results_template_name = "wagtailadmin/reports/base_report_results.html"
9
14
  title = ""
10
15
  paginate_by = 50
16
+ _show_breadcrumbs = True
17
+
18
+ def get_breadcrumbs_items(self):
19
+ return super().get_breadcrumbs_items() + [
20
+ {"url": "", "label": self.get_page_title()}
21
+ ]
22
+
23
+ def get_page_title(self):
24
+ if self.page_title:
25
+ return self.page_title
26
+ # WagtailAdminTemplateMixin uses `page_title`, but the documented approach
27
+ # for ReportView used `title`, so we need to support both during the
28
+ # deprecation period. When `title` is removed, this and the `get_context_data`
29
+ # overrides can be removed.
30
+ warn(
31
+ f"The `title` attribute in `{self.__class__.__name__}` (a `ReportView` subclass) "
32
+ "is deprecated. Use `page_title` instead.",
33
+ RemovedInWagtail70Warning,
34
+ )
35
+ return self.title
11
36
 
12
37
  def get_filtered_queryset(self):
13
38
  return self.filter_queryset(self.get_queryset())
@@ -28,7 +53,7 @@ class ReportView(SpreadsheetExportMixin, BaseListingView):
28
53
 
29
54
  def get_context_data(self, *args, **kwargs):
30
55
  context = super().get_context_data(*args, **kwargs)
31
- context["title"] = self.title
56
+ context["title"] = self.get_page_title()
32
57
  return context
33
58
 
34
59
  def render_to_response(self, context, **response_kwargs):
@@ -40,7 +65,7 @@ class ReportView(SpreadsheetExportMixin, BaseListingView):
40
65
 
41
66
 
42
67
  class PageReportView(ReportView):
43
- template_name = "wagtailadmin/reports/base_page_report.html"
68
+ results_template_name = "wagtailadmin/reports/base_page_report_results.html"
44
69
  export_headings = {
45
70
  "latest_revision_created_at": _("Updated"),
46
71
  "status_string": _("Status"),
@@ -52,3 +77,5 @@ class PageReportView(ReportView):
52
77
  "status_string",
53
78
  "content_type.model_class._meta.verbose_name.title",
54
79
  ]
80
+ context_object_name = "pages"
81
+ permission_policy = page_permission_policy
@@ -3,7 +3,6 @@ import datetime
3
3
  import django_filters
4
4
  from django.conf import settings
5
5
  from django.contrib.auth import get_user_model
6
- from django.core.exceptions import PermissionDenied
7
6
  from django.utils.translation import gettext_lazy as _
8
7
 
9
8
  from wagtail.admin.filters import DateRangePickerWidget, WagtailFilterSet
@@ -34,14 +33,17 @@ class LockedPagesReportFilterSet(WagtailFilterSet):
34
33
 
35
34
 
36
35
  class LockedPagesView(PageReportView):
37
- template_name = "wagtailadmin/reports/locked_pages.html"
38
- title = _("Locked pages")
36
+ results_template_name = "wagtailadmin/reports/locked_pages_results.html"
37
+ page_title = _("Locked pages")
39
38
  header_icon = "lock"
40
39
  list_export = PageReportView.list_export + [
41
40
  "locked_at",
42
41
  "locked_by",
43
42
  ]
44
43
  filterset_class = LockedPagesReportFilterSet
44
+ index_url_name = "wagtailadmin_reports:locked_pages"
45
+ index_results_url_name = "wagtailadmin_reports:locked_pages_results"
46
+ permission_required = "unlock"
45
47
 
46
48
  def get_filename(self):
47
49
  return "locked-pages-report-{}".format(
@@ -65,8 +67,3 @@ class LockedPagesView(PageReportView):
65
67
 
66
68
  self.queryset = pages
67
69
  return super().get_queryset()
68
-
69
- def dispatch(self, request, *args, **kwargs):
70
- if not page_permission_policy.user_has_permission(request.user, "unlock"):
71
- raise PermissionDenied
72
- return super().dispatch(request, *args, **kwargs)
@@ -1,6 +1,5 @@
1
1
  import django_filters
2
2
  from django.conf import settings
3
- from django.core.exceptions import PermissionDenied
4
3
  from django.db.models import Count, F, OuterRef, Q, Subquery
5
4
  from django.utils.translation import gettext_lazy as _
6
5
 
@@ -96,10 +95,14 @@ class PageTypesUsageReportFilterSet(WagtailFilterSet):
96
95
 
97
96
 
98
97
  class PageTypesUsageReportView(ReportView):
99
- template_name = "wagtailadmin/reports/page_types_usage.html"
100
- title = _("Page types usage")
98
+ results_template_name = "wagtailadmin/reports/page_types_usage_results.html"
99
+ page_title = _("Page types usage")
101
100
  header_icon = "doc-empty-inverse"
102
101
  filterset_class = PageTypesUsageReportFilterSet
102
+ index_url_name = "wagtailadmin_reports:page_types_usage"
103
+ index_results_url_name = "wagtailadmin_reports:page_types_usage_results"
104
+ permission_policy = page_permission_policy
105
+ any_permission_required = ["add", "change", "publish"]
103
106
 
104
107
  def __init__(self, **kwargs):
105
108
  super().__init__(**kwargs)
@@ -143,10 +146,3 @@ class PageTypesUsageReportView(ReportView):
143
146
  queryset = queryset.order_by("-count", "app_label", "model")
144
147
 
145
148
  return queryset
146
-
147
- def dispatch(self, request, *args, **kwargs):
148
- if not page_permission_policy.user_has_any_permission(
149
- request.user, ["add", "change", "publish"]
150
- ):
151
- raise PermissionDenied
152
- return super().dispatch(request, *args, **kwargs)
@@ -4,9 +4,11 @@ import django_filters
4
4
  from django import forms
5
5
  from django.contrib.auth import get_user_model
6
6
  from django.contrib.contenttypes.models import ContentType
7
- from django.core.exceptions import PermissionDenied
8
7
  from django.db.models import CharField, Q
9
8
  from django.db.models.functions import Cast
9
+ from django.urls import reverse
10
+ from django.utils.functional import cached_property
11
+ from django.utils.translation import gettext
10
12
  from django.utils.translation import gettext_lazy as _
11
13
 
12
14
  from wagtail.admin.filters import (
@@ -15,6 +17,7 @@ from wagtail.admin.filters import (
15
17
  WagtailFilterSet,
16
18
  )
17
19
  from wagtail.admin.utils import get_latest_str
20
+ from wagtail.admin.widgets.button import HeaderButton
18
21
  from wagtail.coreutils import get_content_type_label
19
22
  from wagtail.models import (
20
23
  Task,
@@ -135,10 +138,14 @@ class WorkflowTasksReportFilterSet(WagtailFilterSet):
135
138
 
136
139
 
137
140
  class WorkflowView(ReportView):
138
- template_name = "wagtailadmin/reports/workflow.html"
139
- title = _("Workflows")
141
+ results_template_name = "wagtailadmin/reports/workflow_results.html"
142
+ page_title = _("Workflows")
140
143
  header_icon = "tasks"
141
144
  filterset_class = WorkflowReportFilterSet
145
+ index_url_name = "wagtailadmin_reports:workflow"
146
+ index_results_url_name = "wagtailadmin_reports:workflow_results"
147
+ permission_policy = page_permission_policy
148
+ any_permission_required = ["add", "change", "publish"]
142
149
 
143
150
  export_headings = {
144
151
  "content_object.pk": _("Page/Snippet ID"),
@@ -170,6 +177,16 @@ class WorkflowView(ReportView):
170
177
  self.FORMAT_XLSX: get_content_type_label,
171
178
  }
172
179
 
180
+ @cached_property
181
+ def header_buttons(self):
182
+ return [
183
+ HeaderButton(
184
+ gettext("By task"),
185
+ reverse("wagtailadmin_reports:workflow_tasks"),
186
+ icon_name="thumbtack",
187
+ )
188
+ ]
189
+
173
190
  def get_title(self, content_object):
174
191
  return get_latest_str(content_object)
175
192
 
@@ -198,19 +215,16 @@ class WorkflowView(ReportView):
198
215
  def decorate_paginated_queryset(self, object_list):
199
216
  return [obj for obj in object_list if obj.content_object]
200
217
 
201
- def dispatch(self, request, *args, **kwargs):
202
- if not page_permission_policy.user_has_any_permission(
203
- request.user, ["add", "change", "publish"]
204
- ):
205
- raise PermissionDenied
206
- return super().dispatch(request, *args, **kwargs)
207
-
208
218
 
209
219
  class WorkflowTasksView(ReportView):
210
- template_name = "wagtailadmin/reports/workflow_tasks.html"
211
- title = _("Workflow tasks")
220
+ results_template_name = "wagtailadmin/reports/workflow_tasks_results.html"
221
+ page_title = _("Workflow tasks")
212
222
  header_icon = "thumbtack"
213
223
  filterset_class = WorkflowTasksReportFilterSet
224
+ index_url_name = "wagtailadmin_reports:workflow_tasks"
225
+ index_results_url_name = "wagtailadmin_reports:workflow_tasks_results"
226
+ permission_policy = page_permission_policy
227
+ any_permission_required = ["add", "change", "publish"]
214
228
 
215
229
  export_headings = {
216
230
  "workflow_state.content_object.pk": _("Page/Snippet ID"),
@@ -244,6 +258,16 @@ class WorkflowTasksView(ReportView):
244
258
  self.FORMAT_XLSX: get_content_type_label,
245
259
  }
246
260
 
261
+ @cached_property
262
+ def header_buttons(self):
263
+ return [
264
+ HeaderButton(
265
+ gettext("By workflow"),
266
+ reverse("wagtailadmin_reports:workflow"),
267
+ icon_name="tasks",
268
+ )
269
+ ]
270
+
247
271
  def get_title(self, content_object):
248
272
  return get_latest_str(content_object)
249
273
 
@@ -17,6 +17,10 @@ class ViewSet(WagtailMenuRegisterable):
17
17
  For more information on how to use this class, see :ref:`using_base_viewset`.
18
18
  """
19
19
 
20
+ #: A special value that, when passed in a kwargs dict to construct a view, indicates that
21
+ #: the attribute should not be written and should instead be left as the view's initial value
22
+ UNDEFINED = object()
23
+
20
24
  #: A name for this viewset, used as the default URL prefix and namespace.
21
25
  name = None
22
26
 
@@ -42,12 +46,13 @@ class ViewSet(WagtailMenuRegisterable):
42
46
  in addition to any kwargs passed to this method. Items from get_common_view_kwargs will be
43
47
  filtered to only include those that are valid for the given view_class.
44
48
  """
49
+ merged_kwargs = self.get_common_view_kwargs()
50
+ merged_kwargs.update(kwargs)
45
51
  filtered_kwargs = {
46
52
  key: value
47
- for key, value in self.get_common_view_kwargs().items()
48
- if hasattr(view_class, key)
53
+ for key, value in merged_kwargs.items()
54
+ if hasattr(view_class, key) and value is not self.UNDEFINED
49
55
  }
50
- filtered_kwargs.update(kwargs)
51
56
  return view_class.as_view(**filtered_kwargs)
52
57
 
53
58
  def inject_view_methods(self, view_class, method_names):
@@ -29,7 +29,7 @@ class ChooserViewSet(ViewSet):
29
29
  ) #: Label for the 'choose' button in the chooser widget, when an item has already been chosen
30
30
  edit_item_text = _("Edit") #: Label for the 'edit' button in the chooser widget
31
31
 
32
- per_page = 10 #: Number of results to show per page
32
+ per_page = ViewSet.UNDEFINED #: Number of results to show per page
33
33
 
34
34
  #: A list of URL query parameters that should be passed on unmodified as part of any links or
35
35
  #: form submissions within the chooser modal workflow.
@@ -1,11 +1,15 @@
1
1
  from warnings import warn
2
2
 
3
+ from django.contrib.auth import get_permission_codename
4
+ from django.contrib.auth.models import Permission
5
+ from django.contrib.contenttypes.models import ContentType
3
6
  from django.core.exceptions import ImproperlyConfigured
4
7
  from django.forms.models import modelform_factory
5
8
  from django.shortcuts import redirect
6
9
  from django.urls import path
7
10
  from django.utils.functional import cached_property
8
11
 
12
+ from wagtail import hooks
9
13
  from wagtail.admin.admin_url_finder import (
10
14
  ModelAdminURLFinder,
11
15
  register_admin_url_finder,
@@ -518,7 +522,7 @@ class ModelViewSet(ViewSet):
518
522
 
519
523
  def is_shown(_self, request):
520
524
  return self.permission_policy.user_has_any_permission(
521
- request.user, ("add", "change", "delete")
525
+ request.user, self.index_view_class.any_permission_required
522
526
  )
523
527
 
524
528
  return type(
@@ -616,6 +620,26 @@ class ModelViewSet(ViewSet):
616
620
  if self.add_to_reference_index:
617
621
  ReferenceIndex.register_model(self.model)
618
622
 
623
+ def get_permissions_to_register(self):
624
+ """
625
+ Returns a queryset of :class:`~django.contrib.auth.models.Permission`
626
+ objects to be registered with the :ref:`register_permissions` hook. By
627
+ default, it returns all permissions for the model if
628
+ :attr:`inspect_view_enabled` is set to ``True``. Otherwise, the "view"
629
+ permission is excluded.
630
+ """
631
+ content_type = ContentType.objects.get_for_model(self.model)
632
+ permissions = Permission.objects.filter(content_type=content_type)
633
+ # Only register the "view" permission if the inspect view is enabled
634
+ if not self.inspect_view_enabled:
635
+ permissions = permissions.exclude(
636
+ codename=get_permission_codename("view", self.model_opts)
637
+ )
638
+ return permissions
639
+
640
+ def register_permissions(self):
641
+ hooks.register("register_permissions", self.get_permissions_to_register)
642
+
619
643
  def get_urlpatterns(self):
620
644
  urlpatterns = [
621
645
  path("", self.index_view, name="index"),
@@ -657,6 +681,7 @@ class ModelViewSet(ViewSet):
657
681
  super().on_register()
658
682
  self.register_admin_url_finder()
659
683
  self.register_reference_index()
684
+ self.register_permissions()
660
685
 
661
686
 
662
687
  class ModelViewSetGroup(ViewSetGroup):
@@ -64,6 +64,7 @@ from wagtail.templatetags.wagtailcore_tags import (
64
64
  wagtail_feature_release_editor_guide_link,
65
65
  wagtail_feature_release_whats_new_link,
66
66
  )
67
+ from wagtail.utils.version import get_main_version
67
68
  from wagtail.whitelist import allow_without_attributes, attribute_rule, check_url
68
69
 
69
70
 
@@ -975,7 +976,7 @@ def register_reports_menu():
975
976
 
976
977
  @hooks.register("register_help_menu_item")
977
978
  def register_whats_new_in_wagtail_version_menu_item():
978
- version = "6.1"
979
+ version = get_main_version(include_patch=False)
979
980
  return DismissibleMenuItem(
980
981
  _("What's new in Wagtail %(version)s") % {"version": version},
981
982
  wagtail_feature_release_whats_new_link(),