wagtail 6.0.2__py3-none-any.whl → 6.1rc1__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 (355) hide show
  1. wagtail/__init__.py +1 -1
  2. wagtail/admin/checks.py +51 -0
  3. wagtail/admin/compare.py +1 -1
  4. wagtail/admin/filters.py +70 -1
  5. wagtail/admin/forms/account.py +1 -1
  6. wagtail/admin/forms/collections.py +15 -0
  7. wagtail/admin/forms/pages.py +49 -0
  8. wagtail/admin/locale/de/LC_MESSAGES/django.mo +0 -0
  9. wagtail/admin/locale/de/LC_MESSAGES/django.po +5 -5
  10. wagtail/admin/locale/en/LC_MESSAGES/django.po +474 -385
  11. wagtail/admin/locale/en/LC_MESSAGES/djangojs.po +3 -3
  12. wagtail/admin/locale/pt_PT/LC_MESSAGES/django.mo +0 -0
  13. wagtail/admin/locale/pt_PT/LC_MESSAGES/django.po +73 -2
  14. wagtail/admin/locale/ro/LC_MESSAGES/django.mo +0 -0
  15. wagtail/admin/locale/ro/LC_MESSAGES/django.po +3 -3
  16. wagtail/admin/panels/comment_panel.py +1 -1
  17. wagtail/admin/panels/field_panel.py +1 -1
  18. wagtail/admin/rich_text/converters/editor_html.py +3 -1
  19. wagtail/admin/rich_text/editors/draftail/__init__.py +28 -2
  20. wagtail/admin/static/wagtailadmin/css/core.css +1 -1
  21. wagtail/admin/static/wagtailadmin/css/panels/draftail.css +1 -1
  22. wagtail/admin/static/wagtailadmin/images/favicon.ico +0 -0
  23. wagtail/admin/static/wagtailadmin/js/bulk-actions.js +1 -1
  24. wagtail/admin/static/wagtailadmin/js/chooser-modal.js +1 -1
  25. wagtail/admin/static/wagtailadmin/js/chooser-widget-telepath.js +1 -1
  26. wagtail/admin/static/wagtailadmin/js/chooser-widget.js +1 -1
  27. wagtail/admin/static/wagtailadmin/js/comments.js +1 -1
  28. wagtail/admin/static/wagtailadmin/js/core.js +1 -1
  29. wagtail/admin/static/wagtailadmin/js/core.js.LICENSE.txt +1 -1
  30. wagtail/admin/static/wagtailadmin/js/date-time-chooser.js +1 -1
  31. wagtail/admin/static/wagtailadmin/js/draftail.js +1 -1
  32. wagtail/admin/static/wagtailadmin/js/expanding-formset.js +1 -1
  33. wagtail/admin/static/wagtailadmin/js/filtered-select.js +1 -1
  34. wagtail/admin/static/wagtailadmin/js/modal-workflow.js +1 -1
  35. wagtail/admin/static/wagtailadmin/js/page-chooser-modal.js +1 -1
  36. wagtail/admin/static/wagtailadmin/js/page-chooser-telepath.js +1 -1
  37. wagtail/admin/static/wagtailadmin/js/page-chooser.js +1 -1
  38. wagtail/admin/static/wagtailadmin/js/preview-panel.js +1 -1
  39. wagtail/admin/static/wagtailadmin/js/privacy-switch.js +1 -1
  40. wagtail/admin/static/wagtailadmin/js/sidebar.js +1 -1
  41. wagtail/admin/static/wagtailadmin/js/task-chooser-modal.js +1 -1
  42. wagtail/admin/static/wagtailadmin/js/task-chooser.js +1 -1
  43. wagtail/admin/static/wagtailadmin/js/telepath/blocks.js +1 -1
  44. wagtail/admin/static/wagtailadmin/js/telepath/telepath.js +1 -1
  45. wagtail/admin/static/wagtailadmin/js/telepath/widgets.js +1 -1
  46. wagtail/admin/static/wagtailadmin/js/userbar.js +1 -1
  47. wagtail/admin/static/wagtailadmin/js/vendor.js +1 -1
  48. wagtail/admin/static/wagtailadmin/js/vendor.js.LICENSE.txt +4 -4
  49. wagtail/admin/static/wagtailadmin/js/wagtailadmin.js +1 -1
  50. wagtail/admin/static/wagtailadmin/js/workflow-action.js +1 -1
  51. wagtail/admin/staticfiles.py +1 -0
  52. wagtail/admin/templates/wagtailadmin/base.html +1 -0
  53. wagtail/admin/templates/wagtailadmin/collection_privacy/set_privacy.html +3 -1
  54. wagtail/admin/templates/wagtailadmin/collections/index_results.html +10 -0
  55. wagtail/admin/templates/wagtailadmin/generic/base.html +1 -9
  56. wagtail/admin/templates/wagtailadmin/generic/form.html +3 -2
  57. wagtail/admin/templates/wagtailadmin/generic/history/action_cell.html +27 -0
  58. wagtail/admin/templates/wagtailadmin/home/workflow_objects_to_moderate.html +3 -3
  59. wagtail/admin/templates/wagtailadmin/icons/keyboard.svg +1 -0
  60. wagtail/admin/templates/wagtailadmin/page_privacy/set_privacy.html +3 -1
  61. wagtail/admin/templates/wagtailadmin/pages/_editor_js.html +0 -14
  62. wagtail/admin/templates/wagtailadmin/pages/action_menu/save_draft.html +3 -1
  63. wagtail/admin/templates/wagtailadmin/pages/choose_parent.html +17 -0
  64. wagtail/admin/templates/wagtailadmin/pages/explorable_index.html +8 -0
  65. wagtail/admin/templates/wagtailadmin/pages/history.html +1 -61
  66. wagtail/admin/templates/wagtailadmin/pages/index.html +1 -3
  67. wagtail/admin/templates/wagtailadmin/pages/listing/_locked_indicator.html +2 -2
  68. wagtail/admin/templates/wagtailadmin/pages/listing/_page_title_column_header.html +25 -27
  69. wagtail/admin/templates/wagtailadmin/pages/page_listing_header.html +2 -1
  70. wagtail/admin/templates/wagtailadmin/panels/multi_field_panel_child.html +1 -1
  71. wagtail/admin/templates/wagtailadmin/panels/publishing/schedule_publishing_panel.html +3 -1
  72. wagtail/admin/templates/wagtailadmin/panels/tabbed_interface.html +1 -1
  73. wagtail/admin/templates/wagtailadmin/shared/active_filters.html +2 -1
  74. wagtail/admin/templates/wagtailadmin/shared/breadcrumbs.html +8 -0
  75. wagtail/admin/templates/wagtailadmin/shared/forms/single_checkbox.html +1 -1
  76. wagtail/admin/templates/wagtailadmin/shared/headers/page_edit_header.html +1 -1
  77. wagtail/admin/templates/wagtailadmin/shared/headers/slim_header.html +21 -9
  78. wagtail/admin/templates/wagtailadmin/shared/human_readable_date.html +1 -1
  79. wagtail/admin/templates/wagtailadmin/shared/keyboard_shortcuts_dialog.html +29 -0
  80. wagtail/admin/templates/wagtailadmin/shared/side_panel_toggle.html +2 -1
  81. wagtail/admin/templates/wagtailadmin/skeleton.html +2 -1
  82. wagtail/admin/templates/wagtailadmin/tables/related_objects_cell.html +9 -0
  83. wagtail/admin/templates/wagtailadmin/tables/title_cell.html +9 -7
  84. wagtail/admin/templates/wagtailadmin/widgets/draftail_rich_text_area.html +1 -1
  85. wagtail/admin/templates/wagtailadmin/workflows/create.html +6 -23
  86. wagtail/admin/templates/wagtailadmin/workflows/create_task.html +6 -15
  87. wagtail/admin/templates/wagtailadmin/workflows/edit.html +6 -23
  88. wagtail/admin/templates/wagtailadmin/workflows/edit_task.html +6 -13
  89. wagtail/admin/templates/wagtailadmin/workflows/includes/task_usage_cell.html +4 -4
  90. wagtail/admin/templates/wagtailadmin/workflows/includes/workflow_tasks_cell.html +18 -0
  91. wagtail/admin/templates/wagtailadmin/workflows/includes/workflow_title_cell.html +7 -0
  92. wagtail/admin/templates/wagtailadmin/workflows/includes/workflow_used_by_cell.html +25 -0
  93. wagtail/admin/templates/wagtailadmin/workflows/index.html +0 -99
  94. wagtail/admin/templates/wagtailadmin/workflows/index_results.html +10 -0
  95. wagtail/admin/templates/wagtailadmin/workflows/task_index.html +0 -30
  96. wagtail/admin/templates/wagtailadmin/workflows/task_index_results.html +10 -0
  97. wagtail/admin/templates/wagtailadmin/workflows/usage.html +1 -1
  98. wagtail/admin/templatetags/wagtailadmin_tags.py +116 -39
  99. wagtail/admin/tests/pages/test_create_page.py +10 -4
  100. wagtail/admin/tests/pages/test_custom_listing.py +37 -0
  101. wagtail/admin/tests/pages/test_edit_page.py +6 -6
  102. wagtail/admin/tests/pages/test_explorer_view.py +19 -18
  103. wagtail/admin/tests/pages/test_move_page.py +1 -1
  104. wagtail/admin/tests/pages/test_page_usage.py +50 -2
  105. wagtail/admin/tests/pages/test_parent_page_chooser_view.py +119 -0
  106. wagtail/admin/tests/pages/test_preview.py +18 -4
  107. wagtail/admin/tests/test_account_management.py +20 -1
  108. wagtail/admin/tests/test_audit_log.py +172 -5
  109. wagtail/admin/tests/test_checks.py +92 -0
  110. wagtail/admin/tests/test_collections_views.py +19 -5
  111. wagtail/admin/tests/test_compare.py +6 -6
  112. wagtail/admin/tests/test_dashboard.py +404 -0
  113. wagtail/admin/tests/test_dbwhitelister.py +4 -5
  114. wagtail/admin/tests/test_edit_handlers.py +2 -2
  115. wagtail/admin/tests/test_keyboard_shortcuts.py +84 -0
  116. wagtail/admin/tests/test_page_chooser.py +31 -18
  117. wagtail/admin/tests/test_privacy.py +36 -2
  118. wagtail/admin/tests/test_rich_text.py +168 -23
  119. wagtail/admin/tests/test_templatetags.py +411 -43
  120. wagtail/admin/tests/test_views.py +4 -2
  121. wagtail/admin/tests/test_workflows.py +531 -9
  122. wagtail/admin/tests/tests.py +3 -1
  123. wagtail/admin/tests/ui/test_tables.py +48 -1
  124. wagtail/admin/tests/viewsets/test_model_viewset.py +126 -29
  125. wagtail/admin/ui/side_panels.py +3 -1
  126. wagtail/admin/ui/tables/__init__.py +13 -1
  127. wagtail/admin/ui/tables/pages.py +17 -6
  128. wagtail/admin/urls/__init__.py +8 -3
  129. wagtail/admin/urls/pages.py +5 -0
  130. wagtail/admin/urls/workflows.py +10 -0
  131. wagtail/admin/views/chooser.py +20 -24
  132. wagtail/admin/views/collections.py +17 -1
  133. wagtail/admin/views/generic/base.py +31 -4
  134. wagtail/admin/views/generic/history.py +220 -51
  135. wagtail/admin/views/generic/mixins.py +7 -4
  136. wagtail/admin/views/generic/models.py +54 -38
  137. wagtail/admin/views/generic/multiple_upload.py +17 -8
  138. wagtail/admin/views/generic/usage.py +17 -11
  139. wagtail/admin/views/home.py +15 -12
  140. wagtail/admin/views/mixins.py +30 -0
  141. wagtail/admin/views/pages/choose_parent.py +73 -0
  142. wagtail/admin/views/pages/history.py +54 -66
  143. wagtail/admin/views/pages/listing.py +187 -106
  144. wagtail/admin/views/pages/usage.py +6 -1
  145. wagtail/admin/views/pages/utils.py +70 -1
  146. wagtail/admin/views/workflows.py +150 -21
  147. wagtail/admin/viewsets/model.py +2 -2
  148. wagtail/admin/viewsets/pages.py +77 -0
  149. wagtail/admin/wagtail_hooks.py +40 -2
  150. wagtail/admin/widgets/button.py +10 -9
  151. wagtail/api/v2/filters.py +1 -1
  152. wagtail/api/v2/tests/test_pages.py +1 -1
  153. wagtail/blocks/base.py +18 -9
  154. wagtail/blocks/field_block.py +9 -7
  155. wagtail/blocks/list_block.py +16 -6
  156. wagtail/blocks/static_block.py +3 -0
  157. wagtail/blocks/stream_block.py +58 -23
  158. wagtail/blocks/struct_block.py +15 -9
  159. wagtail/contrib/forms/locale/en/LC_MESSAGES/django.po +39 -47
  160. wagtail/contrib/forms/models.py +5 -5
  161. wagtail/contrib/forms/templates/wagtailforms/list_submissions.html +44 -33
  162. wagtail/contrib/forms/templates/wagtailforms/submissions_index.html +2 -63
  163. wagtail/contrib/forms/tests/test_models.py +26 -0
  164. wagtail/contrib/forms/urls.py +6 -0
  165. wagtail/contrib/forms/views.py +52 -49
  166. wagtail/contrib/redirects/locale/ca/LC_MESSAGES/django.mo +0 -0
  167. wagtail/contrib/redirects/locale/ca/LC_MESSAGES/django.po +3 -3
  168. wagtail/contrib/redirects/locale/en/LC_MESSAGES/django.po +34 -42
  169. wagtail/contrib/redirects/signal_handlers.py +1 -1
  170. wagtail/contrib/redirects/templates/wagtailredirects/index.html +1 -36
  171. wagtail/contrib/redirects/templates/wagtailredirects/index_results.html +18 -0
  172. wagtail/contrib/redirects/templates/wagtailredirects/redirect_target_cell.html +8 -0
  173. wagtail/contrib/redirects/tests/test_import_command.py +1 -1
  174. wagtail/contrib/redirects/tests/test_redirects.py +79 -8
  175. wagtail/contrib/redirects/urls.py +2 -1
  176. wagtail/contrib/redirects/views.py +85 -55
  177. wagtail/contrib/search_promotions/admin_urls.py +2 -1
  178. wagtail/contrib/search_promotions/locale/en/LC_MESSAGES/django.po +41 -64
  179. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/index.html +1 -16
  180. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/index_results.html +11 -0
  181. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/list.html +0 -51
  182. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/results.html +3 -16
  183. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/search_promotion_column.html +15 -0
  184. wagtail/contrib/search_promotions/tests.py +122 -9
  185. wagtail/contrib/search_promotions/views.py +66 -65
  186. wagtail/contrib/settings/locale/en/LC_MESSAGES/django.po +3 -3
  187. wagtail/contrib/settings/registry.py +10 -5
  188. wagtail/contrib/settings/tests/generic/test_admin.py +9 -0
  189. wagtail/contrib/settings/tests/site_specific/test_admin.py +10 -1
  190. wagtail/contrib/settings/tests/site_specific/test_model.py +3 -3
  191. wagtail/contrib/settings/tests/site_specific/test_templates.py +1 -1
  192. wagtail/contrib/settings/views.py +3 -1
  193. wagtail/contrib/simple_translation/locale/en/LC_MESSAGES/django.po +1 -1
  194. wagtail/contrib/simple_translation/tests/test_wagtail_hooks.py +2 -2
  195. wagtail/contrib/styleguide/locale/en/LC_MESSAGES/django.po +7 -7
  196. wagtail/contrib/table_block/blocks.py +1 -1
  197. wagtail/contrib/table_block/locale/en/LC_MESSAGES/django.po +1 -1
  198. wagtail/contrib/table_block/static/table_block/js/table.js +1 -1
  199. wagtail/contrib/typed_table_block/locale/en/LC_MESSAGES/django.po +1 -1
  200. wagtail/contrib/typed_table_block/static/typed_table_block/js/typed_table_block.js +1 -1
  201. wagtail/coreutils.py +3 -2
  202. wagtail/documents/admin_urls.py +2 -2
  203. wagtail/documents/locale/en/LC_MESSAGES/django.po +22 -22
  204. wagtail/documents/migrations/0013_delete_uploadeddocument.py +16 -0
  205. wagtail/documents/models.py +1 -20
  206. wagtail/documents/rich_text/__init__.py +11 -7
  207. wagtail/documents/static/wagtaildocs/js/document-chooser-modal.js +1 -1
  208. wagtail/documents/static/wagtaildocs/js/document-chooser-telepath.js +1 -1
  209. wagtail/documents/static/wagtaildocs/js/document-chooser.js +1 -1
  210. wagtail/documents/templates/wagtaildocs/documents/index.html +0 -16
  211. wagtail/documents/tests/test_admin_views.py +155 -23
  212. wagtail/documents/tests/test_collection_privacy.py +55 -1
  213. wagtail/documents/tests/test_rich_text.py +14 -0
  214. wagtail/documents/views/documents.py +25 -22
  215. wagtail/documents/views/multiple.py +6 -7
  216. wagtail/documents/views/serve.py +16 -1
  217. wagtail/documents/wagtail_hooks.py +20 -15
  218. wagtail/embeds/blocks.py +5 -0
  219. wagtail/embeds/locale/en/LC_MESSAGES/django.po +2 -2
  220. wagtail/embeds/rich_text/__init__.py +1 -1
  221. wagtail/embeds/tests/test_rich_text.py +14 -0
  222. wagtail/embeds/wagtail_hooks.py +4 -14
  223. wagtail/fields.py +3 -48
  224. wagtail/images/admin_urls.py +2 -2
  225. wagtail/images/check_files/wagtail.jpg +0 -0
  226. wagtail/images/check_files/wagtail.png +0 -0
  227. wagtail/images/fields.py +2 -0
  228. wagtail/images/image_operations.py +1 -1
  229. wagtail/images/locale/en/LC_MESSAGES/django.po +33 -45
  230. wagtail/images/locale/pt_PT/LC_MESSAGES/django.mo +0 -0
  231. wagtail/images/locale/pt_PT/LC_MESSAGES/django.po +4 -0
  232. wagtail/images/migrations/0026_delete_uploadedimage.py +16 -0
  233. wagtail/images/models.py +49 -43
  234. wagtail/images/rich_text/__init__.py +18 -8
  235. wagtail/images/static/wagtailimages/js/image-chooser-modal.js +1 -1
  236. wagtail/images/static/wagtailimages/js/image-chooser-telepath.js +1 -1
  237. wagtail/images/static/wagtailimages/js/image-chooser.js +1 -1
  238. wagtail/images/templates/wagtailimages/images/image_listing_header.html +6 -0
  239. wagtail/images/templates/wagtailimages/images/index.html +11 -51
  240. wagtail/images/tests/test_admin_views.py +119 -62
  241. wagtail/images/tests/test_image_operations.py +10 -0
  242. wagtail/images/tests/test_models.py +35 -33
  243. wagtail/images/tests/test_rich_text.py +14 -0
  244. wagtail/images/tests/utils.py +1 -1
  245. wagtail/images/views/images.py +35 -64
  246. wagtail/images/views/multiple.py +6 -7
  247. wagtail/images/wagtail_hooks.py +4 -14
  248. wagtail/locale/en/LC_MESSAGES/django.po +150 -136
  249. wagtail/locales/locale/en/LC_MESSAGES/django.po +1 -1
  250. wagtail/locales/tests.py +18 -3
  251. wagtail/locales/views.py +0 -1
  252. wagtail/management/commands/rebuild_references_index.py +3 -1
  253. wagtail/migrations/0092_alter_collectionviewrestriction_password_and_more.py +33 -0
  254. wagtail/migrations/0093_uploadedfile.py +53 -0
  255. wagtail/models/__init__.py +147 -32
  256. wagtail/models/i18n.py +1 -1
  257. wagtail/models/{collections.py → media.py} +33 -2
  258. wagtail/models/reference_index.py +1 -1
  259. wagtail/models/view_restrictions.py +10 -3
  260. wagtail/project_template/project_name/settings/base.py +6 -0
  261. wagtail/project_template/requirements.txt +1 -1
  262. wagtail/rich_text/__init__.py +25 -8
  263. wagtail/rich_text/pages.py +19 -8
  264. wagtail/rich_text/rewriters.py +140 -68
  265. wagtail/search/backends/database/mysql/mysql.py +3 -3
  266. wagtail/search/backends/database/postgres/postgres.py +3 -3
  267. wagtail/search/backends/database/sqlite/sqlite.py +2 -2
  268. wagtail/search/backends/elasticsearch7.py +4 -0
  269. wagtail/search/locale/en/LC_MESSAGES/django.po +3 -3
  270. wagtail/search/tests/test_postgres_backend.py +50 -0
  271. wagtail/sites/locale/en/LC_MESSAGES/django.po +8 -8
  272. wagtail/sites/tests.py +35 -9
  273. wagtail/sites/views.py +3 -1
  274. wagtail/snippets/locale/de/LC_MESSAGES/django.mo +0 -0
  275. wagtail/snippets/locale/de/LC_MESSAGES/django.po +5 -6
  276. wagtail/snippets/locale/en/LC_MESSAGES/django.po +16 -56
  277. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser-telepath.js +1 -1
  278. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser.js +1 -1
  279. wagtail/snippets/templates/wagtailsnippets/snippets/action_menu/publish.html +3 -1
  280. wagtail/snippets/templates/wagtailsnippets/snippets/action_menu/save.html +3 -1
  281. wagtail/snippets/templates/wagtailsnippets/snippets/create.html +1 -1
  282. wagtail/snippets/templates/wagtailsnippets/snippets/edit.html +1 -1
  283. wagtail/snippets/tests/test_preview.py +13 -2
  284. wagtail/snippets/tests/test_snippets.py +41 -16
  285. wagtail/snippets/tests/test_viewset.py +63 -18
  286. wagtail/snippets/tests/test_workflows.py +12 -0
  287. wagtail/snippets/views/snippets.py +1 -40
  288. wagtail/templatetags/wagtailcore_tags.py +1 -1
  289. wagtail/test/demosite/models.py +1 -1
  290. wagtail/test/middleware.py +14 -1
  291. wagtail/test/testapp/fixtures/test.json +20 -0
  292. wagtail/test/testapp/migrations/0001_squashed_0073_revisablechildmodel_secret_text.py +8 -8
  293. wagtail/test/testapp/migrations/0023_snippetchoosermodel_full_featured.py +1 -0
  294. wagtail/test/testapp/migrations/0034_custompermissionmodel.py +44 -0
  295. wagtail/test/testapp/migrations/0035_modelwithcustommanager.py +30 -0
  296. wagtail/test/testapp/migrations/0036_complexdefaultstreampage.py +28 -0
  297. wagtail/test/testapp/models.py +79 -2
  298. wagtail/test/testapp/templates/tests/custom_docs_password_required.html +10 -0
  299. wagtail/test/testapp/templates/tests/custom_page_password_required.html +10 -0
  300. wagtail/test/testapp/views.py +24 -2
  301. wagtail/test/testapp/wagtail_hooks.py +19 -0
  302. wagtail/test/utils/wagtail_tests.py +2 -2
  303. wagtail/tests/test_blocks.py +262 -1
  304. wagtail/tests/test_migrations.py +1 -1
  305. wagtail/tests/test_page_model.py +77 -0
  306. wagtail/tests/test_page_privacy.py +18 -1
  307. wagtail/tests/test_rich_text.py +95 -5
  308. wagtail/tests/test_streamfield.py +43 -0
  309. wagtail/tests/test_utils.py +8 -2
  310. wagtail/tests/test_views.py +52 -1
  311. wagtail/tests/test_whitelist.py +7 -7
  312. wagtail/users/forms.py +3 -1
  313. wagtail/users/locale/en/LC_MESSAGES/django.po +124 -96
  314. wagtail/users/migrations/0013_userprofile_density.py +23 -0
  315. wagtail/users/models.py +14 -3
  316. wagtail/users/templates/wagtailusers/groups/create.html +1 -7
  317. wagtail/users/templates/wagtailusers/groups/edit.html +1 -13
  318. wagtail/users/templates/wagtailusers/groups/includes/formatted_permissions.html +46 -2
  319. wagtail/users/templates/wagtailusers/groups/includes/group_form_js.html +0 -2
  320. wagtail/users/templates/wagtailusers/users/create.html +1 -9
  321. wagtail/users/templates/wagtailusers/users/edit.html +1 -9
  322. wagtail/users/templates/wagtailusers/users/index.html +2 -5
  323. wagtail/users/templates/wagtailusers/users/index_results.html +3 -13
  324. wagtail/users/templates/wagtailusers/users/user_cell.html +9 -0
  325. wagtail/users/templatetags/wagtailusers_tags.py +107 -20
  326. wagtail/users/tests/test_admin_views.py +669 -90
  327. wagtail/users/views/groups.py +58 -61
  328. wagtail/users/views/users.py +211 -92
  329. wagtail/users/wagtail_hooks.py +6 -38
  330. wagtail/users/widgets.py +3 -5
  331. wagtail/utils/text.py +1 -1
  332. wagtail/views.py +5 -9
  333. wagtail/whitelist.py +1 -1
  334. {wagtail-6.0.2.dist-info → wagtail-6.1rc1.dist-info}/METADATA +4 -5
  335. {wagtail-6.0.2.dist-info → wagtail-6.1rc1.dist-info}/RECORD +339 -320
  336. wagtail/admin/static/wagtailadmin/js/page-editor.js +0 -1
  337. wagtail/admin/static/wagtailadmin/js/vendor/mousetrap.min.js +0 -1
  338. wagtail/admin/static/wagtailadmin/js/vendor/urlify.js +0 -1
  339. wagtail/admin/static/wagtailadmin/js/vendor/xregexp.min.js +0 -1
  340. wagtail/admin/templates/wagtailadmin/collections/index.html +0 -34
  341. wagtail/admin/templates/wagtailadmin/pages/revisions/_actions.html +0 -22
  342. wagtail/admin/templates/wagtailadmin/shared/page_breadcrumbs.html +0 -55
  343. wagtail/admin/tests/pages/test_dashboard.py +0 -172
  344. wagtail/contrib/redirects/templates/wagtailredirects/results.html +0 -23
  345. wagtail/documents/templates/wagtaildocs/documents/list.html +0 -2
  346. wagtail/search/tests/test_postgres_stemming.py +0 -40
  347. wagtail/sites/templates/wagtailsites/create.html +0 -6
  348. wagtail/sites/templates/wagtailsites/edit.html +0 -6
  349. wagtail/snippets/templates/wagtailsnippets/snippets/revisions/_actions.html +0 -36
  350. wagtail/users/templates/wagtailusers/users/list.html +0 -62
  351. wagtail/users/urls/users.py +0 -12
  352. {wagtail-6.0.2.dist-info → wagtail-6.1rc1.dist-info}/LICENSE +0 -0
  353. {wagtail-6.0.2.dist-info → wagtail-6.1rc1.dist-info}/WHEEL +0 -0
  354. {wagtail-6.0.2.dist-info → wagtail-6.1rc1.dist-info}/entry_points.txt +0 -0
  355. {wagtail-6.0.2.dist-info → wagtail-6.1rc1.dist-info}/top_level.txt +0 -0
