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
@@ -74,15 +74,16 @@ class TestSnippetUsageView(WagtailTestUtils, TestCase):
74
74
  self.assertEqual(sublabel.get_text(strip=True), "Draft-enabled Bar, In Draft")
75
75
 
76
76
  def test_usage(self):
77
- # resave so that usage count gets updated
78
- page = Page.objects.get(pk=2)
79
- page.save()
80
-
81
- gfk_page = GenericSnippetPage(
82
- title="Foobar Title",
83
- snippet_content_object=Advert.objects.get(pk=1),
84
- )
85
- page.add_child(instance=gfk_page)
77
+ with self.captureOnCommitCallbacks(execute=True):
78
+ # resave so that usage count gets updated
79
+ page = Page.objects.get(pk=2)
80
+ page.save()
81
+
82
+ gfk_page = GenericSnippetPage(
83
+ title="Foobar Title",
84
+ snippet_content_object=Advert.objects.get(pk=1),
85
+ )
86
+ page.add_child(instance=gfk_page)
86
87
 
87
88
  response = self.client.get(
88
89
  reverse(
@@ -124,9 +125,10 @@ class TestSnippetUsageView(WagtailTestUtils, TestCase):
124
125
  self.assertRedirects(response, reverse("wagtailadmin_home"))
125
126
 
126
127
  def test_usage_without_edit_permission_on_page(self):
127
- # resave so that usage count gets updated
128
- page = Page.objects.get(pk=2)
129
- page.save()
128
+ with self.captureOnCommitCallbacks(execute=True):
129
+ # resave so that usage count gets updated
130
+ page = Page.objects.get(pk=2)
131
+ page.save()
130
132
 
131
133
  # Create a user with edit access to snippets but not pages
132
134
  user = self.create_user(
@@ -157,9 +159,10 @@ class TestSnippetUsageView(WagtailTestUtils, TestCase):
157
159
  self.assertContains(response, "<li>Advert</li>", html=True)
158
160
 
159
161
  def test_usage_with_describe_on_delete_cascade(self):
160
- # resave so that usage count gets updated
161
- page = Page.objects.get(pk=2)
162
- page.save()
162
+ with self.captureOnCommitCallbacks(execute=True):
163
+ # resave so that usage count gets updated
164
+ page = Page.objects.get(pk=2)
165
+ page.save()
163
166
 
164
167
  response = self.client.get(
165
168
  reverse("wagtailsnippets_tests_advert:usage", args=["1"])
@@ -173,9 +176,10 @@ class TestSnippetUsageView(WagtailTestUtils, TestCase):
173
176
  self.assertContains(response, ": the advert placement will also be deleted")
174
177
 
175
178
  def test_usage_with_describe_on_delete_set_null(self):
176
- # resave so that usage count gets updated
177
- page = EventPage.objects.first()
178
- page.save()
179
+ with self.captureOnCommitCallbacks(execute=True):
180
+ # resave so that usage count gets updated
181
+ page = EventPage.objects.first()
182
+ page.save()
179
183
 
180
184
  self.assertEqual(page.feed_image.get_usage().count(), 1)
181
185
 
@@ -191,13 +195,14 @@ class TestSnippetUsageView(WagtailTestUtils, TestCase):
191
195
  self.assertContains(response, ": will unset the reference")
192
196
 
193
197
  def test_usage_with_describe_on_delete_gfk(self):
194
- advert = Advert.objects.get(pk=1)
198
+ with self.captureOnCommitCallbacks(execute=True):
199
+ advert = Advert.objects.get(pk=1)
195
200
 
196
- gfk_page = GenericSnippetPage(
197
- title="Foobar Title",
198
- snippet_content_object=advert,
199
- )
200
- Page.objects.get(pk=1).add_child(instance=gfk_page)
201
+ gfk_page = GenericSnippetPage(
202
+ title="Foobar Title",
203
+ snippet_content_object=advert,
204
+ )
205
+ Page.objects.get(pk=1).add_child(instance=gfk_page)
201
206
 
202
207
  self.assertEqual(ReferenceIndex.get_grouped_references_to(advert).count(), 1)
203
208
 
@@ -95,7 +95,7 @@ class TestCustomIcon(BaseSnippetViewSetTests):
95
95
  (
96
96
  "revisions_compare",
97
97
  [pk, self.revision_1.id, self.revision_2.id],
98
- "header.html",
98
+ "headers/slim_header.html",
99
99
  ),
100
100
  ("revisions_unschedule", [pk, self.revision_2.id], "header.html"),
101
101
  ]
@@ -358,18 +358,6 @@ class PreviewRevisionView(PermissionCheckedMixin, PreviewRevision):
358
358
  class RevisionsCompareView(PermissionCheckedMixin, generic.RevisionsCompareView):
359
359
  permission_required = "change"
360
360
 
361
- @property
362
- def edit_label(self):
363
- return _("Edit this %(model_name)s") % {
364
- "model_name": self.model._meta.verbose_name
365
- }
366
-
367
- @property
368
- def history_label(self):
369
- return _("%(model_name)s history") % {
370
- "model_name": self.model._meta.verbose_name
371
- }
372
-
373
361
 
374
362
  class UnpublishView(PermissionCheckedMixin, generic.UnpublishView):
375
363
  permission_required = "publish"
wagtail/tasks.py ADDED
@@ -0,0 +1,41 @@
1
+ from django.apps import apps
2
+ from django.db import transaction
3
+ from django.utils.module_loading import import_string
4
+ from django_tasks import task
5
+ from modelcluster.fields import ParentalKey
6
+
7
+ from wagtail.models import ReferenceIndex
8
+
9
+
10
+ @task()
11
+ def update_reference_index_task(app_label, model_name, pk):
12
+ model = apps.get_model(app_label, model_name)
13
+ instance = model.objects.get(pk=pk)
14
+
15
+ # If the model is a child model, find the parent instance and index that instead
16
+ while True:
17
+ parental_keys = list(
18
+ filter(
19
+ lambda field: isinstance(field, ParentalKey),
20
+ instance._meta.get_fields(),
21
+ )
22
+ )
23
+ if not parental_keys:
24
+ break
25
+
26
+ instance = getattr(instance, parental_keys[0].name)
27
+ if instance is None:
28
+ # parent is null, so there is no valid object to record references against
29
+ return
30
+
31
+ if ReferenceIndex.is_indexed(instance._meta.model):
32
+ with transaction.atomic():
33
+ ReferenceIndex.create_or_update_for_object(instance)
34
+
35
+
36
+ @task()
37
+ def delete_file_from_storage_task(deconstructed_storage, path):
38
+ storage_module, storage_args, storage_kwargs = deconstructed_storage
39
+ storage = import_string(storage_module)(*storage_args, **storage_kwargs)
40
+
41
+ storage.delete(path)
@@ -0,0 +1,29 @@
1
+ {% load wagtailcore_tags i18n %}
2
+ {% get_current_language as LANGUAGE_CODE %}
3
+ {% get_current_language_bidi as LANGUAGE_BIDI %}
4
+ <!DOCTYPE html>
5
+ <html lang="{{ LANGUAGE_CODE }}" dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}">
6
+ <head>
7
+ {% block head %}
8
+ <meta charset="UTF-8" />
9
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
10
+ <meta name="robots" content="noindex" />
11
+ <title>{{ page_title }}</title>
12
+
13
+ {% block css %}
14
+ {% endblock %}
15
+ {% endblock %}
16
+ </head>
17
+ <body>
18
+ {% block body %}
19
+ <main>
20
+ {% block content %}
21
+ {% include_block bound_block %}
22
+ {% endblock %}
23
+ </main>
24
+
25
+ {% block js %}
26
+ {% endblock %}
27
+ {% endblock %}
28
+ </body>
29
+ </html>
File without changes
@@ -0,0 +1,37 @@
1
+ # Generated by Django 5.1.4 on 2025-01-03 15:30
2
+
3
+ import django.db.models.deletion
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ initial = True
10
+
11
+ dependencies = [
12
+ ("wagtailcore", "0094_alter_page_locale"),
13
+ ]
14
+
15
+ operations = [
16
+ migrations.CreateModel(
17
+ name="EarlyPage",
18
+ fields=[
19
+ (
20
+ "page_ptr",
21
+ models.OneToOneField(
22
+ auto_created=True,
23
+ on_delete=django.db.models.deletion.CASCADE,
24
+ parent_link=True,
25
+ primary_key=True,
26
+ serialize=False,
27
+ to="wagtailcore.page",
28
+ ),
29
+ ),
30
+ ("intro", models.TextField(blank=True)),
31
+ ],
32
+ options={
33
+ "abstract": False,
34
+ },
35
+ bases=("wagtailcore.page",),
36
+ ),
37
+ ]
File without changes
@@ -0,0 +1,14 @@
1
+ # This module DOES NOT import from wagtail.admin -
2
+ # this tests that we are able to define Page models before wagtail.admin is loaded.
3
+
4
+ from django.db import models
5
+
6
+ from wagtail.models import Page
7
+
8
+
9
+ class EarlyPage(Page):
10
+ intro = models.TextField(blank=True)
11
+
12
+ content_panels = Page.content_panels + [
13
+ "intro",
14
+ ]
wagtail/test/settings.py CHANGED
@@ -135,6 +135,9 @@ MIDDLEWARE = (
135
135
  )
136
136
 
137
137
  INSTALLED_APPS = [
138
+ # Place wagtail.test.earlypage first, to test the behaviour of page models
139
+ # that are defined before wagtail.admin is loaded
140
+ "wagtail.test.earlypage",
138
141
  # Install wagtailredirects with its appconfig
139
142
  # There's nothing special about wagtailredirects, we just need to have one
140
143
  # app which uses AppConfigs to test that hooks load properly
@@ -1027,5 +1027,12 @@
1027
1027
  "fields": {
1028
1028
  "content": "non-url-safe pk modelwithstringtypeprimarykey model"
1029
1029
  }
1030
+ },
1031
+ {
1032
+ "pk": "web_407269_1",
1033
+ "model": "tests.modelwithstringtypeprimarykey",
1034
+ "fields": {
1035
+ "content": "unquote-sensitive modelwithstringtypeprimarykey model"
1036
+ }
1030
1037
  }
1031
1038
  ]
@@ -141,7 +141,8 @@
141
141
  "audience": "public",
142
142
  "location": "The moon",
143
143
  "body": "<p>I haven't worked out the details yet, but it's going to have cake and ponies</p>",
144
- "cost": "Free"
144
+ "cost": "Free",
145
+ "feed_image": 1
145
146
  }
