wagtail 6.3.1__py3-none-any.whl → 6.4rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (307) hide show
  1. wagtail/__init__.py +1 -1
  2. wagtail/actions/publish_revision.py +4 -5
  3. wagtail/admin/auth.py +0 -2
  4. wagtail/admin/checks.py +1 -1
  5. wagtail/admin/filters.py +3 -1
  6. wagtail/admin/forms/account.py +21 -11
  7. wagtail/admin/forms/collections.py +2 -9
  8. wagtail/admin/forms/formsets.py +32 -0
  9. wagtail/admin/forms/pages.py +5 -1
  10. wagtail/admin/forms/workflows.py +2 -13
  11. wagtail/admin/locale/ar/LC_MESSAGES/django.mo +0 -0
  12. wagtail/admin/locale/ar/LC_MESSAGES/django.po +68 -1
  13. wagtail/admin/locale/ar/LC_MESSAGES/djangojs.mo +0 -0
  14. wagtail/admin/locale/ar/LC_MESSAGES/djangojs.po +5 -1
  15. wagtail/admin/locale/en/LC_MESSAGES/django.po +312 -356
  16. wagtail/admin/locale/en/LC_MESSAGES/djangojs.po +21 -16
  17. wagtail/admin/locale/gl/LC_MESSAGES/djangojs.mo +0 -0
  18. wagtail/admin/locale/gl/LC_MESSAGES/djangojs.po +5 -5
  19. wagtail/admin/locale/pt_BR/LC_MESSAGES/django.mo +0 -0
  20. wagtail/admin/locale/pt_BR/LC_MESSAGES/django.po +29 -0
  21. wagtail/admin/menu.py +0 -13
  22. wagtail/admin/panels/base.py +2 -2
  23. wagtail/admin/panels/group.py +4 -1
  24. wagtail/admin/panels/inline_panel.py +5 -2
  25. wagtail/admin/panels/model_utils.py +36 -0
  26. wagtail/admin/panels/page_utils.py +2 -40
  27. wagtail/admin/panels/signal_handlers.py +0 -2
  28. wagtail/admin/static/wagtailadmin/css/core.css +1 -1
  29. wagtail/admin/static/wagtailadmin/css/panels/draftail.css +1 -1
  30. wagtail/admin/static/wagtailadmin/css/panels/streamfield.css +1 -1
  31. wagtail/admin/static/wagtailadmin/js/comments.js +1 -1
  32. wagtail/admin/static/wagtailadmin/js/core.js +1 -1
  33. wagtail/admin/static/wagtailadmin/js/core.js.LICENSE.txt +1 -8
  34. wagtail/admin/static/wagtailadmin/js/draftail.js +1 -1
  35. wagtail/admin/static/wagtailadmin/js/modal-workflow.js +1 -1
  36. wagtail/admin/static/wagtailadmin/js/page-chooser-modal.js +1 -1
  37. wagtail/admin/static/wagtailadmin/js/privacy-switch.js +1 -1
  38. wagtail/admin/static/wagtailadmin/js/sidebar.js +1 -1
  39. wagtail/admin/static/wagtailadmin/js/telepath/blocks.js +1 -1
  40. wagtail/admin/static/wagtailadmin/js/userbar.js +1 -1
  41. wagtail/admin/static/wagtailadmin/js/userbar.js.LICENSE.txt +1 -1
  42. wagtail/admin/static/wagtailadmin/js/vendor.js +1 -1
  43. wagtail/admin/static/wagtailadmin/js/vendor.js.LICENSE.txt +7 -0
  44. wagtail/admin/templates/wagtailadmin/404.html +4 -0
  45. wagtail/admin/templates/wagtailadmin/chooser/browse.html +2 -1
  46. wagtail/admin/templates/wagtailadmin/chooser/tables/parent_page_cell.html +1 -1
  47. wagtail/admin/templates/wagtailadmin/collections/_privacy_switch.html +8 -1
  48. wagtail/admin/templates/wagtailadmin/generic/confirm_delete.html +15 -9
  49. wagtail/admin/templates/wagtailadmin/generic/confirm_unpublish.html +21 -25
  50. wagtail/admin/templates/wagtailadmin/generic/form.html +1 -1
  51. wagtail/admin/templates/wagtailadmin/generic/preview_error.html +3 -0
  52. wagtail/admin/templates/wagtailadmin/generic/revisions/compare.html +63 -76
  53. wagtail/admin/templates/wagtailadmin/pages/_editor_js.html +0 -2
  54. wagtail/admin/templates/wagtailadmin/pages/edit.html +1 -5
  55. wagtail/admin/templates/wagtailadmin/panels/inline_panel_child.html +1 -0
  56. wagtail/admin/templates/wagtailadmin/permissions/includes/collection_member_permissions_form.html +1 -1
  57. wagtail/admin/templates/wagtailadmin/permissions/includes/collection_member_permissions_formset.html +6 -22
  58. wagtail/admin/templates/wagtailadmin/shared/formatted_field.html +2 -2
  59. wagtail/admin/templates/wagtailadmin/shared/header.html +2 -2
  60. wagtail/admin/templates/wagtailadmin/shared/page_status_tag_new.html +32 -39
  61. wagtail/admin/templates/wagtailadmin/shared/revisions/confirm_unschedule.html +13 -17
  62. wagtail/admin/templates/wagtailadmin/shared/side_panels/includes/status/privacy.html +15 -3
  63. wagtail/admin/templates/wagtailadmin/shared/side_panels/preview.html +1 -1
  64. wagtail/admin/templates/wagtailadmin/skeleton.html +4 -2
  65. wagtail/admin/templates/wagtailadmin/workflows/create.html +1 -1
  66. wagtail/admin/templates/wagtailadmin/workflows/edit.html +1 -1
  67. wagtail/admin/templates/wagtailadmin/workflows/includes/workflow_pages_form.html +1 -1
  68. wagtail/admin/templates/wagtailadmin/workflows/includes/workflow_pages_formset.html +6 -23
  69. wagtail/admin/templatetags/wagtailadmin_tags.py +12 -0
  70. wagtail/admin/templatetags/wagtailuserbar.py +2 -3
  71. wagtail/admin/tests/pages/test_create_page.py +110 -1
  72. wagtail/admin/tests/pages/test_edit_page.py +3 -2
  73. wagtail/admin/tests/pages/test_explorer_view.py +18 -0
  74. wagtail/admin/tests/pages/test_page_usage.py +24 -20
  75. wagtail/admin/tests/pages/test_preview.py +69 -1
  76. wagtail/admin/tests/pages/test_revisions.py +40 -6
  77. wagtail/admin/tests/test_account_management.py +39 -1
  78. wagtail/admin/tests/test_audit_log.py +4 -2
  79. wagtail/admin/tests/test_block_preview.py +224 -0
  80. wagtail/admin/tests/test_edit_handlers.py +23 -6
  81. wagtail/admin/tests/test_page_chooser.py +50 -3
  82. wagtail/admin/tests/test_privacy.py +49 -26
  83. wagtail/admin/tests/test_site_summary.py +15 -10
  84. wagtail/admin/tests/test_templatetags.py +19 -0
  85. wagtail/admin/tests/test_userbar.py +82 -1
  86. wagtail/admin/tests/test_views_generic.py +27 -12
  87. wagtail/admin/tests/test_workflows.py +69 -0
  88. wagtail/admin/tests/tests.py +23 -4
  89. wagtail/admin/tests/ui/test_sidebar.py +1 -1
  90. wagtail/admin/tests/viewsets/test_model_viewset.py +15 -13
  91. wagtail/admin/ui/side_panels.py +7 -4
  92. wagtail/admin/urls/__init__.py +6 -0
  93. wagtail/admin/urls/pages.py +1 -1
  94. wagtail/admin/userbar.py +21 -1
  95. wagtail/admin/views/account.py +5 -0
  96. wagtail/admin/views/chooser.py +5 -1
  97. wagtail/admin/views/collections.py +0 -2
  98. wagtail/admin/views/generic/base.py +20 -10
  99. wagtail/admin/views/generic/history.py +0 -1
  100. wagtail/admin/views/generic/models.py +79 -21
  101. wagtail/admin/views/generic/preview.py +50 -1
  102. wagtail/admin/views/mixins.py +4 -2
  103. wagtail/admin/views/pages/bulk_actions/delete.py +11 -23
  104. wagtail/admin/views/pages/bulk_actions/page_bulk_action.py +17 -0
  105. wagtail/admin/views/pages/bulk_actions/publish.py +11 -31
  106. wagtail/admin/views/pages/bulk_actions/unpublish.py +11 -31
  107. wagtail/admin/views/pages/create.py +1 -0
  108. wagtail/admin/views/pages/edit.py +38 -30
  109. wagtail/admin/views/pages/revisions.py +43 -114
  110. wagtail/admin/views/pages/utils.py +0 -1
  111. wagtail/admin/views/tags.py +6 -2
  112. wagtail/admin/views/workflows.py +8 -6
  113. wagtail/admin/viewsets/model.py +0 -4
  114. wagtail/admin/viewsets/pages.py +0 -1
  115. wagtail/admin/widgets/tags.py +1 -0
  116. wagtail/api/v2/tests/test_documents.py +4 -2
  117. wagtail/api/v2/tests/test_images.py +4 -2
  118. wagtail/api/v2/tests/test_pages.py +8 -4
  119. wagtail/blocks/base.py +59 -1
  120. wagtail/blocks/field_block.py +6 -0
  121. wagtail/blocks/list_block.py +4 -0
  122. wagtail/blocks/static_block.py +3 -0
  123. wagtail/blocks/stream_block.py +5 -1
  124. wagtail/blocks/struct_block.py +6 -0
  125. wagtail/compat.py +16 -0
  126. wagtail/contrib/forms/forms.py +27 -7
  127. wagtail/contrib/forms/locale/en/LC_MESSAGES/django.po +2 -2
  128. wagtail/contrib/forms/locale/gl/LC_MESSAGES/django.mo +0 -0
  129. wagtail/contrib/forms/locale/gl/LC_MESSAGES/django.po +4 -4
  130. wagtail/contrib/forms/tests/test_models.py +7 -5
  131. wagtail/contrib/forms/tests/test_views.py +75 -0
  132. wagtail/contrib/frontend_cache/backends/cloudfront.py +1 -1
  133. wagtail/contrib/frontend_cache/tasks.py +83 -0
  134. wagtail/contrib/frontend_cache/tests.py +48 -33
  135. wagtail/contrib/frontend_cache/utils.py +2 -70
  136. wagtail/contrib/redirects/base_formats.py +2 -2
  137. wagtail/contrib/redirects/locale/ar/LC_MESSAGES/django.mo +0 -0
  138. wagtail/contrib/redirects/locale/ar/LC_MESSAGES/django.po +3 -0
  139. wagtail/contrib/redirects/locale/en/LC_MESSAGES/django.po +24 -37
  140. wagtail/contrib/redirects/templates/wagtailredirects/add.html +1 -24
  141. wagtail/contrib/redirects/templates/wagtailredirects/confirm_delete.html +3 -13
  142. wagtail/contrib/redirects/tests/test_redirects.py +122 -110
  143. wagtail/contrib/redirects/tests/test_signal_handlers.py +75 -69
  144. wagtail/contrib/redirects/urls.py +2 -2
  145. wagtail/contrib/redirects/views.py +35 -73
  146. wagtail/contrib/search_promotions/admin_urls.py +10 -3
  147. wagtail/contrib/search_promotions/forms.py +55 -26
  148. wagtail/contrib/search_promotions/locale/en/LC_MESSAGES/django.po +44 -54
  149. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/add.html +21 -31
  150. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/confirm_delete.html +3 -12
  151. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/edit.html +11 -34
  152. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/includes/searchpromotion_form.html +1 -0
  153. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/includes/searchpromotions_formset.js +2 -1
  154. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/index.html +0 -1
  155. wagtail/contrib/search_promotions/tests.py +814 -13
  156. wagtail/contrib/search_promotions/views/__init__.py +1 -0
  157. wagtail/contrib/search_promotions/views/reports.py +56 -0
  158. wagtail/contrib/search_promotions/views/settings.py +258 -0
  159. wagtail/contrib/search_promotions/wagtail_hooks.py +12 -1
  160. wagtail/contrib/settings/locale/ar/LC_MESSAGES/django.mo +0 -0
  161. wagtail/contrib/settings/locale/ar/LC_MESSAGES/django.po +6 -1
  162. wagtail/contrib/settings/locale/en/LC_MESSAGES/django.po +3 -3
  163. wagtail/contrib/settings/templates/wagtailsettings/edit.html +1 -5
  164. wagtail/contrib/settings/tests/generic/test_admin.py +2 -5
  165. wagtail/contrib/settings/tests/generic/test_register.py +1 -1
  166. wagtail/contrib/settings/tests/site_specific/test_admin.py +2 -5
  167. wagtail/contrib/settings/tests/site_specific/test_register.py +1 -1
  168. wagtail/contrib/settings/views.py +9 -23
  169. wagtail/contrib/simple_translation/locale/en/LC_MESSAGES/django.po +1 -1
  170. wagtail/contrib/styleguide/locale/en/LC_MESSAGES/django.po +1 -1
  171. wagtail/contrib/table_block/locale/en/LC_MESSAGES/django.po +1 -1
  172. wagtail/contrib/table_block/tests.py +4 -1
  173. wagtail/contrib/typed_table_block/blocks.py +3 -0
  174. wagtail/contrib/typed_table_block/locale/en/LC_MESSAGES/django.po +10 -10
  175. wagtail/contrib/typed_table_block/static/typed_table_block/js/typed_table_block.js +1 -1
  176. wagtail/contrib/typed_table_block/tests.py +33 -0
  177. wagtail/documents/locale/en/LC_MESSAGES/django.po +26 -26
  178. wagtail/documents/migrations/0011_add_choose_permissions.py +1 -0
  179. wagtail/documents/models.py +1 -0
  180. wagtail/documents/signal_handlers.py +6 -2
  181. wagtail/documents/static/wagtaildocs/js/add-multiple.js +1 -1
  182. wagtail/documents/templates/wagtaildocs/documents/edit.html +1 -3
  183. wagtail/documents/templates/wagtaildocs/multiple/add.html +7 -1
  184. wagtail/documents/tests/test_admin_views.py +74 -33
  185. wagtail/documents/tests/test_views.py +21 -12
  186. wagtail/documents/views/chooser.py +1 -0
  187. wagtail/documents/views/documents.py +1 -2
  188. wagtail/documents/views/multiple.py +0 -1
  189. wagtail/documents/views/serve.py +9 -2
  190. wagtail/documents/wagtail_hooks.py +6 -1
  191. wagtail/embeds/locale/en/LC_MESSAGES/django.po +1 -1
  192. wagtail/embeds/oembed_providers.py +0 -64
  193. wagtail/fields.py +3 -0
  194. wagtail/images/apps.py +2 -1
  195. wagtail/images/blocks.py +14 -2
  196. wagtail/images/forms.py +40 -3
  197. wagtail/images/locale/ar/LC_MESSAGES/django.mo +0 -0
  198. wagtail/images/locale/ar/LC_MESSAGES/django.po +4 -0
  199. wagtail/images/locale/en/LC_MESSAGES/django.po +49 -49
  200. wagtail/images/migrations/0023_add_choose_permissions.py +1 -0
  201. wagtail/images/rich_text/contentstate.py +1 -0
  202. wagtail/images/rich_text/editor_html.py +1 -0
  203. wagtail/images/signal_handlers.py +17 -10
  204. wagtail/images/static/wagtailimages/js/add-multiple.js +1 -1
  205. wagtail/images/static/wagtailimages/js/image-block.js +1 -1
  206. wagtail/images/static/wagtailimages/js/image-chooser-telepath.js +1 -1
  207. wagtail/images/static/wagtailimages/js/image-chooser.js +1 -1
  208. wagtail/images/static/wagtailimages/js/image-url-generator.js +1 -1
  209. wagtail/images/static/wagtailimages/js/vendor/jquery.fileupload-image.js +1 -1
  210. wagtail/images/tasks.py +18 -0
  211. wagtail/images/templates/wagtailimages/images/edit.html +1 -3
  212. wagtail/images/templates/wagtailimages/images/url_generator.html +1 -1
  213. wagtail/images/templates/wagtailimages/multiple/add.html +7 -2
  214. wagtail/images/templates/wagtailimages/widgets/image_chooser.html +1 -1
  215. wagtail/images/tests/test_admin_views.py +53 -29
  216. wagtail/images/tests/test_blocks.py +34 -2
  217. wagtail/images/tests/test_models.py +12 -10
  218. wagtail/images/tests/tests.py +10 -0
  219. wagtail/images/views/chooser.py +1 -0
  220. wagtail/images/views/images.py +1 -3
  221. wagtail/images/views/multiple.py +0 -1
  222. wagtail/images/views/serve.py +18 -2
  223. wagtail/images/widgets.py +3 -0
  224. wagtail/locale/en/LC_MESSAGES/django.po +228 -216
  225. wagtail/locales/locale/en/LC_MESSAGES/django.po +1 -1
  226. wagtail/management/commands/publish_scheduled.py +1 -1
  227. wagtail/migrations/0087_alter_grouppagepermission_unique_together_and_more.py +16 -8
  228. wagtail/models/__init__.py +300 -119
  229. wagtail/models/i18n.py +2 -2
  230. wagtail/models/panels.py +37 -0
  231. wagtail/models/sites.py +7 -6
  232. wagtail/permission_policies/pages.py +2 -2
  233. wagtail/project_template/project_name/settings/base.py +4 -0
  234. wagtail/project_template/requirements.txt +1 -1
  235. wagtail/query.py +145 -0
  236. wagtail/search/backends/database/mysql/mysql.py +25 -17
  237. wagtail/search/backends/database/postgres/postgres.py +44 -83
  238. wagtail/search/backends/database/sqlite/sqlite.py +25 -17
  239. wagtail/search/backends/elasticsearch7.py +4 -0
  240. wagtail/search/locale/en/LC_MESSAGES/django.po +1 -1
  241. wagtail/search/query.py +8 -2
  242. wagtail/search/signal_handlers.py +6 -9
  243. wagtail/search/tasks.py +10 -0
  244. wagtail/search/tests/test_elasticsearch7_backend.py +21 -0
  245. wagtail/search/tests/test_index_functions.py +10 -6
  246. wagtail/search/tests/test_postgres_backend.py +0 -14
  247. wagtail/signal_handlers.py +5 -20
  248. wagtail/sites/locale/en/LC_MESSAGES/django.po +1 -1
  249. wagtail/snippets/locale/en/LC_MESSAGES/django.po +3 -13
  250. wagtail/snippets/locale/sv/LC_MESSAGES/django.mo +0 -0
  251. wagtail/snippets/locale/sv/LC_MESSAGES/django.po +4 -3
  252. wagtail/snippets/tests/test_preview.py +5 -0
  253. wagtail/snippets/tests/test_snippets.py +100 -45
  254. wagtail/snippets/tests/test_usage.py +29 -24
  255. wagtail/snippets/tests/test_viewset.py +1 -1
  256. wagtail/snippets/views/snippets.py +0 -12
  257. wagtail/tasks.py +41 -0
  258. wagtail/templates/wagtailcore/shared/block_preview.html +29 -0
  259. wagtail/test/earlypage/__init__.py +0 -0
  260. wagtail/test/earlypage/migrations/0001_initial.py +37 -0
  261. wagtail/test/earlypage/migrations/__init__.py +0 -0
  262. wagtail/test/earlypage/models.py +14 -0
  263. wagtail/test/settings.py +3 -0
  264. wagtail/test/testapp/fixtures/test.json +7 -0
  265. wagtail/test/testapp/fixtures/test_specific.json +6 -3
  266. wagtail/test/testapp/models.py +58 -44
  267. wagtail/test/testapp/templates/tests/custom_block_preview.html +16 -0
  268. wagtail/test/testapp/templates/tests/static_block_preview.html +5 -0
  269. wagtail/test/testapp/wagtail_hooks.py +9 -0
  270. wagtail/tests/test_blocks.py +189 -2
  271. wagtail/tests/test_hooks.py +166 -1
  272. wagtail/tests/test_management_commands.py +54 -13
  273. wagtail/tests/test_page_allowed_http_methods.py +32 -0
  274. wagtail/tests/test_page_model.py +68 -0
  275. wagtail/tests/test_page_privacy.py +10 -0
  276. wagtail/tests/test_page_queryset.py +79 -0
  277. wagtail/tests/test_reference_index.py +84 -75
  278. wagtail/tests/test_streamfield.py +30 -0
  279. wagtail/tests/test_utils.py +61 -0
  280. wagtail/users/forms.py +2 -9
  281. wagtail/users/locale/en/LC_MESSAGES/django.po +17 -17
  282. wagtail/users/locale/nl/LC_MESSAGES/django.mo +0 -0
  283. wagtail/users/locale/nl/LC_MESSAGES/django.po +4 -3
  284. wagtail/users/templates/wagtailusers/groups/create.html +0 -5
  285. wagtail/users/templates/wagtailusers/groups/includes/page_permissions_form.html +1 -1
  286. wagtail/users/templates/wagtailusers/groups/includes/page_permissions_formset.html +6 -6
  287. wagtail/users/tests/test_admin_views.py +96 -4
  288. wagtail/users/tests/test_utils.py +76 -0
  289. wagtail/users/utils.py +43 -11
  290. wagtail/utils/setup.py +2 -2
  291. wagtail/utils/templates.py +26 -0
  292. wagtail/utils/widgets.py +1 -0
  293. wagtail/views.py +9 -1
  294. wagtail/wagtail_hooks.py +67 -29
  295. {wagtail-6.3.1.dist-info → wagtail-6.4rc1.dist-info}/METADATA +2 -2
  296. {wagtail-6.3.1.dist-info → wagtail-6.4rc1.dist-info}/RECORD +300 -287
  297. wagtail/admin/static/wagtailadmin/js/expanding-formset.js +0 -1
  298. wagtail/admin/static/wagtailadmin/js/vendor/rangy-core.js +0 -1
  299. wagtail/admin/static/wagtailadmin/js/vendor/uuidv4.min.js +0 -1
  300. wagtail/contrib/search_promotions/views.py +0 -323
  301. wagtail/images/static/wagtailimages/js/vendor/canvas-to-blob.min.js +0 -1
  302. wagtail/users/static/wagtailusers/js/group-form.js +0 -1
  303. wagtail/users/templates/wagtailusers/groups/includes/group_form_js.html +0 -3
  304. {wagtail-6.3.1.dist-info → wagtail-6.4rc1.dist-info}/LICENSE +0 -0
  305. {wagtail-6.3.1.dist-info → wagtail-6.4rc1.dist-info}/WHEEL +0 -0
  306. {wagtail-6.3.1.dist-info → wagtail-6.4rc1.dist-info}/entry_points.txt +0 -0
  307. {wagtail-6.3.1.dist-info → wagtail-6.4rc1.dist-info}/top_level.txt +0 -0
