wagtail 7.2.1__py3-none-any.whl → 7.3rc1__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 (312) hide show
  1. wagtail/__init__.py +1 -1
  2. wagtail/actions/copy_for_translation.py +4 -2
  3. wagtail/admin/action_menu.py +4 -1
  4. wagtail/admin/api/actions/convert_alias.py +2 -2
  5. wagtail/admin/api/actions/copy.py +2 -2
  6. wagtail/admin/api/actions/copy_for_translation.py +2 -2
  7. wagtail/admin/api/actions/create_alias.py +2 -2
  8. wagtail/admin/api/actions/delete.py +1 -1
  9. wagtail/admin/api/actions/move.py +1 -1
  10. wagtail/admin/api/actions/publish.py +2 -2
  11. wagtail/admin/api/actions/revert_to_page_revision.py +2 -2
  12. wagtail/admin/api/actions/unpublish.py +1 -1
  13. wagtail/admin/api/filters.py +2 -2
  14. wagtail/admin/compare.py +22 -0
  15. wagtail/admin/forms/account.py +52 -1
  16. wagtail/admin/forms/comments.py +53 -0
  17. wagtail/admin/forms/models.py +36 -0
  18. wagtail/admin/forms/pages.py +7 -0
  19. wagtail/admin/forms/workflows.py +5 -2
  20. wagtail/admin/icons.py +4 -3
  21. wagtail/admin/locale/ar/LC_MESSAGES/django.mo +0 -0
  22. wagtail/admin/locale/ar/LC_MESSAGES/django.po +35 -0
  23. wagtail/admin/locale/en/LC_MESSAGES/django.po +262 -234
  24. wagtail/admin/locale/en/LC_MESSAGES/djangojs.po +72 -43
  25. wagtail/admin/locale/it/LC_MESSAGES/django.mo +0 -0
  26. wagtail/admin/locale/it/LC_MESSAGES/django.po +566 -6
  27. wagtail/admin/locale/it/LC_MESSAGES/djangojs.mo +0 -0
  28. wagtail/admin/locale/it/LC_MESSAGES/djangojs.po +40 -2
  29. wagtail/admin/locale/nl/LC_MESSAGES/django.mo +0 -0
  30. wagtail/admin/locale/nl/LC_MESSAGES/django.po +2 -2
  31. wagtail/admin/locale/sv/LC_MESSAGES/django.mo +0 -0
  32. wagtail/admin/locale/sv/LC_MESSAGES/django.po +330 -15
  33. wagtail/admin/locale/sv/LC_MESSAGES/djangojs.mo +0 -0
  34. wagtail/admin/locale/sv/LC_MESSAGES/djangojs.po +3 -2
  35. wagtail/admin/panels/comment_panel.py +1 -51
  36. wagtail/admin/panels/title_field_panel.py +3 -1
  37. wagtail/admin/static/wagtailadmin/css/core.css +1 -1
  38. wagtail/admin/static/wagtailadmin/js/bulk-actions.js +1 -1
  39. wagtail/admin/static/wagtailadmin/js/comments.js +1 -1
  40. wagtail/admin/static/wagtailadmin/js/core.js +1 -1
  41. wagtail/admin/static/wagtailadmin/js/core.js.LICENSE.txt +1 -1
  42. wagtail/admin/static/wagtailadmin/js/draftail.js +1 -1
  43. wagtail/admin/static/wagtailadmin/js/privacy-switch.js +1 -1
  44. wagtail/admin/static/wagtailadmin/js/sidebar.js +1 -1
  45. wagtail/admin/static/wagtailadmin/js/telepath/blocks.js +1 -1
  46. wagtail/admin/static/wagtailadmin/js/userbar.js +1 -1
  47. wagtail/admin/static/wagtailadmin/js/userbar.js.LICENSE.txt +1 -1
  48. wagtail/admin/static/wagtailadmin/js/vendor.js +1 -1
  49. wagtail/admin/static/wagtailadmin/js/wagtailadmin.js +1 -1
  50. wagtail/admin/templates/wagtailadmin/base.html +1 -1
  51. wagtail/admin/templates/wagtailadmin/generic/edit_partials.html +100 -0
  52. wagtail/admin/templates/wagtailadmin/generic/form.html +26 -5
  53. wagtail/admin/templates/wagtailadmin/generic/includes/_loaded_revision_inputs.html +3 -0
  54. wagtail/admin/templates/wagtailadmin/generic/listing_results.html +1 -17
  55. wagtail/admin/templates/wagtailadmin/pages/create.html +14 -4
  56. wagtail/admin/templates/wagtailadmin/pages/edit.html +16 -3
  57. wagtail/admin/templates/wagtailadmin/pages/edit_partials.html +31 -0
  58. wagtail/admin/templates/wagtailadmin/pages/index_results.html +1 -9
  59. wagtail/admin/templates/wagtailadmin/shared/autosave/indicator.html +36 -0
  60. wagtail/admin/templates/wagtailadmin/shared/breadcrumbs.html +1 -1
  61. wagtail/admin/templates/wagtailadmin/shared/editing_sessions/list.html +2 -2
  62. wagtail/admin/templates/wagtailadmin/shared/editing_sessions/module.html +19 -3
  63. wagtail/admin/templates/wagtailadmin/shared/headers/_actions.html +5 -0
  64. wagtail/admin/templates/wagtailadmin/shared/headers/_history_icon_link.html +2 -2
  65. wagtail/admin/templates/wagtailadmin/shared/headers/slim_header.html +8 -9
  66. wagtail/admin/templates/wagtailadmin/shared/listing/filter_partials.html +19 -0
  67. wagtail/admin/templates/wagtailadmin/shared/side_panel_toggle.html +3 -3
  68. wagtail/admin/templates/wagtailadmin/shared/side_panels/includes/side_panel.html +3 -0
  69. wagtail/admin/templates/wagtailadmin/shared/side_panels/includes/status/privacy.html +18 -6
  70. wagtail/admin/templates/wagtailadmin/shared/side_panels/includes/status/usage.html +11 -4
  71. wagtail/admin/templates/wagtailadmin/shared/side_panels/includes/status/workflow.html +22 -1
  72. wagtail/admin/templates/wagtailadmin/shared/side_panels/preview.html +1 -0
  73. wagtail/admin/templates/wagtailadmin/shared/side_panels.html +1 -2
  74. wagtail/admin/templates/wagtailadmin/shared/unsaved_changes_warning.html +20 -20
  75. wagtail/admin/templates/wagtailadmin/userbar/base.html +2 -0
  76. wagtail/admin/templates/wagtailadmin/userbar/item_accessibility.html +1 -1
  77. wagtail/admin/templatetags/wagtailadmin_tags.py +6 -14
  78. wagtail/admin/tests/pages/test_create_page.py +298 -1
  79. wagtail/admin/tests/pages/test_custom_listing.py +48 -2
  80. wagtail/admin/tests/pages/test_edit_page.py +721 -7
  81. wagtail/admin/tests/pages/test_explorer_view.py +9 -5
  82. wagtail/admin/tests/pages/test_page_search.py +15 -0
  83. wagtail/admin/tests/pages/test_revisions.py +4 -0
  84. wagtail/admin/tests/test_account_management.py +88 -2
  85. wagtail/admin/tests/test_collections_views.py +15 -15
  86. wagtail/admin/tests/test_compare.py +34 -0
  87. wagtail/admin/tests/test_editing_sessions.py +230 -8
  88. wagtail/admin/tests/test_forms.py +192 -1
  89. wagtail/admin/tests/test_icon_sprite.py +22 -2
  90. wagtail/admin/tests/test_page_chooser.py +13 -13
  91. wagtail/admin/tests/test_reports_views.py +4 -1
  92. wagtail/admin/tests/test_userbar.py +69 -5
  93. wagtail/admin/tests/test_views_generic.py +5 -5
  94. wagtail/admin/tests/test_workflows.py +14 -12
  95. wagtail/admin/tests/viewsets/test_model_viewset.py +13 -0
  96. wagtail/admin/ui/autosave.py +5 -0
  97. wagtail/admin/ui/editing_sessions.py +3 -0
  98. wagtail/admin/ui/side_panels.py +19 -20
  99. wagtail/admin/userbar.py +48 -27
  100. wagtail/admin/views/bulk_action/dispatcher.py +2 -2
  101. wagtail/admin/views/chooser.py +6 -6
  102. wagtail/admin/views/editing_sessions.py +20 -7
  103. wagtail/admin/views/generic/__init__.py +1 -0
  104. wagtail/admin/views/generic/chooser.py +5 -5
  105. wagtail/admin/views/generic/mixins.py +143 -26
  106. wagtail/admin/views/generic/models.py +157 -27
  107. wagtail/admin/views/generic/ordering.py +1 -1
  108. wagtail/admin/views/generic/preview.py +4 -4
  109. wagtail/admin/views/home.py +3 -1
  110. wagtail/admin/views/pages/create.py +77 -29
  111. wagtail/admin/views/pages/edit.py +160 -34
  112. wagtail/admin/views/pages/preview.py +4 -4
  113. wagtail/admin/views/pages/revisions.py +3 -0
  114. wagtail/admin/views/pages/search.py +4 -4
  115. wagtail/admin/views/pages/usage.py +2 -2
  116. wagtail/admin/views/tags.py +2 -2
  117. wagtail/admin/views/workflows.py +73 -54
  118. wagtail/admin/viewsets/model.py +34 -8
  119. wagtail/admin/widgets/button.py +11 -4
  120. wagtail/admin/widgets/chooser.py +6 -4
  121. wagtail/admin/widgets/slug.py +1 -1
  122. wagtail/api/v2/filters.py +27 -21
  123. wagtail/api/v2/pagination.py +4 -4
  124. wagtail/api/v2/views.py +7 -7
  125. wagtail/blocks/list_block.py +0 -8
  126. wagtail/blocks/migrations/migrate_operation.py +8 -1
  127. wagtail/blocks/stream_block.py +2 -10
  128. wagtail/blocks/struct_block.py +192 -12
  129. wagtail/compat.py +2 -2
  130. wagtail/contrib/forms/locale/en/LC_MESSAGES/django.po +1 -1
  131. wagtail/contrib/forms/locale/it/LC_MESSAGES/django.mo +0 -0
  132. wagtail/contrib/forms/locale/it/LC_MESSAGES/django.po +40 -2
  133. wagtail/contrib/frontend_cache/backends/azure.py +6 -6
  134. wagtail/contrib/frontend_cache/backends/cloudfront.py +2 -2
  135. wagtail/contrib/frontend_cache/utils.py +1 -1
  136. wagtail/contrib/redirects/forms.py +1 -1
  137. wagtail/contrib/redirects/locale/en/LC_MESSAGES/django.po +14 -14
  138. wagtail/contrib/redirects/locale/it/LC_MESSAGES/django.mo +0 -0
  139. wagtail/contrib/redirects/locale/it/LC_MESSAGES/django.po +15 -2
  140. wagtail/contrib/redirects/locale/sv/LC_MESSAGES/django.mo +0 -0
  141. wagtail/contrib/redirects/locale/sv/LC_MESSAGES/django.po +16 -3
  142. wagtail/contrib/redirects/middleware.py +11 -7
  143. wagtail/contrib/redirects/models.py +17 -1
  144. wagtail/contrib/redirects/tests/test_import_admin_views.py +3 -3
  145. wagtail/contrib/redirects/tests/test_redirects.py +56 -8
  146. wagtail/contrib/search_promotions/locale/en/LC_MESSAGES/django.po +6 -6
  147. wagtail/contrib/search_promotions/locale/it/LC_MESSAGES/django.mo +0 -0
  148. wagtail/contrib/search_promotions/locale/it/LC_MESSAGES/django.po +43 -2
  149. wagtail/contrib/search_promotions/tests.py +1 -1
  150. wagtail/contrib/search_promotions/views/settings.py +24 -25
  151. wagtail/contrib/settings/jinja2tags.py +2 -2
  152. wagtail/contrib/settings/locale/en/LC_MESSAGES/django.po +2 -2
  153. wagtail/contrib/settings/locale/it/LC_MESSAGES/django.mo +0 -0
  154. wagtail/contrib/settings/locale/it/LC_MESSAGES/django.po +6 -2
  155. wagtail/contrib/settings/locale/sv/LC_MESSAGES/django.mo +0 -0
  156. wagtail/contrib/settings/locale/sv/LC_MESSAGES/django.po +3 -2
  157. wagtail/contrib/settings/tests/generic/test_admin.py +118 -2
  158. wagtail/contrib/settings/tests/site_specific/test_admin.py +78 -15
  159. wagtail/contrib/settings/tests/site_specific/test_model.py +2 -2
  160. wagtail/contrib/settings/views.py +7 -0
  161. wagtail/contrib/simple_translation/locale/en/LC_MESSAGES/django.po +1 -1
  162. wagtail/contrib/simple_translation/locale/it/LC_MESSAGES/django.mo +0 -0
  163. wagtail/contrib/simple_translation/locale/it/LC_MESSAGES/django.po +5 -2
  164. wagtail/contrib/styleguide/locale/en/LC_MESSAGES/django.po +7 -7
  165. wagtail/contrib/styleguide/tests.py +2 -0
  166. wagtail/contrib/styleguide/views.py +4 -5
  167. wagtail/contrib/table_block/locale/ar/LC_MESSAGES/django.mo +0 -0
  168. wagtail/contrib/table_block/locale/ar/LC_MESSAGES/django.po +5 -1
  169. wagtail/contrib/table_block/locale/en/LC_MESSAGES/django.po +1 -1
  170. wagtail/contrib/table_block/locale/it/LC_MESSAGES/django.mo +0 -0
  171. wagtail/contrib/table_block/locale/it/LC_MESSAGES/django.po +5 -2
  172. wagtail/contrib/typed_table_block/blocks.py +37 -0
  173. wagtail/contrib/typed_table_block/locale/en/LC_MESSAGES/django.po +10 -10
  174. wagtail/contrib/typed_table_block/locale/sv/LC_MESSAGES/django.mo +0 -0
  175. wagtail/contrib/typed_table_block/locale/sv/LC_MESSAGES/django.po +4 -3
  176. wagtail/contrib/typed_table_block/tests.py +108 -0
  177. wagtail/coreutils.py +4 -4
  178. wagtail/documents/__init__.py +4 -4
  179. wagtail/documents/locale/en/LC_MESSAGES/django.po +15 -15
  180. wagtail/documents/locale/it/LC_MESSAGES/django.mo +0 -0
  181. wagtail/documents/locale/it/LC_MESSAGES/django.po +32 -2
  182. wagtail/documents/locale/sv/LC_MESSAGES/django.mo +0 -0
  183. wagtail/documents/locale/sv/LC_MESSAGES/django.po +32 -4
  184. wagtail/documents/models.py +1 -1
  185. wagtail/documents/tests/test_admin_views.py +19 -4
  186. wagtail/documents/tests/test_search.py +0 -2
  187. wagtail/documents/tests/test_views.py +6 -4
  188. wagtail/documents/views/bulk_actions/add_tags.py +1 -1
  189. wagtail/embeds/finders/facebook.py +3 -3
  190. wagtail/embeds/finders/instagram.py +3 -3
  191. wagtail/embeds/finders/oembed.py +7 -2
  192. wagtail/embeds/locale/en/LC_MESSAGES/django.po +1 -1
  193. wagtail/embeds/oembed_providers.py +8 -0
  194. wagtail/embeds/tests/test_embeds.py +35 -0
  195. wagtail/images/__init__.py +4 -4
  196. wagtail/images/admin_urls.py +3 -3
  197. wagtail/images/blocks.py +1 -1
  198. wagtail/images/formats.py +2 -2
  199. wagtail/images/image_operations.py +2 -2
  200. wagtail/images/locale/en/LC_MESSAGES/django.po +44 -40
  201. wagtail/images/locale/it/LC_MESSAGES/django.mo +0 -0
  202. wagtail/images/locale/it/LC_MESSAGES/django.po +50 -4
  203. wagtail/images/locale/nl/LC_MESSAGES/django.mo +0 -0
  204. wagtail/images/locale/nl/LC_MESSAGES/django.po +3 -3
  205. wagtail/images/locale/sv/LC_MESSAGES/django.mo +0 -0
  206. wagtail/images/locale/sv/LC_MESSAGES/django.po +49 -6
  207. wagtail/images/models.py +11 -10
  208. wagtail/images/templates/wagtailimages/images/index_results.html +1 -1
  209. wagtail/images/templates/wagtailimages/images/url_generator.html +17 -38
  210. wagtail/images/templates/wagtailimages/images/url_generator_output.html +28 -0
  211. wagtail/images/templatetags/wagtailimages_tags.py +4 -4
  212. wagtail/images/tests/test_admin_views.py +432 -59
  213. wagtail/images/tests/test_image_operations.py +2 -2
  214. wagtail/images/tests/test_models.py +0 -2
  215. wagtail/images/views/bulk_actions/add_tags.py +1 -1
  216. wagtail/images/views/images.py +72 -39
  217. wagtail/locale/en/LC_MESSAGES/django.po +103 -105
  218. wagtail/locale/it/LC_MESSAGES/django.mo +0 -0
  219. wagtail/locale/it/LC_MESSAGES/django.po +105 -2
  220. wagtail/locale/sv/LC_MESSAGES/django.mo +0 -0
  221. wagtail/locale/sv/LC_MESSAGES/django.po +95 -12
  222. wagtail/locales/locale/en/LC_MESSAGES/django.po +1 -1
  223. wagtail/locales/locale/it/LC_MESSAGES/django.mo +0 -0
  224. wagtail/locales/locale/it/LC_MESSAGES/django.po +13 -2
  225. wagtail/locales/locale/sv/LC_MESSAGES/django.mo +0 -0
  226. wagtail/locales/locale/sv/LC_MESSAGES/django.po +4 -3
  227. wagtail/locales/tests.py +5 -5
  228. wagtail/models/i18n.py +4 -6
  229. wagtail/models/media.py +18 -0
  230. wagtail/models/pages.py +65 -11
  231. wagtail/models/reference_index.py +15 -0
  232. wagtail/models/revisions.py +40 -12
  233. wagtail/models/workflows.py +0 -3
  234. wagtail/permission_policies/base.py +2 -2
  235. wagtail/permission_policies/collections.py +2 -2
  236. wagtail/project_template/requirements.txt +2 -2
  237. wagtail/search/locale/en/LC_MESSAGES/django.po +1 -1
  238. wagtail/signal_handlers.py +1 -0
  239. wagtail/sites/locale/en/LC_MESSAGES/django.po +1 -1
  240. wagtail/sites/locale/sv/LC_MESSAGES/django.mo +0 -0
  241. wagtail/sites/locale/sv/LC_MESSAGES/django.po +3 -2
  242. wagtail/sites/tests.py +7 -7
  243. wagtail/snippets/action_menu.py +2 -1
  244. wagtail/snippets/locale/en/LC_MESSAGES/django.po +23 -13
  245. wagtail/snippets/locale/sv/LC_MESSAGES/django.mo +0 -0
  246. wagtail/snippets/locale/sv/LC_MESSAGES/django.po +12 -1
  247. wagtail/snippets/tests/test_chooser_block.py +197 -0
  248. wagtail/snippets/tests/test_chooser_panel.py +149 -0
  249. wagtail/snippets/tests/test_chooser_views.py +375 -0
  250. wagtail/snippets/tests/test_chooser_widget.py +22 -0
  251. wagtail/snippets/tests/test_compare_revisions_view.py +167 -0
  252. wagtail/snippets/tests/test_copy_view.py +38 -0
  253. wagtail/snippets/tests/test_create_view.py +1159 -0
  254. wagtail/snippets/tests/test_delete_view.py +225 -0
  255. wagtail/snippets/tests/test_edit_view.py +2882 -0
  256. wagtail/snippets/tests/test_history_view.py +182 -0
  257. wagtail/snippets/tests/test_index_view.py +105 -0
  258. wagtail/snippets/tests/test_list_view.py +786 -0
  259. wagtail/snippets/tests/test_locking.py +28 -0
  260. wagtail/snippets/tests/test_permissions.py +167 -0
  261. wagtail/snippets/tests/test_preview.py +3 -3
  262. wagtail/snippets/tests/test_revert_view.py +343 -0
  263. wagtail/snippets/tests/test_snippet_models.py +112 -0
  264. wagtail/snippets/tests/test_unpublish_view.py +228 -0
  265. wagtail/snippets/tests/test_unschedule_view.py +151 -0
  266. wagtail/snippets/tests/test_viewset.py +38 -5
  267. wagtail/snippets/views/snippets.py +78 -30
  268. wagtail/snippets/widgets.py +2 -2
  269. wagtail/templatetags/wagtailcore_tags.py +2 -2
  270. wagtail/test/dummy_external_storage.py +1 -1
  271. wagtail/test/testapp/fixtures/test.json +22 -0
  272. wagtail/test/testapp/fixtures/test_empty.json +2 -0
  273. wagtail/test/testapp/fixtures/test_explorable_pages.json +10 -0
  274. wagtail/test/testapp/fixtures/test_specific.json +9 -0
  275. wagtail/test/testapp/migrations/0059_nopromotepage.py +25 -0
  276. wagtail/test/testapp/models.py +7 -0
  277. wagtail/test/testapp/views.py +5 -0
  278. wagtail/test/utils/page_tests.py +7 -7
  279. wagtail/test/utils/wagtail_factories/builder.py +2 -2
  280. wagtail/tests/streamfield_migrations/test_migrations.py +35 -0
  281. wagtail/tests/test_blocks.py +321 -61
  282. wagtail/tests/test_collection_model.py +12 -0
  283. wagtail/tests/test_page_model.py +190 -1
  284. wagtail/tests/test_page_privacy.py +5 -0
  285. wagtail/tests/test_reference_index.py +42 -0
  286. wagtail/tests/test_revision_model.py +118 -1
  287. wagtail/tests/test_utils.py +2 -2
  288. wagtail/users/locale/en/LC_MESSAGES/django.po +14 -14
  289. wagtail/users/locale/id_ID/LC_MESSAGES/django.mo +0 -0
  290. wagtail/users/locale/id_ID/LC_MESSAGES/django.po +6 -2
  291. wagtail/users/locale/it/LC_MESSAGES/django.mo +0 -0
  292. wagtail/users/locale/it/LC_MESSAGES/django.po +14 -2
  293. wagtail/users/locale/sv/LC_MESSAGES/django.mo +0 -0
  294. wagtail/users/locale/sv/LC_MESSAGES/django.po +48 -17
  295. wagtail/users/tests/test_admin_views.py +39 -25
  296. wagtail/users/utils.py +4 -1
  297. wagtail/users/views/groups.py +19 -5
  298. wagtail/users/wagtail_hooks.py +1 -1
  299. wagtail/utils/loading.py +2 -2
  300. wagtail-7.3rc1.data/data/AGENTS.md +21 -0
  301. wagtail-7.3rc1.data/data/CHANGELOG.txt +4941 -0
  302. wagtail-7.3rc1.data/data/CODE_OF_CONDUCT.md +71 -0
  303. wagtail-7.3rc1.data/data/CONTRIBUTORS.md +999 -0
  304. wagtail-7.3rc1.data/data/SPONSORS.md +55 -0
  305. {wagtail-7.2.1.dist-info → wagtail-7.3rc1.dist-info}/METADATA +6 -6
  306. {wagtail-7.2.1.dist-info → wagtail-7.3rc1.dist-info}/RECORD +310 -280
  307. {wagtail-7.2.1.dist-info → wagtail-7.3rc1.dist-info}/WHEEL +1 -1
  308. wagtail/images/static/wagtailimages/js/image-url-generator.js +0 -1
  309. wagtail/snippets/tests/test_snippets.py +0 -6377
  310. {wagtail-7.2.1.dist-info → wagtail-7.3rc1.dist-info}/entry_points.txt +0 -0
  311. {wagtail-7.2.1.dist-info → wagtail-7.3rc1.dist-info}/licenses/LICENSE +0 -0
  312. {wagtail-7.2.1.dist-info → wagtail-7.3rc1.dist-info}/top_level.txt +0 -0
