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
@@ -1,30 +1,21 @@
1
- from django.conf import settings
2
- from django.contrib.contenttypes.models import ContentType
3
1
  from django.core.exceptions import PermissionDenied
4
2
  from django.shortcuts import get_object_or_404, redirect
5
3
  from django.template.loader import render_to_string
6
- from django.template.response import TemplateResponse
7
4
  from django.urls import reverse
8
5
  from django.utils.decorators import method_decorator
9
6
  from django.utils.safestring import mark_safe
10
7
  from django.utils.translation import gettext as _
11
- from django.utils.translation import gettext_lazy
12
8
 
13
9
  from wagtail.admin import messages
14
10
  from wagtail.admin.action_menu import PageActionMenu
15
11
  from wagtail.admin.auth import user_has_any_page_permission, user_passes_test
16
- from wagtail.admin.ui.components import MediaContainer
17
- from wagtail.admin.ui.side_panels import (
18
- ChecksSidePanel,
19
- CommentsSidePanel,
20
- PageStatusSidePanel,
21
- PreviewSidePanel,
22
- )
23
12
  from wagtail.admin.views.generic.models import (
24
13
  RevisionsCompareView,
25
14
  RevisionsUnscheduleView,
26
15
  )
27
16
  from wagtail.admin.views.generic.preview import PreviewRevision
17
+ from wagtail.admin.views.pages.edit import EditView
18
+ from wagtail.admin.views.pages.utils import GenericPageBreadcrumbsMixin
28
19
  from wagtail.models import Page
29
20
  from wagtail.utils.timestamps import render_timestamp
30
21
 
@@ -33,116 +24,55 @@ def revisions_index(request, page_id):
33
24
  return redirect("wagtailadmin_pages:history", page_id)
34
25
 
35
26
 
36
- def revisions_revert(request, page_id, revision_id):
37
- # TODO: refactor this into a class-based view that extends the EditView
38
- page = get_object_or_404(Page, id=page_id).specific
39
- page_perms = page.permissions_for_user(request.user)
40
- if not page_perms.can_edit():
41
- raise PermissionDenied
27
+ class RevisionsRevertView(EditView):
28
+ revisions_revert_url_name = "wagtailadmin_pages:revisions_revert"
42
29
 
43
- revision = get_object_or_404(page.revisions, id=revision_id)
44
- revision_page = revision.as_object()
30
+ def get_action_menu(self):
31
+ return PageActionMenu(
32
+ self.request,
33
+ view="revisions_revert",
34
+ is_revision=True,
35
+ page=self.page,
36
+ lock=self.lock,
37
+ locked_for_user=self.locked_for_user,
38
+ )
45
39
 
46
- scheduled_page = page.get_scheduled_revision_as_object()
40
+ def get(self, request, *args, **kwargs):
41
+ self._add_warning_message()
42
+ return super().get(request, *args, **kwargs)
47
43
 
48
- content_type = ContentType.objects.get_for_model(page)
49
- page_class = content_type.model_class()
44
+ def _add_warning_message(self):
45
+ messages.warning(self.request, self.get_warning_message())
50
46
 
