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
@@ -1,110 +1,35 @@
1
- from django.core.exceptions import ImproperlyConfigured
2
- from elasticsearch import NotFoundError
1
+ from wagtailmodelsearch.backends.elasticsearch8 import * # noqa: F403
2
+ from wagtailmodelsearch.backends.elasticsearch8 import (
3
+ Elasticsearch8AutocompleteQueryCompiler as _Elasticsearch8AutocompleteQueryCompiler,
4
+ )
5
+ from wagtailmodelsearch.backends.elasticsearch8 import (
6
+ Elasticsearch8SearchBackend as _Elasticsearch8SearchBackend,
7
+ )
8
+ from wagtailmodelsearch.backends.elasticsearch8 import (
9
+ Elasticsearch8SearchQueryCompiler as _Elasticsearch8SearchQueryCompiler,
10
+ )
3
11
 
4
- from wagtail.search.backends.elasticsearch7 import (
5
- Elasticsearch7AutocompleteQueryCompiler,
6
- Elasticsearch7Index,
7
- Elasticsearch7Mapping,
8
- Elasticsearch7SearchBackend,
9
- Elasticsearch7SearchQueryCompiler,
10
- Elasticsearch7SearchResults,
12
+ from wagtail.search.backends.deprecation import (
13
+ IndexOptionMixin,
14
+ LegacyContentTypeMatchMixin,
11
15
  )
12
- from wagtail.search.index import class_is_indexed
13
16
 
14
17
 
15
- class Elasticsearch8Mapping(Elasticsearch7Mapping):
18
+ class Elasticsearch8SearchQueryCompiler(
19
+ LegacyContentTypeMatchMixin, _Elasticsearch8SearchQueryCompiler
20
+ ):
16
21
  pass
17
22
 
18
23
 
19
- class Elasticsearch8Index(Elasticsearch7Index):
20
- def put(self):
21
- self.es.indices.create(index=self.name, **self.backend.settings)
22
-
23
- def delete(self):
24
- try:
25
- self.es.indices.delete(index=self.name)
26
- except NotFoundError:
27
- pass
28
-
29
- def refresh(self):
30
- self.es.indices.refresh(index=self.name)
31
-
32
- def add_model(self, model):
33
- # Get mapping
34
- mapping = self.mapping_class(model)
35
-
36
- # Put mapping
37
- self.es.indices.put_mapping(index=self.name, **mapping.get_mapping())
38
-
39
- def add_item(self, item):
40
- # Make sure the object can be indexed
41
- if not class_is_indexed(item.__class__):
42
- return
43
-
44
- # Get mapping
45
- mapping = self.mapping_class(item.__class__)
46
-
47
- # Add document to index
48
- self.es.index(
49
- index=self.name,
50
- document=mapping.get_document(item),
51
- id=mapping.get_document_id(item),
52
- )
53
-
54
-
55
- class Elasticsearch8SearchQueryCompiler(Elasticsearch7SearchQueryCompiler):
56
- mapping_class = Elasticsearch8Mapping
57
-
58
-
59
- class Elasticsearch8SearchResults(Elasticsearch7SearchResults):
60
- def _backend_do_search(self, body, **kwargs):
61
- # As of Elasticsearch 7.15, the 'body' parameter is deprecated; instead, the top-level
62
- # keys of the body dict are now kwargs in their own right
63
- return self.backend.es.search(**body, **kwargs)
64
-
65
-
66
- class Elasticsearch8AutocompleteQueryCompiler(Elasticsearch7AutocompleteQueryCompiler):
67
- mapping_class = Elasticsearch8Mapping
24
+ class Elasticsearch8AutocompleteQueryCompiler(
25
+ LegacyContentTypeMatchMixin, _Elasticsearch8AutocompleteQueryCompiler
26
+ ):
27
+ pass
68
28
 
69
29
 
70
- class Elasticsearch8SearchBackend(Elasticsearch7SearchBackend):
71
- mapping_class = Elasticsearch8Mapping
72
- index_class = Elasticsearch8Index
30
+ class Elasticsearch8SearchBackend(IndexOptionMixin, _Elasticsearch8SearchBackend):
73
31
  query_compiler_class = Elasticsearch8SearchQueryCompiler
74
32
  autocomplete_query_compiler_class = Elasticsearch8AutocompleteQueryCompiler
75
- results_class = Elasticsearch8SearchResults
76
- timeout_kwarg_name = "request_timeout"
77
-
78
- def _get_host_config_from_url(self, url):
79
- """Given a parsed URL, return the host configuration to be added to self.hosts"""
80
- use_ssl = url.scheme == "https"
81
- port = url.port or (443 if use_ssl else 80)
82
-
83
- # the verify_certs and http_auth options are no longer valid in Elasticsearch 8
84
- return {
85
- "host": url.hostname,
86
- "port": port,
87
- "path_prefix": url.path,
88
- "scheme": url.scheme,
89
- }
90
-
91
- def _get_options_from_host_urls(self, urls):
92
- """Given a list of parsed URLs, return a dict of additional options to be passed into the
93
- Elasticsearch constructor; necessary for options that aren't valid as part of the 'hosts' config"""
94
- opts = super()._get_options_from_host_urls(urls)
95
-
96
- basic_auth = (urls[0].username, urls[0].password)
97
- # Ensure that all urls have the same credentials
98
- if any((url.username, url.password) != basic_auth for url in urls):
99
- raise ImproperlyConfigured(
100
- "Elasticsearch host configuration is invalid. "
101
- "Elasticsearch 8 does not support multiple hosts with differing authentication credentials."
102
- )
103
-
104
- if basic_auth != (None, None):
105
- opts["basic_auth"] = basic_auth
106
-
107
- return opts
108
33
 
109
34
 
110
35
  SearchBackend = Elasticsearch8SearchBackend
@@ -0,0 +1,35 @@
1
+ from wagtailmodelsearch.backends.elasticsearch9 import * # noqa: F403
2
+ from wagtailmodelsearch.backends.elasticsearch9 import (
3
+ Elasticsearch9AutocompleteQueryCompiler as _Elasticsearch9AutocompleteQueryCompiler,
4
+ )
5
+ from wagtailmodelsearch.backends.elasticsearch9 import (
6
+ Elasticsearch9SearchBackend as _Elasticsearch9SearchBackend,
7
+ )
8
+ from wagtailmodelsearch.backends.elasticsearch9 import (
9
+ Elasticsearch9SearchQueryCompiler as _Elasticsearch9SearchQueryCompiler,
10
+ )
11
+
12
+ from wagtail.search.backends.deprecation import (
13
+ IndexOptionMixin,
14
+ LegacyContentTypeMatchMixin,
15
+ )
16
+
17
+
18
+ class Elasticsearch9SearchQueryCompiler(
19
+ LegacyContentTypeMatchMixin, _Elasticsearch9SearchQueryCompiler
20
+ ):
21
+ pass
22
+
23
+
24
+ class Elasticsearch9AutocompleteQueryCompiler(
25
+ LegacyContentTypeMatchMixin, _Elasticsearch9AutocompleteQueryCompiler
26
+ ):
27
+ pass
28
+
29
+
30
+ class Elasticsearch9SearchBackend(IndexOptionMixin, _Elasticsearch9SearchBackend):
31
+ query_compiler_class = Elasticsearch9SearchQueryCompiler
32
+ autocomplete_query_compiler_class = Elasticsearch9AutocompleteQueryCompiler
33
+
34
+
35
+ SearchBackend = Elasticsearch9SearchBackend
@@ -0,0 +1,35 @@
1
+ from wagtailmodelsearch.backends.opensearch2 import * # noqa: F403
2
+ from wagtailmodelsearch.backends.opensearch2 import (
3
+ OpenSearch2AutocompleteQueryCompiler as _OpenSearch2AutocompleteQueryCompiler,
4
+ )
5
+ from wagtailmodelsearch.backends.opensearch2 import (
6
+ OpenSearch2SearchBackend as _OpenSearch2SearchBackend,
7
+ )
8
+ from wagtailmodelsearch.backends.opensearch2 import (
9
+ OpenSearch2SearchQueryCompiler as _OpenSearch2SearchQueryCompiler,
10
+ )
11
+
12
+ from wagtail.search.backends.deprecation import (
13
+ IndexOptionMixin,
14
+ LegacyContentTypeMatchMixin,
15
+ )
16
+
17
+
18
+ class OpenSearch2SearchQueryCompiler(
19
+ LegacyContentTypeMatchMixin, _OpenSearch2SearchQueryCompiler
20
+ ):
21
+ pass
22
+
23
+
24
+ class OpenSearch2AutocompleteQueryCompiler(
25
+ LegacyContentTypeMatchMixin, _OpenSearch2AutocompleteQueryCompiler
26
+ ):
27
+ pass
28
+
29
+
30
+ class OpenSearch2SearchBackend(IndexOptionMixin, _OpenSearch2SearchBackend):
31
+ query_compiler_class = OpenSearch2SearchQueryCompiler
32
+ autocomplete_query_compiler_class = OpenSearch2AutocompleteQueryCompiler
33
+
34
+
35
+ SearchBackend = OpenSearch2SearchBackend
@@ -0,0 +1,35 @@
1
+ from wagtailmodelsearch.backends.opensearch3 import * # noqa: F403
2
+ from wagtailmodelsearch.backends.opensearch3 import (
3
+ OpenSearch3AutocompleteQueryCompiler as _OpenSearch3AutocompleteQueryCompiler,
4
+ )
5
+ from wagtailmodelsearch.backends.opensearch3 import (
6
+ OpenSearch3SearchBackend as _OpenSearch3SearchBackend,
7
+ )
8
+ from wagtailmodelsearch.backends.opensearch3 import (
9
+ OpenSearch3SearchQueryCompiler as _OpenSearch3SearchQueryCompiler,
10
+ )
11
+
12
+ from wagtail.search.backends.deprecation import (
13
+ IndexOptionMixin,
14
+ LegacyContentTypeMatchMixin,
15
+ )
16
+
17
+
18
+ class OpenSearch3SearchQueryCompiler(
19
+ LegacyContentTypeMatchMixin, _OpenSearch3SearchQueryCompiler
20
+ ):
21
+ pass
22
+
23
+
24
+ class OpenSearch3AutocompleteQueryCompiler(
25
+ LegacyContentTypeMatchMixin, _OpenSearch3AutocompleteQueryCompiler
26
+ ):
27
+ pass
28
+
29
+
30
+ class OpenSearch3SearchBackend(IndexOptionMixin, _OpenSearch3SearchBackend):
31
+ query_compiler_class = OpenSearch3SearchQueryCompiler
32
+ autocomplete_query_compiler_class = OpenSearch3AutocompleteQueryCompiler
33
+
34
+
35
+ SearchBackend = OpenSearch3SearchBackend
wagtail/search/index.py CHANGED
@@ -1,358 +1 @@
1
- import inspect
2
- import logging
3
-
4
- from django.apps import apps
5
- from django.core import checks
6
- from django.core.exceptions import FieldDoesNotExist
7
- from django.db import models
8
- from django.db.models.fields.related import ForeignObjectRel, OneToOneRel, RelatedField
9
- from modelcluster.fields import ParentalManyToManyField
10
-
11
- from wagtail.search.backends import get_search_backends_with_name
12
-
13
- logger = logging.getLogger("wagtail.search.index")
14
-
15
-
16
- class Indexed:
17
- @classmethod
18
- def indexed_get_parent(cls, require_model=True):
19
- for base in cls.__bases__:
20
- if issubclass(base, Indexed) and (
21
- issubclass(base, models.Model) or require_model is False
22
- ):
23
- return base
24
-
25
- @classmethod
26
- def indexed_get_content_type(cls):
27
- # Work out content type
28
- content_type = (cls._meta.app_label + "_" + cls.__name__).lower()
29
-
30
- # Get parent content type
31
- parent = cls.indexed_get_parent()
32
- if parent:
33
- parent_content_type = parent.indexed_get_content_type()
34
- return parent_content_type + "_" + content_type
35
- else:
36
- return content_type
37
-
38
- @classmethod
39
- def indexed_get_toplevel_content_type(cls):
40
- # Get parent content type
41
- parent = cls.indexed_get_parent()
42
- if parent:
43
- return parent.indexed_get_content_type()
44
- else:
45
- # At toplevel, return this content type
46
- return (cls._meta.app_label + "_" + cls.__name__).lower()
47
-
48
- @classmethod
49
- def get_search_fields(cls):
50
- search_fields = {}
51
-
52
- for field in cls.search_fields:
53
- search_fields[(type(field), field.field_name)] = field
54
-
55
- return list(search_fields.values())
56
-
57
- @classmethod
58
- def get_searchable_search_fields(cls):
59
- return [
60
- field for field in cls.get_search_fields() if isinstance(field, SearchField)
61
- ]
62
-
63
- @classmethod
64
- def get_autocomplete_search_fields(cls):
65
- return [
66
- field
67
- for field in cls.get_search_fields()
68
- if isinstance(field, AutocompleteField)
69
- ]
70
-
71
- @classmethod
72
- def get_filterable_search_fields(cls):
73
- return [
74
- field for field in cls.get_search_fields() if isinstance(field, FilterField)
75
- ]
76
-
77
- @classmethod
78
- def get_indexed_objects(cls):
79
- queryset = cls.objects.all()
80
-
81
- # Add prefetch/select related for RelatedFields
82
- for field in cls.get_search_fields():
83
- if isinstance(field, RelatedFields):
84
- queryset = field.select_on_queryset(queryset)
85
-
86
- return queryset
87
-
88
- def get_indexed_instance(self):
89
- """
90
- If the indexed model uses multi table inheritance, override this method
91
- to return the instance in its most specific class so it reindexes properly.
92
- """
93
- return self
94
-
95
- @classmethod
96
- def _has_field(cls, name):
97
- try:
98
- cls._meta.get_field(name)
99
- return True
100
- except FieldDoesNotExist:
101
- return hasattr(cls, name)
102
-
103
- @classmethod
104
- def check(cls, **kwargs):
105
- errors = super().check(**kwargs)
106
- errors.extend(cls._check_search_fields(**kwargs))
107
- return errors
108
-
109
- @classmethod
110
- def _check_search_fields(cls, **kwargs):
111
- errors = []
112
- for field in cls.get_search_fields():
113
- message = "{model}.search_fields contains non-existent field '{name}'"
114
- if not cls._has_field(field.field_name):
115
- errors.append(
116
- checks.Warning(
117
- message.format(model=cls.__name__, name=field.field_name),
118
- obj=cls,
119
- id="wagtailsearch.W004",
120
- )
121
- )
122
- return errors
123
-
124
- search_fields = []
125
-
126
-
127
- def get_indexed_models():
128
- return [
129
- model
130
- for model in apps.get_models()
131
- if issubclass(model, Indexed)
132
- and not model._meta.abstract
133
- and model.search_fields
134
- ]
135
-
136
-
137
- def class_is_indexed(cls):
138
- return (
139
- issubclass(cls, Indexed)
140
- and issubclass(cls, models.Model)
141
- and not cls._meta.abstract
142
- and cls.search_fields
143
- )
144
-
145
-
146
- def get_indexed_instance(instance, check_exists=True):
147
- indexed_instance = instance.get_indexed_instance()
148
- if indexed_instance is None:
149
- return
150
-
151
- # Make sure that the instance is in its class's indexed objects
152
- if (
153
- check_exists
154
- and not type(indexed_instance)
155
- .get_indexed_objects()
156
- .filter(pk=indexed_instance.pk)
157
- .exists()
158
- ):
159
- return
160
-
161
- return indexed_instance
162
-
163
-
164
- def insert_or_update_object(instance):
165
- indexed_instance = get_indexed_instance(instance)
166
-
167
- if indexed_instance:
168
- for backend_name, backend in get_search_backends_with_name(
169
- with_auto_update=True
170
- ):
171
- try:
172
- backend.add(indexed_instance)
173
- except Exception:
174
- # Log all errors
175
- logger.exception(
176
- "Exception raised while adding %r into the '%s' search backend",
177
- indexed_instance,
178
- backend_name,
179
- )
180
-
181
- # Catch exceptions for backends that use an external service like Elasticsearch
182
- # This is to prevent data loss if that external service was to go down and the user's
183
- # save request was to fail.
184
- # But note that we don't want this for database backends though as an error during a
185
- # database transaction will require the transaction to be rolled back anyway. So If
186
- # we caught the error here, the request will only crash again when the next database
187
- # query is made but then the error message wouldn't be very informative.
188
- if not backend.catch_indexing_errors:
189
- raise
190
-
191
-
192
- def remove_object(instance):
193
- indexed_instance = get_indexed_instance(instance, check_exists=False)
194
-
195
- if indexed_instance:
196
- for backend_name, backend in get_search_backends_with_name(
197
- with_auto_update=True
198
- ):
199
- try:
200
- backend.delete(indexed_instance)
201
- except Exception:
202
- # Log all errors
203
- logger.exception(
204
- "Exception raised while deleting %r from the '%s' search backend",
205
- indexed_instance,
206
- backend_name,
207
- )
208
-
209
- # Only catch the exception if the backend requires this
210
- # See the comments in insert_or_update_object for an explanation
211
- if not backend.catch_indexing_errors:
212
- raise
213
-
214
-
215
- class BaseField:
216
- def __init__(self, field_name, **kwargs):
217
- self.field_name = field_name
218
- self.kwargs = kwargs
219
-
220
- def get_field(self, cls):
221
- return cls._meta.get_field(self.field_name)
222
-
223
- def get_attname(self, cls):
224
- try:
225
- field = self.get_field(cls)
226
- return field.attname
227
- except FieldDoesNotExist:
228
- return self.field_name
229
-
230
- def get_definition_model(self, cls):
231
- try:
232
- field = self.get_field(cls)
233
- return field.model
234
- except FieldDoesNotExist:
235
- # Find where it was defined by walking the inheritance tree
236
- for base_cls in inspect.getmro(cls):
237
- if self.field_name in base_cls.__dict__:
238
- return base_cls
239
-
240
- def get_type(self, cls):
241
- if "type" in self.kwargs:
242
- return self.kwargs["type"]
243
-
244
- try:
245
- field = self.get_field(cls)
246
-
247
- # Follow foreign keys to find underlying type
248
- # We use a while loop as it's possible for a foreign key
249
- # to target a foreign key in another model.
250
- # (for example, a foreign key to a child page model will
251
- # point to the `page_ptr_id` field so we need to follow this
252
- # second foreign key to find the `id`` field in the Page model)
253
- while isinstance(field, RelatedField):
254
- field = field.target_field
255
-
256
- return field.get_internal_type()
257
-
258
- except FieldDoesNotExist:
259
- return "CharField"
260
-
261
- def get_value(self, obj):
262
- from taggit.managers import TaggableManager
263
-
264
- try:
265
- field = self.get_field(obj.__class__)
266
- value = field.value_from_object(obj)
267
- if hasattr(field, "get_searchable_content"):
268
- value = field.get_searchable_content(value)
269
- elif isinstance(field, TaggableManager):
270
- # As of django-taggit 1.0, value_from_object returns a list of Tag objects,
271
- # which matches what we want
272
- pass
273
- elif isinstance(field, RelatedField):
274
- # The type of the ForeignKey may have a get_searchable_content method that we should
275
- # call. Firstly we need to find the field its referencing but it may be referencing
276
- # another RelatedField (eg an FK to page_ptr_id) so we need to run this in a while
277
- # loop to find the actual remote field.
278
- remote_field = field
279
- while isinstance(remote_field, RelatedField):
280
- remote_field = remote_field.target_field
281
-
282
- if hasattr(remote_field, "get_searchable_content"):
283
- value = remote_field.get_searchable_content(value)
284
- return value
285
- except FieldDoesNotExist:
286
- value = getattr(obj, self.field_name, None)
287
- if hasattr(value, "__call__"):
288
- value = value()
289
- return value
290
-
291
- def __repr__(self):
292
- return f"<{self.__class__.__name__}: {self.field_name}>"
293
-
294
-
295
- class SearchField(BaseField):
296
- def __init__(self, field_name, boost=None, **kwargs):
297
- super().__init__(field_name, **kwargs)
298
- self.boost = boost
299
-
300
-
301
- class AutocompleteField(BaseField):
302
- pass
303
-
304
-
305
- class FilterField(BaseField):
306
- pass
307
-
308
-
309
- class RelatedFields:
310
- def __init__(self, field_name, fields):
311
- self.field_name = field_name
312
- self.fields = fields
313
-
314
- def get_field(self, cls):
315
- return cls._meta.get_field(self.field_name)
316
-
317
- def get_definition_model(self, cls):
318
- field = self.get_field(cls)
319
- return field.model
320
-
321
- def get_value(self, obj):
322
- field = self.get_field(obj.__class__)
323
-
324
- if isinstance(field, (RelatedField, ForeignObjectRel)):
325
- return getattr(obj, self.field_name)
326
-
327
- def select_on_queryset(self, queryset):
328
- """
329
- This method runs either prefetch_related or select_related on the queryset
330
- to improve indexing speed of the relation.
331
-
332
- It decides which method to call based on the number of related objects:
333
- - single (eg ForeignKey, OneToOne), it runs select_related
334
- - multiple (eg ManyToMany, reverse ForeignKey) it runs prefetch_related
335
- """
336
- try:
337
- field = self.get_field(queryset.model)
338
- except FieldDoesNotExist:
339
- return queryset
340
-
341
- if isinstance(field, RelatedField) and not isinstance(
342
- field, ParentalManyToManyField
343
- ):
344
- if field.many_to_one or field.one_to_one:
345
- queryset = queryset.select_related(self.field_name)
346
- elif field.one_to_many or field.many_to_many:
347
- queryset = queryset.prefetch_related(self.field_name)
348
-
349
- elif isinstance(field, ForeignObjectRel):
350
- # Reverse relation
351
- if isinstance(field, OneToOneRel):
352
- # select_related for reverse OneToOneField
353
- queryset = queryset.select_related(self.field_name)
354
- else:
355
- # prefetch_related for anything else (reverse ForeignKey/ManyToManyField)
356
- queryset = queryset.prefetch_related(self.field_name)
357
-
358
- return queryset
1
+ from wagtailmodelsearch.index import * # noqa: F403
@@ -8,7 +8,7 @@ msgid ""
8
8
  msgstr ""
9
9
  "Project-Id-Version: PACKAGE VERSION\n"
10
10
  "Report-Msgid-Bugs-To: \n"
11
- "POT-Creation-Date: 2025-07-24 16:20+0200\n"
11
+ "POT-Creation-Date: 2025-10-23 16:45+0100\n"
12
12
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -18,14 +18,6 @@ msgstr ""
18
18
  "Content-Transfer-Encoding: 8bit\n"
19
19
  "Plural-Forms: nplurals=2; plural=(n != 1);\n"
20
20
 
21
- #: apps.py:14
21
+ #: apps.py:8
22
22
  msgid "Wagtail search"
23
23
  msgstr ""
24
-
25
- #: models.py:65
26
- msgid "index entry"
27
- msgstr ""
28
-
29
- #: models.py:66
30
- msgid "index entries"
31
- msgstr ""