146
147
  },
147
148
 
@@ -170,7 +171,8 @@
170
171
  "audience": "private",
171
172
  "location": "The moon",
172
173
  "body": "<p>your name's not down, you're not coming in</p>",
173
- "cost": "Free (but not for you)"
174
+ "cost": "Free (but not for you)",
175
+ "feed_image": 1
174
176
  }
175
177
  },
176
178
 
@@ -246,7 +248,8 @@
246
248
  "audience": "public",
247
249
  "location": "Hobart",
248
250
  "body": "<p>Party time</p>",
249
- "cost": "free"
251
+ "cost": "free",
252
+ "feed_image": 1
250
253
  }
251
254
  },
252
255
 
@@ -44,7 +44,8 @@ from wagtail.blocks import (
44
44
  StreamBlock,
45
45
  StructBlock,
46
46
  )
47
- from wagtail.contrib.forms.forms import FormBuilder
47
+ from wagtail.compat import HTTPMethod
48
+ from wagtail.contrib.forms.forms import FormBuilder, WagtailAdminFormPageForm
48
49
  from wagtail.contrib.forms.models import (
49
50
  FORM_FIELD_CHOICES,
50
51
  AbstractEmailForm,
@@ -93,12 +94,7 @@ EVENT_AUDIENCE_CHOICES = (
93
94
  )
94
95
 
95
96
 
96
- COMMON_PANELS = (
97
- FieldPanel("slug"),
98
- FieldPanel("seo_title"),
99
- FieldPanel("show_in_menus"),
100
- FieldPanel("search_description"),
101
- )
97
+ COMMON_PANELS = ("slug", "seo_title", "show_in_menus", "search_description")
102
98
 
103
99
  CUSTOM_PREVIEW_SIZES = [
104
100
  {
@@ -170,9 +166,9 @@ class CarouselItem(LinkFields):
170
166
  caption = models.CharField(max_length=255, blank=True)
171
167
 
172
168
  panels = [
173
- FieldPanel("image"),
174
- FieldPanel("embed_url"),
175
- FieldPanel("caption"),
169
+ "image",
170
+ "embed_url",
171
+ "caption",
176
172
  MultiFieldPanel(LinkFields.panels, "Link"),
177
173
  ]
178
174
 
@@ -187,7 +183,7 @@ class RelatedLink(LinkFields):
187
183
  title = models.CharField(max_length=255, help_text="Link title")
188
184
 
189
185
  panels = [
190
- FieldPanel("title"),
186
+ "title",
191
187
  MultiFieldPanel(LinkFields.panels, "Link"),
192
188
  ]
193
189
 
@@ -320,10 +316,7 @@ class EventPageSpeakerAward(TranslatableMixin, Orderable, models.Model):
320
316
  name = models.CharField("Award name", max_length=255)
321
317
  date_awarded = models.DateField(null=True, blank=True)
322
318
 
323
- panels = [
324
- FieldPanel("name"),
325
- FieldPanel("date_awarded"),
326
- ]
319
+ panels = ["name", "date_awarded"]
327
320
 
328
321
  class Meta(TranslatableMixin.Meta, Orderable.Meta):
329
322
  pass
@@ -351,9 +344,9 @@ class EventPageSpeaker(TranslatableMixin, Orderable, LinkFields, ClusterableMode
351
344
  return self.first_name + " " + self.last_name
352
345
 
353
346
  panels = [
354
- FieldPanel("first_name"),
355
- FieldPanel("last_name"),
356
- FieldPanel("image"),
347
+ "first_name",
348
+ "last_name",
349
+ "image",
357
350
  MultiFieldPanel(LinkFields.panels, "Link"),
358
351
  InlinePanel("awards", label="Awards"),
359
352
  ]
@@ -422,24 +415,24 @@ class EventPage(Page):
422
415
 
423
416
  content_panels = [
424
417
  FieldPanel("title", classname="title"),
425
- FieldPanel("date_from"),
426
- FieldPanel("date_to"),
427
- FieldPanel("time_from"),
428
- FieldPanel("time_to"),
429
- FieldPanel("location"),
418
+ "date_from",
419
+ "date_to",
420
+ "time_from",
421
+ "time_to",
422
+ "location",
430
423
  FieldPanel("audience", help_text="Who this event is for"),
431
- FieldPanel("cost"),
432
- FieldPanel("signup_link"),
424
+ "cost",
425
+ "signup_link",
433
426
  InlinePanel("carousel_items", label="Carousel items"),
434
- FieldPanel("body"),
427
+ "body",
435
428
  InlinePanel(
436
429
  "speakers",
437
- label="Speakers",
430
+ label="Speaker",
438
431
  heading="Speaker lineup",
439
432
  help_text="Put the keynote speaker first",
440
433
  ),
441
434
  InlinePanel("related_links", label="Related links"),
442
- FieldPanel("categories"),
435
+ "categories",
443
436
  # InlinePanel related model uses `pk` not `id`
444
437
  InlinePanel("head_counts", label="Head Counts"),
445
438
  ]
@@ -527,6 +520,9 @@ class EventIndex(Page):
527
520
  intro = RichTextField(blank=True, max_length=50)
528
521
  ajax_template = "tests/includes/event_listing.html"
529
522
 
523
+ # NOTE: Using a mix of enum and string values to test handling of both
524
+ allowed_http_methods = [HTTPMethod.GET, "OPTIONS"]
525
+
530
526
  def get_events(self):
531
527
  return self.get_children().live().type(EventPage)
532
528
 
@@ -715,18 +711,37 @@ class FormPageWithRedirect(AbstractEmailForm):
715
711
  # FormPage with a custom FormSubmission
716
712
 
717
713
 
714
+ class FormPageWithCustomSubmissionForm(WagtailAdminFormPageForm):
715
+ """
716
+ Used to validate that admin forms can validate the page's submissions via
717
+ extending the form class.
718
+ """
719
+
720
+ def clean(self):
721
+ cleaned_data = super().clean()
722
+ from_address = cleaned_data.get("from_address")
723
+ if from_address and "example.com" in from_address:
724
+ raise ValidationError("Email cannot be from example.com")
725
+
726
+ return cleaned_data
727
+
728
+
718
729
  class FormPageWithCustomSubmission(AbstractEmailForm):
719
730
  """
720
- This Form page:
721
- * Have custom submission model
722
- * Have custom related_name (see `FormFieldWithCustomSubmission.page`)
723
- * Saves reference to a user
724
- * Doesn't render html form, if submission for current user is present
731
+ A ``FormPage`` with a custom FormSubmission and other extensive customizations:
732
+
733
+ * A custom submission model
734
+ * A custom related_name (see `FormFieldWithCustomSubmission.page`)
735
+ * Saves reference to a user
736
+ * Doesn't render html form, if submission for current user is present
737
+ * A custom clean method that does not allow the ``from_address`` to be set to anything including example.com
725
738
  """
726
739
 
727
740
  intro = RichTextField(blank=True)
728
741
  thank_you_text = RichTextField(blank=True)
729
742
 
743
+ base_form_class = FormPageWithCustomSubmissionForm
744
+
730
745
  def get_context(self, request, *args, **kwargs):
731
746
  context = super().get_context(request)
732
747
  context["greeting"] = "hello world"
@@ -1816,12 +1831,12 @@ class ImportantPagesGenericSetting(BaseGenericSetting):
1816
1831
  verbose_name_plural = _("Important pages settings")
1817
1832
 
1818
1833
 
1819
- @register_setting(icon="icon-setting-tag")
1834
+ @register_setting(icon="tag")
1820
1835
  class IconSiteSetting(BaseSiteSetting):
1821
1836
  pass
1822
1837
 
1823
1838
 
1824
- @register_setting(icon="icon-setting-tag")
1839
+ @register_setting(icon="tag")
1825
1840
  class IconGenericSetting(BaseGenericSetting):
1826
1841
  pass
1827
1842
 
@@ -2149,13 +2164,13 @@ class PersonPage(Page):
2149
2164
  content_panels = Page.content_panels + [
2150
2165
  MultiFieldPanel(
2151
2166
  [
2152
- FieldPanel("first_name"),
2153
- FieldPanel("last_name"),
2167
+ "first_name",
2168
+ "last_name",
2154
2169
  ],
2155
2170
  "Person",
2156
2171
  ),
2157
- InlinePanel("addresses", label="Address"),
2158
- InlinePanel("social_links", label="Social links"),
2172
+ "addresses",
2173
+ "social_links",
2159
2174
  ]
2160
2175
 
2161
2176
  class Meta:
@@ -2205,10 +2220,7 @@ class SocialLink(index.Indexed, ClusterableModel):
2205
2220
  to="tests.PersonPage", related_name="social_links", verbose_name="Person"
2206
2221
  )
2207
2222
 
2208
- panels = [
2209
- FieldPanel("url"),
2210
- FieldPanel("kind"),
2211
- ]
2223
+ panels = ["url", "kind"]
2212
2224
 
2213
2225
  class Meta:
2214
2226
  verbose_name = "Social link"
@@ -2340,7 +2352,9 @@ class ModelWithNullableParentalKey(models.Model):
2340
2352
 
2341
2353
  class GalleryPage(Page):
2342
2354
  content_panels = Page.content_panels + [
2343
- MultipleChooserPanel("gallery_images", chooser_field_name="image")
2355
+ MultipleChooserPanel(
2356
+ "gallery_images", heading="Gallery images", chooser_field_name="image"
2357
+ )
2344
2358
  ]
2345
2359
 
2346
2360
 
@@ -0,0 +1,16 @@
1
+ {% extends "wagtailcore/shared/block_preview.html" %}
2
+ {% load static %}
3
+
4
+ {% block content %}
5
+ <div class="my-preview-wrapper">
6
+ {{ block.super }}
7
+ </div>
8
+ {% endblock %}
9
+
10
+ {% block css %}
11
+ <link rel="stylesheet" href="{% static 'css/custom.css' %}">
12
+ {% endblock %}
13
+
14
+ {% block js %}
15
+ <script type="module" src="{% static 'js/custom.js' %}"></script>
16
+ {% endblock %}
@@ -0,0 +1,5 @@
1
+ {% extends "wagtailcore/shared/block_preview.html" %}
2
+ {% load static %}
3
+ {% block content %}
4
+ <img src="{% static image_path %}" alt="{{ image_description }}" />
5
+ {% endblock %}
@@ -1,3 +1,5 @@
1
+ import os
2
+
1
3
  from django import forms
2
4
  from django.http import HttpResponse
3
5
  from django.utils.safestring import mark_safe
@@ -432,3 +434,10 @@ def register_animated_advert_chooser_viewset():
432
434
  @hooks.register("register_admin_viewset")
433
435
  def register_event_page_listing_viewset():
434
436
  return event_page_listing_viewset
437
+
438
+
439
+ @hooks.register("get_avatar_url")
440
+ def register_avatar_intercept_url(user, size):
441
+ if os.environ.get("AVATAR_INTERCEPT"):
442
+ return "/some/avatar/fred.png"
443
+ return None