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
@@ -8,7 +8,7 @@ from django.core.exceptions import (
8
8
  from django.db import models, transaction
9
9
  from django.db.models.constants import LOOKUP_SEP
10
10
  from django.db.models.functions import Cast
11
- from django.http import Http404, HttpResponseRedirect
11
+ from django.http import Http404, HttpResponseRedirect, JsonResponse
12
12
  from django.shortcuts import get_object_or_404, redirect
13
13
  from django.urls import reverse
14
14
  from django.utils.functional import cached_property
@@ -39,7 +39,11 @@ from wagtail.admin.ui.tables import (
39
39
  UpdatedAtColumn,
40
40
  )
41
41
  from wagtail.admin.ui.tables.orderable import OrderableTableMixin
42
- from wagtail.admin.utils import get_latest_str, get_valid_next_url_from_request
42
+ from wagtail.admin.utils import (
43
+ get_latest_str,
44
+ get_valid_next_url_from_request,
45
+ set_query_params,
46
+ )
43
47
  from wagtail.admin.views.mixins import SpreadsheetExportMixin
44
48
  from wagtail.admin.widgets.button import (
45
49
  BaseButton,
@@ -55,7 +59,13 @@ from wagtail.models.orderable import set_max_order
55
59
  from wagtail.search.index import class_is_indexed
56
60
 
57
61
  from .base import BaseListingView, WagtailAdminTemplateMixin
58
- from .mixins import BeforeAfterHookMixin, HookResponseMixin, LocaleMixin, PanelMixin
62
+ from .mixins import (
63
+ BeforeAfterHookMixin,
64
+ HookResponseMixin,
65
+ JsonPostResponseMixin,
66
+ LocaleMixin,
67
+ PanelMixin,
68
+ )
59
69
  from .permissions import PermissionCheckedMixin
60
70
 
61
71
 
@@ -468,6 +478,7 @@ class CreateView(
468
478
  PermissionCheckedMixin,
469
479
  BeforeAfterHookMixin,
470
480
  WagtailAdminTemplateMixin,
481
+ JsonPostResponseMixin,
471
482
  BaseCreateView,
472
483
  ):
473
484
  model = None
@@ -475,6 +486,7 @@ class CreateView(
475
486
  index_url_name = None
476
487
  add_url_name = None
477
488
  edit_url_name = None
489
+ usage_url_name = None
478
490
  template_name = "wagtailadmin/generic/create.html"
479
491
  page_title = gettext_lazy("New")
480
492
  permission_required = "add"
@@ -491,6 +503,32 @@ class CreateView(
491
503
  super().setup(request, *args, **kwargs)
492
504
  self.action = self.get_action(request)
493
505
 
506
+ def post(self, request, *args, **kwargs):
507
+ # BaseCreateView.post() would set self.object here, but some subclasses need
508
+ # to do that during setup() instead, so only do that if it hasn't already been set.
509
+ if not hasattr(self, "object"):
510
+ self.object = None
511
+
512
+ form = self.get_form()
513
+ if self.is_valid(form):
514
+ return self.form_valid(form)
515
+ else:
516
+ return self.form_invalid(form)
517
+
518
+ def is_valid(self, form):
519
+ """
520
+ Validate the form, setting the produced_error_message attribute if validation fails.
521
+
522
+ Subclasses that require extra validation conditions not handled by
523
+ form.is_valid() (such as formsets, or a locked status on the model) should
524
+ override this method.
525
+ """
526
+ result = form.is_valid()
527
+ if not result:
528
+ self.produced_error_code = "validation_error"
529
+ self.produced_error_message = self.get_error_message()
530
+ return result
531
+
494
532
  def get_action(self, request):
495
533
  for action in self.get_available_actions():
496
534
  if request.POST.get(f"action-{action}"):
@@ -568,8 +606,13 @@ class CreateView(
568
606
  return [messages.button(self.get_edit_url(), _("Edit"))]
569
607
 
570
608
  def get_error_message(self):
609
+ """
610
+ Return the top-level error message to be shown when the form is invalid.
611
+ """
571
612
  if self.error_message is None:
572
613
  return None
614
+ if self.expects_json_response:
615
+ return _("There are validation errors, click save to highlight them.")
573
616
  return capfirst(
574
617
  self.error_message
575
618
  % {"model_name": self.model and self.model._meta.verbose_name}
@@ -600,6 +643,8 @@ class CreateView(
600
643
  self.request,
601
644
  locale=self.locale,
602
645
  translations=self.translations,
646
+ # Show skeleton for usage info if usage_url_name is set
647
+ usage_url="" if self.usage_url_name else None,
603
648
  )
604
649
  )
605
650
  return MediaContainer(side_panels)
@@ -644,16 +689,32 @@ class CreateView(
644
689
  log(instance=instance, action="wagtail.create", content_changed=True)
645
690
  return instance
646
691
 
692
+ def get_success_json(self):
693
+ edit_url = self.get_edit_url()
694
+ hydrate_url = set_query_params(edit_url, {"_w_hydrate_create_view": "1"})
695
+ result = {
696
+ "success": True,
697
+ "pk": self.object.pk,
698
+ "url": edit_url,
699
+ "hydrate_url": hydrate_url,
700
+ }
701
+ if isinstance(self.form, WagtailAdminModelForm):
702
+ result["field_updates"] = dict(self.form.get_field_updates_for_resave())
703
+ return result
704
+
647
705
  def save_action(self):
648
- success_message = self.get_success_message(self.object)
649
- success_buttons = self.get_success_buttons()
650
- if success_message is not None:
651
- messages.success(
652
- self.request,
653
- success_message,
654
- buttons=success_buttons,
655
- )
656
- return redirect(self.get_success_url())
706
+ if self.expects_json_response:
707
+ return JsonResponse(self.get_success_json())
708
+ else:
709
+ success_message = self.get_success_message(self.object)
710
+ success_buttons = self.get_success_buttons()
711
+ if success_message is not None:
712
+ messages.success(
713
+ self.request,
714
+ success_message,
715
+ buttons=success_buttons,
716
+ )
717
+ return redirect(self.get_success_url())
657
718
 
658
719
  def form_valid(self, form):
659
720
  self.form = form
@@ -670,9 +731,15 @@ class CreateView(
670
731
 
671
732
  def form_invalid(self, form):
672
733
  self.form = form
673
- error_message = self.get_error_message()
674
- if error_message is not None:
675
- messages.validation_error(self.request, error_message, form)
734
+
735
+ if self.expects_json_response:
736
+ return self.json_error_response(
737
+ getattr(self, "produced_error_code", "internal_error"),
738
+ self.produced_error_message or "",
739
+ )
740
+
741
+ if self.produced_error_message is not None:
742
+ messages.validation_error(self.request, self.produced_error_message, form)
676
743
  return super().form_invalid(form)
677
744
 
678
745
 
@@ -696,6 +763,7 @@ class EditView(
696
763
  PermissionCheckedMixin,
697
764
  BeforeAfterHookMixin,
698
765
  WagtailAdminTemplateMixin,
766
+ JsonPostResponseMixin,
699
767
  BaseUpdateView,
700
768
  ):
701
769
  model = None
@@ -710,6 +778,7 @@ class EditView(
710
778
  page_title = gettext_lazy("Editing")
711
779
  context_object_name = None
712
780
  template_name = "wagtailadmin/generic/edit.html"
781
+ partials_template_name = "wagtailadmin/generic/edit_partials.html"
713
782
  permission_required = "change"
714
783
  delete_item_label = gettext_lazy("Delete")
715
784
  success_message = gettext_lazy("%(model_name)s '%(object)s' updated.")
@@ -722,6 +791,37 @@ class EditView(
722
791
  super().setup(request, *args, **kwargs)
723
792
  self.action = self.get_action(request)
724
793
 
794
+ def get_template_names(self):
795
+ if self.hydrate_create_view:
796
+ return [self.partials_template_name]
797
+ return super().get_template_names()
798
+
799
+ def post(self, request, *args, **kwargs):
800
+ # BaseUpdateView.post() would set self.object here, but some subclasses need
801
+ # to do that during setup() instead, so only do that if it hasn't already been set.
802
+ if not hasattr(self, "object"):
803
+ self.object = self.get_object()
804
+
805
+ form = self.get_form()
806
+ if self.is_valid(form):
807
+ return self.form_valid(form)
808
+ else:
809
+ return self.form_invalid(form)
810
+
811
+ def is_valid(self, form):
812
+ """
813
+ Validate the form, setting the produced_error_message attribute if validation fails.
814
+
815
+ Subclasses that require extra validation conditions not handled by
816
+ form.is_valid() (such as formsets, or a locked status on the model) should
817
+ override this method.
818
+ """
819
+ result = form.is_valid()
820
+ if not result:
821
+ self.produced_error_code = "validation_error"
822
+ self.produced_error_message = self.get_error_message()
823
+ return result
824
+
725
825
  @cached_property
726
826
  def object_pk(self):
727
827
  # Must be a cached_property to prevent this from being re-run on the unquoted
@@ -896,16 +996,29 @@ class EditView(
896
996
 
897
997
  return instance
898
998
 
999
+ def get_success_json(self):
1000
+ result = {
1001
+ "success": True,
1002
+ "pk": self.object.pk,
1003
+ "html": self.render_partials(),
1004
+ }
1005
+ if isinstance(self.form, WagtailAdminModelForm):
1006
+ result["field_updates"] = dict(self.form.get_field_updates_for_resave())
1007
+ return result
1008
+
899
1009
  def save_action(self):
900
- success_message = self.get_success_message()
901
- success_buttons = self.get_success_buttons()
902
- if success_message is not None:
903
- messages.success(
904
- self.request,
905
- success_message,
906
- buttons=success_buttons,
907
- )
908
- return redirect(self.get_success_url())
1010
+ if self.expects_json_response:
1011
+ return JsonResponse(self.get_success_json())
1012
+ else:
1013
+ success_message = self.get_success_message()
1014
+ success_buttons = self.get_success_buttons()
1015
+ if success_message is not None:
1016
+ messages.success(
1017
+ self.request,
1018
+ success_message,
1019
+ buttons=success_buttons,
1020
+ )
1021
+ return redirect(self.get_success_url())
909
1022
 
910
1023
  def get_success_message(self):
911
1024
  if self.success_message is None:
@@ -922,8 +1035,13 @@ class EditView(
922
1035
  return [messages.button(self.get_edit_url(), _("Edit"))]
923
1036
 
924
1037
  def get_error_message(self):
1038
+ """
1039
+ Return the top-level error message to be displayed when form validation fails.
1040
+ """
925
1041
  if self.error_message is None:
926
1042
  return None
1043
+ if self.expects_json_response:
1044
+ return _("There are validation errors, click save to highlight them.")
927
1045
  return capfirst(
928
1046
  self.error_message
929
1047
  % {"model_name": self.model and self.model._meta.verbose_name}
@@ -944,15 +1062,25 @@ class EditView(
944
1062
 
945
1063
  def form_invalid(self, form):
946
1064
  self.form = form
947
- error_message = self.get_error_message()
948
- if error_message is not None:
949
- messages.validation_error(self.request, error_message, form)
1065
+
1066
+ if self.expects_json_response:
1067
+ return self.json_error_response(
1068
+ getattr(self, "produced_error_code", "internal_error"),
1069
+ self.produced_error_message or "",
1070
+ )
1071
+
1072
+ if self.produced_error_message is not None:
1073
+ messages.validation_error(self.request, self.produced_error_message, form)
950
1074
  return super().form_invalid(form)
951
1075
 
952
1076
  @cached_property
953
1077
  def has_unsaved_changes(self):
954
1078
  return self.form.is_bound
955
1079
 
1080
+ @cached_property
1081
+ def hydrate_create_view(self):
1082
+ return bool(self.request.GET.get("_w_hydrate_create_view"))
1083
+
956
1084
  def get_context_data(self, **kwargs):
957
1085
  context = super().get_context_data(**kwargs)
958
1086
  self.form = context.get("form")
@@ -964,6 +1092,8 @@ class EditView(
964
1092
  context["submit_button_label"] = self.submit_button_label
965
1093
  context["submit_button_active_label"] = self.submit_button_active_label
966
1094
  context["has_unsaved_changes"] = self.has_unsaved_changes
1095
+ context["is_partial"] = self.expects_json_response or self.hydrate_create_view
1096
+ context["hydrate_create_view"] = self.hydrate_create_view
967
1097
  context["can_delete"] = self.can_delete
968
1098
  if context["can_delete"]:
969
1099
  context["delete_url"] = self.get_delete_url()
@@ -19,7 +19,7 @@ class ReorderView(PermissionCheckedMixin, View):
19
19
  return self.model._default_manager.all().order_by(self.sort_order_field)
20
20
 
21
21
  def post(self, request, *args, **kwargs):
22
- item_to_move = get_object_or_404(self.model, pk=unquote(kwargs.get("pk")))
22
+ item_to_move = get_object_or_404(self.model, pk=unquote(str(kwargs.get("pk"))))
23
23
 
24
24
  try:
25
25
  # Position is an index in the list of items,
@@ -123,8 +123,8 @@ class PreviewOnEdit(View):
123
123
 
124
124
  try:
125
125
  preview_mode = request.GET.get("mode", self.object.default_preview_mode)
126
- except IndexError:
127
- raise PermissionDenied
126
+ except IndexError as e:
127
+ raise PermissionDenied from e
128
128
 
129
129
  extra_attrs = self.get_extra_request_attrs()
130
130
 
@@ -171,8 +171,8 @@ class PreviewRevision(View):
171
171
  preview_mode = request.GET.get(
172
172
  "mode", self.revision_object.default_preview_mode
173
173
  )
174
- except IndexError:
175
- raise PermissionDenied
174
+ except IndexError as e:
175
+ raise PermissionDenied from e
176
176
 
177
177
  return self.revision_object.make_preview_request(request, preview_mode)
178
178
 
@@ -312,7 +312,9 @@ class HomeView(WagtailAdminTemplateMixin, TemplateView):
312
312
 
313
313
  return {**context, **site_details}
314
314
 
315
- def get_media(self, panels=[]):
315
+ def get_media(self, panels=None):
316
+ if panels is None:
317
+ panels = []
316
318
  media = Media()
317
319
 
318
320
  for panel in panels:
@@ -3,7 +3,7 @@ from urllib.parse import quote, urlencode
3
3
  from django.conf import settings
4
4
  from django.contrib.contenttypes.models import ContentType
5
5
  from django.core.exceptions import PermissionDenied
6
- from django.http import Http404
6
+ from django.http import Http404, JsonResponse
7
7
  from django.shortcuts import get_object_or_404, redirect
8
8
  from django.template.response import TemplateResponse
9
9
  from django.urls import reverse
@@ -16,6 +16,7 @@ from django.views.generic.base import View
16
16
  from wagtail.admin import messages
17
17
  from wagtail.admin.action_menu import PageActionMenu
18
18
  from wagtail.admin.telepath import JSContext
19
+ from wagtail.admin.ui.autosave import AutosaveIndicator
19
20
  from wagtail.admin.ui.components import MediaContainer
20
21
  from wagtail.admin.ui.side_panels import (
21
22
  ChecksSidePanel,
@@ -23,8 +24,8 @@ from wagtail.admin.ui.side_panels import (
23
24
  PageStatusSidePanel,
24
25
  PreviewSidePanel,
25
26
  )
26
- from wagtail.admin.utils import get_valid_next_url_from_request
27
- from wagtail.admin.views.generic import HookResponseMixin
27
+ from wagtail.admin.utils import get_valid_next_url_from_request, set_query_params
28
+ from wagtail.admin.views.generic import HookResponseMixin, JsonPostResponseMixin
28
29
  from wagtail.admin.views.generic.base import WagtailAdminTemplateMixin
29
30
  from wagtail.models import (
30
31
  BaseViewRestriction,
@@ -71,9 +72,12 @@ def add_subpage(request, parent_page_id):
71
72
  )
72
73
 
73
74
 
74
- class CreateView(WagtailAdminTemplateMixin, HookResponseMixin, View):
75
+ class CreateView(
76
+ WagtailAdminTemplateMixin, HookResponseMixin, JsonPostResponseMixin, View
77
+ ):
75
78
  template_name = "wagtailadmin/pages/create.html"
76
79
  page_title = gettext_lazy("New")
80
+ edit_url_name = "wagtailadmin_pages:edit"
77
81
 
78
82
  def dispatch(
79
83
  self, request, content_type_app_name, content_type_model_name, parent_page_id
@@ -89,8 +93,8 @@ class CreateView(WagtailAdminTemplateMixin, HookResponseMixin, View):
89
93
  self.page_content_type = ContentType.objects.get_by_natural_key(
90
94
  content_type_app_name, content_type_model_name
91
95
  )
92
- except ContentType.DoesNotExist:
93
- raise Http404
96
+ except ContentType.DoesNotExist as e:
97
+ raise Http404 from e
94
98
 
95
99
  # Get class
96
100
  self.page_class = self.page_content_type.model_class()
@@ -110,7 +114,15 @@ class CreateView(WagtailAdminTemplateMixin, HookResponseMixin, View):
110
114
  "before_create_page", self.request, self.parent_page, self.page_class
111
115
  )
112
116
  if response:
113
- return response
117
+ if self.expects_json_response and not self.response_is_json(response):
118
+ # Hook response is not suitable for a JSON response, so construct our own error response
119
+ return self.json_error_response(
120
+ "blocked_by_hook",
121
+ _("Request to create %(model_name)s was blocked by hook.")
122
+ % {"model_name": Page._meta.verbose_name},
123
+ )
124
+ else:
125
+ return response
114
126
 
115
127
  self.locale = self.parent_page.locale
116
128
  self.page = self.page_class(owner=self.request.user)
@@ -142,6 +154,9 @@ class CreateView(WagtailAdminTemplateMixin, HookResponseMixin, View):
142
154
 
143
155
  self.next_url = get_valid_next_url_from_request(self.request)
144
156
 
157
+ self.autosave_interval = getattr(settings, "WAGTAIL_AUTOSAVE_INTERVAL", 500)
158
+ self.autosave_enabled = self.autosave_interval > 0
159
+
145
160
  return super().dispatch(request)
146
161
 
147
162
  def post(self, request):
@@ -192,9 +207,10 @@ class CreateView(WagtailAdminTemplateMixin, HookResponseMixin, View):
192
207
  return self.page_class.get_verbose_name()
193
208
 
194
209
  def get_edit_message_button(self):
195
- return messages.button(
196
- reverse("wagtailadmin_pages:edit", args=(self.page.id,)), _("Edit")
197
- )
210
+ return messages.button(self.get_edit_url(), _("Edit"))
211
+
212
+ def get_edit_url(self):
213
+ return reverse(self.edit_url_name, args=(self.page.id,))
198
214
 
199
215
  def get_view_draft_message_button(self):
200
216
  return messages.button(
@@ -248,25 +264,48 @@ class CreateView(WagtailAdminTemplateMixin, HookResponseMixin, View):
248
264
  self.set_default_privacy_setting()
249
265
 
250
266
  # Save revision
251
- self.page.save_revision(user=self.request.user, log_action=True, clean=False)
267
+ revision = self.page.save_revision(
268
+ user=self.request.user, log_action=True, clean=False
269
+ )
252
270
 
253
271
  # Save subscription settings
254
272
  self.subscription.page = self.page
255
273
  self.subscription.save()
256
274
 
257
- # Notification
258
- messages.success(
259
- self.request,
260
- _("Page '%(page_title)s' created.")
261
- % {"page_title": self.page.get_admin_display_title()},
262
- )
275
+ if not self.expects_json_response:
276
+ # Notification
277
+ messages.success(
278
+ self.request,
279
+ _("Page '%(page_title)s' created.")
280
+ % {"page_title": self.page.get_admin_display_title()},
281
+ )
263
282
 
264
283
  response = self.run_hook("after_create_page", self.request, self.page)
265
284
  if response:
266
- return response
267
-
268
- # remain on edit page for further edits
269
- return self.redirect_and_remain()
285
+ if self.expects_json_response and not self.response_is_json(response):
286
+ # Hook response is not suitable for a JSON response, so ignore it and just use
287
+ # the standard one
288
+ pass
289
+ else:
290
+ return response
291
+
292
+ if self.expects_json_response:
293
+ edit_url = self.get_edit_url()
294
+ hydrate_url = set_query_params(edit_url, {"_w_hydrate_create_view": "1"})
295
+ return JsonResponse(
296
+ {
297
+ "success": True,
298
+ "pk": self.page.pk,
299
+ "revision_id": revision.pk,
300
+ "revision_created_at": revision.created_at.isoformat(),
301
+ "field_updates": dict(self.form.get_field_updates_for_resave()),
302
+ "url": edit_url,
303
+ "hydrate_url": hydrate_url,
304
+ }
305
+ )
306
+ else:
307
+ # remain on edit page for further edits
308
+ return self.redirect_and_remain()
270
309
 
271
310
  def publish_action(self):
272
311
  self.page = self.form.save(commit=False)
@@ -374,21 +413,27 @@ class CreateView(WagtailAdminTemplateMixin, HookResponseMixin, View):
374
413
  return redirect("wagtailadmin_explore", self.page.get_parent().id)
375
414
 
376
415
  def redirect_and_remain(self):
377
- target_url = reverse("wagtailadmin_pages:edit", args=[self.page.id])
416
+ target_url = self.get_edit_url()
378
417
  if self.next_url:
379
418
  # Ensure the 'next' url is passed through again if present
380
419
  target_url += "?next=%s" % quote(self.next_url)
381
420
  return redirect(target_url)
382
421
 
383
422
  def form_invalid(self, form):
384
- messages.validation_error(
385
- self.request,
386
- _("The page could not be created due to validation errors."),
387
- self.form,
388
- )
389
- self.has_unsaved_changes = True
423
+ if self.expects_json_response:
424
+ return self.json_error_response(
425
+ "validation_error",
426
+ _("There are validation errors, click save to highlight them."),
427
+ )
428
+ else:
429
+ messages.validation_error(
430
+ self.request,
431
+ _("The page could not be created due to validation errors."),
432
+ self.form,
433
+ )
434
+ self.has_unsaved_changes = True
390
435
 
391
- return self.render_to_response(self.get_context_data())
436
+ return self.render_to_response(self.get_context_data())
392
437
 
393
438
  def get(self, request):
394
439
  init_new_page.send(sender=CreateView, page=self.page, parent=self.parent_page)
@@ -474,6 +519,9 @@ class CreateView(WagtailAdminTemplateMixin, HookResponseMixin, View):
474
519
  "has_unsaved_changes": self.has_unsaved_changes,
475
520
  "locale": self.locale,
476
521
  "media": media,
522
+ "autosave_enabled": self.autosave_enabled,
523
+ "autosave_interval": self.autosave_interval,
524
+ "autosave_indicator": AutosaveIndicator(),
477
525
  }
478
526
  )
479
527