51
- if getattr(settings, "WAGTAIL_I18N_ENABLED", False):
52
- locale = page.locale
53
- translations = [
54
- {
55
- "locale": translation.locale,
56
- "url": reverse("wagtailadmin_pages:edit", args=[translation.id]),
57
- }
58
- for translation in page.get_translations()
59
- .only("id", "locale", "depth")
60
- .select_related("locale")
61
- if translation.permissions_for_user(request.user).can_edit()
62
- ]
63
- else:
64
- locale = None
65
- translations = []
66
-
67
- edit_handler = page_class.get_edit_handler()
68
- form_class = edit_handler.get_form_class()
69
-
70
- form = form_class(instance=revision_page, for_user=request.user)
71
- edit_handler = edit_handler.get_bound_panel(
72
- instance=revision_page, request=request, form=form
73
- )
74
-
75
- preview_url = reverse("wagtailadmin_pages:preview_on_edit", args=[page.id])
76
- lock = page.get_lock()
77
-
78
- action_menu = PageActionMenu(
79
- request,
80
- view="revisions_revert",
81
- is_revision=True,
82
- page=page,
83
- lock=lock,
84
- locked_for_user=lock is not None and lock.for_user(request.user),
85
- )
86
- side_panels = [
87
- PageStatusSidePanel(
88
- revision_page,
89
- request,
90
- show_schedule_publishing_toggle=form.show_schedule_publishing_toggle,
91
- live_object=page,
92
- scheduled_object=scheduled_page,
93
- locale=locale,
94
- translations=translations,
95
- ),
96
- ]
97
- if page.is_previewable():
98
- side_panels.append(PreviewSidePanel(page, request, preview_url=preview_url))
99
- side_panels.append(ChecksSidePanel(page, request))
100
- if form.show_comments_toggle:
101
- side_panels.append(CommentsSidePanel(page, request))
102
- side_panels = MediaContainer(side_panels)
103
-
104
- media = MediaContainer([edit_handler, form, action_menu, side_panels]).media
105
-
106
- user_avatar = render_to_string(
107
- "wagtailadmin/shared/user_avatar.html", {"user": revision.user}
108
- )
109
-
110
- messages.warning(
111
- request,
112
- mark_safe(
47
+ def get_object(self):
48
+ return self.previous_revision.as_object()
49
+
50
+ def get_revisions_revert_url(self):
51
+ return reverse(
52
+ self.revisions_revert_url_name,
53
+ args=[self.page.pk, self.revision_id],
54
+ )
55
+
56
+ def get_warning_message(self):
57
+ user_avatar = render_to_string(
58
+ "wagtailadmin/shared/user_avatar.html",
59
+ {"user": self.previous_revision.user},
60
+ )
61
+
62
+ return mark_safe(
113
63
  _(
114
64
  "You are viewing a previous version of this page from <b>%(created_at)s</b> by %(user)s"
115
65
  )
116
66
  % {
117
- "created_at": render_timestamp(revision.created_at),
67
+ "created_at": render_timestamp(self.previous_revision.created_at),
118
68
  "user": user_avatar,
119
69
  }
120
- ),
121
- )
122
-
123
- page_title = _("Editing %(page_type)s") % {
124
- "page_type": page_class.get_verbose_name()
125
- }
126
- page_subtitle = page.get_admin_display_title()
127
- header_title = f"{page_title}: {page_subtitle}"
128
-
129
- return TemplateResponse(
130
- request,
131
- "wagtailadmin/pages/edit.html",
132
- {
133
- "page": page,
134
- "revision": revision,
135
- "is_revision": True,
136
- "content_type": content_type,
137
- "edit_handler": edit_handler,
138
- "errors_debug": None,
139
- "action_menu": action_menu,
140
- "side_panels": side_panels,
141
- "header_title": header_title,
142
- "form": form, # Used in unit tests
143
- "media": media,
144
- },
145
- )
70
+ )
71
+
72
+ def get_context_data(self, **kwargs):
73
+ context = super().get_context_data(**kwargs)
74
+ context["action_url"] = self.get_revisions_revert_url()
75
+ return context
146
76
 
147
77
 
148
78
  @method_decorator(user_passes_test(user_has_any_page_permission), name="dispatch")
@@ -163,12 +93,11 @@ class RevisionsView(PreviewRevision):
163
93
  return page
164
94
 
165
95
 
166
- class RevisionsCompare(RevisionsCompareView):
167
- history_label = gettext_lazy("Page history")
168
- edit_label = gettext_lazy("Edit this page")
96
+ class RevisionsCompare(GenericPageBreadcrumbsMixin, RevisionsCompareView):
169
97
  history_url_name = "wagtailadmin_pages:history"
170
98
  edit_url_name = "wagtailadmin_pages:edit"
171
99
  header_icon = "doc-empty-inverse"
100
+ breadcrumbs_items_to_take = 2
172
101
 
173
102
  @method_decorator(user_passes_test(user_has_any_page_permission))
174
103
  def dispatch(self, request, *args, **kwargs):
@@ -50,7 +50,6 @@ class GenericPageBreadcrumbsMixin:
50
50
  item of the generic view's generated breadcrumbs items.
