wagtail 6.3.1__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 (307) 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/locale/gl/LC_MESSAGES/djangojs.mo +0 -0
  18. wagtail/admin/locale/gl/LC_MESSAGES/djangojs.po +5 -5
  19. wagtail/admin/locale/pt_BR/LC_MESSAGES/django.mo +0 -0
  20. wagtail/admin/locale/pt_BR/LC_MESSAGES/django.po +29 -0
  21. wagtail/admin/menu.py +0 -13
  22. wagtail/admin/panels/base.py +2 -2
  23. wagtail/admin/panels/group.py +4 -1
  24. wagtail/admin/panels/inline_panel.py +5 -2
  25. wagtail/admin/panels/model_utils.py +36 -0
  26. wagtail/admin/panels/page_utils.py +2 -40
  27. wagtail/admin/panels/signal_handlers.py +0 -2
  28. wagtail/admin/static/wagtailadmin/css/core.css +1 -1
  29. wagtail/admin/static/wagtailadmin/css/panels/draftail.css +1 -1
  30. wagtail/admin/static/wagtailadmin/css/panels/streamfield.css +1 -1
  31. wagtail/admin/static/wagtailadmin/js/comments.js +1 -1
  32. wagtail/admin/static/wagtailadmin/js/core.js +1 -1
  33. wagtail/admin/static/wagtailadmin/js/core.js.LICENSE.txt +1 -8
  34. wagtail/admin/static/wagtailadmin/js/draftail.js +1 -1
  35. wagtail/admin/static/wagtailadmin/js/modal-workflow.js +1 -1
  36. wagtail/admin/static/wagtailadmin/js/page-chooser-modal.js +1 -1
  37. wagtail/admin/static/wagtailadmin/js/privacy-switch.js +1 -1
  38. wagtail/admin/static/wagtailadmin/js/sidebar.js +1 -1
  39. wagtail/admin/static/wagtailadmin/js/telepath/blocks.js +1 -1
  40. wagtail/admin/static/wagtailadmin/js/userbar.js +1 -1
  41. wagtail/admin/static/wagtailadmin/js/userbar.js.LICENSE.txt +1 -1
  42. wagtail/admin/static/wagtailadmin/js/vendor.js +1 -1
  43. wagtail/admin/static/wagtailadmin/js/vendor.js.LICENSE.txt +7 -0
  44. wagtail/admin/templates/wagtailadmin/404.html +4 -0
  45. wagtail/admin/templates/wagtailadmin/chooser/browse.html +2 -1
  46. wagtail/admin/templates/wagtailadmin/chooser/tables/parent_page_cell.html +1 -1
  47. wagtail/admin/templates/wagtailadmin/collections/_privacy_switch.html +8 -1
  48. wagtail/admin/templates/wagtailadmin/generic/confirm_delete.html +15 -9
  49. wagtail/admin/templates/wagtailadmin/generic/confirm_unpublish.html +21 -25
  50. wagtail/admin/templates/wagtailadmin/generic/form.html +1 -1
  51. wagtail/admin/templates/wagtailadmin/generic/preview_error.html +3 -0
  52. wagtail/admin/templates/wagtailadmin/generic/revisions/compare.html +63 -76
  53. wagtail/admin/templates/wagtailadmin/pages/_editor_js.html +0 -2
  54. wagtail/admin/templates/wagtailadmin/pages/edit.html +1 -5
  55. wagtail/admin/templates/wagtailadmin/panels/inline_panel_child.html +1 -0
  56. wagtail/admin/templates/wagtailadmin/permissions/includes/collection_member_permissions_form.html +1 -1
  57. wagtail/admin/templates/wagtailadmin/permissions/includes/collection_member_permissions_formset.html +6 -22
  58. wagtail/admin/templates/wagtailadmin/shared/formatted_field.html +2 -2
  59. wagtail/admin/templates/wagtailadmin/shared/header.html +2 -2
  60. wagtail/admin/templates/wagtailadmin/shared/page_status_tag_new.html +32 -39
  61. wagtail/admin/templates/wagtailadmin/shared/revisions/confirm_unschedule.html +13 -17
  62. wagtail/admin/templates/wagtailadmin/shared/side_panels/includes/status/privacy.html +15 -3
  63. wagtail/admin/templates/wagtailadmin/shared/side_panels/preview.html +1 -1
  64. wagtail/admin/templates/wagtailadmin/skeleton.html +4 -2
  65. wagtail/admin/templates/wagtailadmin/workflows/create.html +1 -1
  66. wagtail/admin/templates/wagtailadmin/workflows/edit.html +1 -1
  67. wagtail/admin/templates/wagtailadmin/workflows/includes/workflow_pages_form.html +1 -1
  68. wagtail/admin/templates/wagtailadmin/workflows/includes/workflow_pages_formset.html +6 -23
  69. wagtail/admin/templatetags/wagtailadmin_tags.py +12 -0
  70. wagtail/admin/templatetags/wagtailuserbar.py +2 -3
  71. wagtail/admin/tests/pages/test_create_page.py +110 -1
  72. wagtail/admin/tests/pages/test_edit_page.py +3 -2
  73. wagtail/admin/tests/pages/test_explorer_view.py +18 -0
  74. wagtail/admin/tests/pages/test_page_usage.py +24 -20
  75. wagtail/admin/tests/pages/test_preview.py +69 -1
  76. wagtail/admin/tests/pages/test_revisions.py +40 -6
  77. wagtail/admin/tests/test_account_management.py +39 -1
  78. wagtail/admin/tests/test_audit_log.py +4 -2
  79. wagtail/admin/tests/test_block_preview.py +224 -0
  80. wagtail/admin/tests/test_edit_handlers.py +23 -6
  81. wagtail/admin/tests/test_page_chooser.py +50 -3
  82. wagtail/admin/tests/test_privacy.py +49 -26
  83. wagtail/admin/tests/test_site_summary.py +15 -10
  84. wagtail/admin/tests/test_templatetags.py +19 -0
  85. wagtail/admin/tests/test_userbar.py +82 -1
  86. wagtail/admin/tests/test_views_generic.py +27 -12
  87. wagtail/admin/tests/test_workflows.py +69 -0
  88. wagtail/admin/tests/tests.py +23 -4
  89. wagtail/admin/tests/ui/test_sidebar.py +1 -1
  90. wagtail/admin/tests/viewsets/test_model_viewset.py +15 -13
  91. wagtail/admin/ui/side_panels.py +7 -4
  92. wagtail/admin/urls/__init__.py +6 -0
  93. wagtail/admin/urls/pages.py +1 -1
  94. wagtail/admin/userbar.py +21 -1
  95. wagtail/admin/views/account.py +5 -0
  96. wagtail/admin/views/chooser.py +5 -1
  97. wagtail/admin/views/collections.py +0 -2
  98. wagtail/admin/views/generic/base.py +20 -10
  99. wagtail/admin/views/generic/history.py +0 -1
  100. wagtail/admin/views/generic/models.py +79 -21
  101. wagtail/admin/views/generic/preview.py +50 -1
  102. wagtail/admin/views/mixins.py +4 -2
  103. wagtail/admin/views/pages/bulk_actions/delete.py +11 -23
  104. wagtail/admin/views/pages/bulk_actions/page_bulk_action.py +17 -0
  105. wagtail/admin/views/pages/bulk_actions/publish.py +11 -31
  106. wagtail/admin/views/pages/bulk_actions/unpublish.py +11 -31
  107. wagtail/admin/views/pages/create.py +1 -0
  108. wagtail/admin/views/pages/edit.py +38 -30
  109. wagtail/admin/views/pages/revisions.py +43 -114
  110. wagtail/admin/views/pages/utils.py +0 -1
  111. wagtail/admin/views/tags.py +6 -2
  112. wagtail/admin/views/workflows.py +8 -6
  113. wagtail/admin/viewsets/model.py +0 -4
  114. wagtail/admin/viewsets/pages.py +0 -1
  115. wagtail/admin/widgets/tags.py +1 -0
  116. wagtail/api/v2/tests/test_documents.py +4 -2
  117. wagtail/api/v2/tests/test_images.py +4 -2
  118. wagtail/api/v2/tests/test_pages.py +8 -4
  119. wagtail/blocks/base.py +59 -1
  120. wagtail/blocks/field_block.py +6 -0
  121. wagtail/blocks/list_block.py +4 -0
  122. wagtail/blocks/static_block.py +3 -0
  123. wagtail/blocks/stream_block.py +5 -1
  124. wagtail/blocks/struct_block.py +6 -0
  125. wagtail/compat.py +16 -0
  126. wagtail/contrib/forms/forms.py +27 -7
  127. wagtail/contrib/forms/locale/en/LC_MESSAGES/django.po +2 -2
  128. wagtail/contrib/forms/locale/gl/LC_MESSAGES/django.mo +0 -0
  129. wagtail/contrib/forms/locale/gl/LC_MESSAGES/django.po +4 -4
  130. wagtail/contrib/forms/tests/test_models.py +7 -5
  131. wagtail/contrib/forms/tests/test_views.py +75 -0
  132. wagtail/contrib/frontend_cache/backends/cloudfront.py +1 -1
  133. wagtail/contrib/frontend_cache/tasks.py +83 -0
  134. wagtail/contrib/frontend_cache/tests.py +48 -33
  135. wagtail/contrib/frontend_cache/utils.py +2 -70
  136. wagtail/contrib/redirects/base_formats.py +2 -2
  137. wagtail/contrib/redirects/locale/ar/LC_MESSAGES/django.mo +0 -0
  138. wagtail/contrib/redirects/locale/ar/LC_MESSAGES/django.po +3 -0
  139. wagtail/contrib/redirects/locale/en/LC_MESSAGES/django.po +24 -37
  140. wagtail/contrib/redirects/templates/wagtailredirects/add.html +1 -24
  141. wagtail/contrib/redirects/templates/wagtailredirects/confirm_delete.html +3 -13
  142. wagtail/contrib/redirects/tests/test_redirects.py +122 -110
  143. wagtail/contrib/redirects/tests/test_signal_handlers.py +75 -69
  144. wagtail/contrib/redirects/urls.py +2 -2
  145. wagtail/contrib/redirects/views.py +35 -73
  146. wagtail/contrib/search_promotions/admin_urls.py +10 -3
  147. wagtail/contrib/search_promotions/forms.py +55 -26
  148. wagtail/contrib/search_promotions/locale/en/LC_MESSAGES/django.po +44 -54
  149. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/add.html +21 -31
  150. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/confirm_delete.html +3 -12
  151. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/edit.html +11 -34
  152. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/includes/searchpromotion_form.html +1 -0
  153. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/includes/searchpromotions_formset.js +2 -1
  154. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/index.html +0 -1
  155. wagtail/contrib/search_promotions/tests.py +814 -13
  156. wagtail/contrib/search_promotions/views/__init__.py +1 -0
  157. wagtail/contrib/search_promotions/views/reports.py +56 -0
  158. wagtail/contrib/search_promotions/views/settings.py +258 -0
  159. wagtail/contrib/search_promotions/wagtail_hooks.py +12 -1
  160. wagtail/contrib/settings/locale/ar/LC_MESSAGES/django.mo +0 -0
  161. wagtail/contrib/settings/locale/ar/LC_MESSAGES/django.po +6 -1
  162. wagtail/contrib/settings/locale/en/LC_MESSAGES/django.po +3 -3
  163. wagtail/contrib/settings/templates/wagtailsettings/edit.html +1 -5
  164. wagtail/contrib/settings/tests/generic/test_admin.py +2 -5
  165. wagtail/contrib/settings/tests/generic/test_register.py +1 -1
  166. wagtail/contrib/settings/tests/site_specific/test_admin.py +2 -5
  167. wagtail/contrib/settings/tests/site_specific/test_register.py +1 -1
  168. wagtail/contrib/settings/views.py +9 -23
  169. wagtail/contrib/simple_translation/locale/en/LC_MESSAGES/django.po +1 -1
  170. wagtail/contrib/styleguide/locale/en/LC_MESSAGES/django.po +1 -1
  171. wagtail/contrib/table_block/locale/en/LC_MESSAGES/django.po +1 -1
  172. wagtail/contrib/table_block/tests.py +4 -1
  173. wagtail/contrib/typed_table_block/blocks.py +3 -0
  174. wagtail/contrib/typed_table_block/locale/en/LC_MESSAGES/django.po +10 -10
  175. wagtail/contrib/typed_table_block/static/typed_table_block/js/typed_table_block.js +1 -1
  176. wagtail/contrib/typed_table_block/tests.py +33 -0
  177. wagtail/documents/locale/en/LC_MESSAGES/django.po +26 -26
  178. wagtail/documents/migrations/0011_add_choose_permissions.py +1 -0
  179. wagtail/documents/models.py +1 -0
  180. wagtail/documents/signal_handlers.py +6 -2
  181. wagtail/documents/static/wagtaildocs/js/add-multiple.js +1 -1
  182. wagtail/documents/templates/wagtaildocs/documents/edit.html +1 -3
  183. wagtail/documents/templates/wagtaildocs/multiple/add.html +7 -1
  184. wagtail/documents/tests/test_admin_views.py +74 -33
  185. wagtail/documents/tests/test_views.py +21 -12
  186. wagtail/documents/views/chooser.py +1 -0
  187. wagtail/documents/views/documents.py +1 -2
  188. wagtail/documents/views/multiple.py +0 -1
  189. wagtail/documents/views/serve.py +9 -2
  190. wagtail/documents/wagtail_hooks.py +6 -1
  191. wagtail/embeds/locale/en/LC_MESSAGES/django.po +1 -1
  192. wagtail/embeds/oembed_providers.py +0 -64
  193. wagtail/fields.py +3 -0
  194. wagtail/images/apps.py +2 -1
  195. wagtail/images/blocks.py +14 -2
  196. wagtail/images/forms.py +40 -3
  197. wagtail/images/locale/ar/LC_MESSAGES/django.mo +0 -0
  198. wagtail/images/locale/ar/LC_MESSAGES/django.po +4 -0
  199. wagtail/images/locale/en/LC_MESSAGES/django.po +49 -49
  200. wagtail/images/migrations/0023_add_choose_permissions.py +1 -0
  201. wagtail/images/rich_text/contentstate.py +1 -0
  202. wagtail/images/rich_text/editor_html.py +1 -0
  203. wagtail/images/signal_handlers.py +17 -10
  204. wagtail/images/static/wagtailimages/js/add-multiple.js +1 -1
  205. wagtail/images/static/wagtailimages/js/image-block.js +1 -1
  206. wagtail/images/static/wagtailimages/js/image-chooser-telepath.js +1 -1
  207. wagtail/images/static/wagtailimages/js/image-chooser.js +1 -1
  208. wagtail/images/static/wagtailimages/js/image-url-generator.js +1 -1
  209. wagtail/images/static/wagtailimages/js/vendor/jquery.fileupload-image.js +1 -1
  210. wagtail/images/tasks.py +18 -0
  211. wagtail/images/templates/wagtailimages/images/edit.html +1 -3
  212. wagtail/images/templates/wagtailimages/images/url_generator.html +1 -1
  213. wagtail/images/templates/wagtailimages/multiple/add.html +7 -2
  214. wagtail/images/templates/wagtailimages/widgets/image_chooser.html +1 -1
  215. wagtail/images/tests/test_admin_views.py +53 -29
  216. wagtail/images/tests/test_blocks.py +34 -2
  217. wagtail/images/tests/test_models.py +12 -10
  218. wagtail/images/tests/tests.py +10 -0
  219. wagtail/images/views/chooser.py +1 -0
  220. wagtail/images/views/images.py +1 -3
  221. wagtail/images/views/multiple.py +0 -1
  222. wagtail/images/views/serve.py +18 -2
  223. wagtail/images/widgets.py +3 -0
  224. wagtail/locale/en/LC_MESSAGES/django.po +228 -216
  225. wagtail/locales/locale/en/LC_MESSAGES/django.po +1 -1
  226. wagtail/management/commands/publish_scheduled.py +1 -1
  227. wagtail/migrations/0087_alter_grouppagepermission_unique_together_and_more.py +16 -8
  228. wagtail/models/__init__.py +300 -119
  229. wagtail/models/i18n.py +2 -2
  230. wagtail/models/panels.py +37 -0
  231. wagtail/models/sites.py +7 -6
  232. wagtail/permission_policies/pages.py +2 -2
  233. wagtail/project_template/project_name/settings/base.py +4 -0
  234. wagtail/project_template/requirements.txt +1 -1
  235. wagtail/query.py +145 -0
  236. wagtail/search/backends/database/mysql/mysql.py +25 -17
  237. wagtail/search/backends/database/postgres/postgres.py +44 -83
  238. wagtail/search/backends/database/sqlite/sqlite.py +25 -17
  239. wagtail/search/backends/elasticsearch7.py +4 -0
  240. wagtail/search/locale/en/LC_MESSAGES/django.po +1 -1
  241. wagtail/search/query.py +8 -2
  242. wagtail/search/signal_handlers.py +6 -9
  243. wagtail/search/tasks.py +10 -0
  244. wagtail/search/tests/test_elasticsearch7_backend.py +21 -0
  245. wagtail/search/tests/test_index_functions.py +10 -6
  246. wagtail/search/tests/test_postgres_backend.py +0 -14
  247. wagtail/signal_handlers.py +5 -20
  248. wagtail/sites/locale/en/LC_MESSAGES/django.po +1 -1
  249. wagtail/snippets/locale/en/LC_MESSAGES/django.po +3 -13
  250. wagtail/snippets/locale/sv/LC_MESSAGES/django.mo +0 -0
  251. wagtail/snippets/locale/sv/LC_MESSAGES/django.po +4 -3
  252. wagtail/snippets/tests/test_preview.py +5 -0
  253. wagtail/snippets/tests/test_snippets.py +100 -45
  254. wagtail/snippets/tests/test_usage.py +29 -24
  255. wagtail/snippets/tests/test_viewset.py +1 -1
  256. wagtail/snippets/views/snippets.py +0 -12
  257. wagtail/tasks.py +41 -0
  258. wagtail/templates/wagtailcore/shared/block_preview.html +29 -0
  259. wagtail/test/earlypage/__init__.py +0 -0
  260. wagtail/test/earlypage/migrations/0001_initial.py +37 -0
  261. wagtail/test/earlypage/migrations/__init__.py +0 -0
  262. wagtail/test/earlypage/models.py +14 -0
  263. wagtail/test/settings.py +3 -0
  264. wagtail/test/testapp/fixtures/test.json +7 -0
  265. wagtail/test/testapp/fixtures/test_specific.json +6 -3
  266. wagtail/test/testapp/models.py +58 -44
  267. wagtail/test/testapp/templates/tests/custom_block_preview.html +16 -0
  268. wagtail/test/testapp/templates/tests/static_block_preview.html +5 -0
  269. wagtail/test/testapp/wagtail_hooks.py +9 -0
  270. wagtail/tests/test_blocks.py +189 -2
  271. wagtail/tests/test_hooks.py +166 -1
  272. wagtail/tests/test_management_commands.py +54 -13
  273. wagtail/tests/test_page_allowed_http_methods.py +32 -0
  274. wagtail/tests/test_page_model.py +68 -0
  275. wagtail/tests/test_page_privacy.py +10 -0
  276. wagtail/tests/test_page_queryset.py +79 -0
  277. wagtail/tests/test_reference_index.py +84 -75
  278. wagtail/tests/test_streamfield.py +30 -0
  279. wagtail/tests/test_utils.py +61 -0
  280. wagtail/users/forms.py +2 -9
  281. wagtail/users/locale/en/LC_MESSAGES/django.po +17 -17
  282. wagtail/users/locale/nl/LC_MESSAGES/django.mo +0 -0
  283. wagtail/users/locale/nl/LC_MESSAGES/django.po +4 -3
  284. wagtail/users/templates/wagtailusers/groups/create.html +0 -5
  285. wagtail/users/templates/wagtailusers/groups/includes/page_permissions_form.html +1 -1
  286. wagtail/users/templates/wagtailusers/groups/includes/page_permissions_formset.html +6 -6
  287. wagtail/users/tests/test_admin_views.py +96 -4
  288. wagtail/users/tests/test_utils.py +76 -0
  289. wagtail/users/utils.py +43 -11
  290. wagtail/utils/setup.py +2 -2
  291. wagtail/utils/templates.py +26 -0
  292. wagtail/utils/widgets.py +1 -0
  293. wagtail/views.py +9 -1
  294. wagtail/wagtail_hooks.py +67 -29
  295. {wagtail-6.3.1.dist-info → wagtail-6.4rc1.dist-info}/METADATA +2 -2
  296. {wagtail-6.3.1.dist-info → wagtail-6.4rc1.dist-info}/RECORD +300 -287
  297. wagtail/admin/static/wagtailadmin/js/expanding-formset.js +0 -1
  298. wagtail/admin/static/wagtailadmin/js/vendor/rangy-core.js +0 -1
  299. wagtail/admin/static/wagtailadmin/js/vendor/uuidv4.min.js +0 -1
  300. wagtail/contrib/search_promotions/views.py +0 -323
  301. wagtail/images/static/wagtailimages/js/vendor/canvas-to-blob.min.js +0 -1
  302. wagtail/users/static/wagtailusers/js/group-form.js +0 -1
  303. wagtail/users/templates/wagtailusers/groups/includes/group_form_js.html +0 -3
  304. {wagtail-6.3.1.dist-info → wagtail-6.4rc1.dist-info}/LICENSE +0 -0
  305. {wagtail-6.3.1.dist-info → wagtail-6.4rc1.dist-info}/WHEEL +0 -0
  306. {wagtail-6.3.1.dist-info → wagtail-6.4rc1.dist-info}/entry_points.txt +0 -0
  307. {wagtail-6.3.1.dist-info → wagtail-6.4rc1.dist-info}/top_level.txt +0 -0
