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
@@ -3,6 +3,7 @@ import collections
3
3
  import copy
4
4
  import json
5
5
  import unittest
6
+ import unittest.mock
6
7
  from decimal import Decimal
7
8
 
8
9
  # non-standard import name for gettext_lazy, to prevent strings from being picked up for translation
@@ -57,6 +58,89 @@ class TestBlock(SimpleTestCase):
57
58
  obj = object()
58
59
  self.assertIs(blocks.Block().normalize(obj), obj)
59
60
 
61
+ def test_block_definition_registry(self):
62
+ """Instantiating a Block should register it in the definition registry"""
63
+ block = blocks.Block()
64
+ self.assertIs(blocks.Block.definition_registry[block.definition_prefix], block)
65
+
66
+ def test_block_is_previewable(self):
67
+ class CustomContextBlock(blocks.Block):
68
+ def get_preview_context(self, value, parent_context=None):
69
+ return {"value": value, "foo": "bar"}
70
+
71
+ class CustomTemplateBlock(blocks.Block):
72
+ def get_preview_template(self, value=None, context=None):
73
+ return "foo.html"
74
+
75
+ class CustomValueBlock(blocks.Block):
76
+ def get_preview_value(self):
77
+ return "foo"
78
+
79
+ variants = {
80
+ "no_config": [
81
+ blocks.Block(),
82
+ ],
83
+ "specific_template": [
84
+ blocks.Block(preview_template="foo.html"),
85
+ CustomTemplateBlock(),
86
+ ],
87
+ "custom_value": [
88
+ blocks.Block(preview_value="foo"),
89
+ blocks.Block(default="bar"),
90
+ CustomContextBlock(),
91
+ CustomValueBlock(),
92
+ ],
93
+ "specific_template_and_custom_value": [
94
+ blocks.Block(preview_template="foo.html", preview_value="bar"),
95
+ ],
96
+ }
97
+
98
+ # Test without a global template override
99
+ cases = [
100
+ # Unconfigured block should not be previewable
101
+ ("no_config", False),
102
+ # Providing a specific preview template should make the block
103
+ # previewable even without a custom preview value, as the content
104
+ # may be hardcoded in the template
105
+ ("specific_template", True),
106
+ # Providing a preview value without a custom template should not
107
+ # make the block previewable, as it may be missing the static assets
108
+ ("custom_value", False),
109
+ # Providing both a preview template and value also makes the block
110
+ # previewable, this is the same as providing a custom template only
111
+ ("specific_template_and_custom_value", True),
112
+ ]
113
+ for variant, is_previewable in cases:
114
+ with self.subTest(variant=variant, custom_global_template=False):
115
+ for block in variants[variant]:
116
+ self.assertIs(block.is_previewable, is_previewable)
117
+
118
+ # Test with a global template override
119
+ with unittest.mock.patch(
120
+ "wagtail.blocks.base.template_is_overridden",
121
+ return_value=True,
122
+ ):
123
+ cases = [
124
+ # Global template override + no preview value = not previewable,
125
+ # since it's unlikely the global template alone will provide a
126
+ # useful preview
127
+ ("no_config", False),
128
+ # Unchanged – specific template always makes the block previewable
129
+ ("specific_template", True),
130
+ # Global template override + custom preview value = previewable.
131
+ # We assume the global template will provide the static assets,
132
+ # and the custom value (and the block's real template via
133
+ # {% include_block %}) will provide the content.
134
+ ("custom_value", True),
135
+ # Unchanged – providing both also makes the block previewable
136
+ ("specific_template_and_custom_value", True),
137
+ ]
138
+ for variant, is_previewable in cases:
139
+ with self.subTest(variant=variant, custom_global_template=True):
140
+ for block in variants[variant]:
141
+ del block.is_previewable # Clear cached_property
142
+ self.assertIs(block.is_previewable, is_previewable)
143
+
60
144
 
61
145
  class TestFieldBlock(WagtailTestUtils, SimpleTestCase):
62
146
  def test_charfield_render(self):
