wagtail 7.1.2__py3-none-any.whl → 7.2rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. wagtail/__init__.py +1 -1
  2. wagtail/actions/copy_page.py +1 -1
  3. wagtail/actions/create_alias.py +1 -1
  4. wagtail/actions/delete_page.py +1 -1
  5. wagtail/actions/publish_page_revision.py +1 -1
  6. wagtail/actions/publish_revision.py +1 -1
  7. wagtail/actions/revert_to_page_revision.py +1 -1
  8. wagtail/actions/unpublish.py +1 -1
  9. wagtail/actions/unpublish_page.py +1 -1
  10. wagtail/admin/auth.py +3 -1
  11. wagtail/admin/checks.py +2 -2
  12. wagtail/admin/filters.py +28 -1
  13. wagtail/admin/forms/collections.py +1 -1
  14. wagtail/admin/forms/comments.py +1 -1
  15. wagtail/admin/forms/models.py +1 -1
  16. wagtail/admin/forms/pages.py +1 -1
  17. wagtail/admin/forms/tags.py +1 -1
  18. wagtail/admin/locale/cs/LC_MESSAGES/django.mo +0 -0
  19. wagtail/admin/locale/cs/LC_MESSAGES/django.po +25 -1
  20. wagtail/admin/locale/en/LC_MESSAGES/django.po +278 -192
  21. wagtail/admin/locale/en/LC_MESSAGES/djangojs.po +29 -15
  22. wagtail/admin/locale/it/LC_MESSAGES/django.mo +0 -0
  23. wagtail/admin/locale/it/LC_MESSAGES/django.po +3 -2
  24. wagtail/admin/locale/nl/LC_MESSAGES/django.mo +0 -0
  25. wagtail/admin/locale/nl/LC_MESSAGES/django.po +57 -3
  26. wagtail/admin/locale/nl/LC_MESSAGES/djangojs.mo +0 -0
  27. wagtail/admin/locale/nl/LC_MESSAGES/djangojs.po +8 -2
  28. wagtail/admin/locale/ru/LC_MESSAGES/django.mo +0 -0
  29. wagtail/admin/locale/ru/LC_MESSAGES/django.po +58 -1
  30. wagtail/admin/locale/tr/LC_MESSAGES/django.mo +0 -0
  31. wagtail/admin/locale/tr/LC_MESSAGES/django.po +3 -2
  32. wagtail/admin/static/wagtailadmin/css/core.css +1 -1
  33. wagtail/admin/static/wagtailadmin/js/bulk-actions.js +1 -1
  34. wagtail/admin/static/wagtailadmin/js/chooser-modal.js +1 -1
  35. wagtail/admin/static/wagtailadmin/js/chooser-widget-telepath.js +1 -1
  36. wagtail/admin/static/wagtailadmin/js/chooser-widget.js +1 -1
  37. wagtail/admin/static/wagtailadmin/js/comments.js +1 -1
  38. wagtail/admin/static/wagtailadmin/js/core.js +1 -1
  39. wagtail/admin/static/wagtailadmin/js/core.js.LICENSE.txt +2 -2
  40. wagtail/admin/static/wagtailadmin/js/date-time-chooser.js +1 -1
  41. wagtail/admin/static/wagtailadmin/js/draftail.js +1 -1
  42. wagtail/admin/static/wagtailadmin/js/filtered-select.js +1 -1
  43. wagtail/admin/static/wagtailadmin/js/icons.js +1 -1
  44. wagtail/admin/static/wagtailadmin/js/modal-workflow.js +1 -1
  45. wagtail/admin/static/wagtailadmin/js/page-chooser-modal.js +1 -1
  46. wagtail/admin/static/wagtailadmin/js/page-chooser-telepath.js +1 -1
  47. wagtail/admin/static/wagtailadmin/js/page-chooser.js +1 -1
  48. wagtail/admin/static/wagtailadmin/js/privacy-switch.js +1 -1
  49. wagtail/admin/static/wagtailadmin/js/sidebar.js +1 -1
  50. wagtail/admin/static/wagtailadmin/js/task-chooser-modal.js +1 -1
  51. wagtail/admin/static/wagtailadmin/js/task-chooser.js +1 -1
  52. wagtail/admin/static/wagtailadmin/js/telepath/blocks.js +1 -1
  53. wagtail/admin/static/wagtailadmin/js/telepath/telepath.js +1 -1
  54. wagtail/admin/static/wagtailadmin/js/telepath/widgets.js +1 -1
  55. wagtail/admin/static/wagtailadmin/js/userbar.js +1 -1
  56. wagtail/admin/static/wagtailadmin/js/userbar.js.LICENSE.txt +2 -2
  57. wagtail/admin/static/wagtailadmin/js/vendor/bootstrap-modal.js +1 -1
  58. wagtail/admin/static/wagtailadmin/js/vendor/bootstrap-transition.js +1 -1
  59. wagtail/admin/static/wagtailadmin/js/vendor/jquery-3.6.0.min.js +1 -1
  60. wagtail/admin/static/wagtailadmin/js/vendor/jquery-ui-1.13.2.min.js +1 -1
  61. wagtail/admin/static/wagtailadmin/js/vendor/jquery.datetimepicker.js +1 -1
  62. wagtail/admin/static/wagtailadmin/js/vendor/jquery.fileupload-process.js +1 -1
  63. wagtail/admin/static/wagtailadmin/js/vendor/jquery.fileupload.js +1 -1
  64. wagtail/admin/static/wagtailadmin/js/vendor/jquery.iframe-transport.js +1 -1
  65. wagtail/admin/static/wagtailadmin/js/vendor/tag-it.js +1 -1
  66. wagtail/admin/static/wagtailadmin/js/vendor.js +1 -1
  67. wagtail/admin/static/wagtailadmin/js/vendor.js.LICENSE.txt +1 -1
  68. wagtail/admin/static/wagtailadmin/js/wagtailadmin.js +1 -1
  69. wagtail/admin/static/wagtailadmin/js/workflow-action.js +1 -1
  70. wagtail/admin/templates/wagtailadmin/account/account.html +2 -0
  71. wagtail/admin/templates/wagtailadmin/base.html +14 -0
  72. wagtail/admin/templates/wagtailadmin/generic/chooser/chooser.html +2 -1
  73. wagtail/admin/templates/wagtailadmin/generic/chooser/creation_form.html +2 -1
  74. wagtail/admin/templates/wagtailadmin/panels/multi_field_panel_child.html +1 -1
  75. wagtail/admin/templates/wagtailadmin/panels/object_list.html +1 -1
  76. wagtail/admin/templates/wagtailadmin/panels/tabbed_interface.html +3 -2
  77. wagtail/admin/templates/wagtailadmin/shared/formatted_field.html +1 -1
  78. wagtail/admin/templates/wagtailadmin/shared/forms/single_checkbox.html +1 -1
  79. wagtail/admin/templates/wagtailadmin/shared/keyboard_shortcuts_dialog.html +19 -0
  80. wagtail/admin/templates/wagtailadmin/shared/panel.html +1 -1
  81. wagtail/admin/templates/wagtailadmin/shared/set_privacy.html +15 -0
  82. wagtail/admin/templates/wagtailadmin/shared/side_panels/checks.html +28 -1
  83. wagtail/admin/templates/wagtailadmin/shared/workflow_history/detail.html +2 -2
  84. wagtail/admin/templates/wagtailadmin/{pages/listing/_ordering_header.html → tables/ordering_header.html} +2 -2
  85. wagtail/admin/templates/wagtailadmin/tables/title_cell.html +1 -1
  86. wagtail/admin/templates/wagtailadmin/widgets/{daterange_input.html → range_input.html} +1 -1
  87. wagtail/admin/templates/wagtailadmin/workflows/task_chooser/chooser.html +4 -2
  88. wagtail/admin/templatetags/wagtailadmin_tags.py +41 -22
  89. wagtail/admin/tests/api/test_pages.py +7 -7
  90. wagtail/admin/tests/api/test_renderer_classes.py +16 -0
  91. wagtail/admin/tests/pages/test_create_page.py +34 -2
  92. wagtail/admin/tests/pages/test_edit_page.py +128 -14
  93. wagtail/admin/tests/pages/test_explorer_view.py +34 -7
  94. wagtail/admin/tests/pages/test_reorder_page.py +11 -0
  95. wagtail/admin/tests/test_collections_views.py +12 -0
  96. wagtail/admin/tests/test_edit_handlers.py +3 -3
  97. wagtail/admin/tests/test_filters.py +2 -2
  98. wagtail/admin/tests/test_keyboard_shortcuts.py +52 -2
  99. wagtail/admin/tests/test_menu.py +0 -2
  100. wagtail/admin/tests/test_privacy.py +16 -16
  101. wagtail/admin/tests/test_templatetags.py +137 -0
  102. wagtail/admin/tests/test_workflows.py +34 -0
  103. wagtail/admin/tests/viewsets/test_model_viewset.py +322 -0
  104. wagtail/admin/ui/tables/orderable.py +73 -0
  105. wagtail/admin/ui/tables/pages.py +3 -13
  106. wagtail/admin/views/collection_privacy.py +6 -2
  107. wagtail/admin/views/generic/__init__.py +1 -0
  108. wagtail/admin/views/generic/mixins.py +20 -2
  109. wagtail/admin/views/generic/models.py +67 -1
  110. wagtail/admin/views/generic/ordering.py +79 -0
  111. wagtail/admin/views/home.py +3 -3
  112. wagtail/admin/views/page_privacy.py +5 -2
  113. wagtail/admin/views/pages/create.py +1 -1
  114. wagtail/admin/views/pages/edit.py +2 -2
  115. wagtail/admin/views/pages/listing.py +7 -42
  116. wagtail/admin/views/pages/move.py +1 -1
  117. wagtail/admin/views/pages/ordering.py +1 -1
  118. wagtail/admin/viewsets/base.py +1 -1
  119. wagtail/admin/viewsets/model.py +49 -1
  120. wagtail/admin/wagtail_hooks.py +2 -1
  121. wagtail/admin/widgets/slug.py +10 -10
  122. wagtail/api/v2/serializers.py +1 -1
  123. wagtail/api/v2/tests/test_renderer_classes.py +32 -0
  124. wagtail/apps.py +2 -0
  125. wagtail/bin/wagtail.py +1 -1
  126. wagtail/contrib/forms/locale/en/LC_MESSAGES/django.po +14 -14
  127. wagtail/contrib/forms/locale/nl/LC_MESSAGES/django.mo +0 -0
  128. wagtail/contrib/forms/locale/nl/LC_MESSAGES/django.po +19 -2
  129. wagtail/contrib/forms/locale/ru/LC_MESSAGES/django.mo +0 -0
  130. wagtail/contrib/forms/locale/ru/LC_MESSAGES/django.po +18 -1
  131. wagtail/contrib/frontend_cache/tests.py +4 -2
  132. wagtail/contrib/redirects/locale/en/LC_MESSAGES/django.po +4 -4
  133. wagtail/contrib/redirects/tests/test_tmp_storages.py +20 -0
  134. wagtail/contrib/redirects/tmp_storages.py +1 -1
  135. wagtail/contrib/redirects/views.py +3 -3
  136. wagtail/contrib/search_promotions/locale/en/LC_MESSAGES/django.po +3 -3
  137. wagtail/contrib/search_promotions/locale/tr/LC_MESSAGES/django.mo +0 -0
  138. wagtail/contrib/search_promotions/locale/tr/LC_MESSAGES/django.po +43 -3
  139. wagtail/contrib/search_promotions/static/wagtailsearchpromotions/js/query-chooser-modal.js +1 -1
  140. wagtail/contrib/search_promotions/views/settings.py +2 -2
  141. wagtail/contrib/settings/locale/cs/LC_MESSAGES/django.mo +0 -0
  142. wagtail/contrib/settings/locale/cs/LC_MESSAGES/django.po +6 -1
  143. wagtail/contrib/settings/locale/en/LC_MESSAGES/django.po +1 -1
  144. wagtail/contrib/settings/locale/nl/LC_MESSAGES/django.mo +0 -0
  145. wagtail/contrib/settings/locale/nl/LC_MESSAGES/django.po +6 -2
  146. wagtail/contrib/settings/locale/ru/LC_MESSAGES/django.mo +0 -0
  147. wagtail/contrib/settings/locale/ru/LC_MESSAGES/django.po +6 -1
  148. wagtail/contrib/settings/tests/site_specific/test_admin.py +40 -6
  149. wagtail/contrib/simple_translation/locale/en/LC_MESSAGES/django.po +1 -1
  150. wagtail/contrib/styleguide/locale/en/LC_MESSAGES/django.po +1 -1
  151. wagtail/contrib/styleguide/templates/wagtailstyleguide/base.html +5 -5
  152. wagtail/contrib/table_block/blocks.py +1 -0
  153. wagtail/contrib/table_block/locale/en/LC_MESSAGES/django.po +5 -1
  154. wagtail/contrib/table_block/static/table_block/js/table.js +1 -1
  155. wagtail/contrib/typed_table_block/locale/en/LC_MESSAGES/django.po +1 -1
  156. wagtail/contrib/typed_table_block/static/typed_table_block/js/typed_table_block.js +1 -1
  157. wagtail/coreutils.py +5 -5
  158. wagtail/documents/forms.py +18 -1
  159. wagtail/documents/locale/en/LC_MESSAGES/django.po +10 -10
  160. wagtail/documents/locale/nl/LC_MESSAGES/django.mo +0 -0
  161. wagtail/documents/locale/nl/LC_MESSAGES/django.po +9 -0
  162. wagtail/documents/locale/ru/LC_MESSAGES/django.mo +0 -0
  163. wagtail/documents/locale/ru/LC_MESSAGES/django.po +9 -0
  164. wagtail/documents/models.py +1 -1
  165. wagtail/documents/static/wagtaildocs/js/add-multiple.js +1 -1
  166. wagtail/documents/static/wagtaildocs/js/document-chooser-modal.js +1 -1
  167. wagtail/documents/static/wagtaildocs/js/document-chooser-telepath.js +1 -1
  168. wagtail/documents/static/wagtaildocs/js/document-chooser.js +1 -1
  169. wagtail/documents/templates/wagtaildocs/documents/add.html +0 -34
  170. wagtail/documents/tests/test_admin_views.py +132 -26
  171. wagtail/documents/tests/test_collection_privacy.py +18 -4
  172. wagtail/documents/tests/test_form_overrides.py +1 -1
  173. wagtail/documents/tests/test_search.py +21 -8
  174. wagtail/documents/views/documents.py +1 -1
  175. wagtail/embeds/locale/en/LC_MESSAGES/django.po +1 -1
  176. wagtail/embeds/static/wagtailembeds/js/embed-chooser-modal.js +1 -1
  177. wagtail/images/forms.py +16 -1
  178. wagtail/images/locale/cs/LC_MESSAGES/django.mo +0 -0
  179. wagtail/images/locale/cs/LC_MESSAGES/django.po +12 -1
  180. wagtail/images/locale/en/LC_MESSAGES/django.po +57 -46
  181. wagtail/images/locale/nl/LC_MESSAGES/django.mo +0 -0
  182. wagtail/images/locale/nl/LC_MESSAGES/django.po +37 -14
  183. wagtail/images/locale/ru/LC_MESSAGES/django.mo +0 -0
  184. wagtail/images/locale/ru/LC_MESSAGES/django.po +20 -1
  185. wagtail/images/models.py +1 -1
  186. wagtail/images/static/wagtailimages/js/add-multiple.js +1 -1
  187. wagtail/images/static/wagtailimages/js/focal-point-chooser.js +1 -1
  188. wagtail/images/static/wagtailimages/js/image-block.js +1 -1
  189. wagtail/images/static/wagtailimages/js/image-chooser-modal.js +1 -1
  190. wagtail/images/static/wagtailimages/js/image-chooser-telepath.js +1 -1
  191. wagtail/images/static/wagtailimages/js/image-chooser.js +1 -1
  192. wagtail/images/static/wagtailimages/js/image-url-generator.js +1 -1
  193. wagtail/images/static/wagtailimages/js/vendor/jquery.Jcrop.min.js +1 -1
  194. wagtail/images/static/wagtailimages/js/vendor/jquery.fileupload-image.js +1 -1
  195. wagtail/images/static/wagtailimages/js/vendor/jquery.fileupload-validate.js +1 -1
  196. wagtail/images/static/wagtailimages/js/vendor/load-image.min.js +1 -1
  197. wagtail/images/templates/wagtailimages/chooser/chooser.html +22 -13
  198. wagtail/images/templates/wagtailimages/chooser/image_preview_column_cell.html +10 -0
  199. wagtail/images/templates/wagtailimages/chooser/results.html +24 -20
  200. wagtail/images/templates/wagtailimages/chooser/title_column_cell.html +15 -0
  201. wagtail/images/templates/wagtailimages/images/add.html +0 -34
  202. wagtail/images/templates/wagtailimages/images/index.html +3 -3
  203. wagtail/images/templates/wagtailimages/images/index_results.html +1 -1
  204. wagtail/images/templates/wagtailimages/images/layout_toggle_button.html +8 -7
  205. wagtail/images/templatetags/wagtailimages_tags.py +2 -2
  206. wagtail/images/tests/test_admin_views.py +87 -0
  207. wagtail/images/tests/test_form_overrides.py +1 -1
  208. wagtail/images/tests/test_models.py +48 -9
  209. wagtail/images/views/chooser.py +66 -2
  210. wagtail/locale/en/LC_MESSAGES/django.po +55 -55
  211. wagtail/locale/is_IS/LC_MESSAGES/django.mo +0 -0
  212. wagtail/locale/is_IS/LC_MESSAGES/django.po +3 -3
  213. wagtail/locale/nl/LC_MESSAGES/django.mo +0 -0
  214. wagtail/locale/nl/LC_MESSAGES/django.po +11 -2
  215. wagtail/locale/ru/LC_MESSAGES/django.mo +0 -0
  216. wagtail/locale/ru/LC_MESSAGES/django.po +11 -1
  217. wagtail/locales/locale/en/LC_MESSAGES/django.po +1 -1
  218. wagtail/locales/locale/nl/LC_MESSAGES/django.mo +0 -0
  219. wagtail/locales/locale/nl/LC_MESSAGES/django.po +12 -1
  220. wagtail/locales/locale/ru/LC_MESSAGES/django.mo +0 -0
  221. wagtail/locales/locale/ru/LC_MESSAGES/django.po +10 -1
  222. wagtail/locales/views.py +2 -2
  223. wagtail/models/orderable.py +10 -0
  224. wagtail/models/pages.py +9 -11
  225. wagtail/models/sites.py +1 -1
  226. wagtail/models/workflows.py +8 -5
  227. wagtail/project_template/home/tests.py +6 -7
  228. wagtail/project_template/project_name/settings/base.py +9 -9
  229. wagtail/project_template/requirements.txt +1 -1
  230. wagtail/query.py +7 -2
  231. wagtail/rich_text/rewriters.py +1 -1
  232. wagtail/search/apps.py +4 -49
  233. wagtail/search/backends/__init__.py +1 -113
  234. wagtail/search/backends/base.py +1 -547
  235. wagtail/search/backends/database/__init__.py +1 -50
  236. wagtail/search/backends/database/fallback.py +1 -253
  237. wagtail/search/backends/database/mysql/mysql.py +1 -700
  238. wagtail/search/backends/database/mysql/query.py +1 -258
  239. wagtail/search/backends/database/postgres/postgres.py +1 -749
  240. wagtail/search/backends/database/postgres/query.py +1 -83
  241. wagtail/search/backends/database/postgres/weights.py +1 -63
  242. wagtail/search/backends/database/sqlite/query.py +1 -294
  243. wagtail/search/backends/database/sqlite/sqlite.py +1 -719
  244. wagtail/search/backends/database/sqlite/utils.py +1 -35
  245. wagtail/search/backends/deprecation.py +45 -0
  246. wagtail/search/backends/elasticsearch7.py +18 -1260
  247. wagtail/search/backends/elasticsearch8.py +21 -96
  248. wagtail/search/backends/elasticsearch9.py +35 -0
  249. wagtail/search/backends/opensearch2.py +35 -0
  250. wagtail/search/backends/opensearch3.py +35 -0
  251. wagtail/search/index.py +1 -358
  252. wagtail/search/locale/en/LC_MESSAGES/django.po +2 -10
  253. wagtail/search/management/commands/update_index.py +1 -205
  254. wagtail/search/management/commands/wagtail_update_index.py +1 -4
  255. wagtail/search/models.py +32 -158
  256. wagtail/search/query.py +1 -114
  257. wagtail/search/queryset.py +1 -43
  258. wagtail/search/signal_handlers.py +1 -24
  259. wagtail/search/tasks.py +1 -10
  260. wagtail/search/tests/test_elasticsearch.py +22 -0
  261. wagtail/search/utils.py +1 -206
  262. wagtail/sites/locale/en/LC_MESSAGES/django.po +1 -1
  263. wagtail/snippets/locale/en/LC_MESSAGES/django.po +3 -3
  264. wagtail/snippets/locale/ru/LC_MESSAGES/django.mo +0 -0
  265. wagtail/snippets/locale/ru/LC_MESSAGES/django.po +8 -1
  266. wagtail/snippets/locale/tr/LC_MESSAGES/django.mo +0 -0
  267. wagtail/snippets/locale/tr/LC_MESSAGES/django.po +8 -1
  268. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser-telepath.js +1 -1
  269. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser.js +1 -1
  270. wagtail/snippets/tests/test_reordering.py +319 -0
  271. wagtail/snippets/tests/test_snippets.py +65 -12
  272. wagtail/snippets/views/snippets.py +16 -0
  273. wagtail/test/numberformat.py +30 -0
  274. wagtail/test/settings.py +35 -12
  275. wagtail/test/testapp/fields.py +12 -0
  276. wagtail/test/testapp/migrations/0056_commentablejsonpage.py +50 -0
  277. wagtail/test/testapp/migrations/0057_featurecompletetoy_sort_order.py +23 -0
  278. wagtail/test/testapp/migrations/0058_customlocktask.py +31 -0
  279. wagtail/test/testapp/models.py +27 -0
  280. wagtail/test/testapp/views.py +3 -1
  281. wagtail/test/utils/page_tests.py +17 -17
  282. wagtail/test/utils/template_tests.py +4 -6
  283. wagtail/test/utils/wagtail_tests.py +1 -2
  284. wagtail/tests/test_page_model.py +15 -0
  285. wagtail/{search/tests → tests}/test_page_search.py +29 -2
  286. wagtail/tests/test_search_fields.py +69 -0
  287. wagtail/tests/test_tests.py +62 -6
  288. wagtail/tests/test_workflow.py +25 -1
  289. wagtail/users/locale/cs/LC_MESSAGES/django.mo +0 -0
  290. wagtail/users/locale/cs/LC_MESSAGES/django.po +3 -0
  291. wagtail/users/locale/en/LC_MESSAGES/django.po +2 -2
  292. wagtail/users/locale/nl/LC_MESSAGES/django.mo +0 -0
  293. wagtail/users/locale/nl/LC_MESSAGES/django.po +6 -3
  294. wagtail/users/locale/ru/LC_MESSAGES/django.mo +0 -0
  295. wagtail/users/locale/ru/LC_MESSAGES/django.po +5 -1
  296. wagtail/users/locale/tr/LC_MESSAGES/django.mo +0 -0
  297. wagtail/users/locale/tr/LC_MESSAGES/django.po +78 -4
  298. wagtail/users/templates/wagtailusers/users/create.html +2 -0
  299. wagtail/users/templates/wagtailusers/users/edit.html +2 -0
  300. wagtail/users/tests/test_admin_views.py +4 -0
  301. wagtail/users/views/users.py +1 -1
  302. {wagtail-7.1.2.dist-info → wagtail-7.2rc1.dist-info}/METADATA +7 -6
  303. {wagtail-7.1.2.dist-info → wagtail-7.2rc1.dist-info}/RECORD +309 -315
  304. wagtail/admin/templates/wagtailadmin/collection_privacy/set_privacy.html +0 -13
  305. wagtail/admin/templates/wagtailadmin/page_privacy/set_privacy.html +0 -13
  306. wagtail/search/tests/__init__.py +0 -0
  307. wagtail/search/tests/elasticsearch_common_tests.py +0 -251
  308. wagtail/search/tests/test_backends.py +0 -1215
  309. wagtail/search/tests/test_db_backend.py +0 -62
  310. wagtail/search/tests/test_elasticsearch7_backend.py +0 -1452
  311. wagtail/search/tests/test_elasticsearch8_backend.py +0 -15
  312. wagtail/search/tests/test_index_functions.py +0 -256
  313. wagtail/search/tests/test_indexed_class.py +0 -157
  314. wagtail/search/tests/test_mysql_backend.py +0 -192
  315. wagtail/search/tests/test_postgres_backend.py +0 -210
  316. wagtail/search/tests/test_queries.py +0 -332
  317. wagtail/search/tests/test_related_fields.py +0 -102
  318. wagtail/search/tests/test_sqlite_backend.py +0 -52
  319. wagtail/test/search/__init__.py +0 -0
  320. wagtail/test/search/apps.py +0 -9
  321. wagtail/test/search/fixtures/search.json +0 -545
  322. wagtail/test/search/migrations/0001_initial.py +0 -146
  323. wagtail/test/search/migrations/0002_bookunindexed.py +0 -43
  324. wagtail/test/search/migrations/0003_book_summary.py +0 -18
  325. wagtail/test/search/migrations/__init__.py +0 -0
  326. wagtail/test/search/models.py +0 -137
  327. /wagtail/admin/templates/wagtailadmin/{pages/listing/_ordering_cell.html → tables/ordering_cell.html} +0 -0
  328. /wagtail/{search/checks.py → checks.py} +0 -0
  329. {wagtail-7.1.2.dist-info → wagtail-7.2rc1.dist-info}/WHEEL +0 -0
  330. {wagtail-7.1.2.dist-info → wagtail-7.2rc1.dist-info}/entry_points.txt +0 -0
  331. {wagtail-7.1.2.dist-info → wagtail-7.2rc1.dist-info}/licenses/LICENSE +0 -0
  332. {wagtail-7.1.2.dist-info → wagtail-7.2rc1.dist-info}/top_level.txt +0 -0