@@ -2,6 +2,7 @@ import json
2
2
  from datetime import date, datetime, timedelta
3
3
  from io import StringIO
4
4
 
5
+ from django.contrib.auth.models import Permission
5
6
  from django.core import management
6
7
  from django.test import TestCase
7
8
  from django.urls import reverse
@@ -172,7 +173,7 @@ class TestGetSearchPromotionsTemplateTag(TestCase):
172
173
 
173
174
  class TestSearchPromotionsIndexView(WagtailTestUtils, TestCase):
174
175
  def setUp(self):
175
- self.login()
176
+ self.user = self.login()
176
177
 
177
178
  def test_simple(self):
178
179
  response = self.client.get(reverse("wagtailsearchpromotions:index"))
@@ -230,7 +231,7 @@ class TestSearchPromotionsIndexView(WagtailTestUtils, TestCase):
230
231
  self.assertTemplateUsed(response, "wagtailsearchpromotions/index.html")
231
232
 
232
233
  # Check that we got the correct page
233
- self.assertEqual(response.context["queries"].number, 2)
234
+ self.assertEqual(response.context["page_obj"].number, 2)
234
235
 
235
236
  def test_pagination_invalid(self):
236
237
  self.make_search_picks()
@@ -252,6 +253,41 @@ class TestSearchPromotionsIndexView(WagtailTestUtils, TestCase):
252
253
  # Check response