@@ -1,9 +1,13 @@
1
1
  import hashlib
2
+ import os
2
3
  import pickle
4
+ import tempfile
3
5
  import unittest
6
+ import warnings
4
7
  from io import BytesIO
5
8
  from pathlib import Path
6
9
 
10
+ from django.conf import settings
7
11
  from django.contrib.contenttypes.models import ContentType
8
12
  from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation
9
13
  from django.core.files.uploadedfile import SimpleUploadedFile
@@ -27,7 +31,9 @@ from wagtail.coreutils import (
27
31
  string_to_ascii,
28
32
  )
29
33
  from wagtail.models import Page, Site
34
+ from wagtail.utils.deprecation import RemovedInWagtail70Warning
30
35
  from wagtail.utils.file import hash_filelike
36
+ from wagtail.utils.templates import template_is_overridden
31
37
  from wagtail.utils.utils import deep_update, flatten_choices
32
38
  from wagtail.utils.version import get_main_version
33
39
 
@@ -578,6 +584,41 @@ class HashFileLikeTestCase(SimpleTestCase):
578
584
  )
579
585
 
580
586
 
587
+ class TestTemplateIsOverridden(SimpleTestCase):
588
+ def setUp(self):
589
+ template_is_overridden.cache_clear()
590
+
591
+ def test_template_is_overridden_false(self):
592
+ self.assertIs(
593
+ template_is_overridden(
594
+ "wagtailcore/shared/block_preview.html",
595
+ "templates",
596
+ ),
597
+ False,
598
+ )
599
+
600
+ def test_template_is_overridden_true(self):
601
+ with tempfile.TemporaryDirectory() as temp_dir:
602
+ path = os.path.join(temp_dir, "wagtailcore/shared")
603
+ os.makedirs(path, exist_ok=True)
604
+ with open(os.path.join(path, "block_preview.html"), "w") as f:
605
+ f.write("Custom file")
606
+
607
+ with self.settings(
608
+ TEMPLATES=[
609
+ {**settings.TEMPLATES[0], "DIRS": [temp_dir], "NAME": "tmp"},
610
+ *settings.TEMPLATES,
611
+ ]
612
+ ):
613
+ self.assertIs(
614
+ template_is_overridden(
615
+ "wagtailcore/shared/block_preview.html",
616
+ "templates",
617
+ ),
618
+ True,
619
+ )
620
+
621
+
581
622
  class TestVersion(SimpleTestCase):