@@ -999,12 +999,29 @@ class TestSnippetCreateView(WagtailTestUtils, TestCase):
999
999
 
1000
1000
  def test_create_invalid(self):
1001
1001
  response = self.post(post_data={"foo": "bar"})
1002
- self.assertContains(response, "The advert could not be created due to errors.")
1003
- self.assertContains(response, "error-message", count=1)
1004
- self.assertContains(response, "This field is required", count=1)
1005
1002
 
1006
1003
  soup = self.get_soup(response.content)
1007
1004
 
1005
+ header_messages = soup.css.select(".messages[role='status'] ul > li")
1006
+
1007
+ # there should be one header message that indicates the issue and has a go to error button
1008
+ self.assertEqual(len(header_messages), 1)
1009
+ message = header_messages[0]
1010
+ self.assertIn(
1011
+ "The advert could not be created due to errors.", message.get_text()
1012
+ )
1013
+ buttons = message.find_all("button")
1014
+ self.assertEqual(len(buttons), 1)
1015
+ self.assertEqual(buttons[0].attrs["data-controller"], "w-count w-focus")
1016
+ self.assertIn("Go to the first error", buttons[0].get_text())
1017
+
1018
+ # field specific error should be shown
1019
+ error_messages = soup.css.select(".error-message")
1020
+ self.assertEqual(len(error_messages), 1)
1021
+ error_message = error_messages[0]
1022
+ self.assertEqual(error_message.parent["id"], "panel-child-text-errors")
1023
+ self.assertIn("This field is required", error_message.get_text())
1024
+
1008
1025
  # Should have the unsaved controller set up
