wagtail 6.1.3__py3-none-any.whl → 6.2rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. wagtail/__init__.py +1 -1
  2. wagtail/actions/copy_for_translation.py +15 -1
  3. wagtail/admin/checks.py +20 -30
  4. wagtail/admin/forms/pages.py +10 -0
  5. wagtail/admin/icons.py +43 -0
  6. wagtail/admin/locale/en/LC_MESSAGES/django.po +405 -295
  7. wagtail/admin/locale/en/LC_MESSAGES/djangojs.po +21 -3
  8. wagtail/admin/locale/sl/LC_MESSAGES/django.mo +0 -0
  9. wagtail/admin/locale/sl/LC_MESSAGES/django.po +30 -0
  10. wagtail/admin/menu.py +2 -2
  11. wagtail/admin/migrations/0004_editingsession.py +57 -0
  12. wagtail/admin/migrations/0005_editingsession_is_editing.py +18 -0
  13. wagtail/admin/models.py +36 -3
  14. wagtail/admin/rich_text/editors/draftail/__init__.py +2 -20
  15. wagtail/admin/static/wagtailadmin/css/core.css +1 -1
  16. wagtail/admin/static/wagtailadmin/js/bulk-actions.js +1 -1
  17. wagtail/admin/static/wagtailadmin/js/chooser-modal.js +1 -1
  18. wagtail/admin/static/wagtailadmin/js/chooser-widget-telepath.js +1 -1
  19. wagtail/admin/static/wagtailadmin/js/chooser-widget.js +1 -1
  20. wagtail/admin/static/wagtailadmin/js/comments.js +1 -1
  21. wagtail/admin/static/wagtailadmin/js/core.js +1 -1
  22. wagtail/admin/static/wagtailadmin/js/date-time-chooser.js +1 -1
  23. wagtail/admin/static/wagtailadmin/js/draftail.js +1 -1
  24. wagtail/admin/static/wagtailadmin/js/expanding-formset.js +1 -1
  25. wagtail/admin/static/wagtailadmin/js/filtered-select.js +1 -1
  26. wagtail/admin/static/wagtailadmin/js/modal-workflow.js +1 -1
  27. wagtail/admin/static/wagtailadmin/js/page-chooser-modal.js +1 -1
  28. wagtail/admin/static/wagtailadmin/js/page-chooser-telepath.js +1 -1
  29. wagtail/admin/static/wagtailadmin/js/page-chooser.js +1 -1
  30. wagtail/admin/static/wagtailadmin/js/preview-panel.js +2 -1
  31. wagtail/admin/static/wagtailadmin/js/preview-panel.js.LICENSE.txt +11 -0
  32. wagtail/admin/static/wagtailadmin/js/privacy-switch.js +1 -1
  33. wagtail/admin/static/wagtailadmin/js/sidebar.js +1 -1
  34. wagtail/admin/static/wagtailadmin/js/task-chooser-modal.js +1 -1
  35. wagtail/admin/static/wagtailadmin/js/task-chooser.js +1 -1
  36. wagtail/admin/static/wagtailadmin/js/telepath/blocks.js +1 -1
  37. wagtail/admin/static/wagtailadmin/js/telepath/widgets.js +1 -1
  38. wagtail/admin/static/wagtailadmin/js/userbar.js +2 -1
  39. wagtail/admin/static/wagtailadmin/js/userbar.js.LICENSE.txt +11 -0
  40. wagtail/admin/static/wagtailadmin/js/vendor.js +1 -1
  41. wagtail/admin/static/wagtailadmin/js/vendor.js.LICENSE.txt +0 -12
  42. wagtail/admin/static/wagtailadmin/js/wagtailadmin.js +1 -1
  43. wagtail/admin/static/wagtailadmin/js/workflow-action.js +1 -1
  44. wagtail/admin/templates/wagtailadmin/collection_privacy/ancestor_privacy.html +2 -6
  45. wagtail/admin/templates/wagtailadmin/generic/index_results.html +1 -17
  46. wagtail/admin/templates/wagtailadmin/generic/listing_results.html +20 -1
  47. wagtail/admin/templates/wagtailadmin/home/workflow_objects_to_moderate.html +2 -11
  48. wagtail/admin/templates/wagtailadmin/page_privacy/ancestor_privacy.html +2 -6
  49. wagtail/admin/templates/wagtailadmin/page_privacy/no_privacy.html +2 -0
  50. wagtail/admin/templates/wagtailadmin/pages/_editor_js.html +0 -1
  51. wagtail/admin/templates/wagtailadmin/pages/action_menu/menu.html +1 -1
  52. wagtail/admin/templates/wagtailadmin/reports/aging_pages_results.html +54 -0
  53. wagtail/admin/templates/wagtailadmin/reports/base_page_report.html +1 -17
  54. wagtail/admin/templates/wagtailadmin/reports/base_page_report_results.html +10 -0
  55. wagtail/admin/templates/wagtailadmin/reports/base_report.html +1 -40
  56. wagtail/admin/templates/wagtailadmin/reports/base_report_results.html +1 -0
  57. wagtail/admin/templates/wagtailadmin/reports/listing/_list_page_report.html +21 -27
  58. wagtail/admin/templates/wagtailadmin/reports/listing/_list_page_types_usage.html +48 -54
  59. wagtail/admin/templates/wagtailadmin/reports/{locked_pages.html → locked_pages_results.html} +3 -3
  60. wagtail/admin/templates/wagtailadmin/reports/page_types_usage_results.html +10 -0
  61. wagtail/admin/templates/wagtailadmin/reports/site_history_results.html +53 -0
  62. wagtail/admin/templates/wagtailadmin/reports/workflow_results.html +74 -0
  63. wagtail/admin/templates/wagtailadmin/reports/workflow_tasks_results.html +56 -0
  64. wagtail/admin/templates/wagtailadmin/shared/_workflow_init.html +8 -44
  65. wagtail/admin/templates/wagtailadmin/shared/avatar.html +11 -1
  66. wagtail/admin/templates/wagtailadmin/shared/dialog/dialog.html +5 -4
  67. wagtail/admin/templates/wagtailadmin/shared/dropdown/dropdown_button.html +2 -1
  68. wagtail/admin/templates/wagtailadmin/shared/editing_sessions/list.html +132 -0
  69. wagtail/admin/templates/wagtailadmin/shared/editing_sessions/module.html +44 -0
  70. wagtail/admin/templates/wagtailadmin/shared/headers/slim_header.html +7 -1
  71. wagtail/admin/templates/wagtailadmin/shared/page_status_tag_new.html +1 -1
  72. wagtail/admin/templates/wagtailadmin/shared/side_panels/checks.html +32 -16
  73. wagtail/admin/templates/wagtailadmin/skeleton.html +1 -1
  74. wagtail/admin/templates/wagtailadmin/userbar/item_accessibility.html +9 -11
  75. wagtail/admin/templatetags/wagtailadmin_tags.py +13 -2
  76. wagtail/admin/tests/formats/en/__init__.py +0 -0
  77. wagtail/admin/tests/formats/en/formats.py +1 -0
  78. wagtail/admin/tests/pages/test_create_page.py +47 -0
  79. wagtail/admin/tests/pages/test_edit_page.py +10 -8
  80. wagtail/admin/tests/pages/test_parent_page_chooser_view.py +45 -1
  81. wagtail/admin/tests/test_checks.py +53 -3
  82. wagtail/admin/tests/test_collections_views.py +62 -1
  83. wagtail/admin/tests/test_edit_handlers.py +37 -0
  84. wagtail/admin/tests/test_editing_sessions.py +1336 -0
  85. wagtail/admin/tests/test_icon_sprite.py +12 -21
  86. wagtail/admin/tests/test_page_chooser.py +309 -7
  87. wagtail/admin/tests/test_privacy.py +82 -0
  88. wagtail/admin/tests/test_reports_views.py +464 -70
  89. wagtail/admin/tests/test_userbar.py +93 -6
  90. wagtail/admin/tests/test_workflows.py +223 -33
  91. wagtail/admin/tests/viewsets/test_model_viewset.py +151 -2
  92. wagtail/admin/ui/editing_sessions.py +57 -0
  93. wagtail/admin/urls/__init__.py +9 -15
  94. wagtail/admin/urls/editing_sessions.py +17 -0
  95. wagtail/admin/urls/reports.py +33 -1
  96. wagtail/admin/userbar.py +77 -20
  97. wagtail/admin/views/chooser.py +49 -22
  98. wagtail/admin/views/collections.py +0 -11
  99. wagtail/admin/views/editing_sessions.py +193 -0
  100. wagtail/admin/views/generic/__init__.py +1 -0
  101. wagtail/admin/views/generic/history.py +9 -3
  102. wagtail/admin/views/generic/mixins.py +44 -3
  103. wagtail/admin/views/generic/models.py +46 -72
  104. wagtail/admin/views/generic/permissions.py +20 -10
  105. wagtail/admin/views/home.py +2 -31
  106. wagtail/admin/views/page_privacy.py +20 -5
  107. wagtail/admin/views/pages/choose_parent.py +62 -0
  108. wagtail/admin/views/pages/edit.py +28 -0
  109. wagtail/admin/views/reports/aging_pages.py +6 -10
  110. wagtail/admin/views/reports/audit_logging.py +13 -42
  111. wagtail/admin/views/reports/base.py +31 -4
  112. wagtail/admin/views/reports/locked_pages.py +5 -8
  113. wagtail/admin/views/reports/page_types_usage.py +6 -10
  114. wagtail/admin/views/reports/workflows.py +36 -12
  115. wagtail/admin/viewsets/base.py +8 -3
  116. wagtail/admin/viewsets/chooser.py +1 -1
  117. wagtail/admin/viewsets/model.py +26 -1
  118. wagtail/admin/wagtail_hooks.py +2 -1
  119. wagtail/api/v2/filters.py +6 -0
  120. wagtail/api/v2/tests/test_documents.py +1 -1
  121. wagtail/api/v2/tests/test_images.py +1 -1
  122. wagtail/api/v2/tests/test_pages.py +11 -1
  123. wagtail/api/v2/utils.py +2 -2
  124. wagtail/blocks/base.py +35 -12
  125. wagtail/blocks/definition_lookup.py +85 -0
  126. wagtail/blocks/list_block.py +12 -0
  127. wagtail/blocks/migrations/migrate_operation.py +2 -0
  128. wagtail/blocks/stream_block.py +19 -0
  129. wagtail/blocks/struct_block.py +19 -0
  130. wagtail/contrib/forms/locale/en/LC_MESSAGES/django.po +1 -1
  131. wagtail/contrib/frontend_cache/backends/__init__.py +5 -0
  132. wagtail/contrib/frontend_cache/backends/azure.py +179 -0
  133. wagtail/contrib/frontend_cache/backends/base.py +28 -0
  134. wagtail/contrib/frontend_cache/backends/cloudflare.py +114 -0
  135. wagtail/contrib/frontend_cache/backends/cloudfront.py +99 -0
  136. wagtail/contrib/frontend_cache/backends/http.py +64 -0
  137. wagtail/contrib/frontend_cache/tests.py +59 -17
  138. wagtail/contrib/frontend_cache/utils.py +26 -8
  139. wagtail/contrib/redirects/filters.py +15 -1
  140. wagtail/contrib/redirects/locale/en/LC_MESSAGES/django.po +37 -72
  141. wagtail/contrib/redirects/models.py +6 -5
  142. wagtail/contrib/redirects/templates/wagtailredirects/edit.html +1 -38
  143. wagtail/contrib/redirects/tests/test_redirects.py +141 -1
  144. wagtail/contrib/redirects/urls.py +1 -2
  145. wagtail/contrib/redirects/views.py +39 -80
  146. wagtail/contrib/routable_page/models.py +6 -4
  147. wagtail/contrib/routable_page/tests.py +11 -0
  148. wagtail/contrib/search_promotions/locale/en/LC_MESSAGES/django.po +1 -1
  149. wagtail/contrib/settings/locale/en/LC_MESSAGES/django.po +4 -4
  150. wagtail/contrib/simple_translation/locale/en/LC_MESSAGES/django.po +5 -1
  151. wagtail/contrib/simple_translation/models.py +2 -1
  152. wagtail/contrib/styleguide/locale/en/LC_MESSAGES/django.po +7 -7
  153. wagtail/contrib/table_block/locale/en/LC_MESSAGES/django.po +1 -1
  154. wagtail/contrib/table_block/static/table_block/js/table.js +1 -1
  155. wagtail/contrib/typed_table_block/blocks.py +19 -0
  156. wagtail/contrib/typed_table_block/locale/en/LC_MESSAGES/django.po +10 -10
  157. wagtail/contrib/typed_table_block/static/typed_table_block/js/typed_table_block.js +1 -1
  158. wagtail/contrib/typed_table_block/tests.py +38 -0
  159. wagtail/coreutils.py +1 -1
  160. wagtail/documents/__init__.py +1 -1
  161. wagtail/documents/locale/en/LC_MESSAGES/django.po +8 -8
  162. wagtail/documents/locale/sl/LC_MESSAGES/django.mo +0 -0
  163. wagtail/documents/locale/sl/LC_MESSAGES/django.po +20 -0
  164. wagtail/documents/models.py +5 -1
  165. wagtail/documents/static/wagtaildocs/js/document-chooser-modal.js +1 -1
  166. wagtail/documents/static/wagtaildocs/js/document-chooser-telepath.js +1 -1
  167. wagtail/documents/static/wagtaildocs/js/document-chooser.js +1 -1
  168. wagtail/documents/tests/test_models.py +5 -1
  169. wagtail/embeds/apps.py +2 -0
  170. wagtail/embeds/embeds.py +12 -14
  171. wagtail/embeds/finders/__init__.py +2 -0
  172. wagtail/embeds/finders/facebook.py +17 -33
  173. wagtail/embeds/finders/instagram.py +19 -16
  174. wagtail/embeds/locale/en/LC_MESSAGES/django.po +1 -1
  175. wagtail/embeds/signal_handlers.py +13 -0
  176. wagtail/embeds/tests/test_embeds.py +7 -7
  177. wagtail/fields.py +58 -14
  178. wagtail/images/__init__.py +1 -1
  179. wagtail/images/locale/en/LC_MESSAGES/django.po +34 -34
  180. wagtail/images/locale/sl/LC_MESSAGES/django.mo +0 -0
  181. wagtail/images/locale/sl/LC_MESSAGES/django.po +20 -0
  182. wagtail/images/models.py +2 -0
  183. wagtail/images/static/wagtailimages/js/image-chooser-modal.js +1 -1
  184. wagtail/images/static/wagtailimages/js/image-chooser-telepath.js +1 -1
  185. wagtail/images/static/wagtailimages/js/image-chooser.js +1 -1
  186. wagtail/images/templates/wagtailimages/images/edit.html +4 -4
  187. wagtail/images/tests/test_admin_views.py +26 -2
  188. wagtail/images/views/chooser.py +6 -1
  189. wagtail/locale/en/LC_MESSAGES/django.po +84 -80
  190. wagtail/locales/locale/en/LC_MESSAGES/django.po +2 -2
  191. wagtail/locales/tests.py +16 -0
  192. wagtail/locales/wagtail_hooks.py +0 -9
  193. wagtail/migrations/0094_alter_page_locale.py +19 -0
  194. wagtail/models/__init__.py +11 -5
  195. wagtail/models/i18n.py +6 -1
  196. wagtail/project_template/requirements.txt +1 -1
  197. wagtail/search/locale/en/LC_MESSAGES/django.po +1 -1
  198. wagtail/signals.py +4 -0
  199. wagtail/sites/locale/en/LC_MESSAGES/django.po +2 -2
  200. wagtail/sites/tests.py +15 -0
  201. wagtail/sites/wagtail_hooks.py +0 -9
  202. wagtail/snippets/locale/en/LC_MESSAGES/django.po +9 -9
  203. wagtail/snippets/permissions.py +5 -3
  204. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser-telepath.js +1 -1
  205. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser.js +1 -1
  206. wagtail/snippets/templates/wagtailsnippets/snippets/action_menu/menu.html +1 -1
  207. wagtail/snippets/tests/test_snippets.py +78 -12
  208. wagtail/snippets/tests/test_viewset.py +22 -0
  209. wagtail/snippets/views/snippets.py +19 -14
  210. wagtail/snippets/wagtail_hooks.py +2 -10
  211. wagtail/templatetags/wagtailcore_tags.py +3 -0
  212. wagtail/test/dummy_external_storage.py +1 -1
  213. wagtail/test/i18n/migrations/0003_alter_clusterabletestmodel_locale_and_more.py +40 -0
  214. wagtail/test/routablepage/models.py +4 -0
  215. wagtail/test/snippets/migrations/0012_alter_translatablesnippet_locale.py +20 -0
  216. wagtail/test/testapp/migrations/0038_sociallink.py +52 -0
  217. wagtail/test/testapp/migrations/0039_alter_eventcategory_locale_and_more.py +45 -0
  218. wagtail/test/testapp/models.py +24 -0
  219. wagtail/test/testapp/views.py +1 -0
  220. wagtail/test/testapp/wagtail_hooks.py +9 -0
  221. wagtail/test/urls_multilang.py +6 -1
  222. wagtail/test/urls_multilang_non_root.py +11 -0
  223. wagtail/tests/streamfield_migrations/test_migrations.py +53 -12
  224. wagtail/tests/test_audit_log.py +72 -2
  225. wagtail/tests/test_blocks.py +103 -0
  226. wagtail/tests/test_signals.py +49 -2
  227. wagtail/tests/test_streamfield.py +153 -0
  228. wagtail/tests/test_utils.py +14 -0
  229. wagtail/tests/tests.py +5 -0
  230. wagtail/users/apps.py +1 -0
  231. wagtail/users/forms.py +7 -0
  232. wagtail/users/locale/en/LC_MESSAGES/django.po +55 -50
  233. wagtail/users/models.py +1 -0
  234. wagtail/users/templates/wagtailusers/groups/includes/formatted_permissions.html +3 -2
  235. wagtail/users/templatetags/wagtailusers_tags.py +9 -0
  236. wagtail/users/tests/__init__.py +7 -1
  237. wagtail/users/tests/test_admin_views.py +117 -32
  238. wagtail/users/views/groups.py +4 -0
  239. wagtail/users/views/users.py +58 -14
  240. wagtail/users/wagtail_hooks.py +7 -123
  241. wagtail/utils/utils.py +1 -0
  242. wagtail/utils/version.py +5 -2
  243. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/METADATA +3 -3
  244. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/RECORD +248 -222
  245. wagtail/admin/templates/wagtailadmin/reports/aging_pages.html +0 -58
  246. wagtail/admin/templates/wagtailadmin/reports/page_types_usage.html +0 -18
  247. wagtail/admin/templates/wagtailadmin/reports/site_history.html +0 -57
  248. wagtail/admin/templates/wagtailadmin/reports/workflow.html +0 -81
  249. wagtail/admin/templates/wagtailadmin/reports/workflow_tasks.html +0 -63
  250. wagtail/contrib/frontend_cache/backends.py +0 -400
  251. wagtail/contrib/redirects/templates/wagtailredirects/list.html +0 -43
  252. wagtail/contrib/redirects/templates/wagtailredirects/reports/redirects_report.html +0 -14
  253. wagtail/contrib/redirects/tests/test_reports_view.py +0 -82
  254. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/LICENSE +0 -0
  255. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/WHEEL +0 -0
  256. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/entry_points.txt +0 -0
  257. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/top_level.txt +0 -0