582
623
  def test_get_main_version(self):
583
624
  cases = [
@@ -616,3 +657,23 @@ class TestFlattenChoices(SimpleTestCase):
616
657
  "unknown": "Unknown",
617
658
  },
618
659
  )
660
+
661
+
662
+ class TestWidgetWithScript(TestCase):
663
+ def test_deprecation(self):
664
+ message = "The usage of `WidgetWithScript` hook is deprecated. Use external scripts instead."
665
+
666
+ with unittest.mock.patch("warnings.warn", wraps=warnings.warn) as warn_mock:
667
+ with self.assertWarnsMessage(RemovedInWagtail70Warning, message):
668
+ from wagtail.utils.widgets import WidgetWithScript
669
+
670
+ class MyWidget(WidgetWithScript):
671
+ pass
672
+
673
+ # Make sure warn was called with stacklevel=3, so the actual caller
674
+ # that imports WidgetWithScript is shown in the warning message
675
+ warn_mock.assert_called_with(
676
+ message,
677
+ category=RemovedInWagtail70Warning,
678
+ stacklevel=3,
679
+ )
wagtail/users/forms.py CHANGED
@@ -15,6 +15,7 @@ from django.utils.html import mark_safe
15
15
  from django.utils.translation import gettext_lazy as _
16
16
 