@@ -4,8 +4,9 @@ from urllib.parse import quote
4
4
  from django.conf import settings
5
5
  from django.contrib.auth import get_user_model
6
6
  from django.core.exceptions import PermissionDenied
7
+ from django.db import transaction
7
8
  from django.db.models import Prefetch, Q
8
- from django.http import HttpResponse
9
+ from django.http import HttpResponse, JsonResponse
9
10
  from django.shortcuts import get_object_or_404, redirect
10
11
  from django.urls import reverse
11
12
  from django.utils import timezone
@@ -20,6 +21,7 @@ from wagtail.admin.action_menu import PageActionMenu
20
21
  from wagtail.admin.mail import send_notification
21
22
  from wagtail.admin.models import EditingSession
22
23
  from wagtail.admin.telepath import JSContext
24
+ from wagtail.admin.ui.autosave import AutosaveIndicator
23
25
  from wagtail.admin.ui.components import MediaContainer
24
26
  from wagtail.admin.ui.editing_sessions import EditingSessionsModule
25
27
  from wagtail.admin.ui.side_panels import (
@@ -29,7 +31,7 @@ from wagtail.admin.ui.side_panels import (
29
31
  PreviewSidePanel,
30
32
  )
31
33
  from wagtail.admin.utils import get_valid_next_url_from_request
32
- from wagtail.admin.views.generic import HookResponseMixin
34
+ from wagtail.admin.views.generic import HookResponseMixin, JsonPostResponseMixin
33
35
  from wagtail.admin.views.generic.base import WagtailAdminTemplateMixin
34
36
  from wagtail.exceptions import PageClassNotFoundError
35
37
  from wagtail.locks import BasicLock, ScheduledForPublishLock, WorkflowLock
@@ -39,13 +41,18 @@ from wagtail.models import (
39
41
  CommentReply,
40
42
  Page,
41
43
  PageSubscription,
44
+ Revision,
42
45
  WorkflowState,
43
46
  get_default_page_content_type,
44
47
  )
45
48
  from wagtail.utils.timestamps import render_timestamp
46
49
 
47
50
 
48
- class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
51
+ class EditView(
52
+ WagtailAdminTemplateMixin, HookResponseMixin, JsonPostResponseMixin, View
53
+ ):
54
+ partials_template_name = "wagtailadmin/pages/edit_partials.html"
55
+
49
56
  def get_page_title(self):
50
57
  return _("Editing %(page_type)s") % {
51
58
  "page_type": self.page_class.get_verbose_name()
@@ -57,7 +64,8 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
57
64
  def get_template_names(self):
58
65
  if self.page.alias_of_id:
59
66
  return ["wagtailadmin/pages/edit_alias.html"]
60
-
67
+ elif self.hydrate_create_view:
68
+ return [self.partials_template_name]
61
69
  else:
62
70
  return ["wagtailadmin/pages/edit.html"]
63
71
 
@@ -370,7 +378,15 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
370
378
 
371
379
  response = self.run_hook("before_edit_page", self.request, self.page)
372
380
  if response:
373
- return response
381
+ if self.expects_json_response and not self.response_is_json(response):
382
+ # Hook response is not suitable for a JSON response, so construct our own error response
383
+ return self.json_error_response(
384
+ "blocked_by_hook",
385
+ _("Request to edit %(model_name)s was blocked by hook.")
386
+ % {"model_name": Page._meta.verbose_name},
387
+ )
388
+ else:
389
+ return response
374
390
 
375
391
  self.subscription, created = PageSubscription.objects.get_or_create(
376
392
  page=self.page,
@@ -404,7 +420,10 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
404
420
  else:
405
421
  self.workflow_tasks = []
406
422
 
423
+ self.has_unsaved_changes = False
407
424
  self.errors_debug = None
425
+ self.autosave_interval = getattr(settings, "WAGTAIL_AUTOSAVE_INTERVAL", 500)
426
+ self.autosave_enabled = self.autosave_interval > 0
408
427
 
409
428
  return super().dispatch(request, page_id, **kwargs)
410
429
 
@@ -448,8 +467,6 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
448
467
  parent_page=self.parent,
449
468
  for_user=self.request.user,
450
469
  )
451
- self.has_unsaved_changes = False
452
- self.page_for_status = self.get_page_for_status()
453
470
 
454
471
  return self.render_to_response(self.get_context_data())
455
472
 
@@ -475,12 +492,57 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
475
492
  and self.workflow_state.user_can_cancel(self.request.user)
476
493
  )
477
494
 
495
+ @cached_property
496
+ def hydrate_create_view(self):
497
+ return bool(self.request.GET.get("_w_hydrate_create_view"))
498
+
499
+ @cached_property
500
+ def latest_revision_created_at(self):
501
+ return self.latest_revision and self.latest_revision.created_at.isoformat()
502
+
503
+ @cached_property
504
+ def is_out_of_date(self):
505
+ if not self.latest_revision:
506
+ return False
507
+
508
+ latest_revision_id = str(self.latest_revision.pk)
509
+
510
+ # Two different sessions cannot have the same autosave revision, so if
511
+ # autosave revision is present, it is either the latest or it is not.
512
+ if overwrite_revision_id := self.request.POST.get("overwrite_revision_id"):
513
+ return overwrite_revision_id != latest_revision_id
514
+
515
+ # Client has not made an autosave revision, check the loaded revision.
516
+ if loaded_revision_id := self.request.POST.get("loaded_revision_id"):
517
+ # If the loaded revision is not the latest revision, it is outdated.
518
+ if loaded_revision_id != latest_revision_id:
519
+ return True
520
+
521
+ # It's pointing to the latest revision, but that revision may have
522
+ # been overwritten by another session (via autosave) since the editor
523
+ # loaded it. The created_at is strictly increasing, so assume the
524
+ # loaded revision outdated if it doesn't match the latest created_at.
525
+ return (
526
+ self.request.POST.get("loaded_revision_created_at", "")
527
+ != self.latest_revision_created_at
528
+ )
529
+
530
+ # Not enough information to deduce, assume it's up to date.
531
+ return False
532
+
478
533
  def post(self, request, *args, **kwargs):
479
534
  # Don't allow POST requests if the page is an alias
480
535
  if self.page.alias_of_id:
481
536
  # Return 405 "Method Not Allowed" response
482
537
  return HttpResponse(status=405)
483
538
 
539
+ # For autosave, we only want to save the page if there are no conflicts
540
+ if self.expects_json_response and self.is_out_of_date:
541
+ return self.json_error_response(
542
+ "invalid_revision",
543
+ _("Saving will overwrite a newer version."),
544
+ )
545
+
484
546
  self.form = self.form_class(
485
547
  self.request.POST,
486
548
  self.request.FILES,
@@ -548,18 +610,43 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
548
610
  return self.action_method()
549
611
 
550
612
  def save_action(self):
551
- self.page = self.form.save(commit=not self.page.live)
552
- self.subscription.save()
553
-
554
- # Save revision
555
- revision = self.page.save_revision(
556
- user=self.request.user,
557
- log_action=True, # Always log the new revision on edit
558
- previous_revision=self.previous_revision,
559
- clean=False,
560
- )
613
+ try:
614
+ with transaction.atomic():
615
+ self.page = self.form.save(commit=not self.page.live)
616
+ self.subscription.save()
617
+
618
+ overwrite_revision_id = self.request.POST.get("overwrite_revision_id")
619
+ if overwrite_revision_id:
620
+ try:
621
+ overwrite_revision = self.page.revisions.get(
622
+ pk=overwrite_revision_id
623
+ )
624
+ except Revision.DoesNotExist as e:
625
+ raise PermissionDenied(
626
+ "Cannot overwrite a revision that does not exist."
627
+ ) from e
628
+ else:
629
+ overwrite_revision = None
630
+
631
+ # Save revision
632
+ revision = self.page.save_revision(
633
+ user=self.request.user,
634
+ log_action=True, # Always log the new revision on edit
635
+ previous_revision=self.previous_revision,
636
+ overwrite_revision=overwrite_revision,
637
+ clean=False,
638
+ )
639
+ except PermissionDenied as e:
640
+ # The revision passed to overwrite_revision was not valid
641
+ if self.expects_json_response:
642
+ return self.json_error_response("invalid_revision", str(e))
643
+ else:
644
+ messages.error(self.request, str(e))
645
+ self.has_unsaved_changes = True
646
+ return self.render_to_response(self.get_context_data())
561
647
 
562
- self.add_save_confirmation_message()
648
+ if not self.expects_json_response:
649
+ self.add_save_confirmation_message()
563
650
 
564
651
  if self.has_content_changes and "comments" in self.form.formsets:
565
652
  changes = self.get_commenting_changes()
@@ -568,10 +655,28 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
568
655
 
569
656
  response = self.run_hook("after_edit_page", self.request, self.page)
570
657
  if response:
571
- return response
658
+ if self.expects_json_response and not self.response_is_json(response):
659
+ # Hook response is not suitable for a JSON response, so ignore it and just use
660
+ # the standard one
661
+ pass
662
+ else:
663
+ return response
572
664
 
573
665
  # Just saving - remain on edit page for further edits
574
- return self.redirect_and_remain()
666
+ if self.expects_json_response:
667
+ return JsonResponse(
668
+ {
669
+ "success": True,
670
+ "pk": self.page.pk,
671
+ "revision_id": revision.pk,
672
+ "revision_created_at": revision.created_at.isoformat(),
673
+ "field_updates": dict(self.form.get_field_updates_for_resave()),
674
+ "comments": self.form.serialize_comments(self.request.user),
675
+ "html": self.render_partials(),
676
+ }
677
+ )
678
+ else:
679
+ return self.redirect_and_remain()
575
680
 
576
681
  def publish_action(self):
577
682
  self.page = self.form.save(commit=not self.page.live)
@@ -851,15 +956,26 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
851
956
  self.request.user
852
957
  )
853
958
  elif self.locked_for_user:
854
- messages.error(
855
- self.request, _("The page could not be saved as it is locked.")
856
- )
959
+ if self.expects_json_response:
960
+ return self.json_error_response(
961
+ "locked", _("The page could not be saved as it is locked.")
962
+ )
963
+ else:
964
+ messages.error(
965
+ self.request, _("The page could not be saved as it is locked.")
966
+ )
857
967
  else:
858
- messages.validation_error(
859
- self.request,
860
- _("The page could not be saved due to validation errors."),
861
- self.form,
862
- )
968
+ if self.expects_json_response:
969
+ return self.json_error_response(
970
+ "validation_error",
971
+ _("There are validation errors, click save to highlight them."),
972
+ )
973
+ else:
974
+ messages.validation_error(
975
+ self.request,
976
+ _("The page could not be saved due to validation errors."),
977
+ self.form,
978
+ )
863
979
  self.errors_debug = repr(self.form.errors) + repr(
864
980
  [
865
981
  (name, formset.errors)
@@ -869,8 +985,6 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
869
985
  )
870
986
  self.has_unsaved_changes = True
871
987
 
872
- self.page_for_status = self.get_page_for_status()
873
-
874
988
  return self.render_to_response(self.get_context_data())
875
989
 
876
990
  def get_preview_url(self):
@@ -894,14 +1008,19 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
894
1008
  parent_page=self.page.get_parent(),
895
1009
  ),
896
1010
  ]