253
254
  self.assertEqual(response.status_code, 404)
254
255
 
256
+ def test_num_queries(self):
257
+ url = reverse("wagtailsearchpromotions:index")
258
+ self.make_search_picks()
259
+ # Warm up the cache
260
+ self.client.get(url)
261
+
262
+ # Number of queries with the current number of search picks
263
+ with self.assertNumQueries(11):
264
+ self.client.get(url)
265
+
266
+ # Add more SearchPromotions and QueryDailyHits to some of the queries
267
+ today = date.today()
268
+ for i in range(20):
269
+ query = Query.get("query " + str(i))
270
+ promos = [
271
+ SearchPromotion(
272
+ query=query,
273
+ page_id=j % 2 + 1,
274
+ sort_order=j,
275
+ description=f"Search pick {j}",
276
+ )
277
+ for j in range(5)
278
+ ]
279
+ hits = [
280
+ QueryDailyHits(query=query, date=today - timedelta(days=j), hits=j)
281
+ for j in range(5)
282
+ ]
283
+ SearchPromotion.objects.bulk_create(promos)
284
+ QueryDailyHits.objects.bulk_create(hits)
285
+
286
+ # Number of queries after the addition of more search picks and hits
287
+ # should remain the same (no N+1 queries)
288
+ with self.assertNumQueries(11):
289
+ self.client.get(url)
290
+
255
291
  def test_results_are_ordered_alphabetically(self):