@@ -11,6 +11,7 @@ from django.test import RequestFactory, TestCase
11
11
  from django.test.utils import override_settings
12
12
  from django.urls import reverse
13
13
  from django.utils import timezone, translation
14
+ from freezegun import freeze_time
14
15
  from openpyxl import load_workbook
15
16
 
16
17
  from wagtail.admin.views.mixins import ExcelDateFormatter
@@ -31,32 +32,115 @@ from wagtail.test.testapp.models import (
31
32
  SimplePage,
32
33
  )
33
34
  from wagtail.test.utils import WagtailTestUtils
35
+ from wagtail.test.utils.template_tests import AdminTemplateTestUtils
36
+ from wagtail.utils.deprecation import RemovedInWagtail70Warning
37
+
38
+
39
+ class BaseReportViewTestCase(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
40
+ url_name = None
41
+ header_buttons_parent_selector = "#w-slim-header-buttons"
42
+ drilldown_selector = ".w-drilldown"
43
+ extra_params = ""
44
+ results_only = False
45
+
46
+ @classmethod
47
+ def setUpClass(cls):
48
+ super().setUpClass()
49
+ cls.url = reverse(cls.url_name)
50
+ if cls.results_only:
51
+ cls.header_buttons_parent_selector = (
52
+ '[data-controller="w-teleport"]'
53
+ '[data-w-teleport-target-value="#w-slim-header-buttons"]'
54
+ )
55
+ cls.drilldown_selector = (
56
+ '[data-controller="w-teleport"]'
57
+ '[data-w-teleport-target-value="#filters-drilldown"]'
58
+ )
59
+ cls.extra_params = "&_w_filter_fragment=true"
34
60
 
35
-
36
- class TestLockedPagesView(WagtailTestUtils, TestCase):
37
61
  def setUp(self):
38
62
  self.user = self.login()
39
63
 
40
64
  def get(self, params={}):
41
- return self.client.get(reverse("wagtailadmin_reports:locked_pages"), params)
65
+ if self.results_only:
66
+ params["_w_filter_fragment"] = "true"
67
+ return self.client.get(self.url, params)
68
+
69
+ def assertActiveFilter(self, soup, name, value):
70
+ # Should render the export buttons inside the header "more" dropdown
71
+ # with the filtered URL. When used in a results-only view, these are
72
+ # teleported to the correct element in the skeleton.
73
+ links_parent = soup.select_one(self.header_buttons_parent_selector)
74
+ self.assertIsNotNone(links_parent)
75
+ links = links_parent.select(".w-dropdown a")
76
+ unfiltered_url = reverse(self.url_name)
77
+ filtered_url = f"{unfiltered_url}?{name}={value}{self.extra_params}"
78
+ self.assertEqual(len(links), 2)
79
+ self.assertEqual(
80
+ [link.get("href") for link in links],
81
+ [f"{filtered_url}&export=xlsx", f"{filtered_url}&export=csv"],
82
+ )
83
+
84
+ # Should render the active filter pill
85
+ active_filter = soup.select_one(".w-active-filters .w-pill__content")
86
+ clear_button = soup.select_one(".w-active-filters .w-pill__remove")
87
+ self.assertIsNotNone(active_filter)
88
+ self.assertIsNotNone(clear_button)
89
+ self.assertNotIn(name, clear_button.attrs.get("data-w-swap-src-value"))
90
+ self.assertEqual(clear_button.attrs.get("data-w-swap-reflect-value"), "true")
91
+
92
+ def assertActiveFilterNotRendered(self, soup):
93
+ self.assertIsNone(soup.select_one(".w-active-filters"))
94
+
95
+ def assertBreadcrumbs(self, breadcrumbs, html):
96
+ if self.results_only:
97
+ self.assertBreadcrumbsNotRendered(html)
98
+ else:
99
+ self.assertBreadcrumbsItemsRendered(breadcrumbs, html)
100
+
101
+ def assertPageTitle(self, soup, title):
102
+ page_title = soup.select_one("title")
103
+ if self.results_only:
104
+ self.assertIsNone(page_title)
105
+ else:
106
+ self.assertIsNotNone(page_title)
107
+ self.assertEqual(page_title.text.strip(), title)
108
+
109
+
110
+ class TestLockedPagesView(BaseReportViewTestCase):
111
+ url_name = "wagtailadmin_reports:locked_pages"
42
112
 
43
113
  def test_simple(self):
44
114
  response = self.get()
45
115
  self.assertEqual(response.status_code, 200)
46
- self.assertTemplateUsed(response, "wagtailadmin/reports/locked_pages.html")
116
+ self.assertTemplateNotUsed(
117
+ response,
118
+ "wagtailadmin/reports/base_page_report.html",
119
+ )
120
+ self.assertTemplateUsed(response, "wagtailadmin/reports/base_report.html")
121
+ self.assertTemplateUsed(
122
+ response,
123
+ "wagtailadmin/reports/locked_pages_results.html",
124
+ )
125
+ self.assertBreadcrumbs(
126
+ [{"url": "", "label": "Locked pages"}],
127
+ response.content,
128
+ )
47
129
 
48
130
  # Initially there should be no locked pages
49
131
  self.assertContains(response, "No locked pages found.")
50
132
 
51
- # No user locked anything
52
- self.assertInHTML(
53
- """
54
- <select name="locked_by" id="id_locked_by">
55
- <option value="" selected>---------</option>
56
- </select>
57
- """,
58
- response.content.decode(),
133
+ # Should render the filter inside the drilldown
134
+ soup = self.get_soup(response.content)
135
+ locked_by_options = soup.select(
136
+ f"{self.drilldown_selector} select[name='locked_by'] option"
59
137
  )
138
+ # No user locked anything, so there should be no option for the filter
139
+ self.assertEqual(len(locked_by_options), 1)
140
+ self.assertEqual(locked_by_options[0].text, "---------")
141
+ self.assertEqual(locked_by_options[0].get("value"), "")
142
+ self.assertActiveFilterNotRendered(soup)
143
+ self.assertPageTitle(soup, "Locked pages - Wagtail")
60
144
 
61
145
  parent_page = Page.objects.first()
62
146
  parent_page.add_child(
@@ -79,20 +163,35 @@ class TestLockedPagesView(WagtailTestUtils, TestCase):
79
163
  # Now the listing should contain our locked page
80
164
  response = self.get()
81
165
  self.assertEqual(response.status_code, 200)
82
- self.assertTemplateUsed(response, "wagtailadmin/reports/locked_pages.html")
166
+ self.assertTemplateNotUsed(
167
+ response,
168
+ "wagtailadmin/reports/base_page_report.html",
169
+ )
170
+ self.assertTemplateUsed(response, "wagtailadmin/reports/base_report.html")
171
+ self.assertTemplateUsed(
172
+ response,
173
+ "wagtailadmin/reports/locked_pages_results.html",
174
+ )
175
+ self.assertBreadcrumbs(
176
+ [{"url": "", "label": "Locked pages"}],
177
+ response.content,
178
+ )
83
179
  self.assertNotContains(response, "No locked pages found.")
84
180
  self.assertContains(response, "First locked page")
85
181
  self.assertContains(response, "Second locked page")
86
182
 
87
- self.assertInHTML(
88
- f"""
89
- <select name="locked_by" id="id_locked_by">
90
- <option value="" selected>---------</option>
91
- <option value="{self.user.pk}">{self.user}</option>
92
- </select>
93
- """,
94
- response.content.decode(),
183
+ # Should render the filter inside the drilldown
184
+ soup = self.get_soup(response.content)
185
+ locked_by_options = soup.select(
186
+ f"{self.drilldown_selector} select[name='locked_by'] option"
95
187
  )
188
+ # The options should only display users who have locked pages
189
+ self.assertEqual(len(locked_by_options), 2)
190
+ self.assertEqual(locked_by_options[0].text, "---------")
191
+ self.assertIsNone(locked_by_options[0].value)
192
+ self.assertEqual(locked_by_options[1].text, str(self.user))
193
+ self.assertEqual(locked_by_options[1].get("value"), str(self.user.pk))
194
+ self.assertActiveFilterNotRendered(soup)
96
195
 
97
196
  # Locked by current user shown in indicator
98
197
  self.assertContains(response, "locked-indicator indicator--is-inverse")
@@ -119,7 +218,15 @@ class TestLockedPagesView(WagtailTestUtils, TestCase):
119
218
  response = self.get()
120
219
 
121
220
  self.assertEqual(response.status_code, 200)
122
- self.assertTemplateUsed(response, "wagtailadmin/reports/locked_pages.html")
221
+ self.assertTemplateNotUsed(
222
+ response,
223
+ "wagtailadmin/reports/base_page_report.html",
224
+ )
225
+ self.assertTemplateUsed(response, "wagtailadmin/reports/base_report.html")
226
+ self.assertTemplateUsed(
227
+ response,
228
+ "wagtailadmin/reports/locked_pages_results.html",
229
+ )
123
230
  self.assertContains(response, "No locked pages found.")
124
231
 
125
232
  def test_get_with_no_permissions(self):
@@ -210,8 +317,9 @@ class TestLockedPagesView(WagtailTestUtils, TestCase):
210
317
  self.assertEqual(worksheet["E2"].number_format, ExcelDateFormatter().get())
211
318
 
212
319
 
213
- class TestFilteredLockedPagesView(WagtailTestUtils, TestCase):
320
+ class TestFilteredLockedPagesView(BaseReportViewTestCase):
214
321
  fixtures = ["test.json"]
322
+ url_name = "wagtailadmin_reports:locked_pages"
215
323
 
216
324
  def setUp(self):
217
325
  self.user = self.login()
@@ -229,9 +337,6 @@ class TestFilteredLockedPagesView(WagtailTestUtils, TestCase):
229
337
  self.christmas_page.locked_at = timezone.now()
230
338
  self.christmas_page.save()
231
339
 
232
- def get(self, params={}):
233
- return self.client.get(reverse("wagtailadmin_reports:locked_pages"), params)
234
-
235
340
  def test_filter_by_live(self):
236
341
  response = self.get(params={"live": "true"})
237
342
  self.assertEqual(response.status_code, 200)
@@ -245,6 +350,9 @@ class TestFilteredLockedPagesView(WagtailTestUtils, TestCase):
245
350
  self.assertNotContains(response, "My locked page")
246
351
  self.assertNotContains(response, "Christmas")
247
352
 
353
+ soup = self.get_soup(response.content)
354
+ self.assertActiveFilter(soup, "live", "false")
355
+
248
356
  def test_filter_by_user(self):
249
357
  response = self.get(params={"locked_by": self.user.pk})
250
358
  self.assertEqual(response.status_code, 200)
@@ -253,8 +361,14 @@ class TestFilteredLockedPagesView(WagtailTestUtils, TestCase):
253
361
  self.assertNotContains(response, "My locked page")
254
362
 
255
363
 
256
- class TestFilteredLogEntriesView(WagtailTestUtils, TestCase):
364
+ class TestFilteredLockedPagesResultsView(TestFilteredLockedPagesView):
365
+ url_name = "wagtailadmin_reports:locked_pages_results"
366
+ results_only = True
367
+
368
+
369
+ class TestFilteredLogEntriesView(BaseReportViewTestCase):
257
370
  fixtures = ["test.json"]
371
+ url_name = "wagtailadmin_reports:site_history"
258
372
 
259
373
  def setUp(self):
260
374
  self.user = self.login()
@@ -275,17 +389,32 @@ class TestFilteredLogEntriesView(WagtailTestUtils, TestCase):
275
389
  )
276
390
  editors.user_set.add(self.editor)
277
391
 
392
+ # timezone matches TIME_ZONE = "Asia/Tokyo" in tests/settings.py
393
+ with freeze_time("2024-05-06 12:00:00+09:00"):
394
+ self.today = timezone.now()
395
+
278
396
  self.create_log = PageLogEntry.objects.log_action(
279
- self.home_page, "wagtail.create"
397
+ self.home_page,
398
+ "wagtail.create",
399
+ timestamp=self.today - timezone.timedelta(days=4),
400
+ user=self.user,
280
401
  )
281
402
  self.edit_log_1 = PageLogEntry.objects.log_action(
282
- self.home_page, "wagtail.edit"
403
+ self.home_page,
404
+ "wagtail.edit",
405
+ timestamp=self.today - timezone.timedelta(days=3),
283
406
  )
284
407
  self.edit_log_2 = PageLogEntry.objects.log_action(
285
- self.home_page, "wagtail.edit"
408
+ self.home_page,
409
+ "wagtail.edit",
410
+ timestamp=self.today - timezone.timedelta(days=2),
411
+ user=self.editor,
286
412
  )
287
413
  self.edit_log_3 = PageLogEntry.objects.log_action(
288
- self.home_page, "wagtail.edit"
414
+ self.home_page,
415
+ "wagtail.edit",
416
+ timestamp=self.today - timezone.timedelta(days=1),
417
+ title="The FINAL cut",
289
418
  )
290
419
 
291
420
  self.create_comment_log = PageLogEntry.objects.log_action(
@@ -322,30 +451,37 @@ class TestFilteredLogEntriesView(WagtailTestUtils, TestCase):
322
451
  self.create_custom_log = ModelLogEntry.objects.log_action(
323
452
  self.custom_model,
324
453
  "wagtail.create",
454
+ timestamp=self.today - timezone.timedelta(days=3),
325
455
  )
326
456
 
327
457
  self.edit_custom_log = ModelLogEntry.objects.log_action(
328
458
  self.custom_model,
329
459
  "wagtail.edit",
460
+ timestamp=self.today - timezone.timedelta(days=2),
461
+ title="the final CUT",
330
462
  )
331
463
 
332
- def get(self, params={}):
333
- return self.client.get(reverse("wagtailadmin_reports:site_history"), params)
334
-
335
464
  def assert_log_entries(self, response, expected):
336
465
  actual = set(response.context["object_list"])
337
466
  self.assertSetEqual(actual, set(expected))
338
467
 
339
468
  def assert_filter_actions(self, response, expected):
469
+ soup = self.get_soup(response.content)
340
470
  actual = {
341
- choice[0]
342
- for choice in response.context["filters"].filters["action"].extra["choices"]
471
+ choice.get("value")
472
+ for choice in soup.select(
473
+ f"{self.drilldown_selector} input[name='action'][type='checkbox']"
474
+ )
343
475
  }
344
476
  self.assertSetEqual(actual, set(expected))
345
477
 
346
478
  def test_unfiltered(self):
347
479
  response = self.get()
348
480
  self.assertEqual(response.status_code, 200)
481
+ self.assertBreadcrumbs(
482
+ [{"url": "", "label": "Site history"}],
483
+ response.content,
484
+ )
349
485
  self.assert_log_entries(
350
486
  response,
351
487
  [
@@ -372,6 +508,10 @@ class TestFilteredLogEntriesView(WagtailTestUtils, TestCase):
372
508
  ],
373
509
  )
374
510
 
511
+ soup = self.get_soup(response.content)
512
+ self.assertActiveFilterNotRendered(soup)
513
+ self.assertPageTitle(soup, "Site history - Wagtail")
514
+
375
515
  # The editor should not see the Advert's log entries.
376
516
  self.login(user=self.editor)
377
517
  response = self.get()
@@ -400,6 +540,9 @@ class TestFilteredLogEntriesView(WagtailTestUtils, TestCase):
400
540
  ],
401
541
  )
402
542
 
543
+ soup = self.get_soup(response.content)
544
+ self.assertActiveFilterNotRendered(soup)
545
+
403
546
  def test_filter_by_action(self):
404
547
  response = self.get(params={"action": "wagtail.edit"})
405
548
  self.assertEqual(response.status_code, 200)
@@ -425,23 +568,174 @@ class TestFilteredLogEntriesView(WagtailTestUtils, TestCase):
425
568
  ],
426
569
  )