17
17
  from wagtail import hooks
18
+ from wagtail.admin.forms.formsets import BaseFormSetMixin
18
19
  from wagtail.admin.widgets import AdminPageChooser
19
20
  from wagtail.models import (
20
21
  PAGE_PERMISSION_CODENAMES,
@@ -319,7 +320,7 @@ class PagePermissionsForm(forms.Form):
319
320
  )
320
321
 
321
322
 
322
- class BaseGroupPagePermissionFormSet(forms.BaseFormSet):
323
+ class BaseGroupPagePermissionFormSet(BaseFormSetMixin, forms.BaseFormSet):
323
324
  # defined here for easy access from templates
324
325
  permission_types = PAGE_PERMISSION_TYPES
325
326
 
@@ -350,14 +351,6 @@ class BaseGroupPagePermissionFormSet(forms.BaseFormSet):
350
351
  )
351
352
 
352
353
  super().__init__(data, files, initial=initial_data, prefix=prefix)
353
- for form in self.forms:
354
- form.fields["DELETE"].widget = forms.HiddenInput()
355
-
356
- @property
357
- def empty_form(self):
358
- empty_form = super().empty_form
359
- empty_form.fields["DELETE"].widget = forms.HiddenInput()
360
- return empty_form
361
354
 
362
355
  def clean(self):
