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,700 +1 @@
1
- import re
2
- import warnings
3
- from collections import OrderedDict
4
-
5
- from django.db import (
6
- NotSupportedError,
7
- connections,
8
- router,
9
- transaction,
10
- )
11
- from django.db.models import Case, When
12
- from django.db.models.aggregates import Avg, Count
13
- from django.db.models.constants import LOOKUP_SEP
14
- from django.db.models.expressions import F
15
- from django.db.models.fields import BooleanField, FloatField, TextField
16
- from django.db.models.functions.comparison import Cast
17
- from django.db.models.functions.text import Length
18
- from django.db.models.manager import Manager
19
- from django.db.models.query_utils import Q
20
- from django.utils.encoding import force_str
21
- from django.utils.functional import cached_property
22
-
23
- from wagtail.search.backends.base import (
24
- BaseSearchBackend,
25
- BaseSearchQueryCompiler,
26
- BaseSearchResults,
27
- FilterFieldError,
28
- )
29
- from wagtail.search.backends.database.mysql.query import (
30
- Lexeme,
31
- MatchExpression,
32
- SearchQuery,
33
- )
34
- from wagtail.search.index import (
35
- AutocompleteField,
36
- RelatedFields,
37
- SearchField,
38
- get_indexed_models,
39
- )
40
- from wagtail.search.models import IndexEntry
41
- from wagtail.search.query import And, Boost, MatchAll, Not, Or, Phrase, PlainText
42
- from wagtail.search.utils import (
43
- OR,
44
- balanced_reduce,
45
- get_content_type_pk,
46
- get_descendants_content_types_pks,
47
- )
48
-
49
-
50
- class ObjectIndexer:
51
- """
52
- Responsible for extracting data from an object to be inserted into the index.
53
- """
54
-
55
- def __init__(self, obj, backend):
56
- self.obj = obj
57
- self.search_fields = obj.get_search_fields()
58
- self.config = backend.config
59
-
60
- def prepare_value(self, value):
61
- if isinstance(value, str):
62
- return value
63
-
64
- elif isinstance(value, list):
65
- return ", ".join(self.prepare_value(item) for item in value)
66
-
67
- elif isinstance(value, dict):
68
- return ", ".join(self.prepare_value(item) for item in value.values())
69
-
70
- return force_str(value)
71
-
72
- def prepare_field(self, obj, field):
73
- if isinstance(field, SearchField):
74
- yield (field, self.prepare_value(field.get_value(obj)))
75
-
76
- elif isinstance(field, AutocompleteField):
77
- yield (field, self.prepare_value(field.get_value(obj)))
78
-
79
- elif isinstance(field, RelatedFields):
80
- sub_obj = field.get_value(obj)
81
- if sub_obj is None:
82
- return
83
-
84
- if isinstance(sub_obj, Manager):
85
- sub_objs = sub_obj.all()
86
-
87
- else:
88
- if callable(sub_obj):
89
- sub_obj = sub_obj()
90
-
91
- sub_objs = [sub_obj]
92
-
93
- for sub_obj in sub_objs:
94
- for sub_field in field.fields:
95
- yield from self.prepare_field(sub_obj, sub_field)
96
-
97
- @cached_property
98
- def id(self):
99
- """
100
- Returns the value to use as the ID of the record in the index
101
- """
102
- return force_str(self.obj.pk)
103
-
104
- @cached_property
105
- def title(self):
106
- """
107
- Returns all values to index as "title". This is the value of all SearchFields that have the field_name 'title'
108
- """
109
- texts = []
110
- for field in self.search_fields:
111
- for current_field, value in self.prepare_field(self.obj, field):
112
- if (
113
- isinstance(current_field, SearchField)
114
- and current_field.field_name == "title"
115
- ):
116
- texts.append(value)
117
-
118
- return " ".join(texts)
119
-
120
- @cached_property
121
- def body(self):
122
- """
123
- Returns all values to index as "body". This is the value of all SearchFields excluding the title
124
- """
125
- texts = []
126
- for field in self.search_fields:
127
- for current_field, value in self.prepare_field(self.obj, field):
128
- if (
129
- isinstance(current_field, SearchField)
130
- and not current_field.field_name == "title"
131
- ):
132
- texts.append(value)
133
-
134
- return " ".join(texts)
135
-
136
- @cached_property
137
- def autocomplete(self):
138
- """
139
- Returns all values to index as "autocomplete". This is the value of all AutocompleteFields
140
- """
141
- texts = []
142
- for field in self.search_fields:
143
- for current_field, value in self.prepare_field(self.obj, field):
144
- if isinstance(current_field, AutocompleteField):
145
- texts.append(value)
146
-
147
- return " ".join(texts)
148
-
149
- def as_vector(self, texts, for_autocomplete=False):
150
- """
151
- Converts an array of strings into a SearchVector that can be indexed.
152
- """
153
- texts = [(text.strip(), weight) for text, weight in texts]
154
- texts = [(text, weight) for text, weight in texts if text]
155
-
156
- return " ".join(texts)
157
-
158
-
159
- class Index:
160
- def __init__(self, backend):
161
- self.backend = backend
162
- self.name = self.backend.index_name
163
-
164
- self.read_connection = connections[router.db_for_read(IndexEntry)]
165
- self.write_connection = connections[router.db_for_write(IndexEntry)]
166
-
167
- if (
168
- self.read_connection.vendor != "mysql"
169
- or self.write_connection.vendor != "mysql"
170
- ):
171
- raise NotSupportedError(
172
- "You must select a MySQL database to use MySQL search."
173
- )
174
-
175
- self.entries = IndexEntry._default_manager.all()
176
-
177
- def add_model(self, model):
178
- pass
179
-
180
- def refresh(self):
181
- pass
182
-
183
- def _refresh_title_norms(self, full=False):
184
- """
185
- Refreshes the value of the title_norm field.
186
-
187
- This needs to be set to 'lavg/ld' where:
188
- - lavg is the average length of titles in all documents (also in terms)
189
- - ld is the length of the title field in this document (in terms)
190
- """
191
-
192
- lavg = (
193
- self.entries.annotate(title_length=Length("title"))
194
- .filter(title_length__gt=0)
195
- .aggregate(Avg("title_length"))["title_length__avg"]
196
- )
197
-
198
- if full:
199
- # Update the whole table
200
- # This is the most accurate option but requires a full table rewrite
201
- # so we can't do it too often as it could lead to locking issues.
202
- entries = self.entries
203
-
204
- else:
205
- # Only update entries where title_norm is 1.0
206
- # This is the default value set on new entries.
207
- # It's possible that other entries could have this exact value but there shouldn't be too many of those
208
- entries = self.entries.filter(title_norm=1.0)
209
-
210
- entries.annotate(title_length=Length("title")).filter(
211
- title_length__gt=0
212
- ).update(title_norm=lavg / F("title_length"))
213
-
214
- def delete_stale_model_entries(self, model):
215
- existing_pks = model._default_manager.annotate(
216
- object_id=Cast("pk", TextField())
217
- ).values("object_id")
218
- content_types_pks = get_descendants_content_types_pks(model)
219
- stale_entries = self.entries.filter(
220
- content_type_id__in=content_types_pks
221
- ).exclude(object_id__in=existing_pks)
222
- stale_entries.delete()
223
-
224
- def delete_stale_entries(self):
225
- for model in get_indexed_models():
226
- # We don’t need to delete stale entries for non-root models,
227
- # since we already delete them by deleting roots.
228
- if not model._meta.parents:
229
- self.delete_stale_model_entries(model)
230
-
231
- def add_item(self, obj):
232
- self.add_items(obj._meta.model, [obj])
233
-
234
- def add_items_update_then_create(self, content_type_pk, indexers):
235
- ids_and_data = {}
236
- for indexer in indexers:
237
- ids_and_data[indexer.id] = (
238
- indexer.title,
239
- indexer.autocomplete,
240
- indexer.body,
241
- )
242
-
243
- index_entries_for_ct = self.entries.filter(content_type_id=content_type_pk)
244
- indexed_ids = frozenset(
245
- index_entries_for_ct.filter(object_id__in=ids_and_data.keys()).values_list(
246
- "object_id", flat=True
247
- )
248
- )
249
- for indexed_id in indexed_ids:
250
- title, autocomplete, body = ids_and_data[indexed_id]
251
- index_entries_for_ct.filter(object_id=indexed_id).update(
252
- title=title, autocomplete=autocomplete, body=body
253
- )
254
-
255
- to_be_created = []
256
- for object_id in ids_and_data.keys():
257
- if object_id not in indexed_ids:
258
- title, autocomplete, body = ids_and_data[object_id]
259
- to_be_created.append(
260
- IndexEntry(
261
- content_type_id=content_type_pk,
262
- object_id=object_id,
263
- title=title,
264
- autocomplete=autocomplete,
265
- body=body,
266
- )
267
- )
268
-
269
- self.entries.bulk_create(to_be_created)
270
-
271
- self._refresh_title_norms()
272
-
273
- def add_items(self, model, objs):
274
- search_fields = model.get_search_fields()
275
- if not search_fields:
276
- return
277
-
278
- indexers = [ObjectIndexer(obj, self.backend) for obj in objs]
279
-
280
- # TODO: Delete unindexed objects while dealing with proxy models.
281
- if indexers:
282
- content_type_pk = get_content_type_pk(model)
283
-
284
- update_method = self.add_items_update_then_create
285
- update_method(content_type_pk, indexers)
286
-
287
- def delete_item(self, item):
288
- item.index_entries.all()._raw_delete(using=self.write_connection.alias)
289
-
290
- def __str__(self):
291
- return self.name
292
-
293
-
294
- class MySQLSearchQueryCompiler(BaseSearchQueryCompiler):
295
- DEFAULT_OPERATOR = "and"
296
- LAST_TERM_IS_PREFIX = False
297
- TARGET_SEARCH_FIELD_TYPE = SearchField
298
- FTS_TABLE_FIELDS = ["title", "body"]
299
- HANDLES_ORDER_BY_EXPRESSIONS = True
300
-
301
- def __init__(self, *args, **kwargs):
302
- super().__init__(*args, **kwargs)
303
-
304
- local_search_fields = self.get_search_fields_for_model()
305
-
306
- if self.fields is None:
307
- # search over the fields defined on the current model
308
- self.search_fields = local_search_fields
309
- else:
310
- # build a search_fields set from the passed definition,
311
- # which may involve traversing relations
312
- self.search_fields = {
313
- field_lookup: self.get_search_field(
314
- field_lookup, fields=local_search_fields
315
- )
316
- for field_lookup in self.fields
317
- }
318
-
319
- def get_config(self, backend):
320
- return backend.config
321
-
322
- def get_search_fields_for_model(self):
323
- return self.queryset.model.get_searchable_search_fields()
324
-
325
- def get_search_field(self, field_lookup, fields=None):
326
- if fields is None:
327
- fields = self.search_fields
328
-
329
- if LOOKUP_SEP in field_lookup:
330
- field_lookup, sub_field_name = field_lookup.split(LOOKUP_SEP, 1)
331
- else:
332
- sub_field_name = None
333
-
334
- for field in fields:
335
- if (
336
- isinstance(field, self.TARGET_SEARCH_FIELD_TYPE)
337
- and field.field_name == field_lookup
338
- ):
339
- return field
340
-
341
- # Note: Searching on a specific related field using
342
- # `.search(fields=…)` is not yet supported by Wagtail.
343
- # This method anticipates by already implementing it.
344
- if isinstance(field, RelatedFields) and field.field_name == field_lookup:
345
- return self.get_search_field(sub_field_name, field.fields)
346
-
347
- def build_search_query_content(self, query, invert=False):
348
- if isinstance(query, PlainText):
349
- # For Boolean full text search queries in MySQL,
350
- # non-alphanumeric characters act as separators
351
- terms = [term for term in re.split(r"\W+", query.query_string) if term]
352
-
353
- if not terms:
354
- return SearchQuery("")
355
-
356
- last_term = terms.pop()
357
-
358
- lexemes = Lexeme(last_term, invert=invert, prefix=self.LAST_TERM_IS_PREFIX)
359
- for term in terms:
360
- new_lexeme = Lexeme(term, invert=invert)
361
-
362
- if query.operator == "and":
363
- lexemes &= new_lexeme
364
- else:
365
- lexemes |= new_lexeme
366
-
367
- return SearchQuery(lexemes)
368
-
369
- elif isinstance(query, Phrase):
370
- return SearchQuery(query.query_string, search_type="phrase")
371
-
372
- elif isinstance(query, Boost):
373
- # Not supported
374
- msg = "The Boost query is not supported by the MySQL search backend."
375
- warnings.warn(msg, RuntimeWarning)
376
-
377
- return self.build_search_query_content(query.subquery, invert=invert)
378
-
379
- elif isinstance(query, Not):
380
- return self.build_search_query_content(query.subquery, invert=not invert)
381
-
382
- elif isinstance(query, (And, Or)):
383
- # If this part of the query is inverted, we swap the operator and
384
- # pass down the inversion state to the child queries.
385
- # This works thanks to De Morgan's law.
386
- #
387
- # For example, the following query:
388
- #
389
- # Not(And(Term("A"), Term("B")))
390
- #
391
- # Is equivalent to:
392
- #
393
- # Or(Not(Term("A")), Not(Term("B")))
394
- #
395
- # It's simpler to code it this way as we only need to store the
396
- # invert status of the terms rather than all the operators.
397
-
398
- subquery_lexemes = [
399
- self.build_search_query_content(subquery, invert=invert)
400
- for subquery in query.subqueries
401
- ]
402
-
403
- is_and = isinstance(query, And)
404
-
405
- if invert:
406
- is_and = not is_and
407
-
408
- if is_and:
409
- return balanced_reduce(lambda a, b: a & b, subquery_lexemes)
410
- else:
411
- return balanced_reduce(lambda a, b: a | b, subquery_lexemes)
412
-
413
- raise NotImplementedError(
414
- "`%s` is not supported by the MySQL search backend."
415
- % query.__class__.__name__
416
- )
417
-
418
- def build_search_query(self, query):
419
- return self.build_search_query_content(query)
420
-
421
- def get_index_vectors(self, search_query):
422
- return [
423
- (F("index_entries__title"), F("index_entries__title_norm")),
424
- (F("index_entries__body"), 1.0),
425
- ]
426
-
427
- def get_fields_vectors(self, search_query):
428
- raise NotImplementedError()
429
-
430
- def get_search_vectors(self, search_query):
431
- if self.fields is None:
432
- return self.get_index_vectors(search_query)
433
-
434
- else:
435
- return self.get_fields_vectors(search_query)
436
-
437
- def _build_rank_expression(self, vectors, config):
438
- rank_expressions = [
439
- self.build_tsrank(vector, self.query, config=config) * boost
440
- for vector, boost in vectors
441
- ]
442
-
443
- rank_expression = rank_expressions[0]
444
- for other_rank_expression in rank_expressions[1:]:
445
- rank_expression += other_rank_expression
446
-
447
- return rank_expression
448
-
449
- def search(self, config, start, stop, score_field=None):
450
- # TODO: Handle MatchAll nested inside other search query classes.
451
- if isinstance(self.query, MatchAll):
452
- return self.queryset[start:stop]
453
-
454
- elif isinstance(self.query, Not) and isinstance(self.query.subquery, MatchAll):
455
- return self.queryset.none()
456
-
457
- if isinstance(
458
- self.query, Not
459
- ): # If the outermost operator is a Not, we invert the query. This is done because, if every search term is negated, the Not() will return no results, an we want to match all results instead.
460
- query = self.query.subquery
461
- negated = True
462
- else:
463
- query = self.query
464
- negated = False
465
-
466
- search_query = self.build_search_query(query)
467
- match_expression = MatchExpression(
468
- search_query, columns=self.FTS_TABLE_FIELDS, output_field=BooleanField()
469
- ) # For example: MATCH (`title`, `body`) AGAINST ('+query' IN BOOLEAN MODE)
470
-
471
- # In Django 4.0 the above match expression would produce this SQL WHERE clause:
472
- #
473
- # WHERE ... MATCH (`title`, `body`) AGAINST (query IN BOOLEAN MODE)
474
- #
475
- # In Django 4.1, this behavior was changed:
476
- #
477
- # https://code.djangoproject.com/ticket/32691
478
- # https://github.com/django/django/commit/407fe95cb116599adeb4b9ed01df5673aa5cb1db
479
- #
480
- # so that instead this SQL WHERE clause is generated, explicitly filtering
481
- # against "= True":
482
- #
483
- # WHERE ... MATCH (`title`, `body`) AGAINST (query IN BOOLEAN MODE) = True
484
- #
485
- # This no longer works properly because MATCH returns a floating point score
486
- # as a measurement of the match quality, not a boolean value:
487
- #
488
- # https://dev.mysql.com/doc/refman/8.0/en/fulltext-boolean.html
489
- #
490
- # In order for filtering on "= True" to work, we change the match expression
491
- # SQL to be:
492
- #
493
- # WHERE ... CASE WHEN MATCH (`title`, `body`) AGAINST (query IN BOOLEAN MODE) THEN True ELSE False END = True
494
- match_expression = Case(When(match_expression, then=True), default=False)
495
-
496
- score_expression = MatchExpression(
497
- search_query, columns=["title"], output_field=FloatField()
498
- ) * F("title_norm") + MatchExpression(
499
- search_query, columns=["body"], output_field=FloatField()
500
- )
501
-
502
- index_entries = IndexEntry.objects.annotate(score=score_expression).filter(
503
- content_type_id__in=get_descendants_content_types_pks(self.queryset.model)
504
- )
505
- if not negated:
506
- index_entries = index_entries.filter(match_expression)
507
- if self.order_by_relevance: # Only applies to the case where the outermost query is not a Not(), because if it is, the relevance score is always 0 (anything that matches is excluded from the results).
508
- index_entries = index_entries.order_by(score_expression.desc())
509
- else:
510
- index_entries = index_entries.exclude(match_expression)
511
-
512
- index_entries = index_entries[start:stop] # Trim the results
513
-
514
- object_ids = {
515
- index_entry.object_id for index_entry in index_entries
516
- } # Get the set of IDs from the indexed objects, removes duplicates too
517
-
518
- results = self.queryset.filter(id__in=object_ids)
519
-
520
- return results
521
-
522
- def _process_lookup(self, field, lookup, value):
523
- lhs = field.get_attname(self.queryset.model) + "__" + lookup
524
- return Q(**{lhs: value})
525
-
526
- def _process_match_none(self):
527
- return Q(pk__in=[])
528
-
529
- def _connect_filters(self, filters, connector, negated):
530
- if connector == "AND":
531
- q = Q(*filters)
532
-
533
- elif connector == "OR":
534
- q = OR([Q(fil) for fil in filters])
535
-
536
- else:
537
- return
538
-
539
- if negated:
540
- q = ~q
541
-
542
- return q
543
-
544
-
545
- class MySQLAutocompleteQueryCompiler(MySQLSearchQueryCompiler):
546
- LAST_TERM_IS_PREFIX = True
547
- TARGET_SEARCH_FIELD_TYPE = AutocompleteField
548
- FTS_TABLE_FIELDS = ["autocomplete"]
549
-
550
- def get_config(self, backend):
551
- return backend.autocomplete_config
552
-
553
- def get_search_fields_for_model(self):
554
- return self.queryset.model.get_autocomplete_search_fields()
555
-
556
- def get_index_vectors(self, search_query):
557
- return [(F("index_entries__autocomplete"), 1.0)]
558
-
559
- def get_fields_vectors(self, search_query):
560
- raise NotImplementedError()
561
-
562
-
563
- class MySQLSearchResults(BaseSearchResults):
564
- def get_queryset(self, for_count=False):
565
- if for_count:
566
- start = None
567
- stop = None
568
- else:
569
- start = self.start
570
- stop = self.stop
571
-
572
- return self.query_compiler.search(
573
- self.query_compiler.get_config(self.backend),
574
- start,
575
- stop,
576
- score_field=self._score_field,
577
- )
578
-
579
- def _do_search(self):
580
- return list(self.get_queryset())
581
-
582
- def _do_count(self):
583
- return self.get_queryset(for_count=True).count()
584
-
585
- supports_facet = True
586
-
587
- def facet(self, field_name):
588
- # Get field
589
- field = self.query_compiler._get_filterable_field(field_name)
590
- if field is None:
591
- raise FilterFieldError(
592
- 'Cannot facet search results with field "'
593
- + field_name
594
- + "\". Please add index.FilterField('"
595
- + field_name
596
- + "') to "
597
- + self.query_compiler.queryset.model.__name__
598
- + ".search_fields.",
599
- field_name=field_name,
600
- )
601
-
602
- query = self.query_compiler.search(
603
- self.query_compiler.get_config(self.backend), None, None
604
- )
605
- results = (
606
- query.values(field_name).annotate(count=Count("pk")).order_by("-count")
607
- )
608
-
609
- return OrderedDict(
610
- [(result[field_name], result["count"]) for result in results]
611
- )
612
-
613
-
614
- class MySQLSearchRebuilder:
615
- def __init__(self, index):
616
- self.index = index
617
-
618
- def start(self):
619
- self.index.delete_stale_entries()
620
- return self.index
621
-
622
- def finish(self):
623
- self.index._refresh_title_norms(full=True)
624
-
625
-
626
- class MySQLSearchAtomicRebuilder(MySQLSearchRebuilder):
627
- def __init__(self, index):
628
- super().__init__(index)
629
- self.transaction = transaction.atomic(using=index.write_connection.alias)
630
- self.transaction_opened = False
631
-
632
- def start(self):
633
- self.transaction.__enter__()
634
- self.transaction_opened = True
635
- return super().start()
636
-
637
- def finish(self):
638
- self.index._refresh_title_norms(full=True)
639
-
640
- self.transaction.__exit__(None, None, None)
641
- self.transaction_opened = False
642
-
643
- def __del__(self):
644
- # TODO: Implement a cleaner way to close the connection on failure.
645
- if self.transaction_opened:
646
- self.transaction.needs_rollback = True
647
- self.finish()
648
-
649
-
650
- class MySQLSearchBackend(BaseSearchBackend):
651
- query_compiler_class = MySQLSearchQueryCompiler
652
- autocomplete_query_compiler_class = MySQLAutocompleteQueryCompiler
653
-
654
- results_class = MySQLSearchResults
655
- rebuilder_class = MySQLSearchRebuilder
656
- atomic_rebuilder_class = MySQLSearchAtomicRebuilder
657
-
658
- def __init__(self, params):
659
- super().__init__(params)
660
- self.index_name = params.get("INDEX", "default")
661
-
662
- # MySQL backend currently has no config options
663
- self.config = None
664
- self.autocomplete_config = None
665
-
666
- if params.get("ATOMIC_REBUILD", False):
667
- self.rebuilder_class = self.atomic_rebuilder_class
668
-
669
- def get_index_for_model(self, model):
670
- return Index(self)
671
-
672
- def get_index_for_object(self, obj):
673
- return self.get_index_for_model(obj._meta.model)
674
-
675
- def reset_index(self):
676
- for connection in [
677
- connection
678
- for connection in connections.all()
679
- if connection.vendor == "mysql"
680
- ]:
681
- IndexEntry._default_manager.all()._raw_delete(using=connection.alias)
682
-
683
- def add_type(self, model):
684
- pass # Not needed.
685
-
686
- def refresh_index(self):
687
- pass # Not needed.
688
-
689
- def add(self, obj):
690
- self.get_index_for_object(obj).add_item(obj)
691
-
692
- def add_bulk(self, model, obj_list):
693
- if obj_list:
694
- self.get_index_for_object(obj_list[0]).add_items(model, obj_list)
695
-
696
- def delete(self, obj):
697
- self.get_index_for_object(obj).delete_item(obj)
698
-
699
-
700
- SearchBackend = MySQLSearchBackend
1
+ from wagtailmodelsearch.backends.database.mysql.mysql import * # noqa: F403