427
570
 
428
- def test_hide_commenting_actions(self):
429
- response = self.get(params={"hide_commenting_actions": "on"})
571
+ soup = self.get_soup(response.content)
572
+ self.assertActiveFilter(soup, "action", "wagtail.edit")
573
+
574
+ def test_filter_by_action_multiple(self):
575
+ response = self.get(params={"action": ["wagtail.edit", "wagtail.create"]})
430
576
  self.assertEqual(response.status_code, 200)
431
577
  self.assert_log_entries(
432
578
  response,
433
579
  [
434
580
  self.create_log,
581
+ self.create_custom_log,
435
582
  self.edit_log_1,
436
583
  self.edit_log_2,
437
584
  self.edit_log_3,
585
+ self.edit_custom_log,
586
+ ],
587
+ )
588
+
589
+ self.login(user=self.editor)
590
+ response = self.get(params={"action": ["wagtail.edit", "wagtail.create"]})
591
+ self.assertEqual(response.status_code, 200)
592
+ self.assert_log_entries(
593
+ response,
594
+ [
595
+ self.create_log,
596
+ self.edit_log_1,
597
+ self.edit_log_2,
598
+ self.edit_log_3,
599
+ ],
600
+ )
601
+
602
+ def test_filter_by_timestamp(self):
603
+ today = self.today.date()
604
+ response = self.get(
605
+ params={"timestamp_from": today - timezone.timedelta(days=3)}
606
+ )
607
+ self.assertEqual(response.status_code, 200)
608
+ self.assert_log_entries(
609
+ response,
610
+ [
611
+ # Doesn't contain self.create_log which was created 4 days ago
612
+ self.edit_log_1,
613
+ self.edit_log_2,
614
+ self.edit_log_3,
615
+ self.create_comment_log,
616
+ self.edit_comment_log,
617
+ self.create_reply_log,
618
+ self.create_custom_log,
619
+ self.edit_custom_log,
620
+ ],
621
+ )
622
+
623
+ response = self.get(params={"timestamp_to": today - timezone.timedelta(days=2)})
624
+ self.assertEqual(response.status_code, 200)
625
+ self.assert_log_entries(
626
+ response,
627
+ [
628
+ # Doesn't contain self.edit_log_3 which was created 1 day ago,
629
+ # as well as self.create_comment_log, self.edit_comment_log,
630
+ # and self.create_reply_log which was created without an explicit
631
+ # timestamp (and thus defaults to the current time)
632
+ self.create_log,
633
+ self.edit_log_1,
634
+ self.edit_log_2,
635
+ self.create_custom_log,
636
+ self.edit_custom_log,
637
+ ],
638
+ )
639
+
640
+ response = self.get(
641
+ params={
642
+ "timestamp_from": today - timezone.timedelta(days=3),
643
+ "timestamp_to": today - timezone.timedelta(days=2),
644
+ }
645
+ )
646
+ self.assertEqual(response.status_code, 200)
647
+ self.assert_log_entries(
648
+ response,
649
+ [
650
+ # Doesn't contain
651
+ # self.create_log which was created 4 days ago,
652
+ # self.edit_log_3 which was created 1 day ago,
653
+ # as well as self.create_comment_log, self.edit_comment_log,
654
+ # and self.create_reply_log which was created without an explicit
655
+ # timestamp (and thus defaults to the current time)
656
+ self.edit_log_1,
657
+ self.edit_log_2,
438
658
  self.create_custom_log,
439
659
  self.edit_custom_log,
440
660
  ],
441
661
  )