256
292
  self.make_search_picks()
257
293
  SearchPromotion.objects.create(
@@ -260,6 +296,13 @@ class TestSearchPromotionsIndexView(WagtailTestUtils, TestCase):
260
296
  sort_order=0,
261
297
  description="ooh, it's a snake",
262
298
  )
299
+ # Add another one to make sure it's not ordered descending by pk
300
+ SearchPromotion.objects.create(
301
+ query=Query.get("beloved snake"),
302
+ page_id=1,
303
+ sort_order=0,
304
+ description="beloved snake goes ssSSSS",
305
+ )
263
306
 
264
307
  response = self.client.get(reverse("wagtailsearchpromotions:index"))
265
308
 
@@ -269,6 +312,37 @@ class TestSearchPromotionsIndexView(WagtailTestUtils, TestCase):
269
312
 
270
313
  # "aargh snake" should be the first result alphabetically
271
314
  self.assertEqual(response.context["queries"][0].query_string, "aaargh snake")
315
+ self.assertEqual(response.context["queries"][1].query_string, "beloved snake")
316
+
317
+ def test_multiple_searchpromotions(self):
318
+ today = date.today()
319
+ for i in range(10):
320
+ Query.get("root page").add_hit(date=today - timedelta(days=i))
321
+ SearchPromotion.objects.create(
322
+ query=Query.get("root page"),
323
+ page_id=1,
324
+ sort_order=0,
325
+ description="First search pick",
326
+ )
327
+ SearchPromotion.objects.create(
328
+ query=Query.get("root page"),
329
+ page_id=2,
330
+ sort_order=0,
331
+ description="Second search pick",
332
+ )
333
+ response = self.client.get(reverse("wagtailsearchpromotions:index"))
334
+
335
+ self.assertContains(response, "<td>10</td>", html=True)
336
+ self.assertEqual(Query.get("root page").hits, 10)
337
+
338
+ soup = self.get_soup(response.content)
339
+ root_page_edit_url = reverse("wagtailadmin_pages:edit", args=(1,))
340
+ homepage_edit_url = reverse("wagtailadmin_pages:edit", args=(2,))
341
+ root_page_edit_link = soup.select_one(f'a[href="{root_page_edit_url}"]')
342
+ homepage_edit_link = soup.select_one(f'a[href="{homepage_edit_url}"]')
343
+ self.assertIsNotNone(root_page_edit_link)
344
+ self.assertIsNotNone(homepage_edit_link)
345
+ self.assertEqual(Query.get("root page").editors_picks.count(), 2)
272
346
 
273
347
  def test_results_ordering(self):
274
348
  self.make_search_picks()
@@ -310,29 +384,68 @@ class TestSearchPromotionsIndexView(WagtailTestUtils, TestCase):
310
384
  # ordered by querystring (reversed)
311
385
  response = self.client.get(url + "?ordering=-query_string")
312
386
  self.assertEqual(response.status_code, 200)
313
- self.assertEqual(response.context["queries"][0].query_string, "zyzzyvas")
387
+ self.assertEqual(response.context["page_obj"][0].query_string, "zyzzyvas")
314
388
 
315
389
  # last page, still ordered by query string (reversed)
316
390
  response = self.client.get(url + "?ordering=-query_string&p=3")
317
391
  self.assertEqual(response.status_code, 200)
318
- self.assertEqual(response.context["queries"][-1].query_string, "aardwolf")
392
+ self.assertEqual(response.context["page_obj"][-1].query_string, "aardwolf")
319
393
 
320
394
  # ordered by querystring (not reversed)
321
395
  response = self.client.get(url + "?ordering=query_string")
322
396
  self.assertEqual(response.status_code, 200)