1009
1026
  editor_form = soup.select_one("#w-editor-form")
1010
1027
  self.assertIsNotNone(editor_form)
@@ -1773,7 +1790,7 @@ class TestCreateDraftStateSnippet(WagtailTestUtils, TestCase):
1773
1790
  self.assertFormError(
1774
1791
  response.context["form"],
1775
1792
  "expire_at",
1776
- "Expiry date/time must be in the future",
1793
+ "Expiry date/time must be in the future.",
1777
1794
  )
1778
1795
 
1779
1796
  self.assertContains(
@@ -1992,12 +2009,30 @@ class TestSnippetEditView(BaseTestSnippetEditView):
1992
2009
 
1993
2010
  def test_edit_invalid(self):
1994
2011
  response = self.post(post_data={"foo": "bar"})
1995
- self.assertContains(response, "The advert could not be saved due to errors.")
1996
- self.assertContains(response, "error-message", count=1)
1997
- self.assertContains(response, "This field is required", count=1)
1998
-
1999
2012
  soup = self.get_soup(response.content)
2000
2013
 
2014
+ header_messages = soup.css.select(".messages[role='status'] ul > li")
2015
+
2016
+ # the top level message should indicate that the page could not be saved
2017
+ self.assertEqual(len(header_messages), 1)
2018
+ message = header_messages[0]
2019
+ self.assertIn(
2020
+ "The advert could not be saved due to errors.", message.get_text()
2021
+ )
2022
+
2023
+ # the top level message should provide a go to error button
2024
+ buttons = message.find_all("button")
2025
+ self.assertEqual(len(buttons), 1)
2026
+ self.assertEqual(buttons[0].attrs["data-controller"], "w-count w-focus")
2027
+ self.assertIn("Go to the first error", buttons[0].get_text())
2028
+
2029
+ # the error should only appear once: against the field, not in the header message
2030
+ error_messages = soup.css.select(".error-message")
2031
+ self.assertEqual(len(error_messages), 1)
2032
+ error_message = error_messages[0]
2033
+ self.assertEqual(error_message.parent["id"], "panel-child-text-errors")
2034
+ self.assertIn("This field is required", error_message.get_text())
2035
+
2001
2036
  # Should have the unsaved controller set up
2002
2037
  editor_form = soup.select_one("#w-editor-form")
2003
2038
  self.assertIsNotNone(editor_form)
@@ -3086,7 +3121,7 @@ class TestEditDraftStateSnippet(BaseTestSnippetEditView):
3086
3121
  self.assertFormError(
3087
3122
  response.context["form"],
3088
3123
  "expire_at",
3089
- "Expiry date/time must be in the future",
3124
+ "Expiry date/time must be in the future.",
3090
3125
  )
3091
3126
 
3092
3127
  self.assertContains(
@@ -5972,11 +6007,29 @@ class TestSnippetViewWithCustomPrimaryKey(WagtailTestUtils, TestCase):
5972
6007
 
5973
6008
  def test_edit_invalid(self):
5974
6009
  response = self.post(self.snippet_a, post_data={"foo": "bar"})
5975
- self.assertContains(
5976
- response,
6010
+ soup = self.get_soup(response.content)
6011
+ header_messages = soup.css.select(".messages[role='status'] ul > li")
6012
+
6013
+ # the top level message should indicate that the page could not be saved
6014
+ self.assertEqual(len(header_messages), 1)
6015
+ message = header_messages[0]
6016
+ self.assertIn(
5977
6017
  "The standard snippet with custom primary key could not be saved due to errors.",
6018
+ message.get_text(),
5978
6019
  )
5979
- self.assertContains(response, "This field is required.")
6020
+
6021
+ # the top level message should provide a go to error button
6022
+ buttons = message.find_all("button")
6023
+ self.assertEqual(len(buttons), 1)
6024
+ self.assertEqual(buttons[0].attrs["data-controller"], "w-count w-focus")
6025
+ self.assertIn("Go to the first error", buttons[0].get_text())
6026
+
6027
+ # the errors should appear against the fields with issues
6028
+ error_messages = soup.css.select(".error-message")
6029
+ self.assertEqual(len(error_messages), 2)
6030
+ error_message = error_messages[0]
6031
+ self.assertEqual(error_message.parent["id"], "panel-child-snippet_id-errors")
6032
+ self.assertIn("This field is required", error_message.get_text())
5980
6033
 
5981
6034
  def test_edit(self):
5982
6035
  response = self.post(
@@ -444,6 +444,14 @@ class WorkflowHistoryDetailView(
444
444
  permission_required = "change"
445
445
 
446
446
 
447
+ class ReorderView(generic.ReorderView):
448
+ @cached_property
449
+ def permission_required(self):
450
+ if issubclass(self.model, DraftStateMixin):
451
+ return "publish"
452
+ return super().permission_required
453
+
454
+
447
455
  class SnippetViewSet(ModelViewSet):
448
456
  """
449
457
  A viewset that instantiates the admin views for snippets.
@@ -505,6 +513,9 @@ class SnippetViewSet(ModelViewSet):
505
513
  #: The view class to use for the inspect view; must be a subclass of ``wagtail.snippets.views.snippets.InspectView``.
506
514
  inspect_view_class = InspectView
507
515
 
516
+ #: The view class to use for the reorder view; must be a subclass of ``wagtail.snippets.views.snippets.ReorderView``.
517
+ reorder_view_class = ReorderView
518
+
508
519
  #: The view class to use for previewing revisions; must be a subclass of ``wagtail.snippets.views.snippets.PreviewRevisionView``.
509
520
  revisions_view_class = PreviewRevisionView
510
521
 
@@ -1048,6 +1059,11 @@ class SnippetViewSet(ModelViewSet):
1048
1059
  ),
1049
1060
  ]
1050
1061
 
1062
+ if self.reorder_view_enabled:
1063
+ urlpatterns += [
1064
+ path("reorder/<str:pk>/", self.reorder_view, name="reorder")
1065
+ ]
1066
+
1051
1067
  if self.copy_view_enabled:
1052
1068
  urlpatterns += [path("copy/<str:pk>/", self.copy_view, name="copy")]
1053
1069
 
@@ -4,9 +4,11 @@
4
4
  # USE_THOUSAND_SEPARATOR = True incorrectly reformats numbers that are not intended to be
5
5
  # human-readable (such as image dimensions, or IDs within data attributes).
6
6
 
7
+ from contextlib import contextmanager
7
8
  from decimal import Decimal
8
9
 
9
10
  import django.contrib.humanize.templatetags.humanize
11
+ import django.template.base
10
12
  import django.template.defaultfilters
11
13
  import django.templatetags.l10n
12
14
  import django.utils.numberformat
@@ -17,6 +19,34 @@ from django.utils.translation import gettext, ngettext
17
19
 
18
20
  original_numberformat = django.utils.numberformat.format
19
21
  original_intcomma = django.contrib.humanize.templatetags.humanize.intcomma
22
+ OriginalVariableNode = django.template.base.VariableNode
23
+
24
+
25
+ @contextmanager
26
+ def ignore_numberformat(template_names):
27
+ class VariableNode(OriginalVariableNode):
28
+ def render(self, context):
29
+ template_name = context.render_context.template.origin.template_name
30
+ # If the template being rendered is in the list of templates to ignore,
31
+ # restore the original numberformat function so the template can render
32
+ # normally, otherwise reinforce the patch as there might be multiple
33
+ # templates being rendered in the lifespan of the contextmanager.
34
+ if template_name in template_names:
35
+ django.utils.numberformat.format = original_numberformat
36
+ else:
37
+ django.utils.numberformat.format = patched_numberformat
38
+
39
+ return super().render(context)
40
+
41
+ # Within the contextmanager, use a VariableNode class that conditionally
42
+ # enforces the numberformat patch
43
+ django.template.base.VariableNode = VariableNode
44
+
45
+ try:
46
+ yield
47
+ finally:
48
+ # Restore VariableNode to always enforce the numberformat patch
49
+ django.template.base.VariableNode = OriginalVariableNode
20
50
 
21
51
 
22
52
  def patched_numberformat(*args, use_l10n=None, **kwargs):
wagtail/test/settings.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import os
2
2
 
3
3
  from django.contrib.messages import constants as message_constants
4
+ from django.core.exceptions import ImproperlyConfigured
4
5
  from django.utils.translation import gettext_lazy as _
5
6
 
6
7
  from wagtail.test.numberformat import patch_number_formats
@@ -151,7 +152,6 @@ INSTALLED_APPS = [
151
152
  "wagtail.test.demosite",
152
153
  "wagtail.test.snippets",
153
154
  "wagtail.test.routablepage",
154
- "wagtail.test.search",
155
155
  "wagtail.test.i18n",
156
156
  "wagtail.test.streamfield_migrations",
157
157
  "wagtail.contrib.simple_translation",
@@ -229,25 +229,48 @@ else:
229
229
 
230
230
  if os.environ.get("DATABASE_ENGINE") == "django.db.backends.postgresql":
231
231
  INSTALLED_APPS.append("django.contrib.postgres")
232
- WAGTAILSEARCH_BACKENDS["postgresql"] = {
233
- "BACKEND": "wagtail.search.backends.database",
234
- "AUTO_UPDATE": False,
235
- "SEARCH_CONFIG": "english",
236
- }
237
232
 
238
- if "ELASTICSEARCH_URL" in os.environ:
239
- if os.environ.get("ELASTICSEARCH_VERSION") == "8":
240
- backend = "wagtail.search.backends.elasticsearch8"
241
- elif os.environ.get("ELASTICSEARCH_VERSION") == "7":
242
- backend = "wagtail.search.backends.elasticsearch7"
233
+ # Tests in wagtail.tests.test_page_search.PageSearchTests will be run against each backend defined
234
+ # in WAGTAILSEARCH_BACKENDS. Define an additional one to test the FTS-enabled backend for the
235
+ # currently active database.
236
+
237
+ WAGTAILSEARCH_BACKENDS["database"] = {
238
+ "BACKEND": "wagtail.search.backends.database",
239
+ "AUTO_UPDATE": False,
240
+ "SEARCH_CONFIG": "english",
241
+ }
243
242
 
244
- WAGTAILSEARCH_BACKENDS["elasticsearch"] = {
243
+ if "ELASTICSEARCH_URL" in os.environ:
244
+ # Define an 'elasticsearch' backend; along with wagtail.tests.test_page_search.PageSearchTests
245
+ # this is also used for the Elasticsearch-specific tests in wagtail.images.tests.test_models and
246
+ # wagtail.documents.tests.test_search. We also want to run these tests under Opensearch; for
247
+ # simplicity we use the backend name 'elasticsearch' in this case too.
248
+ if elasticsearch_version := os.environ.get("ELASTICSEARCH_VERSION"):
249
+ backend = f"wagtail.search.backends.elasticsearch{elasticsearch_version}"
250
+ elif opensearch_version := os.environ.get("OPENSEARCH_VERSION"):
251
+ backend = f"wagtail.search.backends.opensearch{opensearch_version}"
252
+ else:
253
+ raise ImproperlyConfigured(
254
+ "If ELASTICSEARCH_URL is defined, either ELASTICSEARCH_VERSION or OPENSEARCH_VERSION must be defined too"
255
+ )
256
+
257
+ elasticsearch_opts = {
245
258
  "BACKEND": backend,
246
259
  "URLS": [os.environ["ELASTICSEARCH_URL"]],
247
260
  "TIMEOUT": 10,
248
261
  "max_retries": 1,
249
262
  "AUTO_UPDATE": False,
250
263
  "INDEX_SETTINGS": {"settings": {"index": {"number_of_shards": 1}}},
264
+ "OPTIONS": {
265
+ "ca_certs": os.environ.get("ELASTICSEARCH_CA_CERTS"),
266
+ },
267
+ }
268
+ WAGTAILSEARCH_BACKENDS["elasticsearch"] = elasticsearch_opts
269
+
270
+ # RemovedInWagtail80Warning
271
+ WAGTAILSEARCH_BACKENDS["elasticsearch_with_index_option"] = {
272
+ **elasticsearch_opts,
273
+ "INDEX": "wagtailtest", # Deprecated option
251
274
  }
252
275
 
253
276
 
@@ -0,0 +1,12 @@
1
+ from django.db import models
2
+
3
+ from wagtail.coreutils import multigetattr
4
+
5
+
6
+ class CommentableJSONField(models.JSONField):
7
+ def get_block_by_content_path(self, value, path_elements):
8
+ try:
9
+ multigetattr(value, ".".join(path_elements))
10
+ except AttributeError:
11
+ return False
12
+ return True
@@ -0,0 +1,50 @@
1
+ # Generated by Django 5.2.5 on 2025-08-14 19:05
2
+
3
+ import django.db.models.deletion
4
+ from django.db import migrations, models
5
+
6
+ import wagtail.fields
7
+ import wagtail.test.testapp.fields
8
+
9
+
10
+ class Migration(migrations.Migration):
11
+
12
+ dependencies = [
13
+ ("tests", "0055_previewable_settings"),
14
+ ("wagtailcore", "0095_groupsitepermission"),
15
+ ]
16
+
17
+ operations = [
18
+ migrations.CreateModel(
19
+ name="CommentableJSONPage",
20
+ fields=[
21
+ (
22
+ "page_ptr",
23
+ models.OneToOneField(
24
+ auto_created=True,
25
+ on_delete=django.db.models.deletion.CASCADE,
26
+ parent_link=True,
27
+ primary_key=True,
28
+ serialize=False,
29
+ to="wagtailcore.page",
30
+ ),
31
+ ),
32
+ (
33
+ "commentable_body",
34
+ wagtail.test.testapp.fields.CommentableJSONField(),
35
+ ),
36
+ ("uncommentable_body", models.JSONField()),
37
+ (
38
+ "stream_body",
39
+ wagtail.fields.StreamField(
40
+ [("text", 0)],
41
+ block_lookup={0: ("wagtail.blocks.CharBlock", (), {})},
42
+ ),
43
+ ),
44
+ ],
45
+ options={
46
+ "abstract": False,
47
+ },
48
+ bases=("wagtailcore.page",),
49
+ ),
50
+ ]
@@ -0,0 +1,23 @@
1
+ # Generated by Django 5.1.6 on 2025-03-08 13:10
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("tests", "0056_commentablejsonpage"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name="featurecompletetoy",
15
+ name="sort_order",
16
+ field=models.IntegerField(blank=True, null=True),
17
+ ),
18
+ migrations.AddField(
19
+ model_name="fullfeaturedsnippet",
20
+ name="sort_order",
21
+ field=models.IntegerField(blank=True, editable=False, null=True),
22
+ ),
23
+ ]
@@ -0,0 +1,31 @@
1
+ # Generated by Django 5.1.6 on 2025-07-14 16:16
2
+
3
+ import django.db.models.deletion
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ("tests", "0057_featurecompletetoy_sort_order"),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.CreateModel(
15
+ name="CustomLockTask",
16
+ fields=[
17
+ (
18
+ "task_ptr",
19
+ models.OneToOneField(
20
+ auto_created=True,
21
+ on_delete=django.db.models.deletion.CASCADE,
22
+ parent_link=True,
23
+ primary_key=True,
24
+ serialize=False,
25
+ to="wagtailcore.task",
26
+ ),
27
+ ),
28
+ ],
29
+ bases=("wagtailcore.task",),
30
+ ),
31
+ ]
@@ -88,6 +88,8 @@ from wagtail.search import index
88
88
  from wagtail.snippets.blocks import SnippetChooserBlock
89
89
  from wagtail.snippets.models import register_snippet
90
90
 
91
+ from ...locks import WorkflowLock
92
+ from .fields import CommentableJSONField
91
93
  from .forms import FormClassAdditionalFieldPageForm, ValidatedPageForm
92
94
 
93
95
  EVENT_AUDIENCE_CHOICES = (
@@ -1301,6 +1303,7 @@ class FullFeaturedSnippet(
1301
1303
  LockableMixin,
1302
1304
  RevisionMixin,
1303
1305
  TranslatableMixin,
1306
+ Orderable,
1304
1307
  index.Indexed,
1305
1308
  models.Model,
1306
1309
  ):
@@ -2448,6 +2451,18 @@ class UserApprovalTask(Task):
2448
2451
  return "Only a specific user can approve this task"
2449
2452
 
2450
2453
 
2454
+ class CustomWorkflowLock(WorkflowLock):
2455
+ def get_message(self, user):
2456
+ return "If there is a door, there must be a key"
2457
+
2458
+
2459
+ class CustomLockTask(Task):
2460
+ lock_class = CustomWorkflowLock
2461
+
2462
+ def locked_for_user(self, obj, user):
2463
+ return True
2464
+
2465
+
2451
2466
  # StreamField media definitions must not be evaluated at startup (e.g. during system checks) -
2452
2467
  # these may fail if e.g. ManifestStaticFilesStorage is in use and collectstatic has not been run.
2453
2468
  # Check this with a media definition that deliberately errors; if media handling is not set up
@@ -2587,6 +2602,8 @@ class FeatureCompleteToy(index.Indexed, models.Model):
2587
2602
  )
2588
2603
  name = models.CharField(max_length=255)
2589
2604
  release_date = models.DateField(default=datetime.date.today)
2605
+ sort_order = models.IntegerField(null=True, blank=True)
2606
+ sort_order_field = "sort_order"
2590
2607
 
2591
2608
  search_fields = [
2592
2609
  index.SearchField("name"),
@@ -2676,3 +2693,13 @@ class RequiredDatePage(Page):
2676
2693
  TitleFieldPanel("title", classname="title"),
2677
2694
  FieldPanel("deadline"),
2678
2695
  ]
2696
+
2697
+
2698
+ class CommentableJSONPage(Page):
2699
+ commentable_body = CommentableJSONField()
2700
+ uncommentable_body = models.JSONField()
2701
+ stream_body = StreamField(
2702
+ [
2703
+ ("text", CharBlock()),
2704
+ ]
2705
+ )
@@ -246,6 +246,7 @@ class FeatureCompleteToyViewSet(ModelViewSet):
246
246
  # search_fields derived from the model
247
247
  inspect_view_enabled = True
248
248
  inspect_view_fields = ["strid", "release_date"]
249
+ sort_order_field = "sort_order"
249
250
 
250
251
  panels = [
251
252
  FieldPanel("name"),
@@ -266,7 +267,7 @@ class FCToyAlt1ViewSet(ModelViewSet):
266
267
  menu_label = "FC Toys Alt 1"
267
268
  inspect_view_class = FCToyAlt1InspectView
268
269
  inspect_view_enabled = True
269
- inspect_view_fields_exclude = ["strid", "release_date"]
270
+ inspect_view_fields_exclude = ["strid", "release_date", "sort_order"]
270
271
  copy_view_enabled = False
271
272
 
272
273
  def get_index_view_kwargs(self, **kwargs):
@@ -295,6 +296,7 @@ class ToyViewSetGroup(ModelViewSetGroup):
295
296
  exclude_form_fields=(),
296
297
  search_fields=["name"],
297
298
  search_backend_name=None,
299
+ sort_order_field=None,
298
300
  ),
299
301
  ModelViewSet(
300
302
  name="fctoy-alt3",
@@ -1,4 +1,4 @@
1
- from typing import Any, Optional
1
+ from typing import Any
2
2
  from unittest import mock
3
3
 
4
4
  from django.conf import settings
@@ -174,8 +174,8 @@ class WagtailPageTestCase(WagtailTestUtils, TestCase):
174
174
  def assertPageIsRoutable(
175
175
  self,
176
176
  page: Page,
177
- route_path: Optional[str] = "/",
178
- msg: Optional[str] = None,
177
+ route_path: str | None = "/",
178
+ msg: str | None = None,
179
179
  ):
180
180
  """
181
181
  Asserts that ``page`` can be routed to without raising a ``Http404`` error.
@@ -216,13 +216,13 @@ class WagtailPageTestCase(WagtailTestUtils, TestCase):
216
216
  def assertPageIsRenderable(
217
217
  self,
218
218
  page: Page,
219
- route_path: Optional[str] = "/",
220
- query_data: Optional[dict[str, Any]] = None,
221
- post_data: Optional[dict[str, Any]] = None,
222
- user: Optional[AbstractBaseUser] = None,
223
- accept_404: Optional[bool] = False,
224
- accept_redirect: Optional[bool] = False,
225
- msg: Optional[str] = None,
219
+ route_path: str | None = "/",
220
+ query_data: dict[str, Any] | None = None,
221
+ post_data: dict[str, Any] | None = None,
222
+ user: AbstractBaseUser | None = None,
223
+ accept_404: bool | None = False,
224
+ accept_redirect: bool | None = False,
225
+ msg: str | None = None,
226
226
  ):
227
227
  """
228
228
  Asserts that ``page`` can be rendered without raising a fatal error.
@@ -297,9 +297,9 @@ class WagtailPageTestCase(WagtailTestUtils, TestCase):
297
297
  def assertPageIsEditable(
298
298
  self,
299
299
  page: Page,
300
- post_data: Optional[dict[str, Any]] = None,
301
- user: Optional[AbstractBaseUser] = None,
302
- msg: Optional[str] = None,
300
+ post_data: dict[str, Any] | None = None,
301
+ user: AbstractBaseUser | None = None,
302
+ msg: str | None = None,
303
303
  ):
304
304
  """
305
305
  Asserts that the page edit view works for ``page`` without raising a fatal error.
@@ -378,10 +378,10 @@ class WagtailPageTestCase(WagtailTestUtils, TestCase):
378
378
  def assertPageIsPreviewable(
379
379
  self,
380
380
  page: Page,
381
- mode: Optional[str] = "",
382
- post_data: Optional[dict[str, Any]] = None,
383
- user: Optional[AbstractBaseUser] = None,
384
- msg: Optional[str] = None,
381
+ mode: str | None = "",
382
+ post_data: dict[str, Any] | None = None,
383
+ user: AbstractBaseUser | None = None,
384
+ msg: str | None = None,
385
385
  ):
386
386
  """
387
387
  Asserts that the page preview view can be loaded for ``page`` without raising a fatal error.
@@ -1,5 +1,3 @@
1
- from typing import Union
2
-
3
1
  from django.test import SimpleTestCase
4
2
 
5
3
  from .wagtail_tests import WagtailTestUtils
@@ -9,9 +7,9 @@ class AdminTemplateTestUtils:
9
7
  base_breadcrumb_items = [{"label": "Home", "url": "/admin/"}]
10
8
 
11
9
  def assertBreadcrumbsItemsRendered(
12
- self: Union[WagtailTestUtils, SimpleTestCase],
10
+ self: WagtailTestUtils | SimpleTestCase,
13
11
  items: list[dict[str, str]],
14
- html: Union[str, bytes],
12
+ html: str | bytes,
15
13
  ):
16
14
  soup = self.get_soup(html)
17
15
  # Select with a class instead of a data-controller attribute because
@@ -79,8 +77,8 @@ class AdminTemplateTestUtils:
79
77
  )
80
78
 
81
79
  def assertBreadcrumbsNotRendered(
82
- self: Union[WagtailTestUtils, SimpleTestCase],
83
- html: Union[str, bytes],
80
+ self: WagtailTestUtils | SimpleTestCase,
81
+ html: str | bytes,
84
82
  ):
85
83
  soup = self.get_soup(html)
86
84
  # Select with a class instead of a data-controller attribute because
@@ -1,6 +1,5 @@
1
1
  import warnings
2
2
  from contextlib import contextmanager
3
- from typing import Union
4
3
 
5
4
  from bs4 import BeautifulSoup
6
5
  from django.contrib.auth import get_user_model
@@ -9,7 +8,7 @@ from django.test.testcases import assert_and_parse_html
9
8
 
10
9
  class WagtailTestUtils:
11
10
  @staticmethod
12
- def get_soup(markup: Union[str, bytes]) -> BeautifulSoup:
11
+ def get_soup(markup: str | bytes) -> BeautifulSoup:
13
12
  # Use an empty string_containers argument so that <script>, <style>, and
14
13
  # <template> tags do not have their text ignored.
15
14
  return BeautifulSoup(markup, "html.parser", string_containers={})
@@ -3,6 +3,7 @@ import json
3
3
  import unittest
4
4
  from unittest.mock import Mock
5
5
 
6
+ from asgiref.sync import async_to_sync
6
7
  from django.conf import settings
7
8
  from django.contrib.auth import get_user_model
8
9
  from django.contrib.auth.models import AnonymousUser, Group
@@ -196,6 +197,20 @@ class TestValidation(TestCase):
196
197
  self.assertEqual(retrieved_page.draft_title, "Hello world edited")
197
198
 
198
199
 
200
+ class TestAsyncMethods(TestCase):
201
+ def test_asave_with_update_fields_none_saves_all_fields(self):
202
+ root = Page.get_first_root_node()
203
+ page = Page(title="Original title", slug="async-page")
204
+ root.add_child(instance=page)
205
+
206
+ # Call asave using async_to_sync
207
+ page.title = "Updated title"
208
+ async_to_sync(page.asave)()
209
+
210
+ refreshed = Page.objects.get(id=page.id)
211
+ assert refreshed.title == "Updated title"
212
+
213
+
199
214
  @override_settings(
200
215
  ALLOWED_HOSTS=[
201
216
  "localhost",
@@ -1,14 +1,41 @@
1
+ from unittest import mock
2
+
1
3
  from django.conf import settings
2
4
  from django.db.models import F
3
- from django.test import TestCase
5
+ from django.test import TestCase, override_settings
4
6
 
5
7
  from wagtail.models import Page
8
+ from wagtail.search import index
6
9
  from wagtail.search.backends import get_search_backend
7
10
  from wagtail.search.backends.base import (
8
11
  BaseSearchQueryCompiler,
9
12
  BaseSearchResults,
10
13
  OrderByFieldError,
11
14
  )
15
+ from wagtail.test.testapp.models import SimplePage
16
+ from wagtail.test.utils import WagtailTestUtils
17
+
18
+
19
+ @mock.patch("wagtail.tests.DummySearchBackend", create=True)
20
+ @override_settings(
21
+ WAGTAILSEARCH_BACKENDS={"default": {"BACKEND": "wagtail.tests.DummySearchBackend"}}
22
+ )
23
+ class TestInsertOrUpdateObject(WagtailTestUtils, TestCase):
24
+ def test_converts_to_specific_page(self, backend):
25
+ root_page = Page.objects.get(id=1)
26
+ page = root_page.add_child(
27
+ instance=SimplePage(title="test", slug="test", content="test")
28
+ )
29
+
30
+ # Convert page into a generic "Page" object and add it into the index
31
+ unspecific_page = page.page_ptr
32
+
33
+ backend().reset_mock()
34
+
35
+ index.insert_or_update_object(unspecific_page)
36
+
37
+ # It should be automatically converted back to the specific version
38
+ backend().add.assert_called_with(page)
12
39
 
13
40
 
14
41
  class PageSearchTests:
@@ -55,7 +82,7 @@ class PageSearchTests:
55
82
  "blah", order_by_relevance=False, backend=self.backend_name
56
83
  )
57
84
  self.assertIn(
58
- 'Cannot sort search results with "OrderBy(F(last_published_at), descending=False)".',
85
+ 'Sorting search results with "OrderBy(F(last_published_at), descending=False)" is not supported by this search backend.',
59
86
  str(ctx.exception),
60
87
  )
61
88