442
662
 
663
+ def test_filter_by_user(self):
664
+ response = self.get(params={"user": self.editor.pk})
665
+ self.assertEqual(response.status_code, 200)
666
+ self.assert_log_entries(response, [self.edit_log_2])
667
+
668
+ response = self.get(params={"user": [self.user.pk, self.editor.pk]})
669
+ self.assertEqual(response.status_code, 200)
670
+ self.assert_log_entries(response, [self.create_log, self.edit_log_2])
671
+
672
+ def test_filter_by_label(self):
673
+ response = self.get(params={"label": "final cut"})
674
+ self.assertEqual(response.status_code, 200)
675
+ self.assert_log_entries(response, [self.edit_log_3, self.edit_custom_log])
676
+
677
+ def test_filter_by_object_type(self):
678
+ response = self.get(
679
+ params={"object_type": ContentType.objects.get_for_model(Page).pk}
680
+ )
681
+ self.assertEqual(response.status_code, 200)
682
+ self.assert_log_entries(
683
+ response,
684
+ [
685
+ self.create_log,
686
+ self.edit_log_1,
687
+ self.edit_log_2,
688
+ self.edit_log_3,
689
+ self.create_comment_log,
690
+ self.edit_comment_log,
691
+ self.create_reply_log,
692
+ ],
693
+ )
694
+
695
+ response = self.get(
696
+ params={"object_type": ContentType.objects.get_for_model(Advert).pk}
697
+ )
698
+ self.assertEqual(response.status_code, 200)
699
+ self.assert_log_entries(
700
+ response,
701
+ [
702
+ self.create_custom_log,
703
+ self.edit_custom_log,
704
+ ],
705
+ )
706
+
707
+ def test_is_commenting_action(self):
708
+ response = self.get(params={"is_commenting_action": "false"})
709
+ self.assertEqual(response.status_code, 200)
710
+ self.assert_log_entries(
711
+ response,
712
+ [
713
+ self.create_log,
714
+ self.edit_log_1,
715
+ self.edit_log_2,
716
+ self.edit_log_3,
717
+ self.create_custom_log,
718
+ self.edit_custom_log,
719
+ ],
720
+ )
721
+ soup = self.get_soup(response.content)
722
+ self.assertActiveFilter(soup, "is_commenting_action", "false")
723
+
724
+ response = self.get(params={"is_commenting_action": "true"})
725
+ self.assertEqual(response.status_code, 200)
726
+ self.assert_log_entries(
727
+ response,
728
+ [
729
+ self.create_comment_log,
730
+ self.edit_comment_log,
731
+ self.create_reply_log,
732
+ ],
733
+ )
734
+ soup = self.get_soup(response.content)
735
+ self.assertActiveFilter(soup, "is_commenting_action", "true")
736
+
443
737
  self.login(user=self.editor)