@@ -41,28 +41,29 @@ class TestAutocreateRedirects(WagtailTestUtils, TestCase):
41
41
  page.save(log_action="wagtail.publish", user=self.user, clean=False)
42
42
 
43
43
  def test_golden_path(self):
44
- # the page we'll be triggering the change for here is...
45
- test_subject = self.event_index
46
-
47
- # identify 'draft' pages in this section
48
- drafts = test_subject.get_descendants().not_live()
49
- self.assertEqual(len(drafts), 4)
50
-
51
- # gather urls for 'live' pages in this branch
52
- request = get_dummy_request()
53
- branch_urls = []
54
- for page in (
55
- test_subject.get_descendants(inclusive=True)
56
- .live()
57
- .specific(defer=True)
58
- .iterator()
59
- ):
60
- main_url = page.get_url(request).rstrip("/")
61
- branch_urls.extend(
62
- main_url + path.rstrip("/") for path in page.get_cached_paths()
63
- )
64
-
65
- self.trigger_page_slug_changed_signal(test_subject)
44
+ with self.captureOnCommitCallbacks(execute=True):
45
+ # the page we'll be triggering the change for here is...
46
+ test_subject = self.event_index
47
+
48
+ # identify 'draft' pages in this section
49
+ drafts = test_subject.get_descendants().not_live()
50
+ self.assertEqual(len(drafts), 4)
51
+
52
+ # gather urls for 'live' pages in this branch
53
+ request = get_dummy_request()
54
+ branch_urls = []
55
+ for page in (
56
+ test_subject.get_descendants(inclusive=True)
57
+ .live()
58
+ .specific(defer=True)
59
+ .iterator()
60
+ ):
61
+ main_url = page.get_url(request).rstrip("/")
62
+ branch_urls.extend(
63
+ main_url + path.rstrip("/") for path in page.get_cached_paths()
64
+ )
65
+
66
+ self.trigger_page_slug_changed_signal(test_subject)
66
67
 