897
- if self.page.is_previewable():
1011
+ if not self.expects_json_response and self.page.is_previewable():
898
1012
  side_panels.append(
899
1013
  PreviewSidePanel(
900
1014
  self.page, self.request, preview_url=self.get_preview_url()
901
1015
  )
902
1016
  )
903
- side_panels.append(ChecksSidePanel(self.page, self.request))
904
- if self.form.show_comments_toggle:
1017
+ if not self.hydrate_create_view:
1018
+ side_panels.append(ChecksSidePanel(self.page, self.request))
1019
+ if (
1020
+ not self.expects_json_response
1021
+ and not self.hydrate_create_view
1022
+ and self.form.show_comments_toggle
1023
+ ):
905
1024
  side_panels.append(CommentsSidePanel(self.page, self.request))
906
1025
  return MediaContainer(side_panels)
907
1026
 
@@ -926,6 +1045,7 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
926
1045
  ),
927
1046
  [],
928
1047
  self.page.latest_revision_id,
1048
+ self.latest_revision_created_at,
929
1049
  )
930
1050
 
931
1051
  def get_action_menu(self):
@@ -956,7 +1076,7 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
956
1076
  context.update(
957
1077
  {
958
1078
  "page": self.page,
959
- "page_for_status": self.page_for_status,
1079
+ "page_for_status": self.get_page_for_status(),
960
1080
  "content_type": self.page_content_type,
961
1081
  "edit_handler": bound_panel,
962
1082
  "edit_handler_data": edit_handler_data,
@@ -985,7 +1105,13 @@ class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
985
1105
  and user_perms.can_unlock(),
986
1106
  "locale": self.locale,
987
1107
  "media": media,
1108
+ "autosave_enabled": self.autosave_enabled,
1109
+ "autosave_interval": self.autosave_interval,
1110
+ "autosave_indicator": AutosaveIndicator(),
988
1111
  "editing_sessions": self.get_editing_sessions(),
1112
+ "loaded_revision_created_at": self.latest_revision_created_at,
1113
+ "is_partial": self.expects_json_response or self.hydrate_create_view,
1114
+ "hydrate_create_view": self.hydrate_create_view,
989
1115
  }
990
1116
  )