444
- response = self.get(params={"hide_commenting_actions": "on"})
738
+ response = self.get(params={"is_commenting_action": "false"})
445
739
  self.assertEqual(response.status_code, 200)
446
740
  self.assert_log_entries(
447
741
  response,
@@ -452,6 +746,21 @@ class TestFilteredLogEntriesView(WagtailTestUtils, TestCase):
452
746
  self.edit_log_3,
453
747
  ],
454
748
  )
749
+ soup = self.get_soup(response.content)
750
+ self.assertActiveFilter(soup, "is_commenting_action", "false")
751
+
752
+ response = self.get(params={"is_commenting_action": "true"})
753
+ self.assertEqual(response.status_code, 200)
754
+ self.assert_log_entries(
755
+ response,
756
+ [
757
+ self.create_comment_log,
758
+ self.edit_comment_log,
759
+ self.create_reply_log,
760
+ ],
761
+ )
762
+ soup = self.get_soup(response.content)
763
+ self.assertActiveFilter(soup, "is_commenting_action", "true")
455
764
 
456
765
  def test_log_entry_with_stale_content_type(self):
457
766
  stale_content_type = ContentType.objects.create(
@@ -499,6 +808,46 @@ class TestFilteredLogEntriesView(WagtailTestUtils, TestCase):
499
808
 
500
809
  self.assertEqual(response.status_code, 200)
501
810
 
811
+ def test_deprecated_title_attribute(self):
812
+ # Remove this test when the deprecation ends
813
+ with mock.patch.object(
814
+ LogEntriesView,
815
+ "page_title",
816
+ return_value=None,
817
+ new_callable=mock.PropertyMock,
818
+ ), mock.patch.object(
819
+ LogEntriesView,
820
+ "title",
821
+ return_value="Deprecated page title",
822
+ new_callable=mock.PropertyMock,
823
+ ):
824
+ with self.assertWarnsMessage(
825
+ RemovedInWagtail70Warning,
826
+ "The `title` attribute in `LogEntriesView` (a `ReportView` subclass) is "
827
+ "deprecated. Use `page_title` instead.",
828
+ ):
829
+ self.assertEqual(LogEntriesView.title, "Deprecated page title")
830
+ self.assertIsNone(LogEntriesView.page_title)
831
+ response = self.get()
832
+ self.assertEqual(response.status_code, 200)
833
+ self.assertPageTitle(
834
+ self.get_soup(response.content),
835
+ "Deprecated page title - Wagtail",
836
+ )
837
+ self.assertEqual(
838
+ response.context["page_title"],
839
+ "Deprecated page title",
840
+ )
841
+ self.assertEqual(
842
+ response.context["title"],
843
+ "Deprecated page title",
844
+ )
845
+
846
+
847
+ class TestFilteredLogEntriesResultsView(TestFilteredLogEntriesView):
848
+ url_name = "wagtailadmin_reports:site_history_results"
849
+ results_only = True
850
+
502
851
 
503
852
  @override_settings(
504
853
  USE_L10N=True,
@@ -523,22 +872,36 @@ class TestExcelDateFormatter(TestCase):
523
872
  self.assertEqual(formatter.format("m/d/Y g:i A"), "mm/dd/yyyy h:mm AM/PM")
524
873
 
525
874
 
526
- class TestAgingPagesView(WagtailTestUtils, TestCase):
875
+ class TestAgingPagesView(BaseReportViewTestCase):
876
+ url_name = "wagtailadmin_reports:aging_pages"
877
+
527
878
  def setUp(self):
528
879
  self.user = self.login()
529
880
  self.root = Page.objects.first()
530
881
  self.home = Page.objects.get(slug="home")
531
882
 
532
- def get(self, params={}):
533
- return self.client.get(reverse("wagtailadmin_reports:aging_pages"), params)
534
-
535
883
  def publish_home_page(self):
536
884
  self.home.save_revision().publish(user=self.user)
537
885
 
538
886
  def test_simple(self):
539
887
  response = self.get()
540
888
  self.assertEqual(response.status_code, 200)
541
- self.assertTemplateUsed(response, "wagtailadmin/reports/aging_pages.html")
889
+ self.assertTemplateNotUsed(
890
+ response,
891
+ "wagtailadmin/reports/base_page_report.html",
892
+ )
893
+ self.assertTemplateUsed(response, "wagtailadmin/reports/base_report.html")
894
+ self.assertTemplateUsed(
895
+ response,
896
+ "wagtailadmin/reports/aging_pages_results.html",
897
+ )
898
+ self.assertBreadcrumbs(
899
+ [{"url": "", "label": "Aging pages"}],
900
+ response.content,
901
+ )
902
+ soup = self.get_soup(response.content)
903
+ self.assertActiveFilterNotRendered(soup)
904
+ self.assertPageTitle(soup, "Aging pages - Wagtail")
542
905
 
543
906
  def test_displays_only_published_pages(self):
544
907
  response = self.get()
@@ -686,13 +1049,12 @@ class TestAgingPagesView(WagtailTestUtils, TestCase):
686
1049
  self.assertContains(response, expected_deleted_string)
687
1050
 
688
1051
 
689
- class TestAgingPagesViewPermissions(WagtailTestUtils, TestCase):
1052
+ class TestAgingPagesViewPermissions(BaseReportViewTestCase):
1053
+ url_name = "wagtailadmin_reports:aging_pages"
1054
+
690
1055
  def setUp(self):
691
1056
  self.user = self.login()
692
1057
 
693
- def get(self, params={}):
694
- return self.client.get(reverse("wagtailadmin_reports:aging_pages"), params)
695
-
696
1058
  def test_simple(self):
697
1059
  response = self.get()
698
1060
  self.assertEqual(response.status_code, 200)
@@ -734,17 +1096,15 @@ class TestAgingPagesViewPermissions(WagtailTestUtils, TestCase):
734
1096
  self.assertEqual(response.status_code, 200)
735
1097
 
736
1098
 
737
- class TestFilteredAgingPagesView(WagtailTestUtils, TestCase):
1099
+ class TestFilteredAgingPagesView(BaseReportViewTestCase):
738
1100
  fixtures = ["test.json"]
1101
+ url_name = "wagtailadmin_reports:aging_pages"
739
1102
 
740
1103
  def setUp(self):
741
1104
  self.user = self.login()
742
1105
  self.home_page = Page.objects.get(slug="home")
743
1106
  self.aboutus_page = Page.objects.get(slug="about-us")
744
1107
 
745
- def get(self, params={}):
746
- return self.client.get(reverse("wagtailadmin_reports:aging_pages"), params)
747
-
748
1108
  def test_filter_by_live(self):
749
1109
  response = self.get(params={"live": "true"})
750
1110
 
@@ -758,14 +1118,25 @@ class TestFilteredAgingPagesView(WagtailTestUtils, TestCase):
758
1118
  self.assertNotContains(response, self.aboutus_page.title)
759
1119
 
760
1120
  def test_filter_by_content_type(self):
761
- response = self.get(
762
- params={"content_type": self.aboutus_page.specific.content_type.pk}
763
- )
1121
+ ct_pk = self.aboutus_page.specific.content_type.pk
1122
+ response = self.get(params={"content_type": ct_pk})
764
1123
 
765
1124
  self.assertEqual(response.status_code, 200)
766
1125
  self.assertContains(response, self.aboutus_page.title)
767
1126
  self.assertNotContains(response, self.home_page.title)
768
1127
 
1128
+ soup = self.get_soup(response.content)
1129
+ self.assertActiveFilter(soup, "content_type", ct_pk)
1130
+
1131
+ # Should render the filter inside the drilldown component
1132
+ ct_select = soup.select_one(
1133
+ f"{self.drilldown_selector} select[name='content_type']"
1134
+ )
1135
+ self.assertIsNotNone(ct_select)
1136
+ selected_option = ct_select.select_one("option[selected]")
1137
+ self.assertIsNotNone(selected_option)
1138
+ self.assertEqual(selected_option.get("value"), str(ct_pk))
1139
+
769
1140
  def test_filter_by_last_published_at(self):
770
1141
  self.home_page.last_published_at = timezone.now()
771
1142
  self.home_page.save()
@@ -776,15 +1147,18 @@ class TestFilteredAgingPagesView(WagtailTestUtils, TestCase):
776
1147
  self.assertNotContains(response, self.home_page.title)
777
1148
 
778
1149
 
779
- class PageTypesUsageReportViewTest(WagtailTestUtils, TestCase):
1150
+ class TestFilteredAgingPagesResultsView(TestFilteredAgingPagesView):
1151
+ url_name = "wagtailadmin_reports:aging_pages_results"
1152
+ results_only = True
1153
+
1154
+
1155
+ class PageTypesUsageReportViewTest(BaseReportViewTestCase):
780
1156
  fixtures = ["test.json"]
1157
+ url_name = "wagtailadmin_reports:page_types_usage"
781
1158
 
782
1159
  def setUp(self):
783
1160
  self.user = self.login()
784
1161
 
785
- def get(self, params={}):
786
- return self.client.get(reverse("wagtailadmin_reports:page_types_usage"), params)
787
-
788
1162
  @staticmethod
789
1163
  def display_name(content_type):
790
1164
  return f"{content_type.app_label}.{content_type.model}"
@@ -792,7 +1166,18 @@ class PageTypesUsageReportViewTest(WagtailTestUtils, TestCase):
792
1166
  def test_simple(self):
793
1167
  response = self.get()
794
1168
  self.assertEqual(response.status_code, 200)
795
- self.assertTemplateUsed(response, "wagtailadmin/reports/page_types_usage.html")
1169
+ self.assertTemplateUsed(response, "wagtailadmin/reports/base_report.html")
1170
+ self.assertTemplateUsed(
1171
+ response,
1172
+ "wagtailadmin/reports/page_types_usage_results.html",
1173
+ )
1174
+ self.assertBreadcrumbs(
1175
+ [{"url": "", "label": "Page types usage"}],
1176
+ response.content,
1177
+ )
1178
+ soup = self.get_soup(response.content)
1179
+ self.assertActiveFilterNotRendered(soup)
1180
+ self.assertPageTitle(soup, "Page types usage - Wagtail")
796
1181
 
797
1182
  def test_displays_only_page_types(self):
798
1183
  """Asserts that the correct models are included in the queryset."""
@@ -904,15 +1289,14 @@ class PageTypesUsageReportViewQuerysetTests(WagtailTestUtils, TestCase):
904
1289
 
905
1290
 
906
1291
  @override_settings(LANGUAGE_CODE="en", WAGTAIL_I18N_ENABLED=True)
907
- class PageTypesReportFiltersTests(WagtailTestUtils, TestCase):
1292
+ class PageTypesReportFiltersTests(BaseReportViewTestCase):
1293
+ url_name = "wagtailadmin_reports:page_types_usage"
1294
+
908
1295
  def setUp(self):
909
1296
  self.user = self.login()
910
1297
  self.default_locale = Locale.get_default()
911
1298
  self.fr_locale, _ = Locale.objects.get_or_create(language_code="fr")
912
1299
 
913
- def get(self, params={}):
914
- return self.client.get(reverse("wagtailadmin_reports:page_types_usage"), params)
915
-
916
1300
  def test_locale_filtering(self):
917
1301
  # Create pages in default locale
918
1302
  event_page = EventPage(
@@ -973,6 +1357,16 @@ class PageTypesReportFiltersTests(WagtailTestUtils, TestCase):
973
1357
  self.assertEqual(event_page_row.last_edited_page.locale, self.fr_locale)
974
1358
  self.assertEqual(simple_page_row.last_edited_page.locale, self.fr_locale)
975
1359
 
1360
+ # Should render the filter inside the drilldown component
1361
+ soup = self.get_soup(response.content)
1362
+ locale_select = soup.select_one(
1363
+ f"{self.drilldown_selector} select[name='page_locale']"
1364
+ )
1365
+ self.assertIsNotNone(locale_select)
1366
+ selected_option = locale_select.select_one("option[selected]")
1367
+ self.assertIsNotNone(selected_option)
1368
+ self.assertEqual(selected_option.get("value"), "fr")
1369
+
976
1370
  def test_site_filtering_with_single_site(self):
977
1371
  """Asserts that the site filter is not displayed when there is only one site."""
978
1372
  sites = Site.objects.all()
@@ -1055,14 +1449,14 @@ class PageTypesReportFiltersTests(WagtailTestUtils, TestCase):
1055
1449
  self.assertCountEqual(choices, expected_choices)
1056
1450
 
1057
1451
 
1058
- class TestPageTypesUsageReportViewPermissions(WagtailTestUtils, TestCase):
1059
- fixtures = ["test.json"]
1452
+ class PageTypesReportFiltersResultsTests(PageTypesReportFiltersTests):
1453
+ url_name = "wagtailadmin_reports:page_types_usage_results"
1454
+ results_only = True
1060
1455
 
1061
- def setUp(self):
1062
- self.user = self.login()
1063
1456
 
1064
- def get(self, params={}):
1065
- return self.client.get(reverse("wagtailadmin_reports:page_types_usage"), params)
1457
+ class TestPageTypesUsageReportViewPermissions(BaseReportViewTestCase):
1458
+ fixtures = ["test.json"]
1459
+ url_name = "wagtailadmin_reports:page_types_usage"
1066
1460
 
1067
1461
  def test_simple(self):
1068
1462
  response = self.get()