@@ -65,6 +149,13 @@ class TestFieldBlock(WagtailTestUtils, SimpleTestCase):
65
149
 
66
150
  self.assertEqual(html, "Hello world!")
67
151
 
152
+ def test_block_definition_registry(self):
153
+ block = blocks.CharBlock(label="Test block")
154
+ registered_block = blocks.Block.definition_registry[block.definition_prefix]
155
+ self.assertIsInstance(registered_block, blocks.CharBlock)
156
+ self.assertEqual(registered_block.meta.label, "Test block")
157
+ self.assertIs(registered_block, block)
158
+
68
159
  def test_charfield_render_with_template(self):
69
160
  block = blocks.CharBlock(template="tests/blocks/heading_block.html")
70
161
  html = block.render("Hello world!")
@@ -83,9 +174,12 @@ class TestFieldBlock(WagtailTestUtils, SimpleTestCase):
83
174
  js_args[2],
84
175
  {
85
176
  "label": "Test block",
177
+ "description": "Some helpful text",
86
178
  "helpText": "Some helpful text",
87
179
  "required": True,
88
180
  "icon": "placeholder",
181
+ "blockDefId": block.definition_prefix,
182
+ "isPreviewable": block.is_previewable,
89
183
  "classname": "w-field w-field--char_field w-field--text_input",
90
184
  "showAddCommentButton": True,
91
185
  "strings": {"ADD_COMMENT": "Add Comment"},
@@ -172,7 +266,7 @@ class TestFieldBlock(WagtailTestUtils, SimpleTestCase):
172
266
  )
173
267
  )
174
268
 
175
- block = ChoiceBlock()
269
+ block = ChoiceBlock(description="A selection of two choices")
176
270
 
177
271
  block.set_name("test_choiceblock")
178
272
  js_args = FieldBlockAdapter().js_args(block)
@@ -190,8 +284,11 @@ class TestFieldBlock(WagtailTestUtils, SimpleTestCase):
190
284
  js_args[2],