51
51
  """
52
52
 
53
- _show_breadcrumbs = True
54
53
  breadcrumbs_items_to_take = 1
55
54
 
56
55
  @cached_property
@@ -19,8 +19,12 @@ def autocomplete(request, app_name=None, model_name=None):
19
19
 
20
20
  term = request.GET.get("term", None)
21
21
  if term:
22
- tags = tag_model.objects.filter(name__istartswith=term).order_by("name")
22
+ tags = (
23
+ tag_model.objects.filter(name__istartswith=term)
24
+ .order_by("name")
25
+ .values_list("name", flat=True)[:10]
26
+ )
23
27
  else:
24
28
  tags = tag_model.objects.none()
25
29
 
26
- return JsonResponse([tag.name for tag in tags], safe=False)
30
+ return JsonResponse(list(tags), safe=False)
@@ -175,7 +175,6 @@ class Create(CreateView):
175
175
  index_url_name = "wagtailadmin_workflows:index"
176
176
  header_icon = "tasks"
177
177
  edit_handler = None
178
- _show_breadcrumbs = True
179
178
 
180
179
  def get_edit_handler(self):
181
180
  if not self.edit_handler:
@@ -263,7 +262,6 @@ class Edit(EditView):
263
262
  header_more_buttons = []
264
263
  edit_handler = None
265
264
  MAX_PAGES = 5
266
- _show_breadcrumbs = True
267
265
 
268
266
  def get_edit_handler(self):
269
267
  if not self.edit_handler:
@@ -304,7 +302,6 @@ class Edit(EditView):
304
302
  pages_formset = self.get_pages_formset()
305
303
  context["edit_handler"] = bound_panel
306
304
  context["pages"] = self.get_paginated_pages()
307
- context["pages_formset"] = pages_formset
308
305
  context["has_workflow_enabled_models"] = bool(get_workflow_enabled_models())
309
306
  context["content_type_form"] = self.get_content_type_form()
310
307
  context["can_disable"] = (
@@ -315,7 +312,14 @@ class Edit(EditView):
315
312
  self.permission_policy is None
316
313
  or self.permission_policy.user_has_permission(self.request.user, "add")
317
314
  ) and not self.object.active
318
- context["media"] = bound_panel.media + form.media + pages_formset.media
315
+ context["media"] = bound_panel.media + form.media
316
+
317
+ # Only add the pages_formset if the workflow is active
318
+ if self.object.active:
319
+ pages_formset = self.get_pages_formset()
320
+ context["pages_formset"] = pages_formset
321
+ context["media"] += pages_formset.media
322
+
319
323
  return context
320
324
 
321
325
  @property
@@ -641,7 +645,6 @@ class CreateTask(CreateView):
641
645
  edit_url_name = "wagtailadmin_workflows:edit_task"
642
646
  index_url_name = "wagtailadmin_workflows:task_index"
643
647
  header_icon = "thumbtack"
644
- _show_breadcrumbs = True
645
648
 
646
649
  @cached_property
647
650
  def model(self):
@@ -704,7 +707,6 @@ class EditTask(EditView):
704
707
  enable_url_name = "wagtailadmin_workflows:enable_task"
705
708
  header_icon = "thumbtack"
706
709
  header_more_buttons = []
707
- _show_breadcrumbs = True
708
710
 
709
711
  @cached_property
710
712
  def model(self):
@@ -63,9 +63,6 @@ class ModelViewSet(ViewSet):
63
63
  #: The view class to use for the inspect view; must be a subclass of ``wagtail.admin.views.generic.InspectView``.
64
64
  inspect_view_class = generic.InspectView
65
65
 
66
- # Breadcrumbs can be turned off until we have a design that can be consistently applied
67
- _show_breadcrumbs = True
68
-
69
66
  #: The prefix of template names to look for when rendering the admin views.
70
67
  template_prefix = ""
71
68
 
@@ -136,7 +133,6 @@ class ModelViewSet(ViewSet):
136
133
  "edit_url_name": self.get_url_name("edit"),
137
134
  "delete_url_name": self.get_url_name("delete"),
138
135
  "header_icon": self.icon,
139
- "_show_breadcrumbs": self._show_breadcrumbs,
140
136
  **kwargs,
141
137
  }
142
138
  )
@@ -31,7 +31,6 @@ class PageListingViewSet(ViewSet):
31
31
  def get_common_view_kwargs(self, **kwargs):
32
32
  return super().get_common_view_kwargs(
33
33
  **{
34
- "_show_breadcrumbs": True,
35
34
  "header_icon": self.icon,
36
35
  "model": self.model,
37
36
  "index_url_name": self.get_url_name("index"),
@@ -48,6 +48,7 @@ class AdminTagWidget(TagWidget):
48
48
  help_text = _("Tags can only consist of a single word, no spaces allowed.")
49
49
 
50
50
  context["widget"]["help_text"] = help_text
51
+ context["widget"]["attrs"]["data-w-tag-delay-value"] = 200
51
52
  context["widget"]["attrs"]["data-w-tag-url-value"] = autocomplete_url
52
53
  context["widget"]["attrs"]["data-w-tag-options-value"] = json.dumps(
53
54
  {
@@ -605,11 +605,13 @@ class TestDocumentCacheInvalidation(TestCase):
605
605
  signal_handlers.unregister_signal_handlers()
606
606
 
607
607
  def test_resave_document_purges(self, purge):
608
- get_document_model().objects.get(id=5).save()
608
+ with self.captureOnCommitCallbacks(execute=True):
609
+ get_document_model().objects.get(id=5).save()
609
610
 
610
611
  purge.assert_any_call("http://api.example.com/api/main/documents/5/")
611
612
 
612
613
  def test_delete_document_purges(self, purge):
613
- get_document_model().objects.get(id=5).delete()
614
+ with self.captureOnCommitCallbacks(execute=True):
615
+ get_document_model().objects.get(id=5).delete()
614
616
 
615
617
  purge.assert_any_call("http://api.example.com/api/main/documents/5/")
@@ -597,11 +597,13 @@ class TestImageCacheInvalidation(TestCase):
597
597
  signal_handlers.unregister_signal_handlers()
598
598
 
599
599
  def test_resave_image_purges(self, purge):
600
- get_image_model().objects.get(id=5).save()
600
+ with self.captureOnCommitCallbacks(execute=True):
601
+ get_image_model().objects.get(id=5).save()
601
602
 
602
603
  purge.assert_any_call("http://api.example.com/api/main/images/5/")
603
604
 
604
605
  def test_delete_image_purges(self, purge):
605
- get_image_model().objects.get(id=5).delete()
606
+ with self.captureOnCommitCallbacks(execute=True):
607
+ get_image_model().objects.get(id=5).delete()
606
608
 
607
609
  purge.assert_any_call("http://api.example.com/api/main/images/5/")
@@ -1886,22 +1886,26 @@ class TestPageCacheInvalidation(TestCase):
1886
1886
  signal_handlers.unregister_signal_handlers()
1887
1887
 
1888
1888
  def test_republish_page_purges(self, purge):
1889
- Page.objects.get(id=2).specific.save_revision().publish()
1889
+ with self.captureOnCommitCallbacks(execute=True):
1890
+ Page.objects.get(id=2).specific.save_revision().publish()
1890
1891
 
1891
1892
  purge.assert_any_call("http://api.example.com/api/main/pages/2/")
1892
1893
 
1893
1894
  def test_unpublish_page_purges(self, purge):
1894
- Page.objects.get(id=2).unpublish()
1895
+ with self.captureOnCommitCallbacks(execute=True):
1896
+ Page.objects.get(id=2).unpublish()
1895
1897
 
1896
1898
  purge.assert_any_call("http://api.example.com/api/main/pages/2/")
1897
1899
 
1898
1900
  def test_delete_page_purges(self, purge):
1899
- Page.objects.get(id=16).delete()
1901
+ with self.captureOnCommitCallbacks(execute=True):
1902
+ Page.objects.get(id=16).delete()
1900
1903
 
1901
1904
  purge.assert_any_call("http://api.example.com/api/main/pages/16/")
1902
1905
 
1903
1906
  def test_save_draft_doesnt_purge(self, purge):
1904
- Page.objects.get(id=2).specific.save_revision()
1907
+ with self.captureOnCommitCallbacks(execute=True):
1908
+ Page.objects.get(id=2).specific.save_revision()
1905
1909
 
1906
1910
  purge.assert_not_called()
1907
1911
 
wagtail/blocks/base.py CHANGED
@@ -20,6 +20,7 @@ from wagtail.admin.staticfiles import versioned_static
20
20
  from wagtail.coreutils import accepts_kwarg
21
21
  from wagtail.telepath import JSContext
22
22
  from wagtail.utils.deprecation import RemovedInWagtail70Warning
23
+ from wagtail.utils.templates import template_is_overridden
23
24
 
24
25
  __all__ = [
25
26
  "BaseBlock",
@@ -55,8 +56,10 @@ class BaseBlock(type):
55
56
  class Block(metaclass=BaseBlock):
56
57
  name = ""
57
58
  creation_counter = 0
59
+ definition_registry = {}
58
60
 
59
61
  TEMPLATE_VAR = "value"
62
+ DEFAULT_PREVIEW_TEMPLATE = "wagtailcore/shared/block_preview.html"
60
63
 
61
64
  class Meta:
62
65
  label = None
@@ -94,6 +97,7 @@ class Block(metaclass=BaseBlock):
94
97
  self.creation_counter = Block.creation_counter
95
98
  Block.creation_counter += 1
96
99
  self.definition_prefix = "blockdef-%d" % self.creation_counter
100
+ Block.definition_registry[self.definition_prefix] = self
97
101
 
98
102
  self.label = self.meta.label or ""
99
103
 
@@ -157,7 +161,7 @@ class Block(metaclass=BaseBlock):
157
161
  model definition time (e.g. something like StructValue which incorporates a
158
162
  pointer back to the block definition object).
159
163
  """
