wagtail 7.1.1__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 (345) 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/generic/form.html +3 -1
  75. wagtail/admin/templates/wagtailadmin/panels/multi_field_panel_child.html +1 -1
  76. wagtail/admin/templates/wagtailadmin/panels/object_list.html +1 -1
  77. wagtail/admin/templates/wagtailadmin/panels/tabbed_interface.html +3 -2
  78. wagtail/admin/templates/wagtailadmin/shared/formatted_field.html +1 -1
  79. wagtail/admin/templates/wagtailadmin/shared/forms/single_checkbox.html +1 -1
  80. wagtail/admin/templates/wagtailadmin/shared/keyboard_shortcuts_dialog.html +19 -0
  81. wagtail/admin/templates/wagtailadmin/shared/panel.html +1 -1
  82. wagtail/admin/templates/wagtailadmin/shared/set_privacy.html +15 -0
  83. wagtail/admin/templates/wagtailadmin/shared/side_panels/checks.html +28 -1
  84. wagtail/admin/templates/wagtailadmin/shared/workflow_history/detail.html +2 -2
  85. wagtail/admin/templates/wagtailadmin/{pages/listing/_ordering_header.html → tables/ordering_header.html} +2 -2
  86. wagtail/admin/templates/wagtailadmin/tables/title_cell.html +1 -1
  87. wagtail/admin/templates/wagtailadmin/userbar/base.html +6 -3
  88. wagtail/admin/templates/wagtailadmin/userbar/item_admin.html +2 -2
  89. wagtail/admin/templates/wagtailadmin/userbar/item_page_add.html +2 -2
  90. wagtail/admin/templates/wagtailadmin/userbar/item_page_edit.html +2 -2
  91. wagtail/admin/templates/wagtailadmin/userbar/item_page_explore.html +2 -2
  92. wagtail/admin/templates/wagtailadmin/widgets/{daterange_input.html → range_input.html} +1 -1
  93. wagtail/admin/templates/wagtailadmin/workflows/task_chooser/chooser.html +4 -2
  94. wagtail/admin/templatetags/wagtailadmin_tags.py +56 -22
  95. wagtail/admin/tests/api/test_pages.py +7 -7
  96. wagtail/admin/tests/api/test_renderer_classes.py +16 -0
  97. wagtail/admin/tests/pages/test_create_page.py +34 -2
  98. wagtail/admin/tests/pages/test_edit_page.py +128 -14
  99. wagtail/admin/tests/pages/test_explorer_view.py +34 -7
  100. wagtail/admin/tests/pages/test_reorder_page.py +11 -0
  101. wagtail/admin/tests/test_collections_views.py +12 -0
  102. wagtail/admin/tests/test_edit_handlers.py +3 -3
  103. wagtail/admin/tests/test_filters.py +2 -2
  104. wagtail/admin/tests/test_keyboard_shortcuts.py +52 -2
  105. wagtail/admin/tests/test_menu.py +0 -2
  106. wagtail/admin/tests/test_privacy.py +16 -16
  107. wagtail/admin/tests/test_templatetags.py +137 -0
  108. wagtail/admin/tests/test_userbar.py +75 -35
  109. wagtail/admin/tests/test_views_generic.py +34 -0
  110. wagtail/admin/tests/test_workflows.py +34 -0
  111. wagtail/admin/tests/viewsets/test_model_viewset.py +322 -0
  112. wagtail/admin/ui/tables/orderable.py +73 -0
  113. wagtail/admin/ui/tables/pages.py +3 -13
  114. wagtail/admin/userbar.py +6 -1
  115. wagtail/admin/views/collection_privacy.py +6 -2
  116. wagtail/admin/views/generic/__init__.py +1 -0
  117. wagtail/admin/views/generic/mixins.py +20 -2
  118. wagtail/admin/views/generic/models.py +67 -1
  119. wagtail/admin/views/generic/ordering.py +79 -0
  120. wagtail/admin/views/home.py +3 -3
  121. wagtail/admin/views/page_privacy.py +5 -2
  122. wagtail/admin/views/pages/create.py +1 -1
  123. wagtail/admin/views/pages/edit.py +2 -2
  124. wagtail/admin/views/pages/listing.py +7 -42
  125. wagtail/admin/views/pages/move.py +1 -1
  126. wagtail/admin/views/pages/ordering.py +1 -1
  127. wagtail/admin/viewsets/base.py +1 -1
  128. wagtail/admin/viewsets/model.py +49 -1
  129. wagtail/admin/wagtail_hooks.py +2 -1
  130. wagtail/admin/widgets/slug.py +10 -10
  131. wagtail/api/v2/serializers.py +1 -1
  132. wagtail/api/v2/tests/test_renderer_classes.py +32 -0
  133. wagtail/apps.py +2 -0
  134. wagtail/bin/wagtail.py +1 -1
  135. wagtail/blocks/struct_block.py +2 -1
  136. wagtail/contrib/forms/locale/en/LC_MESSAGES/django.po +14 -14
  137. wagtail/contrib/forms/locale/nl/LC_MESSAGES/django.mo +0 -0
  138. wagtail/contrib/forms/locale/nl/LC_MESSAGES/django.po +19 -2
  139. wagtail/contrib/forms/locale/ru/LC_MESSAGES/django.mo +0 -0
  140. wagtail/contrib/forms/locale/ru/LC_MESSAGES/django.po +18 -1
  141. wagtail/contrib/frontend_cache/tests.py +4 -2
  142. wagtail/contrib/redirects/locale/en/LC_MESSAGES/django.po +4 -4
  143. wagtail/contrib/redirects/tests/test_tmp_storages.py +20 -0
  144. wagtail/contrib/redirects/tmp_storages.py +1 -1
  145. wagtail/contrib/redirects/views.py +3 -3
  146. wagtail/contrib/search_promotions/locale/en/LC_MESSAGES/django.po +3 -3
  147. wagtail/contrib/search_promotions/locale/tr/LC_MESSAGES/django.mo +0 -0
  148. wagtail/contrib/search_promotions/locale/tr/LC_MESSAGES/django.po +43 -3
  149. wagtail/contrib/search_promotions/static/wagtailsearchpromotions/js/query-chooser-modal.js +1 -1
  150. wagtail/contrib/search_promotions/views/settings.py +2 -2
  151. wagtail/contrib/settings/locale/cs/LC_MESSAGES/django.mo +0 -0
  152. wagtail/contrib/settings/locale/cs/LC_MESSAGES/django.po +6 -1
  153. wagtail/contrib/settings/locale/en/LC_MESSAGES/django.po +1 -1
  154. wagtail/contrib/settings/locale/nl/LC_MESSAGES/django.mo +0 -0
  155. wagtail/contrib/settings/locale/nl/LC_MESSAGES/django.po +6 -2
  156. wagtail/contrib/settings/locale/ru/LC_MESSAGES/django.mo +0 -0
  157. wagtail/contrib/settings/locale/ru/LC_MESSAGES/django.po +6 -1
  158. wagtail/contrib/settings/tests/site_specific/test_admin.py +40 -6
  159. wagtail/contrib/simple_translation/locale/en/LC_MESSAGES/django.po +1 -1
  160. wagtail/contrib/styleguide/locale/en/LC_MESSAGES/django.po +1 -1
  161. wagtail/contrib/styleguide/templates/wagtailstyleguide/base.html +5 -5
  162. wagtail/contrib/table_block/blocks.py +1 -0
  163. wagtail/contrib/table_block/locale/en/LC_MESSAGES/django.po +5 -1
  164. wagtail/contrib/table_block/static/table_block/js/table.js +1 -1
  165. wagtail/contrib/typed_table_block/locale/en/LC_MESSAGES/django.po +1 -1
  166. wagtail/contrib/typed_table_block/static/typed_table_block/js/typed_table_block.js +1 -1
  167. wagtail/coreutils.py +5 -5
  168. wagtail/documents/forms.py +18 -1
  169. wagtail/documents/locale/en/LC_MESSAGES/django.po +10 -10
  170. wagtail/documents/locale/nl/LC_MESSAGES/django.mo +0 -0
  171. wagtail/documents/locale/nl/LC_MESSAGES/django.po +9 -0
  172. wagtail/documents/locale/ru/LC_MESSAGES/django.mo +0 -0
  173. wagtail/documents/locale/ru/LC_MESSAGES/django.po +9 -0
  174. wagtail/documents/models.py +1 -1
  175. wagtail/documents/static/wagtaildocs/js/add-multiple.js +1 -1
  176. wagtail/documents/static/wagtaildocs/js/document-chooser-modal.js +1 -1
  177. wagtail/documents/static/wagtaildocs/js/document-chooser-telepath.js +1 -1
  178. wagtail/documents/static/wagtaildocs/js/document-chooser.js +1 -1
  179. wagtail/documents/templates/wagtaildocs/documents/add.html +0 -34
  180. wagtail/documents/tests/test_admin_views.py +132 -26
  181. wagtail/documents/tests/test_collection_privacy.py +18 -4
  182. wagtail/documents/tests/test_form_overrides.py +1 -1
  183. wagtail/documents/tests/test_search.py +21 -8
  184. wagtail/documents/views/documents.py +1 -1
  185. wagtail/embeds/locale/en/LC_MESSAGES/django.po +1 -1
  186. wagtail/embeds/static/wagtailembeds/js/embed-chooser-modal.js +1 -1
  187. wagtail/images/forms.py +16 -1
  188. wagtail/images/locale/cs/LC_MESSAGES/django.mo +0 -0
  189. wagtail/images/locale/cs/LC_MESSAGES/django.po +12 -1
  190. wagtail/images/locale/en/LC_MESSAGES/django.po +57 -46
  191. wagtail/images/locale/nl/LC_MESSAGES/django.mo +0 -0
  192. wagtail/images/locale/nl/LC_MESSAGES/django.po +37 -14
  193. wagtail/images/locale/ru/LC_MESSAGES/django.mo +0 -0
  194. wagtail/images/locale/ru/LC_MESSAGES/django.po +20 -1
  195. wagtail/images/models.py +1 -1
  196. wagtail/images/static/wagtailimages/js/add-multiple.js +1 -1
  197. wagtail/images/static/wagtailimages/js/focal-point-chooser.js +1 -1
  198. wagtail/images/static/wagtailimages/js/image-block.js +1 -1
  199. wagtail/images/static/wagtailimages/js/image-chooser-modal.js +1 -1
  200. wagtail/images/static/wagtailimages/js/image-chooser-telepath.js +1 -1
  201. wagtail/images/static/wagtailimages/js/image-chooser.js +1 -1
  202. wagtail/images/static/wagtailimages/js/image-url-generator.js +1 -1
  203. wagtail/images/static/wagtailimages/js/vendor/jquery.Jcrop.min.js +1 -1
  204. wagtail/images/static/wagtailimages/js/vendor/jquery.fileupload-image.js +1 -1
  205. wagtail/images/static/wagtailimages/js/vendor/jquery.fileupload-validate.js +1 -1
  206. wagtail/images/static/wagtailimages/js/vendor/load-image.min.js +1 -1
  207. wagtail/images/templates/wagtailimages/chooser/chooser.html +22 -13
  208. wagtail/images/templates/wagtailimages/chooser/image_preview_column_cell.html +10 -0
  209. wagtail/images/templates/wagtailimages/chooser/results.html +24 -20
  210. wagtail/images/templates/wagtailimages/chooser/title_column_cell.html +15 -0
  211. wagtail/images/templates/wagtailimages/images/add.html +0 -34
  212. wagtail/images/templates/wagtailimages/images/index.html +3 -3
  213. wagtail/images/templates/wagtailimages/images/index_results.html +1 -1
  214. wagtail/images/templates/wagtailimages/images/layout_toggle_button.html +8 -7
  215. wagtail/images/templatetags/wagtailimages_tags.py +2 -2
  216. wagtail/images/tests/test_admin_views.py +87 -0
  217. wagtail/images/tests/test_form_overrides.py +1 -1
  218. wagtail/images/tests/test_models.py +48 -9
  219. wagtail/images/views/chooser.py +66 -2
  220. wagtail/locale/en/LC_MESSAGES/django.po +55 -55
  221. wagtail/locale/is_IS/LC_MESSAGES/django.mo +0 -0
  222. wagtail/locale/is_IS/LC_MESSAGES/django.po +3 -3
  223. wagtail/locale/nl/LC_MESSAGES/django.mo +0 -0
  224. wagtail/locale/nl/LC_MESSAGES/django.po +11 -2
  225. wagtail/locale/ru/LC_MESSAGES/django.mo +0 -0
  226. wagtail/locale/ru/LC_MESSAGES/django.po +11 -1
  227. wagtail/locales/locale/en/LC_MESSAGES/django.po +1 -1
  228. wagtail/locales/locale/nl/LC_MESSAGES/django.mo +0 -0
  229. wagtail/locales/locale/nl/LC_MESSAGES/django.po +12 -1
  230. wagtail/locales/locale/ru/LC_MESSAGES/django.mo +0 -0
  231. wagtail/locales/locale/ru/LC_MESSAGES/django.po +10 -1
  232. wagtail/locales/views.py +2 -2
  233. wagtail/models/orderable.py +10 -0
  234. wagtail/models/pages.py +9 -11
  235. wagtail/models/sites.py +1 -1
  236. wagtail/models/workflows.py +8 -5
  237. wagtail/project_template/home/tests.py +6 -7
  238. wagtail/project_template/project_name/settings/base.py +9 -9
  239. wagtail/project_template/requirements.txt +1 -1
  240. wagtail/query.py +7 -2
  241. wagtail/rich_text/rewriters.py +1 -1
  242. wagtail/search/apps.py +4 -49
  243. wagtail/search/backends/__init__.py +1 -113
  244. wagtail/search/backends/base.py +1 -547
  245. wagtail/search/backends/database/__init__.py +1 -50
  246. wagtail/search/backends/database/fallback.py +1 -253
  247. wagtail/search/backends/database/mysql/mysql.py +1 -700
  248. wagtail/search/backends/database/mysql/query.py +1 -258
  249. wagtail/search/backends/database/postgres/postgres.py +1 -749
  250. wagtail/search/backends/database/postgres/query.py +1 -83
  251. wagtail/search/backends/database/postgres/weights.py +1 -63
  252. wagtail/search/backends/database/sqlite/query.py +1 -294
  253. wagtail/search/backends/database/sqlite/sqlite.py +1 -719
  254. wagtail/search/backends/database/sqlite/utils.py +1 -35
  255. wagtail/search/backends/deprecation.py +45 -0
  256. wagtail/search/backends/elasticsearch7.py +18 -1260
  257. wagtail/search/backends/elasticsearch8.py +21 -96
  258. wagtail/search/backends/elasticsearch9.py +35 -0
  259. wagtail/search/backends/opensearch2.py +35 -0
  260. wagtail/search/backends/opensearch3.py +35 -0
  261. wagtail/search/index.py +1 -358
  262. wagtail/search/locale/en/LC_MESSAGES/django.po +2 -10
  263. wagtail/search/management/commands/update_index.py +1 -205
  264. wagtail/search/management/commands/wagtail_update_index.py +1 -4
  265. wagtail/search/models.py +32 -158
  266. wagtail/search/query.py +1 -114
  267. wagtail/search/queryset.py +1 -43
  268. wagtail/search/signal_handlers.py +1 -24
  269. wagtail/search/tasks.py +1 -10
  270. wagtail/search/tests/test_elasticsearch.py +22 -0
  271. wagtail/search/utils.py +1 -206
  272. wagtail/sites/locale/en/LC_MESSAGES/django.po +1 -1
  273. wagtail/snippets/locale/en/LC_MESSAGES/django.po +3 -3
  274. wagtail/snippets/locale/ru/LC_MESSAGES/django.mo +0 -0
  275. wagtail/snippets/locale/ru/LC_MESSAGES/django.po +8 -1
  276. wagtail/snippets/locale/tr/LC_MESSAGES/django.mo +0 -0
  277. wagtail/snippets/locale/tr/LC_MESSAGES/django.po +8 -1
  278. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser-telepath.js +1 -1
  279. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser.js +1 -1
  280. wagtail/snippets/tests/test_preview.py +5 -6
  281. wagtail/snippets/tests/test_reordering.py +319 -0
  282. wagtail/snippets/tests/test_snippets.py +65 -12
  283. wagtail/snippets/views/snippets.py +16 -0
  284. wagtail/test/numberformat.py +30 -0
  285. wagtail/test/settings.py +35 -12
  286. wagtail/test/testapp/fields.py +12 -0
  287. wagtail/test/testapp/migrations/0056_commentablejsonpage.py +50 -0
  288. wagtail/test/testapp/migrations/0057_featurecompletetoy_sort_order.py +23 -0
  289. wagtail/test/testapp/migrations/0058_customlocktask.py +31 -0
  290. wagtail/test/testapp/models.py +27 -0
  291. wagtail/test/testapp/urls.py +1 -0
  292. wagtail/test/testapp/views.py +18 -2
  293. wagtail/test/utils/page_tests.py +17 -17
  294. wagtail/test/utils/template_tests.py +4 -6
  295. wagtail/test/utils/wagtail_tests.py +1 -2
  296. wagtail/tests/test_blocks.py +15 -0
  297. wagtail/tests/test_page_model.py +15 -0
  298. wagtail/{search/tests → tests}/test_page_search.py +29 -2
  299. wagtail/tests/test_search_fields.py +69 -0
  300. wagtail/tests/test_tests.py +62 -6
  301. wagtail/tests/test_workflow.py +25 -1
  302. wagtail/users/locale/cs/LC_MESSAGES/django.mo +0 -0
  303. wagtail/users/locale/cs/LC_MESSAGES/django.po +3 -0
  304. wagtail/users/locale/en/LC_MESSAGES/django.po +2 -2
  305. wagtail/users/locale/nl/LC_MESSAGES/django.mo +0 -0
  306. wagtail/users/locale/nl/LC_MESSAGES/django.po +6 -3
  307. wagtail/users/locale/ru/LC_MESSAGES/django.mo +0 -0
  308. wagtail/users/locale/ru/LC_MESSAGES/django.po +5 -1
  309. wagtail/users/locale/tr/LC_MESSAGES/django.mo +0 -0
  310. wagtail/users/locale/tr/LC_MESSAGES/django.po +78 -4
  311. wagtail/users/templates/wagtailusers/users/create.html +2 -0
  312. wagtail/users/templates/wagtailusers/users/edit.html +2 -0
  313. wagtail/users/tests/test_admin_views.py +4 -0
  314. wagtail/users/views/users.py +1 -1
  315. {wagtail-7.1.1.dist-info → wagtail-7.2rc1.dist-info}/METADATA +7 -6
  316. {wagtail-7.1.1.dist-info → wagtail-7.2rc1.dist-info}/RECORD +322 -328
  317. wagtail/admin/templates/wagtailadmin/collection_privacy/set_privacy.html +0 -13
  318. wagtail/admin/templates/wagtailadmin/page_privacy/set_privacy.html +0 -13
  319. wagtail/search/tests/__init__.py +0 -0
  320. wagtail/search/tests/elasticsearch_common_tests.py +0 -251
  321. wagtail/search/tests/test_backends.py +0 -1215
  322. wagtail/search/tests/test_db_backend.py +0 -62
  323. wagtail/search/tests/test_elasticsearch7_backend.py +0 -1452
  324. wagtail/search/tests/test_elasticsearch8_backend.py +0 -15
  325. wagtail/search/tests/test_index_functions.py +0 -256
  326. wagtail/search/tests/test_indexed_class.py +0 -157
  327. wagtail/search/tests/test_mysql_backend.py +0 -192
  328. wagtail/search/tests/test_postgres_backend.py +0 -210
  329. wagtail/search/tests/test_queries.py +0 -332
  330. wagtail/search/tests/test_related_fields.py +0 -102
  331. wagtail/search/tests/test_sqlite_backend.py +0 -52
  332. wagtail/test/search/__init__.py +0 -0
  333. wagtail/test/search/apps.py +0 -9
  334. wagtail/test/search/fixtures/search.json +0 -545
  335. wagtail/test/search/migrations/0001_initial.py +0 -146
  336. wagtail/test/search/migrations/0002_bookunindexed.py +0 -43
  337. wagtail/test/search/migrations/0003_book_summary.py +0 -18
  338. wagtail/test/search/migrations/__init__.py +0 -0
  339. wagtail/test/search/models.py +0 -137
  340. /wagtail/admin/templates/wagtailadmin/{pages/listing/_ordering_cell.html → tables/ordering_cell.html} +0 -0
  341. /wagtail/{search/checks.py → checks.py} +0 -0
  342. {wagtail-7.1.1.dist-info → wagtail-7.2rc1.dist-info}/WHEEL +0 -0
  343. {wagtail-7.1.1.dist-info → wagtail-7.2rc1.dist-info}/entry_points.txt +0 -0
  344. {wagtail-7.1.1.dist-info → wagtail-7.2rc1.dist-info}/licenses/LICENSE +0 -0
  345. {wagtail-7.1.1.dist-info → wagtail-7.2rc1.dist-info}/top_level.txt +0 -0