363
356
  """Checks that no two forms refer to the same page object"""
@@ -8,7 +8,7 @@ msgid ""
8
8
  msgstr ""
9
9
  "Project-Id-Version: PACKAGE VERSION\n"
10
10
  "Report-Msgid-Bugs-To: \n"
11
- "POT-Creation-Date: 2024-10-21 17:53+0100\n"
11
+ "POT-Creation-Date: 2025-01-20 17:59+0000\n"
12
12
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -22,63 +22,63 @@ msgstr ""
22
22
  msgid "Wagtail users"
23
23
  msgstr ""
24
24
 
25
- #: forms.py:55
25
+ #: forms.py:56
26
26
  msgid "Required. Letters, digits and @/./+/-/_ only."
27
27
  msgstr ""
28
28
 
29
- #: forms.py:60
29
+ #: forms.py:61
30
30
  msgid "This value may contain only letters, numbers and @/./+/-/_ characters."
31
31
  msgstr ""
32
32
 
33
- #: forms.py:86
33
+ #: forms.py:87
34
34
  msgid "A user with that username already exists."
35
35
  msgstr ""
36
36
 
37
- #: forms.py:87
37
+ #: forms.py:88
38
38
  msgid "The two password fields didn't match."
39
39
  msgstr ""
40
40
 
41
- #: forms.py:90
41
+ #: forms.py:91
42
42
  msgid "Email"
43
43
  msgstr ""
44
44
 
45
- #: forms.py:91
45
+ #: forms.py:92
46
46
  msgid "First Name"
47
47
  msgstr ""
48
48
 
49
- #: forms.py:92
49
+ #: forms.py:93
50
50
  msgid "Last Name"
51
51
  msgstr ""
52
52
 
53
- #: forms.py:95
53
+ #: forms.py:96
54
54
  msgid "Password"
55
55
  msgstr ""
56
56
 
57
- #: forms.py:98
57
+ #: forms.py:99
58
58
  msgid "Leave blank if not changing."
59
59
  msgstr ""
60
60
 
61
- #: forms.py:102
61
+ #: forms.py:103
62
62
  msgid "Password confirmation"
63
63
  msgstr ""
64
64
 
65
- #: forms.py:105
65
+ #: forms.py:106
66
66
  msgid "Enter the same password as above, for verification."
67
67
  msgstr ""
68
68
 
69
- #: forms.py:110 forms.py:247 views/users.py:110
69
+ #: forms.py:111 forms.py:248 views/users.py:110
70
70
  msgid "Administrator"
71
71
  msgstr ""
72
72
 
73
- #: forms.py:113 forms.py:249
73
+ #: forms.py:114 forms.py:250
74
74
  msgid "Administrators have full access to manage any object or setting."
75
75
  msgstr ""
76
76
 
77
- #: forms.py:243
77
+ #: forms.py:244
78
78
  msgid "A group with that name already exists."
79
79
  msgstr ""
80
80
 
81
- #: forms.py:378
81
+ #: forms.py:371
82
82
  msgid "You cannot have multiple permission records for the same page."
83
83
  msgstr ""
84
84
 
@@ -400,7 +400,7 @@ msgstr ""
400
400
  msgid "Can view"
401
401
  msgstr ""
402
402
 
403
- #: utils.py:53
403
+ #: utils.py:85
404
404
  #, python-format
405
405
  msgid "user %(id)s (deleted)"
406
406
  msgstr ""
@@ -4,6 +4,7 @@
4
4
  #
5
5
  # Translators:
6
6
  # Harmen <sigmo@sigmo.nl>, 2022
7
+ # Joeri Vlekken, 2024
7
8
  # Kees Hink <keeshink@gmail.com>, 2017
8
9
  # Maarten Kling <kling.maarten@gmail.com>, 2023
9
10
  # Storm Heg <storm@stormbase.digital>, 2021-2024
@@ -15,7 +16,7 @@ msgstr ""
15
16
  "Report-Msgid-Bugs-To: \n"
16
17
  "POT-Creation-Date: 2024-10-21 17:53+0100\n"
17
18
  "PO-Revision-Date: 2014-02-19 11:54+0000\n"
18
- "Last-Translator: Storm Heg <storm@stormbase.digital>, 2021-2024\n"
19
+ "Last-Translator: Joeri Vlekken, 2024\n"
19
20
  "Language-Team: Dutch (http://app.transifex.com/torchbox/wagtail/language/"
20
21
  "nl/)\n"
21
22
  "MIME-Version: 1.0\n"
@@ -130,7 +131,7 @@ msgid "Dark"
130
131
  msgstr "Donker"
131
132
 
132
133
  msgid "admin theme"
133
- msgstr "admine thema"
134
+ msgstr "admin thema"
134
135
 
135
136
  msgid "More contrast"
136
137
  msgstr "Meer contrast"
@@ -185,7 +186,7 @@ msgstr[0] "Verwijder 1 gebruiker"
185
186
  msgstr[1] "Verwijder %(counter)s gebruikers"