991
1117
 
@@ -18,8 +18,8 @@ def view_draft(request, page_id):
18
18
 
19
19
  try:
20
20
  preview_mode = request.GET.get("mode", page.default_preview_mode)
21
- except IndexError:
22
- raise PermissionDenied
21
+ except IndexError as e:
22
+ raise PermissionDenied from e
23
23
 
24
24
  return page.make_preview_request(request, preview_mode)
25
25
 
@@ -82,8 +82,8 @@ class PreviewOnCreate(PreviewOnEdit):
82
82
  content_type = ContentType.objects.get_by_natural_key(
83
83
  content_type_app_name, content_type_model_name
84
84
  )
85
- except ContentType.DoesNotExist:
86
- raise Http404
85
+ except ContentType.DoesNotExist as e:
86
+ raise Http404 from e
87
87
 
88
88
  page = content_type.model_class()()
89
89
  parent_page = get_object_or_404(Page, id=parent_page_id).specific
@@ -72,6 +72,9 @@ class RevisionsRevertView(EditView):
72
72
  def get_context_data(self, **kwargs):
73
73
  context = super().get_context_data(**kwargs)
74
74
  context["action_url"] = self.get_revisions_revert_url()
75
+ # Autosave does not make much sense in this view, we want the user to
76
+ # explicitly confirm they want to revert to the previous revision
77
+ context["autosave_enabled"] = False
75
78
  return context
