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
@@ -11,6 +11,7 @@ from wagtail.coreutils import get_dummy_request
11
11
  from wagtail.models import PAGE_TEMPLATE_VAR, Page, Site
12
12
  from wagtail.test.testapp.models import BusinessChild, BusinessIndex, SimplePage
13
13
  from wagtail.test.utils import WagtailTestUtils
14
+ from wagtail.utils.deprecation import RemovedInWagtail70Warning
14
15
 
15
16
 
16
17
  class TestUserbarTag(WagtailTestUtils, TestCase):
@@ -184,7 +185,7 @@ class TestAccessibilityCheckerConfig(WagtailTestUtils, TestCase):
184
185
  return json.loads(self.get_script().string)
185
186
 
186
187
  def get_hook(self, item_class):
187
- def customise_accessibility_checker(request, items):
188
+ def customise_accessibility_checker(request, items, page):
188
189
  items[:] = [
189
190
  item_class() if isinstance(item, AccessibilityItem) else item
190
191
  for item in items
@@ -455,6 +456,86 @@ class TestUserbarInPageServe(WagtailTestUtils, TestCase):
455
456
  self.assertEqual(response.status_code, 200)
456
457
  self.assertNotContains(response, '<template id="wagtail-userbar-template">')
457
458
 
459
+ def test_construct_wagtail_userbar_hook_passes_page(self):
460
+ kwargs = {}
461
+
462
+ def construct_wagtail_userbar(request, items, page):
463
+ kwargs["page"] = page
464
+ return items
465
+
466
+ with hooks.register_temporarily(
467
+ "construct_wagtail_userbar",
468
+ construct_wagtail_userbar,
469
+ ):
470
+ response = self.page.serve(self.request)
471
+ response.render()
472
+
473
+ self.assertEqual(kwargs.get("page"), self.page)
474
+
475
+ def test_deprecated_construct_wagtail_userbar_hook_without_page(self):
476
+ kwargs = {}
477
+
478
+ def construct_wagtail_userbar(request, items):
479
+ kwargs["called"] = True
480
+ return items
481
+
482
+ with self.assertWarnsMessage(
483
+ RemovedInWagtail70Warning,
484
+ "`construct_wagtail_userbar` hook functions should accept a `page` argument in third position",
485
+ ), hooks.register_temporarily(
486
+ "construct_wagtail_userbar",
487
+ construct_wagtail_userbar,
488
+ ):
489
+ response = self.page.serve(self.request)
490
+ response.render()
491
+
492
+ self.assertTrue(kwargs.get("called"))
493
+
494
+
495
+ class TestUserbarHooksForChecksPanel(WagtailTestUtils, TestCase):
496
+ def setUp(self):
497
+ self.user = self.login()
498
+ self.homepage = Page.objects.get(id=2).specific
499
+
500
+ def test_construct_wagtail_userbar_hook_passes_page(self):
501
+ kwargs = {}
502
+
503
+ def construct_wagtail_userbar(request, items, page):
504
+ kwargs["called"] = True
505
+ return items
506
+
507
+ with hooks.register_temporarily(
508
+ "construct_wagtail_userbar",
509
+ construct_wagtail_userbar,
510
+ ):
511
+ response = self.client.get(
512
+ reverse("wagtailadmin_pages:edit", args=(self.homepage.id,))
513
+ )
514
+
515
+ self.assertEqual(response.status_code, 200)
516
+ self.assertTrue(kwargs.get("called"))
517
+
518
+ def test_deprecated_construct_wagtail_userbar_hook_without_page(self):
519
+ kwargs = {}
520
+
521
+ def construct_wagtail_userbar(request, items):
522
+ kwargs["called"] = True
523
+ return items
524
+
525
+ with self.assertWarnsMessage(
526
+ RemovedInWagtail70Warning,
527
+ "`construct_wagtail_userbar` hook functions should accept a `page` argument in third position",
528
+ ), hooks.register_temporarily(
529
+ "construct_wagtail_userbar",
530
+ construct_wagtail_userbar,
531
+ ):
532
+ response = self.client.get(
533
+ reverse("wagtailadmin_pages:edit", args=(self.homepage.id,))
534
+ )
535
+
536
+ self.assertEqual(response.status_code, 200)
537
+ self.assertTrue(kwargs.get("called"))
538
+
458
539
 
459
540
  class TestUserbarAddLink(WagtailTestUtils, TestCase):
460
541
  fixtures = ["test.json"]
@@ -15,7 +15,7 @@ class TestGenericIndexView(WagtailTestUtils, TestCase):
15
15
  response = self.get()
16
16
  self.assertEqual(response.status_code, 200)
17
17
  response_object_count = response.context_data["object_list"].count()
18
- self.assertEqual(response_object_count, 3)
18
+ self.assertEqual(response_object_count, 4)
19
19
  self.assertContains(response, "first modelwithstringtypeprimarykey model")
20
20
  self.assertContains(response, "second modelwithstringtypeprimarykey model")
21
21
  soup = self.get_soup(response.content)
@@ -34,7 +34,7 @@ class TestGenericIndexViewWithoutModel(WagtailTestUtils, TestCase):
34
34
  response = self.get()
35
35
  self.assertEqual(response.status_code, 200)
36
36
  response_object_count = response.context_data["object_list"].count()
37
- self.assertEqual(response_object_count, 3)
37
+ self.assertEqual(response_object_count, 4)
38
38
 
39
39
 
40
40
  class TestGenericEditView(WagtailTestUtils, TestCase):
@@ -58,19 +58,29 @@ class TestGenericEditView(WagtailTestUtils, TestCase):
58
58
  response, "non-url-safe pk modelwithstringtypeprimarykey model"
59
59
  )