186
187
 
187
188
  msgid "Delete"
188
- msgstr "Mag verwijderen"
189
+ msgstr "Verwijder"
189
190
 
190
191
  msgid "Are you sure you want to delete these users?"
191
192
  msgstr "Weet je zeker dat je deze gebruikers wilt verwijderen?"
@@ -24,8 +24,3 @@
24
24
  </div>
25
25
  {% endblock %}
26
26
  {% endblock %}
27
-
28
- {% block extra_js %}
29
- {{ block.super }}
30
- {% include "wagtailusers/groups/includes/group_form_js.html" %}
31
- {% endblock %}
@@ -10,5 +10,5 @@
10
10
  {% endfor %}
11
11
 
12
12
  <td>
13
- <button class="button button-small no" type="button" id="{{ form.DELETE.id_for_label }}-button">{% trans "Delete" %}</button>
13
+ <button class="button button-small no" type="button" id="{{ form.DELETE.id_for_label }}-button" data-action="w-formset#delete">{% trans "Delete" %}</button>
14
14
  </td>
@@ -1,7 +1,7 @@
1
1
  {% load i18n wagtailadmin_tags %}
2
2
 
3
3
  {% trans "Page permissions" as heading %}
4
- {% panel id="page-permissions" heading=heading icon="folder-open-inverse" %}
4
+ {% panel id="page-permissions" heading=heading icon="folder-open-inverse" attrs=formset.attrs %}
5
5
 
6
6
  {{ formset.management_form }}
7
7
 
@@ -27,9 +27,9 @@
27
27
  {% endfor %}
28
28
  </tr>
29
29
  </thead>
30
- <tbody id="id_{{ formset.prefix }}-FORMS">
30
+ <tbody id="id_{{ formset.prefix }}-FORMS" data-w-formset-target="forms">
31
31
  {% for form in formset.forms %}
32
- <tr id="inline_child_{{ form.prefix }}"{% if form.DELETE.value %} style="display: none;"{% endif %}>
32
+ <tr id="inline_child_{{ form.prefix }}"{% if form.DELETE.value %} hidden data-w-formset-target="deleted"{% else %}data-w-formset-target="child"{% endif %}>
33
33
  {% if form.non_field_errors %}
34
34
  <p class="error-message">
35
35
  {% for error in form.non_field_errors %}
@@ -43,14 +43,14 @@
43
43
  </tbody>
44
44
  </table>
45
45
 
46
- <template id="id_{{ formset.prefix }}-EMPTY_FORM_TEMPLATE">
47
- <tr id="inline_child_{{ formset.empty_form.prefix }}">
46
+ <template id="id_{{ formset.prefix }}-EMPTY_FORM_TEMPLATE" data-w-formset-target="template">
47
+ <tr id="inline_child_{{ formset.empty_form.prefix }}" data-w-formset-target="child">
48
48
  {% include "wagtailusers/groups/includes/page_permissions_form.html" with form=formset.empty_form only %}
49
49
  </tr>
50
50
  </template>
51
51
 
52
52
  <p class="add">
53
- <button class="button bicolor button--icon" id="id_{{ formset.prefix }}-ADD" value="Add" type="button">{% icon name="plus" wrapped=1 %}{% trans "Add a page permission" %}</button>
53
+ <button class="button bicolor button--icon" id="id_{{ formset.prefix }}-ADD" value="Add" type="button" data-action="w-formset#add">{% icon name="plus" wrapped=1 %}{% trans "Add a page permission" %}</button>
54
54
  </p>
55
55
 
56
56
  {% endpanel %}
@@ -1735,9 +1735,7 @@ class TestGroupCreateView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
1735
1735
  )
1736
1736
  # Should contain the JS from the form and the template include
1737
1737
  page_chooser_js = versioned_static("wagtailadmin/js/page-chooser.js")
1738
- group_form_js = versioned_static("wagtailusers/js/group-form.js")
1739
1738
  self.assertContains(response, page_chooser_js)
1740
- self.assertContains(response, group_form_js)
1741
1739
 
1742
1740
  def test_num_queries(self):
1743
1741
  # Warm up the cache
@@ -2009,6 +2007,49 @@ class TestGroupCreateView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
2009
2007
  custom_label.get_text(strip=True), "Can sync roadmap items from GitHub"
2010
2008
  )
2011
2009
 
2010
+ def test_formset_data_attributes(self):
2011
+ response = self.get()
2012
+ soup = self.get_soup(response.content)
2013
+
2014
+ panel = soup.find(id="page-permissions-section")
2015
+ self.assertIn("w-formset", panel.attrs["data-controller"])
2016
+ self.assertEqual(
2017
+ "totalFormsInput",
2018
+ panel.find(id="id_page_permissions-TOTAL_FORMS").attrs[
2019
+ "data-w-formset-target"
2020
+ ],
2021
+ )
2022
+ self.assertEqual(
2023
+ "template",
2024
+ panel.find("template").attrs["data-w-formset-target"],
2025
+ )
2026
+
2027
+ self.assertEqual(
2028
+ "forms",
2029
+ panel.find("table").find("tbody").attrs["data-w-formset-target"],
2030
+ )
2031
+
2032
+ # Other panels are rendered with different formset classes, test one of them
2033
+
2034
+ panel = soup.find(id="collection-management-permissions-section")
2035
+ self.assertIn("w-formset", panel.attrs["data-controller"])
2036
+
2037
+ self.assertEqual(
2038
+ "totalFormsInput",
2039
+ panel.find(id="id_collection_permissions-TOTAL_FORMS").attrs[
2040
+ "data-w-formset-target"
2041
+ ],
2042
+ )
2043
+ self.assertEqual(
2044
+ "template",
2045
+ panel.find("template").attrs["data-w-formset-target"],
2046
+ )
2047
+
2048
+ self.assertEqual(
2049
+ "forms",
2050
+ panel.find("table").find("tbody").attrs["data-w-formset-target"],
2051
+ )
2052
+
2012
2053
 
2013
2054
  class TestGroupEditView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
2014
2055
  def setUp(self):
@@ -2109,9 +2150,7 @@ class TestGroupEditView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
2109
2150
  )
2110
2151
  # Should contain the JS from the form and the template include
2111
2152
  page_chooser_js = versioned_static("wagtailadmin/js/page-chooser.js")
2112
- group_form_js = versioned_static("wagtailusers/js/group-form.js")
2113
2153
  self.assertContains(response, page_chooser_js)