67
68
  # gather all of the redirects that were created
68
69
  redirects = Redirect.objects.all()
@@ -97,32 +98,34 @@ class TestAutocreateRedirects(WagtailTestUtils, TestCase):
97
98
  )
98
99
 
99
100
  def test_no_redirects_created_when_page_is_root_for_all_sites_it_belongs_to(self):
100
- self.trigger_page_slug_changed_signal(self.home_page)
101
+ with self.captureOnCommitCallbacks(execute=True):
102
+ self.trigger_page_slug_changed_signal(self.home_page)
101
103
  self.assertFalse(Redirect.objects.exists())
102
104
  self.assertEqual(len(PURGED_URLS), 0)
103
105
 
104
106
  def test_handling_of_existing_redirects(self):
105
- # the page we'll be triggering the change for here is...
106
- test_subject = self.event_index
107
-
108
- descendants = test_subject.get_descendants().live()
109
-
110
- # but before we do, let's add some redirects that we'll expect to conflict
111
- # with ones created by the signal handler
112
- redirect1 = Redirect.objects.create(
113
- old_path=Redirect.normalise_path(descendants.first().specific.url),
114
- site=self.site,
115
- redirect_link="/some-place",
116
- automatically_created=False,
117
- )
118
- redirect2 = Redirect.objects.create(
119
- old_path=Redirect.normalise_path(descendants.last().specific.url),
120
- site=self.site,
121
- redirect_link="/some-other-place",
122
- automatically_created=True,
123
- )
107
+ with self.captureOnCommitCallbacks(execute=True):
108
+ # the page we'll be triggering the change for here is...
109
+ test_subject = self.event_index
110
+
111
+ descendants = test_subject.get_descendants().live()
112
+
113
+ # but before we do, let's add some redirects that we'll expect to conflict
114
+ # with ones created by the signal handler
115
+ redirect1 = Redirect.objects.create(
116
+ old_path=Redirect.normalise_path(descendants.first().specific.url),
117
+ site=self.site,
118
+ redirect_link="/some-place",
119
+ automatically_created=False,
120
+ )
121
+ redirect2 = Redirect.objects.create(
122
+ old_path=Redirect.normalise_path(descendants.last().specific.url),
123
+ site=self.site,
124
+ redirect_link="/some-other-place",
125
+ automatically_created=True,
126
+ )
124
127
 