60
60
 
61
- def test_using_quote_in_edit_url(self):
62
- object_pk = 'string-pk-:#?;@&=+$,"[]<>%'
61
+ def test_unquote_sensitive_primary_key(self):
62
+ object_pk = "web_407269_1"
63
63
  response = self.get(quote(object_pk))
64
- edit_url = response.context_data["action_url"]
65
- edit_url_pk = edit_url.split("/")[-2]
66
- self.assertEqual(edit_url_pk, quote(object_pk))
64
+ self.assertEqual(response.status_code, 200)
65
+ self.assertContains(
66
+ response, "unquote-sensitive modelwithstringtypeprimarykey model"
67
+ )
68
+
69
+ def test_using_quote_in_edit_url(self):
70
+ for object_pk in ('string-pk-:#?;@&=+$,"[]<>%', "web_407269_1"):
71
+ with self.subTest(object_pk=object_pk):
72
+ response = self.get(quote(object_pk))
73
+ edit_url = response.context_data["action_url"]
74
+ edit_url_pk = edit_url.split("/")[-2]
75
+ self.assertEqual(edit_url_pk, quote(object_pk))
67
76
 
68
77
  def test_using_quote_in_delete_url(self):
69
- object_pk = 'string-pk-:#?;@&=+$,"[]<>%'
70
- response = self.get(quote(object_pk))
71
- delete_url = response.context_data["delete_url"]
72
- delete_url_pk = delete_url.split("/")[-2]
73
- self.assertEqual(delete_url_pk, quote(object_pk))
78
+ for object_pk in ('string-pk-:#?;@&=+$,"[]<>%', "web_407269_1"):
79
+ with self.subTest(object_pk=object_pk):
80
+ response = self.get(quote(object_pk))
81
+ delete_url = response.context_data["delete_url"]
82
+ delete_url_pk = delete_url.split("/")[-2]
83
+ self.assertEqual(delete_url_pk, quote(object_pk))
74
84
 
75
85
 
76
86
  class TestGenericDeleteView(WagtailTestUtils, TestCase):
@@ -89,3 +99,8 @@ class TestGenericDeleteView(WagtailTestUtils, TestCase):
89
99
  object_pk = 'string-pk-:#?;@&=+$,"[]<>%'
90
100
  response = self.get(quote(object_pk))
91
101
  self.assertEqual(response.status_code, 200)
102
+
103
+ def test_with_unquote_sensitive_primary_key(self):
104
+ object_pk = "web_407269_1"
105
+ response = self.get(quote(object_pk))
106
+ self.assertEqual(response.status_code, 200)
@@ -431,6 +431,40 @@ class TestWorkflowsCreateView(AdminTemplateTestUtils, WagtailTestUtils, TestCase
431
431
  response.content,
432
432
  )
433
433
 