2114
- self.assertContains(response, group_form_js)
2115
2154
 
2116
2155
  soup = self.get_soup(response.content)
2117
2156
  header = soup.select_one(".w-slim-header")
@@ -2608,6 +2647,59 @@ class TestGroupEditView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
2608
2647
  self.assertGreaterEqual(len(toggle_add_items), 30)
2609
2648
  self.assertEqual(toggle_add_items[0]["data-action"], "w-bulk#toggle")
2610
2649
 
2650
+ def test_formset_data_attributes(self):
2651
+ response = self.get()
2652
+ soup = self.get_soup(response.content)
2653
+
2654
+ panel = soup.find(id="page-permissions-section")
2655
+ self.assertIn("w-formset", panel.attrs["data-controller"])
2656
+ self.assertEqual(
2657
+ "totalFormsInput",
2658
+ panel.find(id="id_page_permissions-TOTAL_FORMS").attrs[
2659
+ "data-w-formset-target"
2660
+ ],
2661
+ )
2662
+ self.assertEqual(
2663
+ "template",
2664
+ panel.find("template").attrs["data-w-formset-target"],
2665
+ )
2666
+
2667
+ tbody = panel.find("table").find("tbody")
2668
+ self.assertEqual(
2669
+ "forms",
2670
+ tbody.attrs["data-w-formset-target"],
2671
+ )
2672
+
2673
+ row = tbody.find("tr")
2674
+ self.assertEqual(
2675
+ "child",
2676
+ row.attrs["data-w-formset-target"],
2677
+ )
2678
+ self.assertEqual(
2679
+ "deleteInput",
2680
+ row.find(id="id_page_permissions-0-DELETE").attrs["data-w-formset-target"],
2681
+ )
2682
+
2683
+ # Other panels are rendered with different formset classes, test one of them
2684
+
2685
+ panel = soup.find(id="collection-management-permissions-section")
2686
+ self.assertIn("w-formset", panel.attrs["data-controller"])
2687
+
2688
+ self.assertEqual(
2689
+ "totalFormsInput",
2690
+ panel.find(id="id_collection_permissions-TOTAL_FORMS").attrs[
2691
+ "data-w-formset-target"
2692
+ ],
2693
+ )
2694
+ self.assertEqual(
2695
+ "template",
2696
+ panel.find("template").attrs["data-w-formset-target"],
2697
+ )
2698
+ self.assertEqual(
2699
+ "forms",
2700
+ panel.find("table").find("tbody").attrs["data-w-formset-target"],
2701
+ )
2702
+
2611
2703
 
2612
2704
  class TestGroupHistoryView(WagtailTestUtils, TestCase):
2613
2705
  # More thorough tests are in test_model_viewset
@@ -0,0 +1,76 @@
1
+ from django.test import TestCase, override_settings
2
+
3
+ from wagtail.users.utils import get_gravatar_url
4
+
5
+
6
+ class TestGravatar(TestCase):
7
+ def test_gravatar_default(self):
8
+ """Test with the default settings"""
9
+ self.assertEqual(
10
+ get_gravatar_url("something@example.com"),
11
+ "//www.gravatar.com/avatar/76ebd6fecabc982c205dd056e8f0415a?d=mp&s=100",
12
+ )
13
+
14
+ def test_gravatar_custom_size(self):
15
+ """Test with a custom size (note that the size will be doubled)"""
16
+ self.assertEqual(
17
+ get_gravatar_url("something@example.com", size=100),
18
+ "//www.gravatar.com/avatar/76ebd6fecabc982c205dd056e8f0415a?d=mp&s=200",
19
+ )
20
+
21
+ @override_settings(
22
+ WAGTAIL_GRAVATAR_PROVIDER_URL="https://robohash.org/avatar?d=robohash&s=200"
23
+ )
24
+ def test_gravatar_params_that_overlap(self):
25
+ """
26
+ Test with params that overlap with default s (size) and d (default_image)
27
+ Also test the `s` is not overridden by the provider URL's query parameters.
28
+ """
29
+ self.assertEqual(
30
+ get_gravatar_url("something@example.com", size=80),
31
+ "https://robohash.org/avatar/76ebd6fecabc982c205dd056e8f0415a?d=robohash&s=160",
32
+ )
33
+
34
+ @override_settings(WAGTAIL_GRAVATAR_PROVIDER_URL="https://robohash.org/avatar?f=y")
35
+ def test_gravatar_params_that_dont_overlap(self):
36
+ """Test with params that don't default `s (size)` and `d (default_image)`"""
37
+ self.assertEqual(
38
+ get_gravatar_url("something@example.com"),
39
+ "https://robohash.org/avatar/76ebd6fecabc982c205dd056e8f0415a?d=mp&f=y&s=100",
40
+ )
41
+
42
+ @override_settings(
43
+ WAGTAIL_GRAVATAR_PROVIDER_URL="https://robohash.org/avatar?d=robohash&f=y"
44
+ )
45
+ def test_gravatar_query_params_override_default_params(self):
46
+ """Test that query parameters of `WAGTAIL_GRAVATAR_PROVIDER_URL` override default_params"""
47
+ self.assertEqual(
48
+ get_gravatar_url(
49
+ "something@example.com", default_params={"d": "monsterid"}
50
+ ),
51
+ "https://robohash.org/avatar/76ebd6fecabc982c205dd056e8f0415a?d=robohash&f=y&s=100",
52
+ )
53
+
54
+ @override_settings(WAGTAIL_GRAVATAR_PROVIDER_URL="https://robohash.org/avatar/")
55
+ def test_gravatar_trailing_slash(self):
56
+ """Test with a trailing slash in the URL"""
57
+ self.assertEqual(
58
+ get_gravatar_url("something@example.com"),
59
+ "https://robohash.org/avatar/76ebd6fecabc982c205dd056e8f0415a?d=mp&s=100",
60
+ )
61
+
62
+ @override_settings(WAGTAIL_GRAVATAR_PROVIDER_URL="https://robohash.org/avatar")
63
+ def test_gravatar_no_trailing_slash(self):
64
+ """Test with no trailing slash in the URL"""
65
+ self.assertEqual(
66
+ get_gravatar_url("something@example.com"),
67
+ "https://robohash.org/avatar/76ebd6fecabc982c205dd056e8f0415a?d=mp&s=100",
68
+ )
69
+
70
+ @override_settings(WAGTAIL_GRAVATAR_PROVIDER_URL="https://robohash.org/avatar?")
71
+ def test_gravatar_trailing_question_mark(self):
72
+ """Test with a trailing question mark in the URL"""
73
+ self.assertEqual(
74
+ get_gravatar_url("something@example.com"),
75
+ "https://robohash.org/avatar/76ebd6fecabc982c205dd056e8f0415a?d=mp&s=100",
76
+ )
wagtail/users/utils.py CHANGED
@@ -1,3 +1,5 @@
1
+ from urllib.parse import parse_qs, urlparse, urlunparse
2
+
1
3
  from django.conf import settings