323
- self.assertEqual(response.context["queries"][0].query_string, "aardwolf")
397
+ self.assertEqual(response.context["page_obj"][0].query_string, "aardwolf")
324
398
 
325
399
  # ordered by sum of daily hits (reversed)
326
400
  response = self.client.get(url + "?ordering=-views")
327
401
  self.assertEqual(response.status_code, 200)
328
- self.assertEqual(response.context["queries"][0].query_string, "optimal")
329
- self.assertEqual(response.context["queries"][1].query_string, "suboptimal")
402
+ self.assertEqual(response.context["page_obj"][0].query_string, "optimal")
403
+ self.assertEqual(response.context["page_obj"][1].query_string, "suboptimal")
330
404
 
331
405
  # ordered by sum of daily hits, last page (not reversed)
332
406
  response = self.client.get(url + "?ordering=views&p=3")
333
407
  self.assertEqual(response.status_code, 200)
334
- self.assertEqual(response.context["queries"][-1].query_string, "optimal")
335
- self.assertEqual(response.context["queries"][-2].query_string, "suboptimal")
408
+ self.assertEqual(response.context["page_obj"][-1].query_string, "optimal")
409
+ self.assertEqual(response.context["page_obj"][-2].query_string, "suboptimal")
410
+
411
+ def test_get_with_no_permission(self):
412
+ self.user.is_superuser = False
413
+ self.user.save()
414
+ # Only basic access_admin permission is given
415
+ self.user.user_permissions.add(
416
+ Permission.objects.get(
417
+ content_type__app_label="wagtailadmin",
418
+ codename="access_admin",
419
+ )
420
+ )
421
+
422
+ response = self.client.get(reverse("wagtailsearchpromotions:index"))
423
+ self.assertEqual(response.status_code, 302)
424
+ self.assertRedirects(response, reverse("wagtailadmin_home"))
425
+
426
+ def test_get_with_edit_permission_only(self):
427
+ self.user.is_superuser = False
428
+ self.user.save()
429
+ # Only basic access_admin permission is given
430
+ self.user.user_permissions.add(
431
+ Permission.objects.get(
432
+ content_type__app_label="wagtailadmin",
433
+ codename="access_admin",
434
+ ),
435
+ Permission.objects.get(
436
+ content_type__app_label="wagtailsearchpromotions",
437
+ codename="change_searchpromotion",
438
+ ),
439
+ )
440
+
441
+ response = self.client.get(reverse("wagtailsearchpromotions:index"))
442
+ self.assertEqual(response.status_code, 200)
443
+ self.assertTemplateUsed(response, "wagtailsearchpromotions/index.html")
444
+
445
+ soup = self.get_soup(response.content)
446
+ add_url = reverse("wagtailsearchpromotions:add")
447
+ # Should not render add link
448
+ self.assertIsNone(soup.select_one(f'a[href="{add_url}"]'))
336
449
 
337
450
 
338
451
  class TestSearchPromotionsAddView(WagtailTestUtils, TestCase):
@@ -6,80 +6,81 @@ from django.shortcuts import get_object_or_404, redirect
6
6
  from django.template.response import TemplateResponse
7
7
  from django.urls import reverse
8
8
  from django.utils.translation import gettext as _
9
- from django.views.decorators.vary import vary_on_headers
9
+ from django.utils.translation import gettext_lazy
10
10
 
11
11
  from wagtail.admin import messages
12
- from wagtail.admin.auth import any_permission_required, permission_required
12
+ from wagtail.admin.auth import permission_required
13
13
  from wagtail.admin.forms.search import SearchForm
14
14
  from wagtail.admin.modal_workflow import render_modal_workflow
15
+ from wagtail.admin.ui.tables import Column, RelatedObjectsColumn, TitleColumn
16
+ from wagtail.admin.views import generic
15
17
  from wagtail.contrib.search_promotions import forms, models
16
- from wagtail.contrib.search_promotions.models import Query
18
+ from wagtail.contrib.search_promotions.models import Query, SearchPromotion
17
19
  from wagtail.log_actions import log
20
+ from wagtail.permission_policies.base import ModelPermissionPolicy
18
21
  from wagtail.search.utils import normalise_query_string
19
22
 
20
23
 
21
- @any_permission_required(
22
- "wagtailsearchpromotions.add_searchpromotion",
23
- "wagtailsearchpromotions.change_searchpromotion",
24
- "wagtailsearchpromotions.delete_searchpromotion",
25
- )
26
- @vary_on_headers("X-Requested-With")
27
- def index(request):
28
- # Ordering
29
- valid_ordering = ["query_string", "-query_string", "views", "-views"]
30
- ordering = valid_ordering[0]
31
-
32
- if "ordering" in request.GET and request.GET["ordering"] in valid_ordering:
33
- ordering = request.GET["ordering"]
34
-
35
- # Query
36
- queries = Query.objects.filter(editors_picks__isnull=False).distinct()
37
-
38
- if "views" in ordering:
39
- queries = queries.annotate(views=functions.Coalesce(Sum("daily_hits__hits"), 0))
40
-
41
- queries = queries.order_by(ordering)
42
-
43
- # Search
44
- is_searching = False
45
- query_string = request.GET.get("q", "")
46
-
47
- if query_string:
48
- queries = queries.filter(query_string__icontains=query_string)
49
- is_searching = True
50
-
51
- # Paginate
52
- paginator = Paginator(queries, per_page=20)
53
- try:
54
- queries = paginator.page(request.GET.get("p", 1))
55
- except InvalidPage:
56
- raise Http404
57
-
58
- if request.headers.get("x-requested-with") == "XMLHttpRequest":
59
- return TemplateResponse(
60
- request,
61
- "wagtailsearchpromotions/results.html",
62
- {
63
- "is_searching": is_searching,
64
- "ordering": ordering,
65
- "queries": queries,
66
- "query_string": query_string,
67
- },
68
- )
69
- else:
70
- return TemplateResponse(
71
- request,
72
- "wagtailsearchpromotions/index.html",
73
- {
74
- "is_searching": is_searching,
75
- "ordering": ordering,
76
- "queries": queries,
77
- "query_string": query_string,
78
- "search_form": SearchForm(
79
- data={"q": query_string} if query_string else None,
80
- ),
81
- },
82
- )
24
+ class SearchPromotionColumn(RelatedObjectsColumn):
25
+ cell_template_name = "wagtailsearchpromotions/search_promotion_column.html"
26
+
27
+
28
+ class IndexView(generic.IndexView):
29
+ model = Query
30
+ template_name = "wagtailsearchpromotions/index.html"
31
+ results_template_name = "wagtailsearchpromotions/index_results.html"
32
+ context_object_name = "queries"
33
+ page_title = gettext_lazy("Search Terms")
34
+ header_icon = "pick"
35
+ paginate_by = 20
36
+ permission_policy = ModelPermissionPolicy(SearchPromotion)
37
+ index_url_name = "wagtailsearchpromotions:index"
38
+ index_results_url_name = "wagtailsearchpromotions:index_results"
39
+ _show_breadcrumbs = True
40
+ search_fields = ["query_string"]
41
+ default_ordering = "query_string"
42
+ add_url_name = "wagtailsearchpromotions:add"
43
+ add_item_label = gettext_lazy("Add new promoted result")
44
+ columns = [
45
+ TitleColumn(
46
+ "query_string",
47
+ label=gettext_lazy("Search term(s)"),
48
+ width="40%",
49
+ url_name="wagtailsearchpromotions:edit",
50
+ sort_key="query_string",
51
+ ),
52
+ SearchPromotionColumn(
53
+ "editors_picks",
54
+ label=gettext_lazy("Promoted results"),
55
+ width="40%",
56
+ ),
57
+ Column(
58
+ "views",
59
+ label=gettext_lazy("Views (past week)"),
60
+ width="20%",
61
+ sort_key="views",
62
+ ),
63
+ ]
64
+
65
+ def get_base_queryset(self):
66
+ # Use a subquery to filter out the Query objects that do not have a
67
+ # SearchPromotion instead of using .filter(editors_picks__isnull=False).
68
+ # The latter would use a JOIN which would result in duplicate rows before
69
+ # the sum is calculated, causing the sum to be incorrect.
70
+ has_promotions = SearchPromotion.objects.values_list("query_id", flat=True)
71
+ queryset = self.model.objects.filter(pk__in=has_promotions)
72
+
73
+ # Prevent N+1 queries by annotating the sum instead of using the
74
+ # Query.hits property and prefetching the related editors_picks.
75
+ queryset = queryset.annotate(
76
+ views=functions.Coalesce(Sum("daily_hits__hits"), 0)
77
+ ).prefetch_related("editors_picks", "editors_picks__page")
78
+ return queryset
79
+
80
+ def get_breadcrumbs_items(self):
81
+ breadcrumbs = super().get_breadcrumbs_items()
82
+ breadcrumbs[-1]["label"] = _("Promoted search results")
83
+ return breadcrumbs
83
84
 