160
- return self.normalize(self.meta.default)
164
+ return self.normalize(getattr(self.meta, "default", None))
161
165
 
162
166
  def clean(self, value):
163
167
  """
@@ -269,6 +273,60 @@ class Block(metaclass=BaseBlock):
269
273
 
270
274
  return mark_safe(render_to_string(template, new_context))
271
275
 
276
+ def get_preview_context(self, value, parent_context=None):
277
+ # We do not fall back to `get_context` here, because the preview context
278
+ # will be used for a complete view, not just the block. Instead, the
279
+ # default preview context uses `{% include_block %}`, which will use
280
+ # `get_context`.
281
+ return parent_context or {}
282
+
283
+ def get_preview_template(self, value, context=None):
284
+ # We do not fall back to `get_template` here, because the template will
285
+ # be used for a complete view, not just the block. In most cases, the
286
+ # block's template cannot stand alone for the preview, as it would be
287
+ # missing the necessary static assets.
288
+ #
289
+ # Instead, the default preview template uses `{% include_block %}`,
290
+ # which will use `get_template` if a template is defined.
291
+ return (
292
+ getattr(self.meta, "preview_template", None)
293
+ or self.DEFAULT_PREVIEW_TEMPLATE
294
+ )
295
+
296
+ def get_preview_value(self):
297
+ if hasattr(self.meta, "preview_value"):
298
+ return self.normalize(self.meta.preview_value)
299
+ return self.get_default()
300
+
301
+ @cached_property
302
+ def is_previewable(self):
303
+ # To prevent showing a broken preview if the block preview has not been
304
+ # configured, consider the block to be previewable if either:
305
+ # - a specific preview template is configured for the block
306
+ # - a preview value is provided and the global template has been overridden
307
+ # which are the intended ways to configure block previews.
308
+ #
309
+ # If a block is made previewable by other means, the `is_previewable`
310
+ # property should be overridden to return `True`.
311
+ has_specific_template = (
312
+ hasattr(self.meta, "preview_template")
313
+ or self.__class__.get_preview_template is not Block.get_preview_template
314
+ )
315
+ has_preview_value = (
316
+ hasattr(self.meta, "preview_value")
317
+ or getattr(self.meta, "default", None) is not None
318
+ or self.__class__.get_preview_context is not Block.get_preview_context
319
+ or self.__class__.get_preview_value is not Block.get_preview_value
320
+ )
321
+ has_global_template = template_is_overridden(
322
+ self.DEFAULT_PREVIEW_TEMPLATE,
323
+ "templates",
324
+ )
325
+ return has_specific_template or (has_preview_value and has_global_template)
326
+
327
+ def get_description(self):
328
+ return getattr(self.meta, "description", "")
329
+
272
330
  def get_api_representation(self, value, context=None):
273
331
  """