2
4
  from django.utils.http import urlencode
3
5
  from django.utils.translation import gettext_lazy as _
@@ -25,11 +27,29 @@ def user_can_delete_user(current_user, user_to_delete):
25
27
  return True
26
28
 
27
29
 
28
- def get_gravatar_url(email, size=50):
29
- default = "mm"
30
- size = (
31
- int(size) * 2
32
- ) # requested at retina size by default and scaled down at point of use with css
30
+ def get_gravatar_url(email, size=50, default_params={"d": "mp"}):
31
+ """
32
+ See https://gravatar.com/site/implement/images/ for Gravatar image options.
33
+
34
+ Example usage:
35
+
36
+ .. code-block:: python
37
+
38
+ # Basic usage
39
+ gravatar_url = get_gravatar_url('user@example.com')
40
+
41
+ # Customize size and default image
42
+ gravatar_url = get_gravatar_url(
43
+ 'user@example.com',
44
+ size=100,
45
+ default_params={'d': 'robohash', 'f': 'y'}
46
+ )
47
+
48
+ Note:
49
+ If any parameter in ``default_params`` also exists in the provider URL,
50
+ it will be overridden by the provider URL's query parameter.
51
+ """
52
+
33
53
  gravatar_provider_url = getattr(
34
54
  settings, "WAGTAIL_GRAVATAR_PROVIDER_URL", "//www.gravatar.com/avatar"
35
55
  )
@@ -37,14 +57,26 @@ def get_gravatar_url(email, size=50):
37
57
  if (not email) or (gravatar_provider_url is None):
38
58
  return None
39
59
 
40
- email_bytes = email.lower().encode("utf-8")
41
- hash = safe_md5(email_bytes, usedforsecurity=False).hexdigest()
42
- gravatar_url = "{gravatar_provider_url}/{hash}?{params}".format(
43
- gravatar_provider_url=gravatar_provider_url.rstrip("/"),
44
- hash=hash,
45
- params=urlencode({"s": size, "d": default}),
60
+ parsed_url = urlparse(gravatar_provider_url)
61
+
62
+ params = {
63
+ **default_params,
64
+ **(parse_qs(parsed_url.query or "")),
65
+ # requested at retina size by default and scaled down at point of use with css
66
+ "s": int(size) * 2,
67
+ }
68
+
69
+ email_hash = safe_md5(
70
+ email.lower().encode("utf-8"), usedforsecurity=False
71
+ ).hexdigest()
72
+
73
+ parsed_url = parsed_url._replace(
74
+ path=f"{parsed_url.path.rstrip('/')}/{email_hash}",
75
+ query=urlencode(params, doseq=True),
46
76
  )
47
77
 
78
+ gravatar_url = urlunparse(parsed_url)
79
+
48
80
  return gravatar_url
49
81
 
50
82
 
wagtail/utils/setup.py CHANGED
@@ -29,10 +29,10 @@ class assets_mixin:
29
29
  Writes the current Wagtail version number into package.json
30
30
  """
31
31
  path = os.path.join(".", "client", "package.json")
32
- input_file = open(path)
33
32
 
34
33
  try:
35
- package = json.loads(input_file.read().decode("utf-8"))
34
+ with open(path, "r") as f:
35
+ package = json.loads(f.read())
36
36
  except ValueError as e:
37
37
  print("Unable to read " + path + " " + e) # noqa: T201
38
38
  raise SystemExit(1)
@@ -0,0 +1,26 @@
1
+ from functools import lru_cache
2
+ from pathlib import Path
3
+
4
+ from django.template import TemplateDoesNotExist
5
+ from django.template.loader import select_template
6
+
7
+ import wagtail
8
+
9
+
10
+ @lru_cache(maxsize=None)
11
+ def template_is_overridden(template_name: str, expected_location: str) -> bool:
12
+ """
13
+ Check if a template has been overridden.
14
+
15
+ A template is overridden if the resolved template file is different from the
16
+ expected location within the `wagtail` package directory.
17
+ """
18
+ try:
19
+ template = select_template([template_name])
20
+ except TemplateDoesNotExist:
21
+ return False
22
+
23
+ root = Path(wagtail.__file__).resolve().parent
24
+ path = str(root / expected_location / template_name)
25
+
26
+ return template.origin.name != path
wagtail/utils/widgets.py CHANGED
@@ -10,6 +10,7 @@ class WidgetWithScript(Widget):
10
10
  warn(
11
11
  "The usage of `WidgetWithScript` hook is deprecated. Use external scripts instead.",
12
12
  category=RemovedInWagtail70Warning,
13
+ stacklevel=3,
13
14
  )
14
15
 
15
16
  def render_html(self, name, value, attrs):
wagtail/views.py CHANGED
@@ -9,6 +9,10 @@ from wagtail.forms import PasswordViewRestrictionForm
9
9
  from wagtail.models import Page, PageViewRestriction
10
10
 
11
11
 
12
+ def serve_chain(page, request, args, kwargs):
13
+ return page.serve(request, *args, **kwargs)
14
+
15
+
12
16
  def serve(request, path):
13
17
  route_result = Page.route_for_request(request, path)
14
18
  if route_result is None:
@@ -16,12 +20,16 @@ def serve(request, path):
16
20
  else:
17
21
  page, args, kwargs = route_result
18
22
 
23
+ on_serve_chain = serve_chain
24
+ for fn in reversed(hooks.get_hooks("on_serve_page")):
25
+ on_serve_chain = fn(on_serve_chain)
26
+
19
27
  for fn in hooks.get_hooks("before_serve_page"):
20
28
  result = fn(page, request, args, kwargs)
21
29
  if isinstance(result, HttpResponse):
22
30
  return result
23
31
 
24
- return page.serve(request, *args, **kwargs)
32
+ return on_serve_chain(page, request, args, kwargs)
25
33
 
26
34
 
27
35
  def authenticate_with_password(request, page_view_restriction_id, page_id):