434
+ # Check the correct data attributes have been set on the form
435
+ soup = self.get_soup(response.content)
436
+ workflow_pages_panel = soup.find(id="workflow-pages-section")
437
+ self.assertIn(
438
+ "w-formset",
439
+ workflow_pages_panel.attrs["data-controller"],
440
+ )
441
+ self.assertEqual(
442
+ "totalFormsInput",
443
+ workflow_pages_panel.find(id="id_pages-TOTAL_FORMS").attrs[
444
+ "data-w-formset-target"
445
+ ],
446
+ )
447
+ self.assertEqual(
448
+ "template",
449
+ workflow_pages_panel.find("template").attrs["data-w-formset-target"],
450
+ )
451
+
452
+ tbody = workflow_pages_panel.find("table").find("tbody")
453
+ self.assertEqual(
454
+ "forms",
455
+ tbody.attrs["data-w-formset-target"],
456
+ )
457
+
458
+ row = tbody.find("tr")
459
+ self.assertEqual(
460
+ "child",
461
+ row.attrs["data-w-formset-target"],
462
+ )
463
+ self.assertEqual(
464
+ "deleteInput",
465
+ row.find(id="id_pages-0-DELETE").attrs["data-w-formset-target"],
466
+ )
467
+
434
468
  def test_post(self):