191
285
  {
192
286
  "label": "Test choiceblock",
287
+ "description": "A selection of two choices",
193
288
  "required": True,
194
289
  "icon": "placeholder",
290
+ "blockDefId": block.definition_prefix,
291
+ "isPreviewable": block.is_previewable,
195
292
  "classname": "w-field w-field--choice_field w-field--select",
196
293
  "showAddCommentButton": True,
197
294
  "strings": {"ADD_COMMENT": "Add Comment"},
@@ -601,6 +698,9 @@ class TestRichTextBlock(TestCase):
601
698
  "classname": "w-field w-field--char_field w-field--custom_rich_text_area",
602
699
  "icon": "pilcrow",
603
700
  "label": "Test richtextblock",
701
+ "description": "",
702
+ "blockDefId": block.definition_prefix,
703
+ "isPreviewable": block.is_previewable,
604
704
  "required": True,
605
705
  "showAddCommentButton": True,
606
706
  "strings": {"ADD_COMMENT": "Add Comment"},
@@ -621,8 +721,11 @@ class TestRichTextBlock(TestCase):
621
721
  js_args[2],
622
722
  {
623
723
  "label": "Test richtextblock",
724
+ "description": "",
624
725
  "required": True,
625
726
  "icon": "pilcrow",
727
+ "blockDefId": block.definition_prefix,
728
+ "isPreviewable": block.is_previewable,
626
729
  "classname": "w-field w-field--char_field w-field--draftail_rich_text_area",
627
730
  "showAddCommentButton": False, # Draftail manages its own comments
628
731
  "strings": {"ADD_COMMENT": "Add Comment"},
@@ -643,8 +746,11 @@ class TestRichTextBlock(TestCase):
643
746
  js_args[2],
644
747
  {
645
748
  "label": "Test richtextblock",
749
+ "description": "",
646
750
  "required": True,
647
751
  "icon": "pilcrow",
752
+ "blockDefId": block.definition_prefix,
753
+ "isPreviewable": block.is_previewable,
648
754
  "classname": "w-field w-field--char_field w-field--draftail_rich_text_area",
649
755
  "showAddCommentButton": False, # Draftail manages its own comments
650
756
  "strings": {"ADD_COMMENT": "Add Comment"},
@@ -758,8 +864,11 @@ class TestChoiceBlock(WagtailTestUtils, SimpleTestCase):
758
864
  js_args[2],
759
865
  {
760
866
  "label": "Test choiceblock",
867
+ "description": "",
761
868
  "required": True,
762
869
  "icon": "placeholder",
870
+ "blockDefId": block.definition_prefix,
871
+ "isPreviewable": block.is_previewable,
763
872
  "classname": "w-field w-field--choice_field w-field--select",
764
873
  "showAddCommentButton": True,
765
874
  "strings": {"ADD_COMMENT": "Add Comment"},
@@ -1159,8 +1268,11 @@ class TestMultipleChoiceBlock(WagtailTestUtils, SimpleTestCase):
1159
1268
  js_args[2],
1160
1269
  {
1161
1270
  "label": "Test choiceblock",
1271
+ "description": "",
1162
1272
  "required": True,
1163
1273
  "icon": "placeholder",
1274
+ "blockDefId": block.definition_prefix,
1275
+ "isPreviewable": block.is_previewable,
1164
1276
  "classname": "w-field w-field--multiple_choice_field w-field--select_multiple",
1165
1277
  "showAddCommentButton": True,
1166
1278
  "strings": {"ADD_COMMENT": "Add Comment"},
@@ -1595,8 +1707,11 @@ class TestRawHTMLBlock(unittest.TestCase):
1595
1707
  js_args[2],
1596
1708
  {
1597
1709
  "label": "Test rawhtmlblock",
1710
+ "description": "",
1598
1711
  "required": True,
1599
1712
  "icon": "code",
1713
+ "blockDefId": block.definition_prefix,
1714
+ "isPreviewable": block.is_previewable,
1600
1715
  "classname": "w-field w-field--char_field w-field--textarea",
1601
1716
  "showAddCommentButton": True,
1602
1717
  "strings": {"ADD_COMMENT": "Add Comment"},
@@ -1937,8 +2052,11 @@ class TestStructBlock(SimpleTestCase):
1937
2052
  js_args[2],
1938
2053
  {
1939
2054
  "label": "Test structblock",
2055
+ "description": "",
1940
2056
  "required": False,
1941
2057
  "icon": "placeholder",
2058
+ "blockDefId": block.definition_prefix,
2059
+ "isPreviewable": block.is_previewable,
1942
2060
  "classname": "struct-block",
1943
2061
  },
1944
2062
  )
@@ -1966,8 +2084,11 @@ class TestStructBlock(SimpleTestCase):
1966
2084
  js_args[2],
1967
2085
  {
1968
2086
  "label": "Test structblock",
2087
+ "description": "",
1969
2088
  "required": False,
1970
2089
  "icon": "placeholder",
2090
+ "blockDefId": block.definition_prefix,
2091
+ "isPreviewable": block.is_previewable,
1971
2092
  "classname": "struct-block",
1972
2093
  "formTemplate": "<div>Hello</div>",
1973
2094
  },
@@ -1990,8 +2111,11 @@ class TestStructBlock(SimpleTestCase):
1990
2111
  js_args[2],
1991
2112
  {
1992
2113
  "label": "Test structblock",
2114
+ "description": "",
1993
2115
  "required": False,
1994
2116
  "icon": "placeholder",
2117
+ "blockDefId": block.definition_prefix,
2118
+ "isPreviewable": block.is_previewable,
1995
2119
  "classname": "struct-block",
1996
2120
  "formTemplate": "<div>Hello</div>",
1997
2121
  },
@@ -2023,8 +2147,11 @@ class TestStructBlock(SimpleTestCase):
2023
2147
  js_args[2],
2024
2148
  {
2025
2149
  "label": "Test structblock",
2150
+ "description": "Self-promotion is encouraged",
2026
2151
  "required": False,
2027
2152
  "icon": "placeholder",
2153
+ "blockDefId": block.definition_prefix,
2154
+ "isPreviewable": block.is_previewable,
2028
2155
  "classname": "struct-block",
2029
2156
  "helpIcon": (
2030
2157
  '<svg class="icon icon-help default" aria-hidden="true">'
@@ -2048,8 +2175,11 @@ class TestStructBlock(SimpleTestCase):
2048
2175
  js_args[2],
2049
2176
  {
2050
2177
  "label": "Test structblock",
2178
+ "description": "Self-promotion is encouraged",
2051
2179
  "required": False,
2052
2180
  "icon": "placeholder",
2181
+ "blockDefId": block.definition_prefix,
2182
+ "isPreviewable": block.is_previewable,
2053
2183
  "classname": "struct-block",
2054
2184
  "helpIcon": (
2055
2185
  '<svg class="icon icon-help default" aria-hidden="true">'
@@ -2677,7 +2807,10 @@ class TestListBlock(WagtailTestUtils, SimpleTestCase):
2677
2807
  js_args[3],
2678
2808
  {
2679
2809
  "label": "Test listblock",
2810
+ "description": "",
2680
2811
  "icon": "placeholder",
2812
+ "blockDefId": block.definition_prefix,
2813
+ "isPreviewable": block.is_previewable,
2681
2814
  "classname": None,
2682
2815
  "collapsed": False,
2683
2816
  "strings": {
@@ -2685,6 +2818,7 @@ class TestListBlock(WagtailTestUtils, SimpleTestCase):
2685
2818
  "DUPLICATE": "Duplicate",
2686
2819
  "MOVE_DOWN": "Move down",
2687
2820
  "MOVE_UP": "Move up",
2821
+ "DRAG": "Drag",
2688
2822
  "ADD": "Add",
2689
2823
  },
2690
2824
  },
@@ -2707,7 +2841,10 @@ class TestListBlock(WagtailTestUtils, SimpleTestCase):
2707
2841
  js_args[3],
2708
2842
  {
2709
2843
  "label": "Test listblock",
2844
+ "description": "",
2710
2845
  "icon": "placeholder",
2846
+ "blockDefId": block.definition_prefix,
2847
+ "isPreviewable": block.is_previewable,
2711
2848
  "classname": None,
2712
2849
  "collapsed": False,
2713
2850
  "minNum": 2,
@@ -2717,6 +2854,7 @@ class TestListBlock(WagtailTestUtils, SimpleTestCase):
2717
2854
  "DUPLICATE": "Duplicate",
2718
2855
  "MOVE_DOWN": "Move down",
2719
2856
  "MOVE_UP": "Move up",
2857
+ "DRAG": "Drag",
2720
2858
  "ADD": "Add",
2721
2859
  },
2722
2860
  },
@@ -2881,7 +3019,10 @@ class TestListBlock(WagtailTestUtils, SimpleTestCase):
2881
3019
  js_args[3],
2882
3020
  {
2883
3021
  "label": "Test listblock",
3022
+ "description": "",
2884
3023
  "icon": "placeholder",
3024
+ "blockDefId": block.definition_prefix,
3025
+ "isPreviewable": block.is_previewable,
2885
3026
  "classname": "special-list-class",
2886
3027
  "collapsed": False,
2887
3028
  "strings": {
@@ -2889,6 +3030,7 @@ class TestListBlock(WagtailTestUtils, SimpleTestCase):
2889
3030
  "DUPLICATE": "Duplicate",
2890
3031
  "MOVE_DOWN": "Move down",
2891
3032
  "MOVE_UP": "Move up",
3033
+ "DRAG": "Drag",
2892
3034
  "ADD": "Add",
2893
3035
  },
2894
3036
  },
@@ -2914,7 +3056,10 @@ class TestListBlock(WagtailTestUtils, SimpleTestCase):
2914
3056
  js_args[3],
2915
3057
  {
2916
3058
  "label": "Test listblock",
3059
+ "description": "",
2917
3060
  "icon": "placeholder",
3061
+ "blockDefId": block.definition_prefix,
3062
+ "isPreviewable": block.is_previewable,
2918
3063
  "classname": "custom-list-class",
2919
3064
  "collapsed": False,
2920
3065
  "strings": {
@@ -2922,6 +3067,7 @@ class TestListBlock(WagtailTestUtils, SimpleTestCase):
2922
3067
  "DUPLICATE": "Duplicate",
2923
3068
  "MOVE_DOWN": "Move down",
2924
3069
  "MOVE_UP": "Move up",
3070
+ "DRAG": "Drag",
2925
3071
  "ADD": "Add",
2926
3072
  },
2927
3073
  },
@@ -3551,7 +3697,10 @@ class TestStreamBlock(WagtailTestUtils, SimpleTestCase):
3551
3697
  js_args[3],
3552
3698
  {
3553
3699
  "label": "Test streamblock",
3700
+ "description": "",
3554
3701
  "icon": "placeholder",
3702
+ "blockDefId": block.definition_prefix,
3703
+ "isPreviewable": block.is_previewable,
3555
3704
  "classname": None,
3556
3705
  "collapsed": False,
3557
3706
  "maxNum": None,
@@ -3563,6 +3712,7 @@ class TestStreamBlock(WagtailTestUtils, SimpleTestCase):
3563
3712
  "DUPLICATE": "Duplicate",
3564
3713
  "MOVE_DOWN": "Move down",
3565
3714
  "MOVE_UP": "Move up",
3715
+ "DRAG": "Drag",
3566
3716
  "ADD": "Add",
3567
3717
  },
3568
3718
  },
@@ -4277,7 +4427,10 @@ class TestStreamBlock(WagtailTestUtils, SimpleTestCase):
4277
4427
  js_args[3],
4278
4428
  {
4279
4429
  "label": "Test streamblock",
4430
+ "description": "",
4280
4431
  "icon": "placeholder",
4432
+ "blockDefId": block.definition_prefix,
4433
+ "isPreviewable": block.is_previewable,
4281
4434
  "minNum": None,
4282
4435
  "maxNum": None,
4283
4436
  "blockCounts": {},
@@ -4289,6 +4442,7 @@ class TestStreamBlock(WagtailTestUtils, SimpleTestCase):
4289
4442
  "DUPLICATE": "Duplicate",
4290
4443
  "MOVE_DOWN": "Move down",
4291
4444
  "MOVE_UP": "Move up",
4445
+ "DRAG": "Drag",
4292
4446
  "ADD": "Add",
4293
4447
  },
4294
4448
  },
@@ -4379,7 +4533,10 @@ class TestStreamBlock(WagtailTestUtils, SimpleTestCase):
4379
4533
  js_args[3],
4380
4534
  {
4381
4535
  "label": "Test streamblock",
4536
+ "description": "",
4382
4537
  "icon": "placeholder",
4538
+ "blockDefId": block.definition_prefix,
4539
+ "isPreviewable": block.is_previewable,
4383
4540
  "minNum": None,
4384
4541
  "maxNum": None,
4385
4542
  "blockCounts": {},
@@ -4391,6 +4548,7 @@ class TestStreamBlock(WagtailTestUtils, SimpleTestCase):
4391
4548
  "DUPLICATE": "Duplicate",
4392
4549
  "MOVE_DOWN": "Move down",
4393
4550
  "MOVE_UP": "Move up",
4551
+ "DRAG": "Drag",
4394
4552
  "ADD": "Add",
4395
4553
  },
4396
4554
  },
@@ -4710,7 +4868,9 @@ class TestPageChooserBlock(TestCase):
4710
4868
  def test_adapt(self):
4711
4869
  from wagtail.admin.widgets.chooser import AdminPageChooser
4712
4870
 
4713
- block = blocks.PageChooserBlock(help_text="pick a page, any page")
4871
+ block = blocks.PageChooserBlock(
4872
+ help_text="pick a page, any page", description="A featured page"
4873
+ )
4714
4874
 
4715
4875
  block.set_name("test_pagechooserblock")
4716
4876
  js_args = FieldBlockAdapter().js_args(block)
@@ -4723,8 +4883,11 @@ class TestPageChooserBlock(TestCase):
4723
4883
  js_args[2],
4724
4884
  {
4725
4885
  "label": "Test pagechooserblock",
4886
+ "description": "A featured page",
4726
4887
  "required": True,
4727
4888
  "icon": "doc-empty-inverse",
4889
+ "blockDefId": block.definition_prefix,
4890
+ "isPreviewable": block.is_previewable,
4728
4891
  "helpText": "pick a page, any page",
4729
4892
  "classname": "w-field w-field--model_choice_field w-field--admin_page_chooser",
4730
4893
  "showAddCommentButton": True,
@@ -4928,7 +5091,10 @@ class TestStaticBlock(unittest.TestCase):
4928
5091
  {
4929
5092
  "text": "Latest posts - This block doesn't need to be configured, it will be displayed automatically",
4930
5093
  "icon": "placeholder",
5094
+ "blockDefId": block.definition_prefix,
5095
+ "isPreviewable": block.is_previewable,
4931
5096
  "label": "Posts static block",
5097
+ "description": "",
4932
5098
  },
4933
5099
  )
4934
5100
 
@@ -4949,7 +5115,10 @@ class TestStaticBlock(unittest.TestCase):
4949
5115
  {
4950
5116
  "text": "Latest posts - This block doesn't need to be configured, it will be displayed automatically",
4951
5117
  "icon": "placeholder",
5118
+ "blockDefId": block.definition_prefix,
5119
+ "isPreviewable": block.is_previewable,
4952
5120
  "label": "Posts static block",
5121
+ "description": "",
4953
5122
  },
4954
5123
  )
4955
5124
 
@@ -4969,7 +5138,10 @@ class TestStaticBlock(unittest.TestCase):
4969
5138
  {
4970
5139
  "text": "Latest posts: this block has no options.",
4971
5140
  "icon": "placeholder",
5141
+ "blockDefId": block.definition_prefix,
5142
+ "isPreviewable": block.is_previewable,
4972
5143
  "label": "Latest posts",
5144
+ "description": "",
4973
5145
  },
4974
5146
  )
4975
5147
 
@@ -4990,7 +5162,10 @@ class TestStaticBlock(unittest.TestCase):
4990
5162
  {
4991
5163
  "text": "Posts static block: this block has no options.",
4992
5164
  "icon": "placeholder",
5165
+ "blockDefId": block.definition_prefix,
5166
+ "isPreviewable": block.is_previewable,
4993
5167
  "label": "Posts static block",
5168
+ "description": "",
4994
5169
  },
4995
5170
  )
4996
5171
 
@@ -5011,7 +5186,10 @@ class TestStaticBlock(unittest.TestCase):
5011
5186
  {
5012
5187
  "html": "<b>Latest posts</b> - This block doesn't need to be configured, it will be displayed automatically",
5013
5188
  "icon": "placeholder",
5189
+ "blockDefId": block.definition_prefix,
5190
+ "isPreviewable": block.is_previewable,
5014
5191
  "label": "Posts static block",
5192
+ "description": "",
5015
5193
  },
5016
5194
  )
5017
5195
 
@@ -5063,8 +5241,11 @@ class TestDateBlock(TestCase):
5063
5241
  js_args[2],
5064
5242
  {
5065
5243
  "label": "Test dateblock",
5244
+ "description": "",
5066
5245
  "required": True,
5067
5246
  "icon": "date",
5247
+ "blockDefId": block.definition_prefix,
5248
+ "isPreviewable": block.is_previewable,
5068
5249
  "classname": "w-field w-field--date_field w-field--admin_date_input",
5069
5250
  "showAddCommentButton": True,
5070
5251
  "strings": {"ADD_COMMENT": "Add Comment"},
@@ -5096,8 +5277,11 @@ class TestTimeBlock(TestCase):
5096
5277
  js_args[2],
5097
5278
  {
5098
5279
  "label": "Test timeblock",
5280
+ "description": "",
5099
5281
  "required": True,
5100
5282
  "icon": "time",
5283
+ "blockDefId": block.definition_prefix,
5284
+ "isPreviewable": block.is_previewable,
5101
5285
  "classname": "w-field w-field--time_field w-field--admin_time_input",
5102
5286
  "showAddCommentButton": True,
5103
5287
  "strings": {"ADD_COMMENT": "Add Comment"},
@@ -5129,8 +5313,11 @@ class TestDateTimeBlock(TestCase):
5129
5313
  js_args[2],
5130
5314
  {
5131
5315
  "label": "Test datetimeblock",
5316
+ "description": "",
5132
5317
  "required": True,
5133
5318
  "icon": "date",
5319
+ "blockDefId": block.definition_prefix,
5320
+ "isPreviewable": block.is_previewable,
5134
5321
  "classname": "w-field w-field--date_time_field w-field--admin_date_time_input",
5135
5322
  "showAddCommentButton": True,
5136
5323
  "strings": {"ADD_COMMENT": "Add Comment"},
@@ -1,7 +1,14 @@
1
- from django.test import TestCase
1
+ from unittest import mock
2
+
3
+ from django.contrib.sessions.middleware import SessionMiddleware
4
+ from django.http import HttpResponse
5
+ from django.test import RequestFactory, TestCase
2
6
 
3
7
  from wagtail import hooks
8
+ from wagtail.models import Page, PageViewRestriction
4
9
  from wagtail.test.utils import WagtailTestUtils
10
+ from wagtail.views import serve, serve_chain
11
+ from wagtail.wagtail_hooks import check_view_restrictions
5
12
 
6
13
 
7
14
  def test_hook():
@@ -34,3 +41,161 @@ class TestLoginView(WagtailTestUtils, TestCase):
34
41
  with self.register_hook("test_hook_name", after_hook, order=1):
35
42
  hook_fns = hooks.get_hooks("test_hook_name")
36
43
  self.assertEqual(hook_fns, [test_hook, after_hook])
44
+
45
+
46
+ class TestServeHooks(WagtailTestUtils, TestCase):
47
+ fixtures = ["test.json"]
48
+
49
+ def setUp(self):
50
+ self.page = Page.objects.get(id=2)
51
+ self.request = RequestFactory().get("/test/")
52
+ middleware = SessionMiddleware(lambda x: None)
53
+ middleware.process_request(self.request)
54
+ self.request.session.save()
55
+
56
+ def test_serve_chain_order(self):
57
+ order_calls = []
58
+
59
+ def hook_1(next_fn):
60
+ def wrapper(page, request, *args, **kwargs):
61
+ order_calls.append(1)
62
+ return next_fn(page, request, *args, **kwargs)
63
+
64
+ return wrapper
65
+
66
+ def hook_2(next_fn):
67
+ def wrapper(page, request, *args, **kwargs):
68
+ order_calls.append(2)
69
+ return next_fn(page, request, *args, **kwargs)
70
+
71
+ return wrapper
72
+
73
+ def hook_3(next_fn):
74
+ def wrapper(page, request, *args, **kwargs):
75
+ order_calls.append(3)
76
+ return next_fn(page, request, *args, **kwargs)
77
+
78
+ return wrapper
79
+
80
+ with self.register_hook("on_serve_page", hook_1):
81
+ with self.register_hook("on_serve_page", hook_2):
82
+ with self.register_hook("on_serve_page", hook_3):
83
+ serve(self.request, self.page.url)
84
+
85
+ self.assertEqual(order_calls, [1, 2, 3])
86
+
87
+ def test_serve_chain_modification(self):
88
+ def hook_modifier(next_fn):
89
+ def wrapper(page, request, *args, **kwargs):
90
+ response = next_fn(page, request, *args, **kwargs)
91
+ response.content = b"Modified content"
92
+ return response
93
+
94
+ return wrapper
95
+
96
+ with self.register_hook("on_serve_page", hook_modifier):
97
+ response = serve(self.request, self.page.url)
98
+ self.assertEqual(response.content, b"Modified content")
99
+
100
+ def test_serve_chain_halt_execution(self):
101
+ def hook_halt(next_fn):
102
+ def wrapper(page, request, *args, **kwargs):
103
+ return HttpResponse("Halted")
104
+
105
+ return wrapper
106
+
107
+ with self.register_hook("on_serve_page", hook_halt):
108
+ response = serve(self.request, self.page.url)
109
+ self.assertEqual(response.content, b"Halted")
110
+
111
+ def test_serve_chain_view_restriction(self):
112
+ restriction = PageViewRestriction.objects.create(
113
+ page=self.page,
114
+ restriction_type=PageViewRestriction.PASSWORD,
115
+ password="password",
116
+ )
117
+
118
+ with self.register_hook("on_serve_page", check_view_restrictions):
119
+ response = self.client.get(self.page.url)
120
+ self.assertEqual(response.status_code, 200)
121
+ self.assertTemplateUsed(response, "wagtailcore/password_required.html")
122
+
123
+ restriction.delete()
124
+
125
+ def test_serve_always_called_last(self):
126
+ hook_calls = []
127
+ serve_called = []
128
+
129
+ def hook_1(next_fn):
130
+ def wrapper(page, request, *args, **kwargs):
131
+ hook_calls.append(1)
132
+ return next_fn(page, request, *args, **kwargs)
133
+
134
+ return wrapper
135
+
136
+ def hook_2(next_fn):
137
+ def wrapper(page, request, *args, **kwargs):
138
+ hook_calls.append(2)
139
+ return next_fn(page, request, *args, **kwargs)
140
+
141
+ return wrapper
142
+
143
+ def hook_3(next_fn):
144
+ def wrapper(page, request, *args, **kwargs):
145
+ hook_calls.append(3)
146
+ return next_fn(page, request, *args, **kwargs)
147
+
148
+ return wrapper
149
+
150
+ original_serve_chain = serve_chain
151
+
152
+ def mock_serve_chain(page, request, *args, **kwargs):
153
+ serve_called.append(True)
154
+ return original_serve_chain(page, request, *args, **kwargs)
155
+
156
+ with mock.patch("wagtail.views.serve_chain", mock_serve_chain):
157
+ with self.register_hook("on_serve_page", hook_1):
158
+ with self.register_hook("on_serve_page", hook_2):
159
+ with self.register_hook("on_serve_page", hook_3):
160
+ serve(self.request, self.page.url)
161
+
162
+ self.assertEqual(hook_calls, [1, 2, 3])
163
+ self.assertEqual(len(serve_called), 1)
164
+ self.assertTrue(serve_called[0])
165
+
166
+ def test_check_view_restrictions_receives_correct_parameters(self):
167
+ received_params = []
168
+
169
+ def hook_spy(next_fn):
170
+ def wrapper(page, request, *args, **kwargs):
171
+ received_params.append(
172
+ {"page": page, "request": request, "args": args, "kwargs": kwargs}
173
+ )
174
+ return next_fn(page, request, *args, **kwargs)
175
+
176
+ return wrapper
177
+
178
+ self.assertIsNotNone(self.page, "Test page should not be None")
179
+ self.assertIsNotNone(self.request, "Test request should not be None")
180
+
181
+ with self.register_hook("on_serve_page", hook_spy):
182
+ route_result = Page.route_for_request(self.request, self.page.url)
183
+
184
+ self.assertIsNotNone(route_result, "route_result should not be None")
185
+
186
+ if route_result:
187
+ page, args, kwargs = route_result
188
+ serve(self.request, self.page.url)
189
+
190
+ self.assertEqual(len(received_params), 1)
191
+
192
+ params = received_params[0]
193
+
194
+ self.assertIsNotNone(params["page"], "Hook received None as page")
195
+ self.assertIsNotNone(params["request"], "Hook received None as request")
196
+
197
+ self.assertEqual(params["page"], self.page)
198
+ self.assertEqual(params["request"], self.request)
199
+
200
+ self.assertEqual(params["args"], ([], {}))
201
+ self.assertEqual(params["kwargs"], {})