274
332
  Can be used to customise the API response and defaults to the value returned by get_prep_value.
@@ -81,6 +81,9 @@ class FieldBlock(Block):
81
81
  self.field.prepare_value(self.value_for_form(value))
82
82
  )
83
83
 
84
+ def get_description(self):
85
+ return super().get_description() or self.field.help_text or ""
86
+
84
87
  class Meta:
85
88
  # No icon specified here, because that depends on the purpose that the
86
89
  # block is being used for. Feel encouraged to specify an icon in your
@@ -110,8 +113,11 @@ class FieldBlockAdapter(Adapter):
110
113
 
111
114
  meta = {
112
115
  "label": block.label,
116
+ "description": block.get_description(),
113
117
  "required": block.required,
114
118
  "icon": block.meta.icon,
119
+ "blockDefId": block.definition_prefix,
120
+ "isPreviewable": block.is_previewable,
115
121
  "classname": " ".join(classname),
116
122
  "showAddCommentButton": getattr(
117
123
  block.field.widget, "show_add_comment_button", True
@@ -449,12 +449,16 @@ class ListBlockAdapter(Adapter):
449
449
  def js_args(self, block):
450
450
  meta = {
451
451
  "label": block.label,
452
+ "description": block.get_description(),
452
453
  "icon": block.meta.icon,
454
+ "blockDefId": block.definition_prefix,
455
+ "isPreviewable": block.is_previewable,
453
456
  "classname": block.meta.form_classname,
454
457
  "collapsed": block.meta.collapsed,
455
458
  "strings": {
456
459
  "MOVE_UP": _("Move up"),
457
460
  "MOVE_DOWN": _("Move down"),
461
+ "DRAG": _("Drag"),
458
462
  "DUPLICATE": _("Duplicate"),
459
463
  "DELETE": _("Delete"),
460
464
  "ADD": _("Add"),
@@ -58,6 +58,9 @@ class StaticBlockAdapter(Adapter):
58
58
  text_or_html: admin_text,
59
59
  "icon": block.meta.icon,
60
60
  "label": block.label,
61
+ "description": block.get_description(),
62
+ "blockDefId": block.definition_prefix,
63
+ "isPreviewable": block.is_previewable,
61
64
  },
62
65
  ]
63
66
 
@@ -710,7 +710,7 @@ class StreamValue(MutableSequence):
710
710
  raw_values = OrderedDict(
711
711
  (i, raw_item["value"])
712
712
  for i, raw_item in enumerate(self._raw_data)
713
- if raw_item["type"] == type_name and self._bound_blocks[i] is None
713
+ if self._bound_blocks[i] is None and raw_item["type"] == type_name
714
714
  )
715
715
  # pass the raw block values to bulk_to_python as a list
716
716
  converted_values = child_block.bulk_to_python(raw_values.values())
@@ -826,8 +826,11 @@ class StreamBlockAdapter(Adapter):
826
826
  def js_args(self, block):
827
827
  meta = {
828
828
  "label": block.label,
829
+ "description": block.get_description(),
829
830
  "required": block.required,
830
831
  "icon": block.meta.icon,
832
+ "blockDefId": block.definition_prefix,
833
+ "isPreviewable": block.is_previewable,
831
834
  "classname": block.meta.form_classname,
832
835
  "maxNum": block.meta.max_num,
833
836
  "minNum": block.meta.min_num,
@@ -836,6 +839,7 @@ class StreamBlockAdapter(Adapter):
836
839
  "strings": {
837
840
  "MOVE_UP": _("Move up"),
838
841
  "MOVE_DOWN": _("Move down"),
842
+ "DRAG": _("Drag"),
839
843
  "DUPLICATE": _("Duplicate"),
840
844
  "DELETE": _("Delete"),
841
845
  "ADD": _("Add"),
@@ -360,6 +360,9 @@ class BaseStructBlock(Block):
360
360
  )
361
361
  return mark_safe(render_to_string(self.meta.form_template, context))
362
362
 
363
+ def get_description(self):
364
+ return super().get_description() or getattr(self.meta, "help_text", "")
365
+
363
366
  def get_form_context(self, value, prefix="", errors=None):
364
367
  return {
365
368
  "children": collections.OrderedDict(
@@ -401,8 +404,11 @@ class StructBlockAdapter(Adapter):
401
404
  def js_args(self, block):
402
405
  meta = {
403
406
  "label": block.label,
407
+ "description": block.get_description(),
404
408
  "required": block.required,
405
409
  "icon": block.meta.icon,
410
+ "blockDefId": block.definition_prefix,
411
+ "isPreviewable": block.is_previewable,
406
412
  "classname": block.meta.form_classname,
407
413
  }
408
414
 
wagtail/compat.py CHANGED
@@ -11,3 +11,19 @@ except ValueError:
11
11
  raise ImproperlyConfigured(
12
12
  "AUTH_USER_MODEL must be of the form" " 'app_label.model_name'"
13
13
  )
14
+
15
+
16
+ try:
17
+ from http import HTTPMethod
18
+ except ImportError:
19
+ # For Python < 3.11
20
+ from enum import Enum
21
+
22
+ class HTTPMethod(Enum):
23
+ GET = "GET"
24
+ HEAD = "HEAD"
25
+ OPTIONS = "OPTIONS"
26
+ POST = "POST"
27
+ PUT = "PUT"
28
+ DELETE = "DELETE"
29
+ PATCH = "PATCH"
@@ -173,16 +173,34 @@ class SelectDateForm(django.forms.Form):
173
173
 
174
174
  class WagtailAdminFormPageForm(WagtailAdminPageForm):
175
175
  def clean(self):
176
- super().clean()
176
+ """
177
+ Dynamically detect all related AbstractFormField subclasses to ensure
178
+ validation is applied regardless of the related_name or if there are multiple
179
+ AbstractFormField subclasses related to this page.
180
+ """
181
+
182
+ from .models import AbstractFormField
183
+
184
+ cleaned_data = super().clean()
177
185
 
178
- # Check for duplicate form fields by comparing their internal clean_names
179
- if "form_fields" in self.formsets:
180
- forms = self.formsets["form_fields"].forms
186
+ form_fields_related_names = [
187
+ related_object.related_name
188
+ for related_object in self.instance._meta.related_objects
189
+ if issubclass(related_object.related_model, AbstractFormField)
190
+ ]
191
+
192
+ for related_name in form_fields_related_names:
193
+ if related_name not in self.formsets:
194
+ continue
195
+
196
+ forms = self.formsets[related_name].forms
181
197
  for form in forms:
182
198
  form.is_valid()
183
199
 
184
- # Use existing clean_name or generate for new fields.
185
- # clean_name is set in FormField.save
200
+ # Use existing clean_name or generate for new fields,
201
+ # raise an error if there are duplicate resolved clean names.
202
+ # Note: `clean_name` is set in `FormField.save`.
203
+
186
204
  clean_names = [
187
205
  f.instance.clean_name or f.instance.get_field_clean_name()
188
206
  for f in forms
@@ -193,7 +211,7 @@ class WagtailAdminFormPageForm(WagtailAdminPageForm):
193
211
  if duplicate_clean_name:
194
212
  duplicate_form_field = next(
195
213
  f
196
- for f in self.formsets["form_fields"].forms
214
+ for f in self.formsets[related_name].forms
197
215
  if f.instance.get_field_clean_name() == duplicate_clean_name
198
216
  )
199
217
  duplicate_form_field.add_error(
@@ -205,3 +223,5 @@ class WagtailAdminFormPageForm(WagtailAdminPageForm):
205
223
  % {"label_name": duplicate_form_field.instance.label}
206
224
  ),
207
225
  )
226
+
227
+ return cleaned_data