84
85
 
85
86
  def save_searchpicks(query, new_query, searchpicks_formset):
@@ -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-01-24 13:51+0000\n"
11
+ "POT-Creation-Date: 2024-04-18 17:28+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"
@@ -48,11 +48,11 @@ msgstr ""
48
48
  msgid "This setting could not be opened because there is no site defined."
49
49
  msgstr ""
50
50
 
51
- #: views.py:84
51
+ #: views.py:83
52
52
  msgid "The setting could not be saved due to errors."
53
53
  msgstr ""
54
54
 
55
- #: views.py:147
55
+ #: views.py:149
56
56
  #, python-format
57
57
  msgid "%(setting_type)s updated."
58
58
  msgstr ""
@@ -67,12 +67,17 @@ class GenericSettingAdminURLFinder(ModelAdminURLFinder):
67
67
 
68
68
 
69
69
  class Registry(list):
70
- def register(self, model, **kwargs):
70
+ def __init__(self):
71
+ self._model_icons = {}
72
+
73
+ def register(self, model, icon="cog", **kwargs):
71
74
  from .models import BaseGenericSetting, BaseSiteSetting
72
75
 
73
76
  """
74
77
  Register a model as a setting, adding it to the wagtail admin menu
75
78
  """
79
+ if icon:
80
+ self._model_icons[model] = icon
76
81
 
77
82
  # Don't bother registering this if it is already registered
78
83
  if model in self:
@@ -82,7 +87,7 @@ class Registry(list):
82
87
  # Register a new menu item in the settings menu
83
88
  @hooks.register("register_settings_menu_item")
84
89
  def menu_hook():
85
- return SettingMenuItem(model, **kwargs)
90
+ return SettingMenuItem(model, icon=self._model_icons.get(model), **kwargs)
86
91
 
87
92
  @hooks.register("register_permissions")
88
93
  def permissions_hook():
@@ -113,13 +118,13 @@ class Registry(list):
113
118
 
114
119
  return model
115
120
 
116
- def register_decorator(self, model=None, **kwargs):
121
+ def register_decorator(self, model=None, icon="cog", **kwargs):
117
122
  """
118
123
  Register a model as a setting in the Wagtail admin
119
124
  """
120
125
  if model is None:
121
- return lambda model: self.register(model, **kwargs)
122
- return self.register(model, **kwargs)
126
+ return lambda model: self.register(model, icon=icon, **kwargs)
127
+ return self.register(model, icon=icon, **kwargs)
123
128
 
124
129
  def get_by_natural_key(self, app_label, model_name):
125
130
  """
@@ -126,6 +126,15 @@ class TestGenericSettingEditView(BaseTestGenericSettingView):
126
126
  )
127
127
  self.assertEqual(response.status_code, 404)
128
128
 
129
+ def test_register_with_icon(self):
130
+ edit_url = reverse("wagtailsettings:edit", args=("tests", "IconGenericSetting"))
131
+ edit_response = self.client.get(edit_url, follow=True)
132
+
133
+ self.assertContains(
134
+ edit_response,
135
+ """<svg class="icon icon-icon-setting-tag w-header__glyph" aria-hidden="true"><use href="#icon-icon-setting-tag"></use></svg>""",
136
+ )
137
+
129
138
  def test_edit_invalid(self):
130
139
  response = self.post(post_data={"foo": "bar"})
131
140
  self.assertContains(response, "The setting could not be saved due to errors.")
@@ -120,12 +120,21 @@ class TestSiteSettingEditView(BaseTestSiteSettingView):
120
120
  response = self.get()
121
121
  self.assertEqual(response.status_code, 200)
122
122
 
123
- def test_non_existant_model(self):
123
+ def test_non_existent_model(self):
124
124
  response = self.client.get(
125
125
  reverse("wagtailsettings:edit", args=["test", "foo", 1])
126
126
  )
127
127
  self.assertEqual(response.status_code, 404)
128
128
 
129
+ def test_register_with_icon(self):
130
+ edit_url = reverse("wagtailsettings:edit", args=("tests", "IconGenericSetting"))
131
+ edit_response = self.client.get(edit_url, follow=True)
132
+
133
+ self.assertContains(
134
+ edit_response,
135
+ """<svg class="icon icon-icon-setting-tag w-header__glyph" aria-hidden="true"><use href="#icon-icon-setting-tag"></use></svg>""",
136
+ )
137
+
129
138
  def test_edit_invalid(self):
130
139
  response = self.post(post_data={"foo": "bar"})
131
140
  self.assertContains(response, "The setting could not be saved due to errors.")
@@ -74,7 +74,7 @@ class SettingModelTestCase(SiteSettingsTestMixin, TestCase):
74
74
  def test_importantpages_object_is_pickleable(self):
75
75
  obj = self._create_importantpagessitesetting_object()
76
76
  # Triggers creation of the InvokeViaAttributeShortcut instance,
77
- # and also gives us a value we can use for comparisson
77
+ # and also gives us a value we can use for comparison
78
78
  signup_page_url = obj.page_url.sign_up_page
79
79
 
80
80
  # Attempt to pickle ImportantPages instance
@@ -82,7 +82,7 @@ class SettingModelTestCase(SiteSettingsTestMixin, TestCase):
82
82
  pickled = pickle.dumps(obj, -1)
83
83
  except Exception as e: # noqa: BLE001
84
84
  raise AssertionError(
85
- f"An error occured when attempting to pickle {obj!r}: {e}"
85
+ f"An error occurred when attempting to pickle {obj!r}: {e}"
86
86
  )
87
87
 
88
88
  # Now unpickle the pickled ImportantPages
@@ -90,7 +90,7 @@ class SettingModelTestCase(SiteSettingsTestMixin, TestCase):
90
90
  unpickled = pickle.loads(pickled)
91
91
  except Exception as e: # noqa: BLE001
92
92
  raise AssertionError(
93
- f"An error occured when attempting to unpickle {obj!r}: {e}"
93
+ f"An error occurred when attempting to unpickle {obj!r}: {e}"
94
94
  )
95
95
 
96
96
  # Using 'page_url' should create a new InvokeViaAttributeShortcut
@@ -174,7 +174,7 @@ class TestTemplateTag(TemplateTestCase):
174
174
 
175
175
  self.assertEqual(template.render(context), "")
176
176
 
177
- def test_get_settings_variable_assigment_use_default(self):
177
+ def test_get_settings_variable_assignment_use_default(self):
178
178
  """
179
179
  Check that assigning the setting to a context variable with
180
180
  {% get_settings use_default_site=True as wagtail_settings %} works.
@@ -80,7 +80,6 @@ def redirect_to_relevant_instance(request, app_name, model_name):
80
80
 
81
81
  class EditView(generic.EditView):
82
82
  template_name = "wagtailsettings/edit.html"
83
- header_icon = "cogs"
84
83
  error_message = gettext_lazy("The setting could not be saved due to errors.")
85
84
 
86
85
  def setup(self, request, app_name, model_name, *args, **kwargs):
@@ -129,11 +128,14 @@ class EditView(generic.EditView):
129
128
 
130
129
  media = form.media + edit_handler.media
131
130
 
131
+ header_icon = registry._model_icons.get(self.model)
132
+
132
133
  context.update(
133
134
  {
134
135
  "edit_handler": edit_handler,
135
136
  "site_switcher": site_switcher,
136
137
  "media": media,
138
+ "header_icon": header_icon,
137
139
  }
138
140
  )
139
141
 
@@ -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-01-24 13:51+0000\n"
11
+ "POT-Creation-Date: 2024-04-18 17:28+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"
@@ -160,7 +160,7 @@ class TestConstructSyncedPageTreeListHook(Utils):
160
160
  # Login to access the admin
161
161
  self.login()
162
162
 
163
- # Make sur the French homepage is published/live
163
+ # Make sure the French homepage is published/live
164
164
  self.fr_homepage.live = True
165
165
  self.fr_homepage.save()
166
166
  self.assertTrue(self.en_homepage.live)
@@ -259,7 +259,7 @@ class TestMovingTranslatedPages(Utils):
259
259
  self.assertEqual(self.en_blog_post.get_parent().id, self.en_blog_index.id)