76
79
 
77
80
 
@@ -91,15 +91,15 @@ class SearchView(PageListingMixin, PermissionCheckedMixin, BaseListingView):
91
91
  if "content_type" in request.GET:
92
92
  try:
93
93
  app_label, model_name = request.GET["content_type"].split(".")
94
- except ValueError:
95
- raise Http404
94
+ except ValueError as e:
95
+ raise Http404 from e
96
96
 
97
97
  try:
98
98
  self.selected_content_type = ContentType.objects.get_by_natural_key(
99
99
  app_label, model_name
100
100
  )
101
- except ContentType.DoesNotExist:
102
- raise Http404
101
+ except ContentType.DoesNotExist as e:
102
+ raise Http404 from e
103
103
 
104
104
  else:
105
105
  self.selected_content_type = None
@@ -46,8 +46,8 @@ class ContentTypeUseView(PageListingMixin, PermissionCheckedMixin, BaseListingVi
46
46
  content_type = ContentType.objects.get_by_natural_key(
47
47
  content_type_app_name, content_type_model_name
48
48
  )
49
- except ContentType.DoesNotExist:
50
- raise Http404
49
+ except ContentType.DoesNotExist as e:
50
+ raise Http404 from e
51
51
 
52
52
  self.page_class = content_type.model_class()
53
53
 
@@ -9,8 +9,8 @@ def autocomplete(request, app_name=None, model_name=None):
9
9
  if app_name and model_name:
10
10
  try:
11
11
  content_type = ContentType.objects.get_by_natural_key(app_name, model_name)
12
- except ContentType.DoesNotExist:
13
- raise Http404
12
+ except ContentType.DoesNotExist as e:
13
+ raise Http404 from e
14
14
 
15
15
  tag_model = content_type.model_class()
16
16
  if not issubclass(tag_model, TagBase):
@@ -184,6 +184,12 @@ class Create(CreateView):
184
184
  def get_form_class(self):
185
185
  return self.get_edit_handler().get_form_class()
186
186
 
187
+ def get_initial_form_instance(self):
188
+ # defining this method ensures that self.object is set to a new instance
189
+ # while constructing the form, making it available to get_pages_formset
190
+ # and get_content_type_form during is_valid
191
+ return self.model()
192
+
187
193
  def get_pages_formset(self):
188
194
  if self.request.method == "POST":
189
195
  return WorkflowPagesFormSet(
@@ -213,36 +219,41 @@ class Create(CreateView):
213
219
  context["media"] = form.media + bound_panel.media + pages_formset.media
214
220
  return context
215
221
 
222
+ def is_valid(self, form):
223
+ if not super().is_valid(form):
224
+ return False
225
+
226
+ self.pages_formset = self.get_pages_formset()
227
+ self.content_type_form = self.get_content_type_form()
228
+
229
+ if not self.pages_formset.is_valid() or not self.content_type_form.is_valid():
230
+ self.produced_error_message = self.get_error_message()
231
+ return False
232
+
233
+ return True
234
+
216
235
  def form_valid(self, form):
217
236
  self.form = form
218
237
 
219
238
  with transaction.atomic():
220
239
  self.object = self.save_instance()
221
240
 
222
- pages_formset = self.get_pages_formset()
223
- content_type_form = self.get_content_type_form()
224
- if pages_formset.is_valid() and content_type_form.is_valid():
225
- pages_formset.save()
226
- content_type_form.save()
227
-
228
- success_message = self.get_success_message(self.object)
229
- if success_message is not None:
230
- messages.success(
231
- self.request,
232
- success_message,
233
- buttons=[
234
- messages.button(
235
- reverse(self.edit_url_name, args=(self.object.id,)),
236
- _("Edit"),
237
- )
238
- ],
239
- )
240
- return redirect(self.get_success_url())
241
-
242
- else:
243
- transaction.set_rollback(True)
244
-
245
- return self.form_invalid(form)
241
+ self.pages_formset.save()
242
+ self.content_type_form.save()
243
+
244
+ success_message = self.get_success_message(self.object)
245
+ if success_message is not None:
246
+ messages.success(
247
+ self.request,
248
+ success_message,
249
+ buttons=[
250
+ messages.button(
251
+ reverse(self.edit_url_name, args=(self.object.id,)),
252
+ _("Edit"),
253
+ )
254
+ ],
255
+ )
256
+ return redirect(self.get_success_url())
246
257
 
247
258
 
248
259
  class Edit(EditView):
@@ -326,42 +337,50 @@ class Edit(EditView):
326
337
  def get_enable_url(self):
327
338
  return reverse(self.enable_url_name, args=(self.object.pk,))
328
339
 
340
+ def is_valid(self, form):
341
+ if not super().is_valid(form):
342
+ return False
343
+
344
+ if self.object.active:
345
+ # Note: These are hidden when the workflow is inactive
346
+ self.pages_formset = self.get_pages_formset()
347
+ self.content_type_form = self.get_content_type_form()
348
+
349
+ if (
350
+ not self.pages_formset.is_valid()
351
+ or not self.content_type_form.is_valid()
352
+ ):
353
+ self.produced_error_message = self.get_error_message()
354
+ return False
355
+
356
+ return True
357
+
329
358
  @transaction.atomic()
330
359
  def form_valid(self, form):
331
360
  self.form = form
332
361
 
333
362
  with transaction.atomic():
334
363
  self.object = self.save_instance()
335
- successful = True
336
364
 
337
- # Save pages formset and content type form
338
- # Note: These are hidden when the workflow is inactive
339
365
  if self.object.active:
340
- pages_formset = self.get_pages_formset()
341
- content_type_form = self.get_content_type_form()
342
- if pages_formset.is_valid() and content_type_form.is_valid():
343
- pages_formset.save()
344
- content_type_form.save()
345
- else:
346
- transaction.set_rollback(True)
347
- successful = False
348
-
349
- if successful:
350
- success_message = self.get_success_message()
351
- if success_message is not None:
352
- messages.success(
353
- self.request,
354
- success_message,
355
- buttons=[
356
- messages.button(
357
- reverse(self.edit_url_name, args=(self.object.id,)),
358
- _("Edit"),
359
- )
360
- ],
361
- )
362
- return redirect(self.get_success_url())
363
-
364
- return self.form_invalid(form)
366
+ # Save pages formset and content type form
367
+ # Note: These are hidden when the workflow is inactive
368
+ self.pages_formset.save()
369
+ self.content_type_form.save()
370
+
371
+ success_message = self.get_success_message()
372
+ if success_message is not None:
373
+ messages.success(
374
+ self.request,
375
+ success_message,
376
+ buttons=[
377
+ messages.button(
378
+ reverse(self.edit_url_name, args=(self.object.id,)),
379
+ _("Edit"),
380
+ )
381
+ ],
382
+ )
383
+ return redirect(self.get_success_url())
365
384
 
366
385
 
367
386
  class Disable(DeleteView):
@@ -651,8 +670,8 @@ class CreateTask(CreateView):
651
670
  content_type = ContentType.objects.get_by_natural_key(
652
671
  self.kwargs["app_label"], self.kwargs["model_name"]
653
672
  )
654
- except (ContentType.DoesNotExist, AttributeError):
655
- raise Http404
673
+ except (ContentType.DoesNotExist, AttributeError) as e:
674
+ raise Http404 from e
656
675
 
657
676
  # Get class
658
677
  model = content_type.model_class()
@@ -2,8 +2,10 @@ from django.contrib.auth import get_permission_codename
2
2
  from django.contrib.auth.models import Permission
3
3
  from django.contrib.contenttypes.models import ContentType
4
4
  from django.core.exceptions import ImproperlyConfigured
5
+ from django.db import models
5
6
  from django.forms.models import modelform_factory
6
7
  from django.urls import path
8
+ from django.urls.converters import get_converters
7
9
  from django.utils.functional import cached_property
8
10
  from django.utils.text import capfirst
9
11
 
@@ -129,6 +131,24 @@ class ModelViewSet(ViewSet):
129
131
  def permission_policy(self):
130
132
  return ModelPermissionPolicy(self.model)
131
133
 
134
+ @cached_property
135
+ def pk_path_converter(self):
136
+ """
137
+ :ref:`Path converter <topics/http/urls:path converters>` to use for
138
+ the model's primary key in URL patterns. Defaults to ``"int"`` for
139
+ ``IntegerField``, ``"uuid"`` for ``UUIDField``, and ``"str"`` for all
140
+ other types.
141
+
142
+ .. versionadded:: 7.3
143
+ The ``pk_path_converter`` property was added.
144
+ """
145
+ if isinstance(self.model_opts.pk, models.UUIDField):
146
+ return "uuid"
147
+ if isinstance(self.model_opts.pk, models.IntegerField):
148
+ return "int"
149
+ # Default to string if unknown
150
+ return "str"
151
+
132
152
  @cached_property
133
153
  def name(self):
134
154
  """
@@ -649,38 +669,44 @@ class ModelViewSet(ViewSet):
649
669
  hooks.register("register_permissions", self.get_permissions_to_register)
650
670
 
651
671
  def get_urlpatterns(self):
672
+ conv = self.pk_path_converter
652
673
  urlpatterns = [
653
674
  path("", self.index_view, name="index"),
654
675
  path("results/", self.index_results_view, name="index_results"),
655
676
  path("new/", self.add_view, name="add"),
656
- path("edit/<str:pk>/", self.edit_view, name="edit"),
657
- path("delete/<str:pk>/", self.delete_view, name="delete"),
658
- path("history/<str:pk>/", self.history_view, name="history"),
677
+ path(f"edit/<{conv}:pk>/", self.edit_view, name="edit"),
678
+ path(f"delete/<{conv}:pk>/", self.delete_view, name="delete"),
679
+ path(f"history/<{conv}:pk>/", self.history_view, name="history"),
659
680
  path(
660
- "history-results/<str:pk>/",
681
+ f"history-results/<{conv}:pk>/",
661
682
  self.history_results_view,
662
683
  name="history_results",
663
684
  ),
664
- path("usage/<str:pk>/", self.usage_view, name="usage"),
685
+ path(f"usage/<{conv}:pk>/", self.usage_view, name="usage"),
665
686
  ]
666
687
 
667
688
  if self.reorder_view_enabled:
668
689
  urlpatterns.append(
669
- path("reorder/<str:pk>/", self.reorder_view, name="reorder")
690
+ path(f"reorder/<{conv}:pk>/", self.reorder_view, name="reorder")
670
691
  )
671
692
 
672
693
  if self.inspect_view_enabled:
673
694
  urlpatterns.append(
674
- path("inspect/<str:pk>/", self.inspect_view, name="inspect")
695
+ path(f"inspect/<{conv}:pk>/", self.inspect_view, name="inspect")
675
696
  )
676
697
 
677
698
  if self.copy_view_enabled:
678
- urlpatterns.append(path("copy/<str:pk>/", self.copy_view, name="copy"))
699
+ urlpatterns.append(path(f"copy/<{conv}:pk>/", self.copy_view, name="copy"))
679
700
 
680
701
  return urlpatterns
681
702
 
682
703
  def on_register(self):
683
704
  super().on_register()
705
+ if self.pk_path_converter not in get_converters():
706
+ raise ImproperlyConfigured(
707
+ f"{self.__class__.__name__}.pk_path_converter is not a "
708
+ "registered path converter"
709
+ )
684
710
  self.register_admin_url_finder()
685
711
  self.register_reference_index()
686
712
  self.register_permissions()