@@ -1,547 +1 @@
1
- import datetime
2
- from warnings import warn
3
-
4
- from django.db.models import OrderBy
5
- from django.db.models.functions.datetime import Extract as ExtractDate
6
- from django.db.models.functions.datetime import ExtractYear
7
- from django.db.models.lookups import Lookup
8
- from django.db.models.query import QuerySet
9
- from django.db.models.sql.where import NothingNode, WhereNode
10
-
11
- from wagtail.search.index import class_is_indexed, get_indexed_models
12
- from wagtail.search.query import MATCH_ALL, PlainText
13
-
14
-
15
- class FilterError(Exception):
16
- pass
17
-
18
-
19
- class FieldError(Exception):
20
- def __init__(self, *args, field_name=None, **kwargs):
21
- self.field_name = field_name
22
- super().__init__(*args, **kwargs)
23
-
24
-
25
- class SearchFieldError(FieldError):
26
- pass
27
-
28
-
29
- class FilterFieldError(FieldError):
30
- pass
31
-
32
-
33
- class OrderByFieldError(FieldError):
34
- pass
35
-
36
-
37
- class BaseSearchQueryCompiler:
38
- DEFAULT_OPERATOR = "or"
39
- HANDLES_ORDER_BY_EXPRESSIONS = False
40
-
41
- def __init__(
42
- self,
43
- queryset,
44
- query,
45
- fields=None,
46
- operator=None,
47
- order_by_relevance=True,
48
- ):
49
- self.queryset = queryset
50
- if query is None:
51
- warn(
52
- "Querying `None` is deprecated, use `MATCH_ALL` instead.",
53
- DeprecationWarning,
54
- )
55
- query = MATCH_ALL
56
- elif isinstance(query, str):
57
- query = PlainText(query, operator=operator or self.DEFAULT_OPERATOR)
58
- self.query = query
59
- self.fields = fields
60
- self.order_by_relevance = order_by_relevance
61
-
62
- def _get_filterable_field(self, field_attname):
63
- # Get field
64
- field = {
65
- field.get_attname(self.queryset.model): field
66
- for field in self.queryset.model.get_filterable_search_fields()
67
- }.get(field_attname, None)
68
-
69
- return field
70
-
71
- def _process_lookup(self, field, lookup, value):
72
- raise NotImplementedError
73
-
74
- def _process_match_none(self):
75
- raise NotImplementedError
76
-
77
- def _connect_filters(self, filters, connector, negated):
78
- raise NotImplementedError
79
-
80
- def _process_filter(self, field_attname, lookup, value, check_only=False):
81
- # Get the field
82
- field = self._get_filterable_field(field_attname)
83
-
84
- if field is None:
85
- raise FilterFieldError(
86
- 'Cannot filter search results with field "'
87
- + field_attname
88
- + "\". Please add index.FilterField('"
89
- + field_attname
90
- + "') to "
91
- + self.queryset.model.__name__
92
- + ".search_fields.",
93
- field_name=field_attname,
94
- )
95
-
96
- # Process the lookup
97
- if not check_only:
98
- result = self._process_lookup(field, lookup, value)
99
-
100
- if result is None:
101
- raise FilterError(
102
- 'Could not apply filter on search results: "'
103
- + field_attname
104
- + "__"
105
- + lookup
106
- + " = "
107
- + str(value)
108
- + '". Lookup "'
109
- + lookup
110
- + '"" not recognised.'
111
- )
112
-
113
- return result
114
-
115
- def _get_filters_from_where_node(self, where_node, check_only=False):
116
- # Check if this is a leaf node
117
- if isinstance(where_node, Lookup):
118
- if isinstance(where_node.lhs, ExtractDate):
119
- if not isinstance(where_node.lhs, ExtractYear):
120
- raise FilterError(
121
- 'Cannot apply filter on search results: "'
122
- + where_node.lhs.lookup_name
123
- + '" queries are not supported.'
124
- )
125
- else:
126
- field_attname = where_node.lhs.lhs.target.attname
127
- lookup = where_node.lookup_name
128
- if lookup == "gte":
129
- # filter on year(date) >= value
130
- # i.e. date >= Jan 1st of that year
131
- value = datetime.date(int(where_node.rhs), 1, 1)
132
- elif lookup == "gt":
133
- # filter on year(date) > value
134
- # i.e. date >= Jan 1st of the next year
135
- value = datetime.date(int(where_node.rhs) + 1, 1, 1)
136
- lookup = "gte"
137
- elif lookup == "lte":
138
- # filter on year(date) <= value
139
- # i.e. date < Jan 1st of the next year
140
- value = datetime.date(int(where_node.rhs) + 1, 1, 1)
141
- lookup = "lt"
142
- elif lookup == "lt":
143
- # filter on year(date) < value
144
- # i.e. date < Jan 1st of that year
145
- value = datetime.date(int(where_node.rhs), 1, 1)
146
- elif lookup == "exact":
147
- # filter on year(date) == value
148
- # i.e. date >= Jan 1st of that year and date < Jan 1st of the next year
149
- filter1 = self._process_filter(
150
- field_attname,
151
- "gte",
152
- datetime.date(int(where_node.rhs), 1, 1),
153
- check_only=check_only,
154
- )
155
- filter2 = self._process_filter(
156
- field_attname,
157
- "lt",
158
- datetime.date(int(where_node.rhs) + 1, 1, 1),
159
- check_only=check_only,
160
- )
161
- if check_only:
162
- return
163
- else:
164
- return self._connect_filters(
165
- [filter1, filter2], "AND", False
166
- )
167
- else:
168
- raise FilterError(
169
- 'Cannot apply filter on search results: "'
170
- + where_node.lhs.lookup_name
171
- + '" queries are not supported.'
172
- )
173
- else:
174
- field_attname = where_node.lhs.target.attname
175
- lookup = where_node.lookup_name
176
- value = where_node.rhs
177
-
178
- # Ignore pointer fields that show up in specific page type queries
179
- if field_attname.endswith("_ptr_id"):
180
- return
181
-
182
- # Process the filter
183
- return self._process_filter(
184
- field_attname, lookup, value, check_only=check_only
185
- )
186
-
187
- elif isinstance(where_node, NothingNode):
188
- return self._process_match_none()
189
-
190
- elif isinstance(where_node, WhereNode):
191
- # Get child filters
192
- connector = where_node.connector
193
- child_filters = [
194
- self._get_filters_from_where_node(child)
195
- for child in where_node.children
196
- ]
197
-
198
- if not check_only:
199
- child_filters = [
200
- child_filter for child_filter in child_filters if child_filter
201
- ]
202
- return self._connect_filters(
203
- child_filters, connector, where_node.negated
204
- )
205
-
206
- else:
207
- raise FilterError(
208
- "Could not apply filter on search results: Unknown where node: "
209
- + str(type(where_node))
210
- )
211
-
212
- def _get_filters_from_queryset(self):
213
- return self._get_filters_from_where_node(self.queryset.query.where)
214
-
215
- def _get_order_by(self):
216
- if self.order_by_relevance:
217
- return
218
-
219
- for field_name in self.queryset.query.order_by:
220
- reverse = False
221
-
222
- if isinstance(field_name, OrderBy):
223
- if self.HANDLES_ORDER_BY_EXPRESSIONS:
224
- continue
225
- else:
226
- raise OrderByFieldError(
227
- f'Cannot sort search results with "{field_name}". '
228
- "Please use a search backend that handles these "
229
- "(e.g. database backend) or specify simple fields.",
230
- field_name=field_name,
231
- )
232
-
233
- if field_name.startswith("-"):
234
- reverse = True
235
- field_name = field_name[1:]
236
-
237
- field = self._get_filterable_field(field_name)
238
-
239
- if field is None:
240
- raise OrderByFieldError(
241
- 'Cannot sort search results with field "'
242
- + field_name
243
- + "\". Please add index.FilterField('"
244
- + field_name
245
- + "') to "
246
- + self.queryset.model.__name__
247
- + ".search_fields.",
248
- field_name=field_name,
249
- )
250
-
251
- yield reverse, field
252
-
253
- def check(self):
254
- # Check search fields
255
- if self.fields:
256
- allowed_fields = {
257
- field.field_name
258
- for field in self.queryset.model.get_searchable_search_fields()
259
- }
260
-
261
- for field_name in self.fields:
262
- if field_name not in allowed_fields:
263
- raise SearchFieldError(
264
- 'Cannot search with field "'
265
- + field_name
266
- + "\". Please add index.SearchField('"
267
- + field_name
268
- + "') to "
269
- + self.queryset.model.__name__
270
- + ".search_fields.",
271
- field_name=field_name,
272
- )
273
-
274
- # Check where clause
275
- # Raises FilterFieldError if an unindexed field is being filtered on
276
- self._get_filters_from_where_node(self.queryset.query.where, check_only=True)
277
-
278
- # Check order by
279
- # Raises OrderByFieldError if an unindexed field is being used to order by
280
- list(self._get_order_by())
281
-
282
-
283
- class BaseSearchResults:
284
- supports_facet = False
285
-
286
- def __init__(self, backend, query_compiler, prefetch_related=None):
287
- self.backend = backend
288
- self.query_compiler = query_compiler
289
- self.prefetch_related = prefetch_related
290
- self.start = 0
291
- self.stop = None
292
- self._results_cache = None
293
- self._count_cache = None
294
- self._score_field = None
295
- # Attach the model to mimic a QuerySet so that we can inspect it after
296
- # doing a search, e.g. to get the model's name in a paginator.
297
- # The query_compiler may be None, e.g. when using EmptySearchResults.
298
- self.model = query_compiler.queryset.model if query_compiler else None
299
-
300
- def _set_limits(self, start=None, stop=None):
301
- if stop is not None:
302
- if self.stop is not None:
303
- self.stop = min(self.stop, self.start + stop)
304
- else:
305
- self.stop = self.start + stop
306
-
307
- if start is not None:
308
- if self.stop is not None:
309
- self.start = min(self.stop, self.start + start)
310
- else:
311
- self.start = self.start + start
312
-
313
- def _clone(self):
314
- klass = self.__class__
315
- new = klass(
316
- self.backend, self.query_compiler, prefetch_related=self.prefetch_related
317
- )
318
- new.start = self.start
319
- new.stop = self.stop
320
- new._score_field = self._score_field
321
- return new
322
-
323
- def _do_search(self):
324
- raise NotImplementedError
325
-
326
- def _do_count(self):
327
- raise NotImplementedError
328
-
329
- def results(self):
330
- if self._results_cache is None:
331
- self._results_cache = list(self._do_search())
332
- return self._results_cache
333
-
334
- def count(self):
335
- if self._count_cache is None:
336
- if self._results_cache is not None:
337
- self._count_cache = len(self._results_cache)
338
- else:
339
- self._count_cache = self._do_count()
340
- return self._count_cache
341
-
342
- def __getitem__(self, key):
343
- new = self._clone()
344
-
345
- if isinstance(key, slice):
346
- # Set limits
347
- start = int(key.start) if key.start is not None else None
348
- stop = int(key.stop) if key.stop is not None else None
349
- new._set_limits(start, stop)
350
-
351
- # Copy results cache
352
- if self._results_cache is not None:
353
- new._results_cache = self._results_cache[key]
354
-
355
- return new
356
- else:
357
- if self._results_cache is not None:
358
- return self._results_cache[key]
359
-
360
- new.start = self.start + key
361
- new.stop = self.start + key + 1
362
- return list(new)[0]
363
-
364
- def __iter__(self):
365
- return iter(self.results())
366
-
367
- def __len__(self):
368
- return len(self.results())
369
-
370
- def __repr__(self):
371
- data = list(self[:21])
372
- if len(data) > 20:
373
- data[-1] = "...(remaining elements truncated)..."
374
- return "<SearchResults %r>" % data
375
-
376
- def annotate_score(self, field_name):
377
- clone = self._clone()
378
- clone._score_field = field_name
379
- return clone
380
-
381
- def facet(self, field_name):
382
- raise NotImplementedError("This search backend does not support faceting")
383
-
384
-
385
- class EmptySearchResults(BaseSearchResults):
386
- def __init__(self):
387
- super().__init__(None, None)
388
-
389
- def _clone(self):
390
- return self.__class__()
391
-
392
- def _do_search(self):
393
- return []
394
-
395
- def _do_count(self):
396
- return 0
397
-
398
-
399
- class NullIndex:
400
- """
401
- Index class that provides do-nothing implementations of the indexing operations required by
402
- BaseSearchBackend. Use this for search backends that do not maintain an index, such as the
403
- database backend.
404
- """
405
-
406
- def add_model(self, model):
407
- pass
408
-
409
- def refresh(self):
410
- pass
411
-
412
- def add_item(self, item):
413
- pass
414
-
415
- def add_items(self, model, items):
416
- pass
417
-
418
- def delete_item(self, item):
419
- pass
420
-
421
-
422
- class BaseSearchBackend:
423
- query_compiler_class = None
424
- autocomplete_query_compiler_class = None
425
- results_class = None
426
- rebuilder_class = None
427
- catch_indexing_errors = False
428
-
429
- def __init__(self, params):
430
- pass
431
-
432
- def get_index_for_model(self, model):
433
- return NullIndex()
434
-
435
- def get_rebuilder(self):
436
- return None
437
-
438
- def reset_index(self):
439
- raise NotImplementedError
440
-
441
- def add_type(self, model):
442
- self.get_index_for_model(model).add_model(model)
443
-
444
- def refresh_index(self):
445
- refreshed_indexes = []
446
- for model in get_indexed_models():
447
- index = self.get_index_for_model(model)
448
- if index not in refreshed_indexes:
449
- index.refresh()
450
- refreshed_indexes.append(index)
451
-
452
- def add(self, obj):
453
- self.get_index_for_model(type(obj)).add_item(obj)
454
-
455
- def add_bulk(self, model, obj_list):
456
- self.get_index_for_model(model).add_items(model, obj_list)
457
-
458
- def delete(self, obj):
459
- self.get_index_for_model(type(obj)).delete_item(obj)
460
-
461
- def _search(self, query_compiler_class, query, model_or_queryset, **kwargs):
462
- # Find model/queryset
463
- if isinstance(model_or_queryset, QuerySet):
464
- model = model_or_queryset.model
465
- queryset = model_or_queryset
466
- else:
467
- model = model_or_queryset
468
- queryset = model_or_queryset.objects.all()
469
-
470
- # Model must be a class that is in the index
471
- if not class_is_indexed(model):
472
- return EmptySearchResults()
473
-
474
- # Check that there's still a query string after the clean up
475
- if query == "":
476
- return EmptySearchResults()
477
-
478
- # Search
479
- search_query_compiler = query_compiler_class(queryset, query, **kwargs)
480
-
481
- # Check the query
482
- search_query_compiler.check()
483
-
484
- return self.results_class(self, search_query_compiler)
485
-
486
- def search(
487
- self,
488
- query,
489
- model_or_queryset,
490
- fields=None,
491
- operator=None,
492
- order_by_relevance=True,
493
- ):
494
- return self._search(
495
- self.query_compiler_class,
496
- query,
497
- model_or_queryset,
498
- fields=fields,
499
- operator=operator,
500
- order_by_relevance=order_by_relevance,
501
- )
502
-
503
- def autocomplete(
504
- self,
505
- query,
506
- model_or_queryset,
507
- fields=None,
508
- operator=None,
509
- order_by_relevance=True,
510
- ):
511
- if self.autocomplete_query_compiler_class is None:
512
- raise NotImplementedError(
513
- "This search backend does not support the autocomplete API"
514
- )
515
-
516
- return self._search(
517
- self.autocomplete_query_compiler_class,
518
- query,
519
- model_or_queryset,
520
- fields=fields,
521
- operator=operator,
522
- order_by_relevance=order_by_relevance,
523
- )
524
-
525
-
526
- def get_model_root(model):
527
- """
528
- This function finds the root model for any given model. The root model is
529
- the highest concrete model that it descends from. If the model doesn't
530
- descend from another concrete model then the model is it's own root model so
531
- it is returned.
532
-
533
- Examples:
534
- >>> get_model_root(wagtailcore.Page)
535
- wagtailcore.Page
536
-
537
- >>> get_model_root(myapp.HomePage)
538
- wagtailcore.Page
539
-
540
- >>> get_model_root(wagtailimages.Image)
541
- wagtailimages.Image
542
- """
543
- if model._meta.parents:
544
- parent_model = list(model._meta.parents.items())[0][0]
545
- return get_model_root(parent_model)
546
-
547
- return model
1
+ from wagtailmodelsearch.backends.base import * # noqa: F403
@@ -1,50 +1 @@
1
- import warnings
2
-
3
- from django.db import connection
4
-
5
- USE_SQLITE_FTS = None # True if sqlite FTS is available, False if not, None if untested
6
-
7
-
8
- def SearchBackend(params):
9
- """
10
- Returns the appropriate search backend for the current 'default' database system
11
- """
12
- if connection.vendor == "postgresql":
13
- from .postgres.postgres import PostgresSearchBackend
14
-
15
- return PostgresSearchBackend(params)
16
- elif connection.vendor == "mysql":
17
- from .mysql.mysql import MySQLSearchBackend
18
-
19
- return MySQLSearchBackend(params)
20
- elif connection.vendor == "sqlite":
21
- global USE_SQLITE_FTS
22
-
23
- if USE_SQLITE_FTS is None:
24
- from .sqlite.utils import fts5_available, fts_table_exists
25
-
26
- if not fts5_available():
27
- USE_SQLITE_FTS = False
28
- elif not fts_table_exists():
29
- USE_SQLITE_FTS = False
30
- warnings.warn(
31
- "The installed SQLite library supports full-text search, but the table for storing "
32
- "searchable content is missing. This probably means SQLite was upgraded after the "
33
- "migration was applied. To enable full-text search, reapply wagtailsearch migration 0006 "
34
- "or create the table manually."
35
- )
36
- else:
37
- USE_SQLITE_FTS = True
38
-
39
- if USE_SQLITE_FTS:
40
- from .sqlite.sqlite import SQLiteSearchBackend
41
-
42
- return SQLiteSearchBackend(params)
43
- else:
44
- from .fallback import DatabaseSearchBackend
45
-
46
- return DatabaseSearchBackend(params)
47
- else:
48
- from .fallback import DatabaseSearchBackend
49
-
50
- return DatabaseSearchBackend(params)
1
+ from wagtailmodelsearch.backends.database import * # noqa: F403