260
260
 
261
261
  # Confirm the fr and de blog post pages are under the blog index page
262
- # We'll confirm these have not moved after ther POST request.
262
+ # We'll confirm these have not moved after the POST request.
263
263
  original_translated_parent_ids = [
264
264
  p.id for p in self.en_blog_index.get_translations()
265
265
  ]
@@ -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-01-24 13:51+0000\n"
11
+ "POT-Creation-Date: 2024-04-18 17:28+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"
@@ -34,26 +34,26 @@ msgstr ""
34
34
  msgid "Delete image"
35
35
  msgstr ""
36
36
 
37
- #: views.py:105 wagtail_hooks.py:20
37
+ #: views.py:110 wagtail_hooks.py:20
38
38
  msgid "Styleguide"
39
39
  msgstr ""
40
40
 
41
- #: views.py:113
41
+ #: views.py:118
42
42
  msgid "Success message"
43
43
  msgstr ""
44
44
 
45
- #: views.py:115 views.py:123 views.py:131
45
+ #: views.py:120 views.py:128 views.py:136
46
46
  msgid "View live"
47
47
  msgstr ""
48
48
 
49
- #: views.py:116 views.py:124 views.py:132
49
+ #: views.py:121 views.py:129 views.py:137
50
50
  msgid "Edit"
51
51
  msgstr ""
52
52
 
53
- #: views.py:121
53
+ #: views.py:126
54
54
  msgid "Warning message"
55
55
  msgstr ""
56
56
 
57
- #: views.py:129
57
+ #: views.py:134
58
58
  msgid "Error message"
59
59
  msgstr ""
@@ -130,7 +130,7 @@ class TableBlock(FieldBlock):
130
130
  """
131
131
  If value came from a table block stored before Wagtail 6.0, we need to set an appropriate
132
132
  value for the header choice. I would really like to have this default to "" and force the
133
- editor to reaffirm they don't want any headers, but that woud be a breaking change.
133
+ editor to reaffirm they don't want any headers, but that would be a breaking change.
134
134
  """
135
135
  if value and not value.get("table_header_choice", ""):