125
- self.trigger_page_slug_changed_signal(test_subject)
128
+ self.trigger_page_slug_changed_signal(test_subject)
126
129
 
127
130
  # pre-existing manually-created redirects should be preserved
128
131
  from_db = Redirect.objects.get(id=redirect1.id)
@@ -163,17 +166,18 @@ class TestAutocreateRedirects(WagtailTestUtils, TestCase):
163
166
  )
164
167
 
165
168
  def test_redirect_creation_for_custom_route_paths(self):
166
- # Add a page that has overridden get_route_paths()
167
- homepage = Page.objects.get(id=2)
168
- routable_page = homepage.add_child(
169
- instance=RoutablePageTest(
170
- title="Routable Page",
171
- live=True,
169
+ with self.captureOnCommitCallbacks(execute=True):
170
+ # Add a page that has overridden get_route_paths()
171
+ homepage = Page.objects.get(id=2)
172
+ routable_page = homepage.add_child(
173
+ instance=RoutablePageTest(
174
+ title="Routable Page",
175
+ live=True,
176
+ )
172
177
  )
173
- )
174
178
 
175
- # Move from below the homepage to below the event index
176
- routable_page.move(self.event_index, pos="last-child")
179
+ # Move from below the homepage to below the event index
180
+ routable_page.move(self.event_index, pos="last-child")
177
181
 
178
182
  # Redirects should have been created for each path returned by get_route_paths()
179
183
  self.assertEqual(
@@ -206,23 +210,24 @@ class TestAutocreateRedirects(WagtailTestUtils, TestCase):
206
210
  )
207
211
 
208
212
  def test_no_redirects_created_when_pages_are_moved_to_a_different_site(self):
209
- # Add a new home page
210
- homepage_2 = Page(
211
- title="Second home",
212
- slug="second-home",
213
- )
214
- root_page = Page.objects.get(depth=1)
215
- root_page.add_child(instance=homepage_2)
216
-
217
- # Create a site with the above as the root_page
218
- Site.objects.create(
219
- root_page=homepage_2,
220
- hostname="newsite.com",
221
- port=80,
222
- )
213
+ with self.captureOnCommitCallbacks(execute=True):
214
+ # Add a new home page
215
+ homepage_2 = Page(
216
+ title="Second home",
217
+ slug="second-home",
218
+ )
219
+ root_page = Page.objects.get(depth=1)
220
+ root_page.add_child(instance=homepage_2)
221
+
222
+ # Create a site with the above as the root_page
223
+ Site.objects.create(
224
+ root_page=homepage_2,
225
+ hostname="newsite.com",
226
+ port=80,
227
+ )
223
228
 
224
- # Move the event index to the new site
225
- self.event_index.move(homepage_2, pos="last-child")
229
+ # Move the event index to the new site
230
+ self.event_index.move(homepage_2, pos="last-child")
226
231
 
227
232
  # No redirects should have been created
228
233
  self.assertFalse(Redirect.objects.exists())
@@ -230,6 +235,7 @@ class TestAutocreateRedirects(WagtailTestUtils, TestCase):
230
235
 
231
236
  @override_settings(WAGTAILREDIRECTS_AUTO_CREATE=False)
232
237
  def test_no_redirects_created_if_disabled(self):
233
- self.trigger_page_slug_changed_signal(self.event_index)
238
+ with self.captureOnCommitCallbacks(execute=True):
239
+ self.trigger_page_slug_changed_signal(self.event_index)
234
240
  self.assertFalse(Redirect.objects.exists())
235
241
  self.assertEqual(len(PURGED_URLS), 0)
@@ -6,9 +6,9 @@ app_name = "wagtailredirects"
6
6
  urlpatterns = [
7
7
  path("", views.IndexView.as_view(), name="index"),
8
8
  path("results/", views.IndexView.as_view(results_only=True), name="index_results"),
9
- path("add/", views.add, name="add"),
9
+ path("add/", views.CreateView.as_view(), name="add"),
10
10
  path("<int:redirect_id>/", views.EditView.as_view(), name="edit"),
11
- path("<int:redirect_id>/delete/", views.delete, name="delete"),
11
+ path("<int:redirect_id>/delete/", views.DeleteView.as_view(), name="delete"),
12
12
  path("import/", views.start_import, name="start_import"),
13
13
  path("import/process/", views.process_import, name="process_import"),
14
14
  ]
@@ -1,9 +1,8 @@
1
1
  import os
2
2
 
3
- from django.core.exceptions import PermissionDenied, SuspiciousOperation
3
+ from django.core.exceptions import SuspiciousOperation
4
4
  from django.db import transaction
5
- from django.shortcuts import get_object_or_404, redirect, render
6
- from django.template.response import TemplateResponse
5
+ from django.shortcuts import redirect, render
7
6
  from django.urls import reverse
8
7
  from django.utils.encoding import force_str
9
8
  from django.utils.functional import cached_property
@@ -18,7 +17,6 @@ from wagtail.admin.ui.tables import Column, StatusTagColumn, TitleColumn
18
17
  from wagtail.admin.views import generic
19
18
  from wagtail.admin.widgets.button import Button
20
19
  from wagtail.contrib.frontend_cache.utils import PurgeBatch, purge_urls_from_cache
21
- from wagtail.contrib.redirects import models
22
20
  from wagtail.contrib.redirects.filters import RedirectsReportFilterSet
23
21
  from wagtail.contrib.redirects.forms import (
24
22
  ConfirmImportForm,
@@ -147,7 +145,6 @@ class EditView(generic.EditView):
147
145
  pk_url_kwarg = "redirect_id"
148
146
  error_message = gettext_lazy("The redirect could not be saved due to errors.")
149
147
  header_icon = "redirect"
150
- _show_breadcrumbs = True
151
148
 
152
149
  def get_success_message(self):
153
150
  return _("Redirect '%(redirect_title)s' updated.") % {
@@ -169,80 +166,45 @@ class EditView(generic.EditView):
169
166
  return instance
170
167
 
171
168
 
172
- @permission_checker.require("delete")
173
- def delete(request, redirect_id):
174
- theredirect = get_object_or_404(models.Redirect, id=redirect_id)
175
-
176
- if not permission_policy.user_has_permission_for_instance(
177
- request.user, "delete", theredirect
178
- ):
179
- raise PermissionDenied
180
-
181
- if request.method == "POST":
182
- with transaction.atomic():
183
- log(instance=theredirect, action="wagtail.delete")
184
- theredirect.delete()
169
+ class DeleteView(generic.DeleteView):
170
+ model = Redirect
171
+ pk_url_kwarg = "redirect_id"
172
+ permission_policy = permission_policy
173
+ template_name = "wagtailredirects/confirm_delete.html"
174
+ index_url_name = "wagtailredirects:index"
175
+ delete_url_name = "wagtailredirects:delete"
176
+ header_icon = "redirect"
185
177
 
186
- purge_urls_from_cache(theredirect.old_links())
178
+ def delete_action(self):
179
+ super().delete_action()
180
+ purge_urls_from_cache(self.object.old_links())
187
181
 
188
- messages.success(
189
- request,
190
- _("Redirect '%(redirect_title)s' deleted.")
191
- % {"redirect_title": theredirect.title},
192
- )
193
- return redirect("wagtailredirects:index")
182
+ def get_success_message(self):
183
+ return _("Redirect '%(redirect_title)s' deleted.") % {
184
+ "redirect_title": self.object.title
185
+ }
194
186
 
195
- return TemplateResponse(
196
- request,
197
- "wagtailredirects/confirm_delete.html",
198
- {
199
- "redirect": theredirect,
200
- },
201
- )
202
187
 
188
+ class CreateView(generic.CreateView):
189
+ model = Redirect
190
+ form_class = RedirectForm
191
+ permission_policy = permission_policy
192
+ template_name = "wagtailredirects/add.html"
193
+ add_url_name = "wagtailredirects:add"
194
+ index_url_name = "wagtailredirects:index"
195
+ edit_url_name = "wagtailredirects:edit"
196
+ error_message = gettext_lazy("The redirect could not be created due to errors.")
197
+ header_icon = "redirect"
203
198
 
204
- @permission_checker.require("add")
205
- def add(request):
206
- if request.method == "POST":
207
- form = RedirectForm(request.POST, request.FILES)
208
- if form.is_valid():
209
- with transaction.atomic():
210
- theredirect = form.save()
211
- log(instance=theredirect, action="wagtail.create")
212
-
213
- purge_urls_from_cache(theredirect.old_links())
214
-
215
- messages.success(
216
- request,
217
- _("Redirect '%(redirect_title)s' added.")
218
- % {"redirect_title": theredirect.title},
219
- buttons=[
220
- messages.button(
221
- reverse("wagtailredirects:edit", args=(theredirect.id,)),
222
- _("Edit"),
223
- )
224
- ],
225
- )
226
- return redirect("wagtailredirects:index")
227
- else:
228
- messages.error(
229
- request, _("The redirect could not be created due to errors.")
230
- )
231
- else:
232
- form = RedirectForm()
199
+ def get_success_message(self, instance):
200
+ return _("Redirect '%(redirect_title)s' added.") % {
201
+ "redirect_title": instance.title
202
+ }
233
203
 
234
- return TemplateResponse(
235
- request,
236
- "wagtailredirects/add.html",
237
- {
238
- "form": form,
239
- # Remove these when this view is refactored to a generic.CreateView subclass.
240
- # Avoid defining new translatable strings.
241
- "submit_button_label": generic.CreateView.submit_button_label,
242
- "submit_button_active_label": generic.CreateView.submit_button_active_label,
243
- "media": form.media,
244
- },
245
- )
204
+ def save_instance(self):
205
+ instance = super().save_instance()
206
+ purge_urls_from_cache(instance.old_links())
207
+ return instance
246
208
 
247
209
 
248
210
  @permission_checker.require_any("add")
@@ -1,18 +1,25 @@
1
1
  from django.urls import path
2
2
 
3
3
  from wagtail.contrib.search_promotions import views
4
+ from wagtail.contrib.search_promotions.views.reports import SearchTermsReportView
4
5
 
5
6
  app_name = "wagtailsearchpromotions"
6
7
  urlpatterns = [
7
8
  path("", views.IndexView.as_view(), name="index"),
8
9
  path("results/", views.IndexView.as_view(results_only=True), name="index_results"),
9
- path("add/", views.add, name="add"),
10
- path("<int:query_id>/", views.edit, name="edit"),
11
- path("<int:query_id>/delete/", views.delete, name="delete"),
10
+ path("add/", views.CreateView.as_view(), name="add"),
11
+ path("<int:query_id>/", views.EditView.as_view(), name="edit"),
12
+ path("<int:query_id>/delete/", views.DeleteView.as_view(), name="delete"),
12
13
  path("queries/chooser/", views.chooser, name="chooser"),
13
14
  path(
14
15
  "queries/chooser/results/",
15
16
  views.chooserresults,
16
17
  name="chooserresults",
17
18
  ),
19
+ path("reports/search-terms/", SearchTermsReportView.as_view(), name="search_terms"),
20
+ path(
21
+ "reports/search-terms/results/",
22
+ SearchTermsReportView.as_view(results_only=True),
23
+ name="search_terms_results",
24
+ ),
18
25
  ]
@@ -6,7 +6,7 @@ from wagtail.admin.widgets import AdminPageChooser
6
6
  from wagtail.contrib.search_promotions.models import Query, SearchPromotion
7
7
 
8
8
 
9
- class QueryForm(forms.Form):
9
+ class QueryForm(forms.ModelForm):
10
10
  query_string = forms.CharField(
11
11
  label=_("Search term(s)/phrase"),
12
12
  help_text=_(
@@ -17,6 +17,17 @@ class QueryForm(forms.Form):
17
17
  required=True,
18
18
  )
19
19
 
20
+ def clean(self):
21
+ # We allow using an existing query string on the CreateView, so we need
22
+ # to skip the unique validation on `query_string`. This can be done by
23
+ # overriding the `clean()` method without calling `super().clean()`:
24
+ # https://docs.djangoproject.com/en/stable/topics/forms/modelforms/#overriding-the-clean-method
25
+ pass
26
+
27
+ class Meta:
28
+ model = Query
29
+ fields = ["query_string"]
30
+
20
31
 
21
32
  class SearchPromotionForm(forms.ModelForm):
22
33
  sort_order = forms.IntegerField(required=False)
@@ -25,6 +36,49 @@ class SearchPromotionForm(forms.ModelForm):
25
36
  super().__init__(*args, **kwargs)
26
37
  self.fields["page"].widget = AdminPageChooser()
27
38
 
39
+ def clean(self):
40
+ cleaned_data = super().clean()
41
+
42
+ # Use the raw value instead of from form.cleaned_data so we don't
43
+ # consider an invalid field as empty. For example, leaving the
44
+ # page field empty and entering an invalid external_link_url
45
+ # shouldn't raise an error about needing to enter a page or URL,
46
+ # since the user *has* entered (or tried to enter) a URL.
47
+ page = self["page"].value()
48
+ external_link_url = self["external_link_url"].value()
49
+ external_link_text = self["external_link_text"].value()
50
+
51
+ # Must supply a page or external_link_url (but not both)
52
+ if page is None:
53
+ if external_link_url:
54
+ # if an external_link_url is supplied,
55
+ # then external_link_text is also required
56
+ if not external_link_text:
57
+ self.add_error(
58
+ "external_link_text",
59
+ forms.ValidationError(
60
+ _(
61
+ "You must enter an external link text if you enter an external link URL."
62
+ )
63
+ ),
64
+ )
65
+ else:
66
+ self.add_error(
67
+ None,
68
+ forms.ValidationError(
69
+ _("You must recommend a page OR an external link.")
70
+ ),
71
+ )
72
+ elif external_link_url:
73
+ self.add_error(
74
+ None,
75
+ forms.ValidationError(
76
+ _("Please only select a page OR enter an external link.")
77
+ ),
78
+ )
79
+
80
+ return cleaned_data
81
+
28
82
  class Meta:
29
83
  model = SearchPromotion
30
84
  fields = (
@@ -73,31 +127,6 @@ class SearchPromotionsFormSet(SearchPromotionsFormSetBase):
73
127
  non_empty_forms = 0
74
128
  for i in range(0, self.total_form_count()):
75
129
  form = self.forms[i]
76
-
77
- page = form.cleaned_data["page"]
78
- external_link_url = form.cleaned_data["external_link_url"]
79
- external_link_text = form.cleaned_data["external_link_text"]
80
-
81
- # only a page or external_link_url can be supplied
82
- if page is None:
83
- if external_link_url:
84
- # if an external_link_url then external_link_text is also required
85
- if not external_link_text:
86
- raise forms.ValidationError(
87
- _(
88
- "You must enter an external link text if you enter an external link URL."
89
- )
90
- )
91
- else:
92
- raise forms.ValidationError(
93
- _("You must recommend a page OR an external link.")
94
- )
95
- else:
96
- if external_link_url:
97
- raise forms.ValidationError(
98
- _("Please only select a page OR enter an external link.")
99
- )
100
-
101
130
  if self.can_delete and self._should_delete_form(form):
102
131
  non_deleted_forms -= 1
103
132
  if not (form.instance.id is None and not form.has_changed()):
@@ -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: 2024-10-21 17:53+0100\n"
11
+ "POT-Creation-Date: 2025-01-20 17:59+0000\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"
@@ -32,22 +32,22 @@ msgid ""
32
32
  "Promoted Results to be displayed, wildcards are NOT allowed."
33
33
  msgstr ""
34
34
 
35
- #: forms.py:56
36
- msgid "Please specify at least one recommendation for this search term."
37
- msgstr ""
38
-
39
- #: forms.py:88
35
+ #: forms.py:61
40
36
  msgid "You must enter an external link text if you enter an external link URL."
41
37
  msgstr ""
42
38
 
43
- #: forms.py:93
39
+ #: forms.py:69
44
40
  msgid "You must recommend a page OR an external link."
45
41
  msgstr ""
46
42
 
47
- #: forms.py:98
43
+ #: forms.py:76
48
44
  msgid "Please only select a page OR enter an external link."
49
45
  msgstr ""
50
46
 
47
+ #: forms.py:110
48
+ msgid "Please specify at least one recommendation for this search term."
49
+ msgstr ""
50
+
51
51
  #: models.py:92 models.py:93
52
52
  msgid "Query Daily Hits"
53
53
  msgstr ""
@@ -80,15 +80,7 @@ msgstr ""
80
80
  msgid "search promotion"
81
81
  msgstr ""
82
82
 
83
- #: templates/wagtailsearchpromotions/add.html:3
84
- msgid "Add search promotion"
85
- msgstr ""
86
-
87
- #: templates/wagtailsearchpromotions/add.html:5
88
- msgid "Add search pick"
89
- msgstr ""
90
-
91
- #: templates/wagtailsearchpromotions/add.html:11
83
+ #: templates/wagtailsearchpromotions/add.html:7
92
84
  msgid ""
93
85
  "<p>Promoted search results are a means of recommending specific pages or "
94
86
  "external links that might not organically come high up in search results. E."
@@ -96,7 +88,7 @@ msgid ""
96
88
  "common term \"<em>giving</em>\".</p>"
97
89
  msgstr ""
98
90
 
99
- #: templates/wagtailsearchpromotions/add.html:15
91
+ #: templates/wagtailsearchpromotions/add.html:11
100
92
  msgid ""
101
93
  "<p>The \"Search term(s)/phrase\" field below must contain the full and exact "
102
94
  "search for which you wish to provide recommended results, <em>including</em> "
@@ -110,28 +102,10 @@ msgid "Delete %(query)s"
110
102
  msgstr ""
111
103
 
112
104
  #: templates/wagtailsearchpromotions/confirm_delete.html:5
113
- #: templates/wagtailsearchpromotions/includes/searchpromotion_form.html:10
114
- msgid "Delete"
115
- msgstr ""
116
-
117
- #: templates/wagtailsearchpromotions/confirm_delete.html:9
118
105
  msgid ""
119
106
  "Are you sure you want to delete all promoted results for this search term?"
120
107
  msgstr ""
121
108
 
122
- #: templates/wagtailsearchpromotions/confirm_delete.html:12
123
- msgid "Yes, delete"
124
- msgstr ""
125
-
126
- #: templates/wagtailsearchpromotions/edit.html:3
127
- #, python-format
128
- msgid "Editing %(query)s"
129
- msgstr ""
130
-
131
- #: templates/wagtailsearchpromotions/edit.html:5
132
- msgid "Editing"
133
- msgstr ""
134
-
135
109
  #: templates/wagtailsearchpromotions/includes/searchpromotion_form.html:8
136
110
  msgid "Move up"
137
111
  msgstr ""
@@ -140,12 +114,20 @@ msgstr ""
140
114
  msgid "Move down"
141
115
  msgstr ""
142
116
 
143
- #: templates/wagtailsearchpromotions/includes/searchpromotion_form.html:13
117
+ #: templates/wagtailsearchpromotions/includes/searchpromotion_form.html:10
118
+ msgid "Drag"
119
+ msgstr ""
120
+
121
+ #: templates/wagtailsearchpromotions/includes/searchpromotion_form.html:11
122
+ msgid "Delete"
123
+ msgstr ""
124
+
125
+ #: templates/wagtailsearchpromotions/includes/searchpromotion_form.html:14
144
126
  msgid "Recommended search result"
145
127
  msgstr ""
146
128
 
147
129
  #: templates/wagtailsearchpromotions/includes/searchpromotions_formset.html:4
148
- #: views.py:81 wagtail_hooks.py:36
130
+ #: views/settings.py:31 wagtail_hooks.py:36
149
131
  msgid "Promoted search results"
150
132
  msgstr ""
151
133
 
@@ -184,7 +166,7 @@ msgid "Terms"
184
166
  msgstr ""
185
167
 
186
168
  #: templates/wagtailsearchpromotions/queries/chooser/results.html:10
187
- #: views.py:58
169
+ #: views/settings.py:56
188
170
  msgid "Views (past week)"
189
171
  msgstr ""
190
172
 
@@ -204,44 +186,52 @@ msgstr ""
204
186
  msgid "None"
205
187
  msgstr ""
206
188
 
207
- #: views.py:33
208
- msgid "Search Terms"
189
+ #: views/reports.py:13
190
+ msgid "Date"
209
191
  msgstr ""
210
192
 
211
- #: views.py:42
212
- msgid "Add new promoted result"
193
+ #: views/reports.py:24 wagtail_hooks.py:47
194
+ msgid "Search terms"
213
195
  msgstr ""
214
196
 
215
- #: views.py:46
197
+ #: views/reports.py:33 views/reports.py:37 views/settings.py:44
216
198
  msgid "Search term(s)"
217
199
  msgstr ""
218
200
 
219
- #: views.py:53
201
+ #: views/reports.py:34 views/reports.py:38
202
+ msgid "Views"
203
+ msgstr ""
204
+
205
+ #: views/settings.py:40
206
+ msgid "Add new promoted result"
207
+ msgstr ""
208
+
209
+ #: views/settings.py:51
220
210
  msgid "Promoted results"
221
211
  msgstr ""
222
212
 
223
- #: views.py:144
224
- #, python-format
225
- msgid "Editor's picks for '%(query)s' created."
213
+ #: views/settings.py:91
214
+ msgid "Promoted search result"
226
215
  msgstr ""
227
216
 
228
- #: views.py:148 views.py:212
229
- msgid "Edit"
217
+ #: views/settings.py:180
218
+ #, python-format
219
+ msgid "Editor's picks for '%(query)s' created."
230
220
  msgstr ""
231
221
 
232
- #: views.py:166
222
+ #: views/settings.py:181
233
223
  msgid "Recommendations have not been created due to errors"
234
224
  msgstr ""
235
225
 
236
- #: views.py:208
226
+ #: views/settings.py:190
237
227
  #, python-format
238
228
  msgid "Editor's picks for '%(query)s' updated."
239
229
  msgstr ""
240
230
 
241
- #: views.py:228
231
+ #: views/settings.py:191
242
232
  msgid "Recommendations have not been saved due to errors"
243
233
  msgstr ""
244
234
 
245
- #: views.py:267
235
+ #: views/settings.py:200
246
236
  msgid "Editor's picks deleted."
247
237
  msgstr ""