435
469
  response = self.post(
436
470
  {
@@ -663,6 +697,41 @@ class TestWorkflowsEditView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
663
697
  # Check that the list of pages has the page to which this workflow is assigned
664
698
  self.assertContains(response, self.page.title)
665
699
 
700
+ # Check the correct data attributes have been set on the form
701
+ soup = self.get_soup(response.content)
702
+
703
+ workflow_pages_panel = soup.find(id="workflow-pages-section")
704
+ self.assertIn(
705
+ "w-formset",
706
+ workflow_pages_panel.attrs["data-controller"],
707
+ )
708
+ self.assertEqual(
709
+ "totalFormsInput",
710
+ workflow_pages_panel.find(id="id_pages-TOTAL_FORMS").attrs[
711
+ "data-w-formset-target"
712
+ ],
713
+ )
714
+ self.assertEqual(
715
+ "template",
716
+ workflow_pages_panel.find("template").attrs["data-w-formset-target"],
717
+ )
718
+
719
+ tbody = workflow_pages_panel.find("table").find("tbody")
720
+ self.assertEqual(
721
+ "forms",
722
+ tbody.attrs["data-w-formset-target"],
723
+ )
724
+
725
+ row = tbody.find("tr")
726
+ self.assertEqual(
727
+ "child",
728
+ row.attrs["data-w-formset-target"],
729
+ )
730
+ self.assertEqual(
731
+ "deleteInput",
732
+ row.find(id="id_pages-0-DELETE").attrs["data-w-formset-target"],
733
+ )
734
+
666
735
  def test_post(self):
667
736
  response = self.post(
668
737
  {
@@ -374,6 +374,26 @@ class TestTagsAutocomplete(WagtailTestUtils, TestCase):
374
374
 
375
375
  self.assertEqual(data, [])
376
376
 
377
+ def test_tags_autocomplete_limit(self):
378
+ tags = [Tag(name=f"Tag {i}", slug=f"tag-{i}") for i in range(15)]
379
+ Tag.objects.bulk_create(tags)
380
+
381
+ # Send a request to the autocomplete endpoint with a broad search term
382
+ response = self.client.get(
383
+ reverse("wagtailadmin_tag_autocomplete"), {"term": "Tag"}
384
+ )
385
+
386
+ # Confirm the response is successful
387
+ self.assertEqual(response.status_code, 200)
388
+ self.assertEqual(response["Content-Type"], "application/json")
389
+
390
+ data = json.loads(response.content.decode("utf-8"))
391
+
392
+ # The results should be limited to avoid performance issues (#12415)
393
+ self.assertEqual(len(data), 10)
394
+ sorted_tags = sorted(tags, key=lambda t: t.name)
395
+ self.assertEqual(data, [tag.name for tag in sorted_tags[:10]])
396
+
377
397
 
378
398
  class TestMenuItem(WagtailTestUtils, TestCase):
379
399
  def setUp(self):
@@ -381,10 +401,6 @@ class TestMenuItem(WagtailTestUtils, TestCase):
381
401
  response = self.client.get(reverse("wagtailadmin_home"))
382
402
  self.request = response.wsgi_request
383
403
 
384
- def test_menuitem_reverse_lazy_url_pass(self):
385
- menuitem = MenuItem(_("Test"), reverse_lazy("wagtailadmin_home"))
386
- self.assertIs(menuitem.is_active(self.request), True)
387
-
388
404
  def test_menuitem_with_classname(self):
389
405
  menuitem = MenuItem(
390
406
  _("Test"),
@@ -492,6 +508,9 @@ class Test404(WagtailTestUtils, TestCase):
492
508
  # Check 404 error after CommonMiddleware redirect
493
509
  self.assertEqual(response.status_code, 404)
494
510
  self.assertTemplateUsed(response, "wagtailadmin/404.html")
511
+ soup = self.get_soup(response.content)
512
+ self.assertFalse(soup.select("script"))
513
+ self.assertFalse(soup.select("[data-sprite]"))
495
514
 
496
515
  def test_not_logged_in_redirect(self):
497
516
  response = self.client.get("/admin/sdfgdsfgdsfgsdf/")
@@ -283,7 +283,7 @@ class TestAdaptMainMenuModule(WagtailTestUtils, DjangoTestCase):
283
283
  ],
284
284
  {
285
285
  "name": user.first_name or user.get_username(),
286
- "avatarUrl": "//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=100&d=mm",
286
+ "avatarUrl": "//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?d=mp&s=100",
287
287
  },
288
288
  ],
289
289
  },
@@ -910,10 +910,11 @@ class TestBreadcrumbs(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
910
910
  self.assertBreadcrumbsItemsRendered(items, response.content)
911
911
 
912
912
  def test_usage_view_pagination(self):
913
- for i in range(25):
914
- VariousOnDeleteModel.objects.create(
915
- text=f"Toybox {i}", cascading_toy=self.object
916
- )
913
+ with self.captureOnCommitCallbacks(execute=True):
914
+ for i in range(25):
915
+ VariousOnDeleteModel.objects.create(
916
+ text=f"Toybox {i}", cascading_toy=self.object
917
+ )
917
918
 
918
919
  usage_url = reverse(
919
920
  "feature_complete_toy:usage",
@@ -1208,15 +1209,16 @@ class TestHistoryView(WagtailTestUtils, TestCase):
1208
1209
  class TestUsageView(WagtailTestUtils, TestCase):
1209
1210
  @classmethod
1210
1211
  def setUpTestData(cls):
1211
- cls.user = cls.create_test_user()
1212
- cls.object = FeatureCompleteToy.objects.create(name="Buzz")
1213
- cls.url = reverse(
1214
- "feature_complete_toy:usage",
1215
- args=(quote(cls.object.pk),),
1216
- )
1217
- cls.tbx = VariousOnDeleteModel.objects.create(
1218
- text="Toybox", cascading_toy=cls.object
1219
- )
1212
+ with cls.captureOnCommitCallbacks(execute=True):
1213
+ cls.user = cls.create_test_user()
1214
+ cls.object = FeatureCompleteToy.objects.create(name="Buzz")
1215
+ cls.url = reverse(
1216
+ "feature_complete_toy:usage",
1217
+ args=(quote(cls.object.pk),),
1218
+ )
1219
+ cls.tbx = VariousOnDeleteModel.objects.create(
1220
+ text="Toybox", cascading_toy=cls.object
1221
+ )
1220
1222
 
1221
1223
  def setUp(self):
1222
1224
  self.user = self.login(self.user)
@@ -5,9 +5,8 @@ from django.urls import reverse
5
5
  from django.utils.text import capfirst
6
6
  from django.utils.translation import gettext_lazy, ngettext
7
7
 
8
- from wagtail import hooks
9
8
  from wagtail.admin.ui.components import Component
10
- from wagtail.admin.userbar import AccessibilityItem
9
+ from wagtail.admin.userbar import AccessibilityItem, apply_userbar_hooks
11
10
  from wagtail.models import DraftStateMixin, LockableMixin, Page, ReferenceIndex
12
11
  from wagtail.utils.deprecation import RemovedInWagtail70Warning
13
12
 
@@ -241,6 +240,7 @@ class StatusSidePanel(BaseSidePanel):
241
240
 
242
241
  class PageStatusSidePanel(StatusSidePanel):
243
242
  def __init__(self, *args, **kwargs):
243
+ self.parent_page = kwargs.pop("parent_page", None)
244
244
  super().__init__(*args, **kwargs)
245
245
  if self.object.pk:
246
246
  self.usage_url = reverse("wagtailadmin_pages:usage", args=(self.object.pk,))
@@ -272,6 +272,9 @@ class PageStatusSidePanel(StatusSidePanel):
272
272
  context = super().get_context_data(parent_context)
273
273
  page = self.object
274
274
 
275
+ if self.parent_page:
276
+ context["parent_page"] = self.parent_page
277
+
275
278
  if page.id:
276
279
  context.update(
277
280
  {
@@ -325,8 +328,8 @@ class ChecksSidePanel(BaseSidePanel):
325
328
  def get_axe_configuration(self):
326
329
  # Retrieve the Axe configuration from the userbar.
327
330
  userbar_items = [AccessibilityItem()]
328
- for fn in hooks.get_hooks("construct_wagtail_userbar"):
329
- fn(self.request, userbar_items)
331
+ page = self.object if issubclass(self.model, Page) else None
332
+ apply_userbar_hooks(self.request, userbar_items, page)
330
333
 
331
334
  for item in userbar_items:
332
335
  if isinstance(item, AccessibilityItem):
@@ -19,6 +19,7 @@ from wagtail.admin.urls import reports as wagtailadmin_reports_urls
19
19
  from wagtail.admin.urls import workflows as wagtailadmin_workflows_urls
20
20
  from wagtail.admin.views import account, chooser, dismissibles, home, tags
21
21
  from wagtail.admin.views.bulk_action import index as bulk_actions
22
+ from wagtail.admin.views.generic.preview import StreamFieldBlockPreview
22
23
  from wagtail.admin.views.pages import listing
23
24
  from wagtail.utils.urlpatterns import decorate_urlpatterns
24
25
 
@@ -118,6 +119,11 @@ urlpatterns = [
118
119
  namespace="wagtailadmin_editing_sessions",
119
120
  ),
120
121
  ),
122
+ path(
123
+ "block-preview/",
124
+ StreamFieldBlockPreview.as_view(),
125
+ name="wagtailadmin_block_preview",
126
+ ),
121
127
  ]
122
128
 
123
129
 
@@ -106,7 +106,7 @@ urlpatterns = [
106
106
  ),
107
107
  path(
108
108
  "<int:page_id>/revisions/<int:revision_id>/revert/",
109
- revisions.revisions_revert,
109
+ revisions.RevisionsRevertView.as_view(),
110
110
  name="revisions_revert",
111
111
  ),
112
112
  path(
wagtail/admin/userbar.py CHANGED
@@ -1,6 +1,12 @@
1
+ from warnings import warn
2
+
1
3
  from django.template.loader import render_to_string
2
4
  from django.utils.translation import gettext_lazy as _
3
5
 
6
+ from wagtail import hooks
7
+ from wagtail.coreutils import accepts_kwarg
8
+ from wagtail.utils.deprecation import RemovedInWagtail70Warning
9
+
4
10
 
5
11
  class BaseItem:
6
12
  template = "wagtailadmin/userbar/item_base.html"
@@ -55,6 +61,7 @@ class AccessibilityItem(BaseItem):
55
61
  "input-button-name",
56
62
  "link-name",
57
63
  "p-as-heading",
64
+ "alt-text-quality",
58
65
  ]
59
66
 
60
67
  #: A dictionary that maps axe-core rule IDs to a dictionary of rule options,
@@ -257,7 +264,7 @@ class ExplorePageItem(BaseItem):
257
264
  if not request.user.has_perm("wagtailadmin.access_admin"):
258
265
  return ""
259
266
 
260
- # Don't render if user doesn't have ability to edit or publish sub-pages on the parent page
267
+ # Don't render if user doesn't have ability to edit or publish subpages on the parent page
261
268
  permission_checker = self.parent_page.permissions_for_user(request.user)
262
269
  if (
263
270
  not permission_checker.can_edit()
@@ -297,3 +304,16 @@ class EditPageItem(BaseItem):
297
304
  return ""
298
305
 
299
306
  return super().render(request)
307
+
308
+
309
+ def apply_userbar_hooks(request, items, page):
310
+ for fn in hooks.get_hooks("construct_wagtail_userbar"):
311
+ if accepts_kwarg(fn, "page"):
312
+ fn(request, items, page)
313
+ else:
314
+ warn(
315
+ "`construct_wagtail_userbar` hook functions should accept a `page` argument in third position -"
316
+ f" {fn.__module__}.{fn.__name__} needs to be updated",
317
+ category=RemovedInWagtail70Warning,
318
+ )
319
+ fn(request, items)
@@ -232,6 +232,11 @@ class AccountView(WagtailAdminTemplateMixin, TemplateView):
232
232
  page_title = gettext_lazy("Account")
233
233
  header_icon = "user"
234
234
 
235
+ def get_breadcrumbs_items(self):
236
+ return super().get_breadcrumbs_items() + [
237
+ {"url": "", "label": self.get_page_title()}
238
+ ]
239
+
235
240
  def get_context_data(self, **kwargs):
236
241
  context = super().get_context_data(**kwargs)
237
242
  panels = self.get_panels()
@@ -408,7 +408,11 @@ class SearchView(View):
408
408
  @property
409
409
  def columns(self):
410
410
  cols = [
411
- PageTitleColumn("title", label=_("Title")),
411
+ PageTitleColumn(
412
+ "title",
413
+ label=_("Title"),
414
+ is_multiple_choice=self.is_multiple_choice,
415
+ ),
412
416
  ParentPageColumn("parent", label=_("Parent")),
413
417
  DateColumn(
414
418
  "updated",
@@ -50,7 +50,6 @@ class Create(CreateView):
50
50
  edit_url_name = "wagtailadmin_collections:edit"
51
51
  index_url_name = "wagtailadmin_collections:index"
52
52
  header_icon = "folder-open-1"
53
- _show_breadcrumbs = True
54
53
 
55
54
  def get_form(self, form_class=None):
56
55
  form = super().get_form(form_class)
@@ -80,7 +79,6 @@ class Edit(EditView):
80
79
  delete_url_name = "wagtailadmin_collections:delete"
81
80
  context_object_name = "collection"
82
81
  header_icon = "folder-open-1"
83
- _show_breadcrumbs = True
84
82
 
85
83
  def _user_may_move_collection(self, user, instance):
86
84
  """
@@ -41,9 +41,17 @@ class WagtailAdminTemplateMixin(TemplateResponseMixin, ContextMixin):
41
41
  page_title = ""
42
42
  page_subtitle = ""
43
43
  header_icon = ""
44
- # Breadcrumbs are opt-in until we have a design that can be consistently applied
45
- _show_breadcrumbs = False
44
+
46
45
  breadcrumbs_items = [{"url": reverse_lazy("wagtailadmin_home"), "label": _("Home")}]
46
+ """
47
+ The base set of breadcrumbs items to be displayed on the page.
48
+ The property can be overridden by subclasses and viewsets to provide
49
+ custom base items, e.g. page tree breadcrumbs or add the "Snippets" item.
50
+
51
+ Views should copy and append to this list in :meth:`get_breadcrumbs_items()`
52
+ to define the path to the current view.
53
+ """
54
+
47
55
  template_name = "wagtailadmin/generic/base.html"
48
56
  header_buttons = []
49
57
  header_more_buttons = []
@@ -65,6 +73,12 @@ class WagtailAdminTemplateMixin(TemplateResponseMixin, ContextMixin):
65
73
  return self.header_icon
66
74
 
67
75
  def get_breadcrumbs_items(self):
76
+ """
77
+ Define the current path to the view by copying the base
78
+ :attr:`breadcrumbs_items` and appending the list.
79
+
80
+ If breadcrumbs are not required, return an empty list.
81
+ """
68
82
  return self.breadcrumbs_items
69
83
 
70
84
  def get_header_buttons(self):
@@ -92,14 +106,11 @@ class WagtailAdminTemplateMixin(TemplateResponseMixin, ContextMixin):
92
106
  context["page_title"] = self.get_page_title()
93
107
  context["page_subtitle"] = self.get_page_subtitle()
94
108
  context["header_icon"] = self.get_header_icon()
95
-
96
- # Once all appropriate views use "wagtailadmin/generic/base.html" and
97
- # the slim_header.html, _show_breadcrumbs can be removed
98
109
  context["header_title"] = self.get_header_title()
99
- context["breadcrumbs_items"] = None
100
- if self._show_breadcrumbs:
101
- context["breadcrumbs_items"] = self.get_breadcrumbs_items()
102
- context["header_buttons"] = self.get_header_buttons()
110
+
111
+ # Breadcrumbs are enabled by default.
112
+ context["breadcrumbs_items"] = self.get_breadcrumbs_items()
113
+ context["header_buttons"] = self.get_header_buttons()
103
114
  return context
104
115
 
105
116
  def get_template_names(self):
@@ -203,7 +214,6 @@ class BaseListingView(WagtailAdminTemplateMixin, BaseListView):
203
214
  default_ordering = None
204
215
  filterset_class = None
205
216
  verbose_name_plural = None
206
- _show_breadcrumbs = True
207
217
 
208
218
  def get_template_names(self):
209
219
  if self.results_only:
@@ -415,7 +415,6 @@ class WorkflowHistoryDetailView(
415
415
  page_title = gettext_lazy("Workflow progress")
416
416
  header_icon = "list-ul"
417
417
  object_icon = "doc-empty-inverse"
418
- _show_breadcrumbs = True
419
418
 
420
419
  @cached_property
421
420
  def index_url(self):