136
136
  if value.get("first_row_is_table_header", False) and value.get(
@@ -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-01-24 13:51+0000\n"
11
+ "POT-Creation-Date: 2024-04-18 17:28+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"
@@ -1 +1 @@
1
- (()=>{"use strict";var e,t={2618:(e,t,a)=>{var n=a(5311),l=a.n(n),i=a(1544);const o={contenteditable:"true","plaintext-only":"true",tabindex:"0"};function r(e,t){const a=e+"-handsontable-container";var n=e+"-handsontable-header",r=e+"-handsontable-col-header",s=e+"-table-header-choice";const d=e+"-handsontable-col-caption",c=l()("#"+e);var h=l()("#"+n),p=l()("#"+r),f=l()("#"+s);const u=l()("#"+d),b={};let g=null,v=null,w=!1;const m=l()("#"+e).parent(),_=function(){let e=0;return m.find(".htCore").each((function(){e+=l()(this).height()})),e+m.find("[data-field]").first().height()},y=[`#${a}`,".wtHider",".wtHolder"],C=function(t){const a=l()("#"+e);l().each(y,(function(){a.closest("[data-field]").find(this).height(t)}))};try{v=JSON.parse(c.val())}catch(e){}null!==v&&((0,i.R)(v,"table_caption")&&u.prop("value",v.table_caption),(0,i.R)(v,"table_header_choice")&&f.prop("value",v.table_header_choice)),(0,i.R)(t,"width")&&(0,i.R)(t,"height")||l()(window).on("resize",(()=>{var e;g.updateSettings({width:l()(".w-field--table_input").closest(".w-panel").width(),height:_()}),e="100%",l().each(y,(function(){l()(this).width(e)})),l()(".w-field--table_input").width(e)}));const $=function(){const e=[],t=[];g.getCellsMeta().forEach((t=>{let a,n;(0,i.R)(t,"className")&&(a=t.className),(0,i.R)(t,"hidden")&&(n=!0),(void 0!==a||n)&&e.push({row:t.row,col:t.col,className:a,hidden:n})})),g.getPlugin("mergeCells").isEnabled()&&g.getPlugin("mergeCells").mergedCellsCollection.mergedCells.forEach((e=>{t.push({row:e.row,col:e.col,rowspan:e.rowspan,colspan:e.colspan})})),c.val(JSON.stringify({data:g.getData(),cell:e,mergeCells:t,first_row_is_table_header:h.val(),first_col_is_header:p.val(),table_header_choice:f.val(),table_caption:u.val()}))},O=function(e,t){C(_()),$(),l()((()=>{l()(m).find("td, th").attr(o)}))};f.on("change",(()=>{$()})),u.on("change",(()=>{$()}));const S={afterChange:function(e,t){w&&"loadData"!==t&&"MergeCells"!==t&&$()},afterCreateCol:O,afterCreateRow:O,afterRemoveCol:O,afterRemoveRow:O,afterSetCellMeta:function(e,t,a,n){w&&"className"===a&&$()},afterMergeCells:function(e,t,a){w&&$()},afterUnmergeCells:function(e,t){w&&$()},afterInit:function(){w=!0}};null!==v&&((0,i.R)(v,"data")&&(S.data=v.data),(0,i.R)(v,"cell")&&(S.cell=v.cell)),Object.keys(S).forEach((e=>{b[e]=S[e]})),Object.keys(t).forEach((e=>{b[e]=t[e]})),(0,i.R)(b,"mergeCells")&&!0===b.mergeCells&&null!==v&&(0,i.R)(v,"mergeCells")&&(b.mergeCells=v.mergeCells),g=new Handsontable(document.getElementById(a),b),window.addEventListener("load",(()=>{g.render(),C(_()),m.find("td, th").attr(o),window.dispatchEvent(new Event("resize"))}))}window.initTable=r,window.telepath.register("wagtail.widgets.TableInput",class{constructor(e,t){this.options=e,this.strings=t}render(e,t,a,n){const i=document.createElement("div");i.innerHTML=`\n <div class="w-field__wrapper" data-field-wrapper>\n <label class="w-field__label" for="${a}-table-header-choice">${this.strings["Table headers"]}</label>\n <select id="${a}-table-header-choice" name="table-header-choice">\n <option value="">Select a header option</option>\n <option value="row">\n ${this.strings["Display the first row as a header"]}\n </option>\n <option value="column">\n ${this.strings["Display the first column as a header"]}\n </option>\n <option value="both">\n ${this.strings["Display the first row AND first column as headers"]}\n </option>\n <option value="neither">\n ${this.strings["No headers"]}\n </option>\n </select>\n <p class="help">${this.strings["Which cells should be displayed as headers?"]}</p>\n </div>\n <div class="w-field__wrapper" data-field-wrapper>\n <label class="w-field__label" for="${a}-handsontable-col-caption">${this.strings["Table caption"]}</label>\n <div class="w-field w-field--char_field w-field--text_input" data-field>\n <div class="w-field__help" id="${a}-handsontable-col-caption-helptext" data-field-help>\n <div class="help">${this.strings["A heading that identifies the overall topic of the table, and is useful for screen reader users."]}</div>\n </div>\n <div class="w-field__input" data-field-input>\n <input type="text" id="${a}-handsontable-col-caption" name="handsontable-col-caption" aria-describedby="${a}-handsontable-col-caption-helptext" />\n </div>\n </div>\n </div>\n <div id="${a}-handsontable-container"></div>\n <input type="hidden" name="${t}" id="${a}" placeholder="${this.strings.Table}">\n `,l()((()=>{const e=document.getElementById(`${a}-handsontable-container`);l()(e).find("td, th").attr(o)})),e.replaceWith(i);const s=i.querySelector(`input[name="${t}"]`),d=this.options,c={getValue:()=>JSON.parse(s.value),getState:()=>JSON.parse(s.value),setState(e){s.value=JSON.stringify(e),r(a,d)},focus(){}};return c.setState(n),c}})},5311:e=>{e.exports=jQuery}},a={};function n(e){var l=a[e];if(void 0!==l)return l.exports;var i=a[e]={id:e,loaded:!1,exports:{}};return t[e].call(i.exports,i,i.exports,n),i.loaded=!0,i.exports}n.m=t,e=[],n.O=(t,a,l,i)=>{if(!a){var o=1/0;for(c=0;c<e.length;c++){for(var[a,l,i]=e[c],r=!0,s=0;s<a.length;s++)(!1&i||o>=i)&&Object.keys(n.O).every((e=>n.O[e](a[s])))?a.splice(s--,1):(r=!1,i<o&&(o=i));if(r){e.splice(c--,1);var d=l();void 0!==d&&(t=d)}}return t}i=i||0;for(var c=e.length;c>0&&e[c-1][2]>i;c--)e[c]=e[c-1];e[c]=[a,l,i]},n.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},n.d=(e,t)=>{for(var a in t)n.o(t,a)&&!n.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),n.j=986,(()=>{var e={986:0};n.O.j=t=>0===e[t];var t=(t,a)=>{var l,i,[o,r,s]=a,d=0;if(o.some((t=>0!==e[t]))){for(l in r)n.o(r,l)&&(n.m[l]=r[l]);if(s)var c=s(n)}for(t&&t(a);d<o.length;d++)i=o[d],n.o(e,i)&&e[i]&&e[i][0](),e[i]=0;return n.O(c)},a=globalThis.webpackChunkwagtail=globalThis.webpackChunkwagtail||[];a.forEach(t.bind(null,0)),a.push=t.bind(null,a.push.bind(a))})();var l=n.O(void 0,[751],(()=>n(2618)));l=n.O(l)})();
1
+ (()=>{"use strict";var e,t={6865:(e,t,a)=>{var n=a(1669),l=a.n(n),i=a(4188);const o={contenteditable:"true","plaintext-only":"true",tabindex:"0"};function r(e,t){const a=e+"-handsontable-container";var n=e+"-handsontable-header",r=e+"-handsontable-col-header",s=e+"-table-header-choice";const d=e+"-handsontable-col-caption",c=l()("#"+e);var h=l()("#"+n),p=l()("#"+r),f=l()("#"+s);const u=l()("#"+d),b={};let g=null,v=null,w=!1;const m=l()("#"+e).parent(),_=function(){let e=0;return m.find(".htCore").each((function(){e+=l()(this).height()})),e+m.find("[data-field]").first().height()},y=[`#${a}`,".wtHider",".wtHolder"],$=function(t){const a=l()("#"+e);l().each(y,(function(){a.closest("[data-field]").find(this).height(t)}))};try{v=JSON.parse(c.val())}catch(e){}null!==v&&((0,i.$)(v,"table_caption")&&u.prop("value",v.table_caption),(0,i.$)(v,"table_header_choice")&&f.prop("value",v.table_header_choice)),(0,i.$)(t,"width")&&(0,i.$)(t,"height")||l()(window).on("resize",(()=>{var e;g.updateSettings({width:l()(".w-field--table_input").closest(".w-panel").width(),height:_()}),e="100%",l().each(y,(function(){l()(this).width(e)})),l()(".w-field--table_input").width(e)}));const C=function(){const e=[],t=[];g.getCellsMeta().forEach((t=>{let a,n;(0,i.$)(t,"className")&&(a=t.className),(0,i.$)(t,"hidden")&&(n=!0),(void 0!==a||n)&&e.push({row:t.row,col:t.col,className:a,hidden:n})})),g.getPlugin("mergeCells").isEnabled()&&g.getPlugin("mergeCells").mergedCellsCollection.mergedCells.forEach((e=>{t.push({row:e.row,col:e.col,rowspan:e.rowspan,colspan:e.colspan})})),c.val(JSON.stringify({data:g.getData(),cell:e,mergeCells:t,first_row_is_table_header:h.val(),first_col_is_header:p.val(),table_header_choice:f.val(),table_caption:u.val()}))},O=function(e,t){$(_()),C(),l()((()=>{l()(m).find("td, th").attr(o)}))};f.on("change",(()=>{C()})),u.on("change",(()=>{C()}));const S={afterChange:function(e,t){w&&"loadData"!==t&&"MergeCells"!==t&&C()},afterCreateCol:O,afterCreateRow:O,afterRemoveCol:O,afterRemoveRow:O,afterSetCellMeta:function(e,t,a,n){w&&"className"===a&&C()},afterMergeCells:function(e,t,a){w&&C()},afterUnmergeCells:function(e,t){w&&C()},afterInit:function(){w=!0}};null!==v&&((0,i.$)(v,"data")&&(S.data=v.data),(0,i.$)(v,"cell")&&(S.cell=v.cell)),Object.keys(S).forEach((e=>{b[e]=S[e]})),Object.keys(t).forEach((e=>{b[e]=t[e]})),(0,i.$)(b,"mergeCells")&&!0===b.mergeCells&&null!==v&&(0,i.$)(v,"mergeCells")&&(b.mergeCells=v.mergeCells),g=new Handsontable(document.getElementById(a),b),window.addEventListener("load",(()=>{g.render(),$(_()),m.find("td, th").attr(o),window.dispatchEvent(new Event("resize"))}))}window.initTable=r,window.telepath.register("wagtail.widgets.TableInput",class{constructor(e,t){this.options=e,this.strings=t}render(e,t,a,n){const i=document.createElement("div");i.innerHTML=`\n <div class="w-field__wrapper" data-field-wrapper>\n <label class="w-field__label" for="${a}-table-header-choice">${this.strings["Table headers"]}</label>\n <select id="${a}-table-header-choice" name="table-header-choice">\n <option value="">Select a header option</option>\n <option value="row">\n ${this.strings["Display the first row as a header"]}\n </option>\n <option value="column">\n ${this.strings["Display the first column as a header"]}\n </option>\n <option value="both">\n ${this.strings["Display the first row AND first column as headers"]}\n </option>\n <option value="neither">\n ${this.strings["No headers"]}\n </option>\n </select>\n <p class="help">${this.strings["Which cells should be displayed as headers?"]}</p>\n </div>\n <div class="w-field__wrapper" data-field-wrapper>\n <label class="w-field__label" for="${a}-handsontable-col-caption">${this.strings["Table caption"]}</label>\n <div class="w-field w-field--char_field w-field--text_input" data-field>\n <div class="w-field__help" id="${a}-handsontable-col-caption-helptext" data-field-help>\n <div class="help">${this.strings["A heading that identifies the overall topic of the table, and is useful for screen reader users."]}</div>\n </div>\n <div class="w-field__input" data-field-input>\n <input type="text" id="${a}-handsontable-col-caption" name="handsontable-col-caption" aria-describedby="${a}-handsontable-col-caption-helptext" />\n </div>\n </div>\n </div>\n <div id="${a}-handsontable-container"></div>\n <input type="hidden" name="${t}" id="${a}" placeholder="${this.strings.Table}">\n `,l()((()=>{const e=document.getElementById(`${a}-handsontable-container`);l()(e).find("td, th").attr(o)})),e.replaceWith(i);const s=i.querySelector(`input[name="${t}"]`),d=this.options,c={getValue:()=>JSON.parse(s.value),getState:()=>JSON.parse(s.value),setState(e){s.value=JSON.stringify(e),r(a,d)},focus(){}};return c.setState(n),c}})},1669:e=>{e.exports=jQuery}},a={};function n(e){var l=a[e];if(void 0!==l)return l.exports;var i=a[e]={id:e,loaded:!1,exports:{}};return t[e].call(i.exports,i,i.exports,n),i.loaded=!0,i.exports}n.m=t,e=[],n.O=(t,a,l,i)=>{if(!a){var o=1/0;for(c=0;c<e.length;c++){for(var[a,l,i]=e[c],r=!0,s=0;s<a.length;s++)(!1&i||o>=i)&&Object.keys(n.O).every((e=>n.O[e](a[s])))?a.splice(s--,1):(r=!1,i<o&&(o=i));if(r){e.splice(c--,1);var d=l();void 0!==d&&(t=d)}}return t}i=i||0;for(var c=e.length;c>0&&e[c-1][2]>i;c--)e[c]=e[c-1];e[c]=[a,l,i]},n.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},n.d=(e,t)=>{for(var a in t)n.o(t,a)&&!n.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),n.j=853,(()=>{var e={853:0};n.O.j=t=>0===e[t];var t=(t,a)=>{var l,i,[o,r,s]=a,d=0;if(o.some((t=>0!==e[t]))){for(l in r)n.o(r,l)&&(n.m[l]=r[l]);if(s)var c=s(n)}for(t&&t(a);d<o.length;d++)i=o[d],n.o(e,i)&&e[i]&&e[i][0](),e[i]=0;return n.O(c)},a=globalThis.webpackChunkwagtail=globalThis.webpackChunkwagtail||[];a.forEach(t.bind(null,0)),a.push=t.bind(null,a.push.bind(a))})();var l=n.O(void 0,[321],(()=>n(6865)));l=n.O(l)})();