wagtail 6.3.2__py3-none-any.whl → 6.4rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. wagtail/__init__.py +1 -1
  2. wagtail/actions/publish_revision.py +4 -5
  3. wagtail/admin/auth.py +0 -2
  4. wagtail/admin/checks.py +1 -1
  5. wagtail/admin/filters.py +3 -1
  6. wagtail/admin/forms/account.py +21 -11
  7. wagtail/admin/forms/collections.py +2 -9
  8. wagtail/admin/forms/formsets.py +32 -0
  9. wagtail/admin/forms/pages.py +5 -1
  10. wagtail/admin/forms/workflows.py +2 -13
  11. wagtail/admin/locale/ar/LC_MESSAGES/django.mo +0 -0
  12. wagtail/admin/locale/ar/LC_MESSAGES/django.po +68 -1
  13. wagtail/admin/locale/ar/LC_MESSAGES/djangojs.mo +0 -0
  14. wagtail/admin/locale/ar/LC_MESSAGES/djangojs.po +5 -1
  15. wagtail/admin/locale/en/LC_MESSAGES/django.po +312 -356
  16. wagtail/admin/locale/en/LC_MESSAGES/djangojs.po +21 -16
  17. wagtail/admin/menu.py +0 -13
  18. wagtail/admin/panels/base.py +2 -2
  19. wagtail/admin/panels/group.py +4 -1
  20. wagtail/admin/panels/inline_panel.py +5 -2
  21. wagtail/admin/panels/model_utils.py +36 -0
  22. wagtail/admin/panels/page_utils.py +2 -40
  23. wagtail/admin/panels/signal_handlers.py +0 -2
  24. wagtail/admin/static/wagtailadmin/css/core.css +1 -1
  25. wagtail/admin/static/wagtailadmin/css/panels/draftail.css +1 -1
  26. wagtail/admin/static/wagtailadmin/css/panels/streamfield.css +1 -1
  27. wagtail/admin/static/wagtailadmin/js/comments.js +1 -1
  28. wagtail/admin/static/wagtailadmin/js/core.js +1 -1
  29. wagtail/admin/static/wagtailadmin/js/core.js.LICENSE.txt +1 -8
  30. wagtail/admin/static/wagtailadmin/js/draftail.js +1 -1
  31. wagtail/admin/static/wagtailadmin/js/modal-workflow.js +1 -1
  32. wagtail/admin/static/wagtailadmin/js/page-chooser-modal.js +1 -1
  33. wagtail/admin/static/wagtailadmin/js/privacy-switch.js +1 -1
  34. wagtail/admin/static/wagtailadmin/js/sidebar.js +1 -1
  35. wagtail/admin/static/wagtailadmin/js/telepath/blocks.js +1 -1
  36. wagtail/admin/static/wagtailadmin/js/userbar.js +1 -1
  37. wagtail/admin/static/wagtailadmin/js/userbar.js.LICENSE.txt +1 -1
  38. wagtail/admin/static/wagtailadmin/js/vendor.js +1 -1
  39. wagtail/admin/static/wagtailadmin/js/vendor.js.LICENSE.txt +7 -0
  40. wagtail/admin/templates/wagtailadmin/404.html +4 -0
  41. wagtail/admin/templates/wagtailadmin/chooser/browse.html +2 -1
  42. wagtail/admin/templates/wagtailadmin/chooser/tables/parent_page_cell.html +1 -1
  43. wagtail/admin/templates/wagtailadmin/collections/_privacy_switch.html +8 -1
  44. wagtail/admin/templates/wagtailadmin/generic/confirm_delete.html +15 -9
  45. wagtail/admin/templates/wagtailadmin/generic/confirm_unpublish.html +21 -25
  46. wagtail/admin/templates/wagtailadmin/generic/form.html +1 -1
  47. wagtail/admin/templates/wagtailadmin/generic/preview_error.html +3 -0
  48. wagtail/admin/templates/wagtailadmin/generic/revisions/compare.html +63 -76
  49. wagtail/admin/templates/wagtailadmin/pages/_editor_js.html +0 -2
  50. wagtail/admin/templates/wagtailadmin/pages/edit.html +1 -5
  51. wagtail/admin/templates/wagtailadmin/panels/inline_panel_child.html +1 -0
  52. wagtail/admin/templates/wagtailadmin/permissions/includes/collection_member_permissions_form.html +1 -1
  53. wagtail/admin/templates/wagtailadmin/permissions/includes/collection_member_permissions_formset.html +6 -22
  54. wagtail/admin/templates/wagtailadmin/shared/formatted_field.html +2 -2
  55. wagtail/admin/templates/wagtailadmin/shared/header.html +2 -2
  56. wagtail/admin/templates/wagtailadmin/shared/page_status_tag_new.html +32 -39
  57. wagtail/admin/templates/wagtailadmin/shared/revisions/confirm_unschedule.html +13 -17
  58. wagtail/admin/templates/wagtailadmin/shared/side_panels/includes/status/privacy.html +15 -3
  59. wagtail/admin/templates/wagtailadmin/shared/side_panels/preview.html +1 -1
  60. wagtail/admin/templates/wagtailadmin/skeleton.html +4 -2
  61. wagtail/admin/templates/wagtailadmin/workflows/create.html +1 -1
  62. wagtail/admin/templates/wagtailadmin/workflows/edit.html +1 -1
  63. wagtail/admin/templates/wagtailadmin/workflows/includes/workflow_pages_form.html +1 -1
  64. wagtail/admin/templates/wagtailadmin/workflows/includes/workflow_pages_formset.html +6 -23
  65. wagtail/admin/templatetags/wagtailadmin_tags.py +12 -0
  66. wagtail/admin/templatetags/wagtailuserbar.py +2 -3
  67. wagtail/admin/tests/pages/test_create_page.py +110 -1
  68. wagtail/admin/tests/pages/test_edit_page.py +3 -2
  69. wagtail/admin/tests/pages/test_explorer_view.py +18 -0
  70. wagtail/admin/tests/pages/test_page_usage.py +24 -20
  71. wagtail/admin/tests/pages/test_preview.py +69 -1
  72. wagtail/admin/tests/pages/test_revisions.py +40 -6
  73. wagtail/admin/tests/test_account_management.py +39 -1
  74. wagtail/admin/tests/test_audit_log.py +4 -2
  75. wagtail/admin/tests/test_block_preview.py +224 -0
  76. wagtail/admin/tests/test_edit_handlers.py +23 -6
  77. wagtail/admin/tests/test_page_chooser.py +50 -3
  78. wagtail/admin/tests/test_privacy.py +49 -26
  79. wagtail/admin/tests/test_site_summary.py +15 -10
  80. wagtail/admin/tests/test_templatetags.py +19 -0
  81. wagtail/admin/tests/test_userbar.py +82 -1
  82. wagtail/admin/tests/test_views_generic.py +27 -12
  83. wagtail/admin/tests/test_workflows.py +69 -0
  84. wagtail/admin/tests/tests.py +23 -4
  85. wagtail/admin/tests/ui/test_sidebar.py +1 -1
  86. wagtail/admin/tests/viewsets/test_model_viewset.py +15 -13
  87. wagtail/admin/ui/side_panels.py +7 -4
  88. wagtail/admin/urls/__init__.py +6 -0
  89. wagtail/admin/urls/pages.py +1 -1
  90. wagtail/admin/userbar.py +21 -1
  91. wagtail/admin/views/account.py +5 -0
  92. wagtail/admin/views/chooser.py +5 -1
  93. wagtail/admin/views/collections.py +0 -2
  94. wagtail/admin/views/generic/base.py +20 -10
  95. wagtail/admin/views/generic/history.py +0 -1
  96. wagtail/admin/views/generic/models.py +79 -21
  97. wagtail/admin/views/generic/preview.py +50 -1
  98. wagtail/admin/views/mixins.py +4 -2
  99. wagtail/admin/views/pages/bulk_actions/delete.py +11 -23
  100. wagtail/admin/views/pages/bulk_actions/page_bulk_action.py +17 -0
  101. wagtail/admin/views/pages/bulk_actions/publish.py +11 -31
  102. wagtail/admin/views/pages/bulk_actions/unpublish.py +11 -31
  103. wagtail/admin/views/pages/create.py +1 -0
  104. wagtail/admin/views/pages/edit.py +38 -30
  105. wagtail/admin/views/pages/revisions.py +43 -114
  106. wagtail/admin/views/pages/utils.py +0 -1
  107. wagtail/admin/views/tags.py +6 -2
  108. wagtail/admin/views/workflows.py +8 -6
  109. wagtail/admin/viewsets/model.py +0 -4
  110. wagtail/admin/viewsets/pages.py +0 -1
  111. wagtail/admin/widgets/tags.py +1 -0
  112. wagtail/api/v2/tests/test_documents.py +4 -2
  113. wagtail/api/v2/tests/test_images.py +4 -2
  114. wagtail/api/v2/tests/test_pages.py +8 -4
  115. wagtail/blocks/base.py +59 -1
  116. wagtail/blocks/field_block.py +6 -0
  117. wagtail/blocks/list_block.py +4 -0
  118. wagtail/blocks/static_block.py +3 -0
  119. wagtail/blocks/stream_block.py +5 -1
  120. wagtail/blocks/struct_block.py +6 -0
  121. wagtail/compat.py +16 -0
  122. wagtail/contrib/forms/forms.py +27 -7
  123. wagtail/contrib/forms/locale/en/LC_MESSAGES/django.po +2 -2
  124. wagtail/contrib/forms/tests/test_models.py +7 -5
  125. wagtail/contrib/forms/tests/test_views.py +75 -0
  126. wagtail/contrib/frontend_cache/tasks.py +83 -0
  127. wagtail/contrib/frontend_cache/tests.py +47 -32
  128. wagtail/contrib/frontend_cache/utils.py +2 -70
  129. wagtail/contrib/redirects/base_formats.py +2 -2
  130. wagtail/contrib/redirects/locale/ar/LC_MESSAGES/django.mo +0 -0
  131. wagtail/contrib/redirects/locale/ar/LC_MESSAGES/django.po +3 -0
  132. wagtail/contrib/redirects/locale/en/LC_MESSAGES/django.po +24 -37
  133. wagtail/contrib/redirects/templates/wagtailredirects/add.html +1 -24
  134. wagtail/contrib/redirects/templates/wagtailredirects/confirm_delete.html +3 -13
  135. wagtail/contrib/redirects/tests/test_redirects.py +122 -110
  136. wagtail/contrib/redirects/tests/test_signal_handlers.py +75 -69
  137. wagtail/contrib/redirects/urls.py +2 -2
  138. wagtail/contrib/redirects/views.py +35 -73
  139. wagtail/contrib/search_promotions/admin_urls.py +10 -3
  140. wagtail/contrib/search_promotions/forms.py +55 -26
  141. wagtail/contrib/search_promotions/locale/en/LC_MESSAGES/django.po +44 -54
  142. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/add.html +21 -31
  143. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/confirm_delete.html +3 -12
  144. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/edit.html +11 -34
  145. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/includes/searchpromotion_form.html +1 -0
  146. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/includes/searchpromotions_formset.js +2 -1
  147. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/index.html +0 -1
  148. wagtail/contrib/search_promotions/tests.py +814 -13
  149. wagtail/contrib/search_promotions/views/__init__.py +1 -0
  150. wagtail/contrib/search_promotions/views/reports.py +56 -0
  151. wagtail/contrib/search_promotions/views/settings.py +258 -0
  152. wagtail/contrib/search_promotions/wagtail_hooks.py +12 -1
  153. wagtail/contrib/settings/locale/ar/LC_MESSAGES/django.mo +0 -0
  154. wagtail/contrib/settings/locale/ar/LC_MESSAGES/django.po +6 -1
  155. wagtail/contrib/settings/locale/en/LC_MESSAGES/django.po +3 -3
  156. wagtail/contrib/settings/templates/wagtailsettings/edit.html +1 -5
  157. wagtail/contrib/settings/tests/generic/test_admin.py +2 -5
  158. wagtail/contrib/settings/tests/generic/test_register.py +1 -1
  159. wagtail/contrib/settings/tests/site_specific/test_admin.py +2 -5
  160. wagtail/contrib/settings/tests/site_specific/test_register.py +1 -1
  161. wagtail/contrib/settings/views.py +9 -23
  162. wagtail/contrib/simple_translation/locale/en/LC_MESSAGES/django.po +1 -1
  163. wagtail/contrib/styleguide/locale/en/LC_MESSAGES/django.po +1 -1
  164. wagtail/contrib/table_block/locale/en/LC_MESSAGES/django.po +1 -1
  165. wagtail/contrib/table_block/tests.py +4 -1
  166. wagtail/contrib/typed_table_block/blocks.py +3 -0
  167. wagtail/contrib/typed_table_block/locale/en/LC_MESSAGES/django.po +10 -10
  168. wagtail/contrib/typed_table_block/static/typed_table_block/js/typed_table_block.js +1 -1
  169. wagtail/contrib/typed_table_block/tests.py +33 -0
  170. wagtail/documents/locale/en/LC_MESSAGES/django.po +26 -26
  171. wagtail/documents/migrations/0011_add_choose_permissions.py +1 -0
  172. wagtail/documents/models.py +1 -0
  173. wagtail/documents/signal_handlers.py +6 -2
  174. wagtail/documents/static/wagtaildocs/js/add-multiple.js +1 -1
  175. wagtail/documents/templates/wagtaildocs/documents/edit.html +1 -3
  176. wagtail/documents/templates/wagtaildocs/multiple/add.html +7 -1
  177. wagtail/documents/tests/test_admin_views.py +74 -33
  178. wagtail/documents/tests/test_views.py +21 -12
  179. wagtail/documents/views/chooser.py +1 -0
  180. wagtail/documents/views/documents.py +1 -2
  181. wagtail/documents/views/multiple.py +0 -1
  182. wagtail/documents/views/serve.py +9 -2
  183. wagtail/documents/wagtail_hooks.py +6 -1
  184. wagtail/embeds/locale/en/LC_MESSAGES/django.po +1 -1
  185. wagtail/embeds/oembed_providers.py +0 -64
  186. wagtail/fields.py +3 -0
  187. wagtail/images/apps.py +2 -1
  188. wagtail/images/blocks.py +6 -2
  189. wagtail/images/forms.py +40 -3
  190. wagtail/images/locale/ar/LC_MESSAGES/django.mo +0 -0
  191. wagtail/images/locale/ar/LC_MESSAGES/django.po +4 -0
  192. wagtail/images/locale/en/LC_MESSAGES/django.po +49 -49
  193. wagtail/images/migrations/0023_add_choose_permissions.py +1 -0
  194. wagtail/images/rich_text/contentstate.py +1 -0
  195. wagtail/images/rich_text/editor_html.py +1 -0
  196. wagtail/images/signal_handlers.py +17 -10
  197. wagtail/images/static/wagtailimages/js/add-multiple.js +1 -1
  198. wagtail/images/static/wagtailimages/js/image-block.js +1 -1
  199. wagtail/images/static/wagtailimages/js/image-chooser-telepath.js +1 -1
  200. wagtail/images/static/wagtailimages/js/image-chooser.js +1 -1
  201. wagtail/images/static/wagtailimages/js/image-url-generator.js +1 -1
  202. wagtail/images/static/wagtailimages/js/vendor/jquery.fileupload-image.js +1 -1
  203. wagtail/images/tasks.py +18 -0
  204. wagtail/images/templates/wagtailimages/images/edit.html +1 -3
  205. wagtail/images/templates/wagtailimages/images/url_generator.html +1 -1
  206. wagtail/images/templates/wagtailimages/multiple/add.html +7 -2
  207. wagtail/images/templates/wagtailimages/widgets/image_chooser.html +1 -1
  208. wagtail/images/tests/test_admin_views.py +53 -29
  209. wagtail/images/tests/test_blocks.py +3 -2
  210. wagtail/images/tests/test_models.py +12 -10
  211. wagtail/images/tests/tests.py +10 -0
  212. wagtail/images/views/chooser.py +1 -0
  213. wagtail/images/views/images.py +1 -3
  214. wagtail/images/views/multiple.py +0 -1
  215. wagtail/images/views/serve.py +18 -2
  216. wagtail/images/widgets.py +3 -0
  217. wagtail/locale/en/LC_MESSAGES/django.po +228 -216
  218. wagtail/locales/locale/en/LC_MESSAGES/django.po +1 -1
  219. wagtail/management/commands/publish_scheduled.py +1 -1
  220. wagtail/migrations/0087_alter_grouppagepermission_unique_together_and_more.py +16 -8
  221. wagtail/models/__init__.py +300 -119
  222. wagtail/models/i18n.py +2 -2
  223. wagtail/models/panels.py +37 -0
  224. wagtail/models/sites.py +7 -6
  225. wagtail/permission_policies/pages.py +2 -2
  226. wagtail/project_template/project_name/settings/base.py +4 -0
  227. wagtail/project_template/requirements.txt +1 -1
  228. wagtail/query.py +145 -0
  229. wagtail/search/backends/database/mysql/mysql.py +25 -17
  230. wagtail/search/backends/database/postgres/postgres.py +44 -83
  231. wagtail/search/backends/database/sqlite/sqlite.py +25 -17
  232. wagtail/search/backends/elasticsearch7.py +4 -0
  233. wagtail/search/locale/en/LC_MESSAGES/django.po +1 -1
  234. wagtail/search/query.py +8 -2
  235. wagtail/search/signal_handlers.py +6 -9
  236. wagtail/search/tasks.py +10 -0
  237. wagtail/search/tests/test_elasticsearch7_backend.py +21 -0
  238. wagtail/search/tests/test_index_functions.py +10 -6
  239. wagtail/search/tests/test_postgres_backend.py +0 -14
  240. wagtail/signal_handlers.py +5 -20
  241. wagtail/sites/locale/en/LC_MESSAGES/django.po +1 -1
  242. wagtail/snippets/locale/en/LC_MESSAGES/django.po +3 -13
  243. wagtail/snippets/tests/test_preview.py +5 -0
  244. wagtail/snippets/tests/test_snippets.py +100 -45
  245. wagtail/snippets/tests/test_usage.py +29 -24
  246. wagtail/snippets/tests/test_viewset.py +1 -1
  247. wagtail/snippets/views/snippets.py +0 -12
  248. wagtail/tasks.py +41 -0
  249. wagtail/templates/wagtailcore/shared/block_preview.html +29 -0
  250. wagtail/test/earlypage/__init__.py +0 -0
  251. wagtail/test/earlypage/migrations/0001_initial.py +37 -0
  252. wagtail/test/earlypage/migrations/__init__.py +0 -0
  253. wagtail/test/earlypage/models.py +14 -0
  254. wagtail/test/settings.py +3 -0
  255. wagtail/test/testapp/fixtures/test.json +7 -0
  256. wagtail/test/testapp/fixtures/test_specific.json +6 -3
  257. wagtail/test/testapp/models.py +58 -44
  258. wagtail/test/testapp/templates/tests/custom_block_preview.html +16 -0
  259. wagtail/test/testapp/templates/tests/static_block_preview.html +5 -0
  260. wagtail/test/testapp/wagtail_hooks.py +9 -0
  261. wagtail/tests/test_blocks.py +189 -2
  262. wagtail/tests/test_hooks.py +166 -1
  263. wagtail/tests/test_management_commands.py +54 -13
  264. wagtail/tests/test_page_allowed_http_methods.py +32 -0
  265. wagtail/tests/test_page_model.py +68 -0
  266. wagtail/tests/test_page_privacy.py +10 -0
  267. wagtail/tests/test_page_queryset.py +79 -0
  268. wagtail/tests/test_reference_index.py +84 -75
  269. wagtail/tests/test_streamfield.py +30 -0
  270. wagtail/tests/test_utils.py +61 -0
  271. wagtail/users/forms.py +2 -9
  272. wagtail/users/locale/en/LC_MESSAGES/django.po +17 -17
  273. wagtail/users/templates/wagtailusers/groups/create.html +0 -5
  274. wagtail/users/templates/wagtailusers/groups/includes/page_permissions_form.html +1 -1
  275. wagtail/users/templates/wagtailusers/groups/includes/page_permissions_formset.html +6 -6
  276. wagtail/users/tests/test_admin_views.py +96 -4
  277. wagtail/users/tests/test_utils.py +76 -0
  278. wagtail/users/utils.py +43 -11
  279. wagtail/utils/setup.py +2 -2
  280. wagtail/utils/templates.py +26 -0
  281. wagtail/utils/widgets.py +1 -0
  282. wagtail/views.py +9 -1
  283. wagtail/wagtail_hooks.py +67 -29
  284. {wagtail-6.3.2.dist-info → wagtail-6.4rc1.dist-info}/METADATA +2 -2
  285. {wagtail-6.3.2.dist-info → wagtail-6.4rc1.dist-info}/RECORD +289 -276
  286. wagtail/admin/static/wagtailadmin/js/expanding-formset.js +0 -1
  287. wagtail/admin/static/wagtailadmin/js/vendor/rangy-core.js +0 -1
  288. wagtail/admin/static/wagtailadmin/js/vendor/uuidv4.min.js +0 -1
  289. wagtail/contrib/search_promotions/views.py +0 -323
  290. wagtail/images/static/wagtailimages/js/vendor/canvas-to-blob.min.js +0 -1
  291. wagtail/users/static/wagtailusers/js/group-form.js +0 -1
  292. wagtail/users/templates/wagtailusers/groups/includes/group_form_js.html +0 -3
  293. {wagtail-6.3.2.dist-info → wagtail-6.4rc1.dist-info}/LICENSE +0 -0
  294. {wagtail-6.3.2.dist-info → wagtail-6.4rc1.dist-info}/WHEEL +0 -0
  295. {wagtail-6.3.2.dist-info → wagtail-6.4rc1.dist-info}/entry_points.txt +0 -0
  296. {wagtail-6.3.2.dist-info → wagtail-6.4rc1.dist-info}/top_level.txt +0 -0
@@ -8,7 +8,7 @@ msgid ""
8
8
  msgstr ""
9
9
  "Project-Id-Version: PACKAGE VERSION\n"
10
10
  "Report-Msgid-Bugs-To: \n"
11
- "POT-Creation-Date: 2024-10-21 17:53+0100\n"
11
+ "POT-Creation-Date: 2025-01-20 17:59+0000\n"
12
12
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -30,7 +30,7 @@ msgstr ""
30
30
  msgid "Date to"
31
31
  msgstr ""
32
32
 
33
- #: forms.py:203
33
+ #: forms.py:221
34
34
  #, python-format
35
35
  msgid ""
36
36
  "There is another field with the label %(label_name)s, please change one of "
@@ -603,11 +603,13 @@ class TestFormPageWithCustomFormBuilder(WagtailTestUtils, TestCase):
603
603
  html=True,
604
604
  )
605
605
  # check ip address field has rendered
606
- self.assertContains(
607
- response,
608
- '<input type="text" name="device_ip_address" required id="id_device_ip_address" />',
609
- html=True,
610
- )
606
+ # (not comparing HTML directly because https://docs.djangoproject.com/en/5.1/releases/5.1.5/
607
+ # added a maxlength attribute)
608
+ soup = self.get_soup(response.content)
609
+ input = soup.find("input", {"name": "device_ip_address"})
610
+ self.assertEqual(input["type"], "text")
611
+ self.assertEqual(input["required"], "")
612
+ self.assertEqual(input["id"], "id_device_ip_address")
611
613
 
612
614
  def test_post_invalid_form(self):
613
615
  response = self.client.post(
@@ -1973,6 +1973,52 @@ class TestDuplicateFormFieldLabels(WagtailTestUtils, TestCase):
1973
1973
  text="There is another field with the label Test field, please change one of them.",
1974
1974
  )
1975
1975
 
1976
+ def test_adding_duplicate_form_labels_with_custom_related_name(self):
1977
+ """
1978
+ Ensure duplicate field names are checked, even if a custom related_name is used.
1979
+ ``FormPageWithCustomSubmission`` uses the related_name `custom_form_fields`.
1980
+ """
1981
+
1982
+ post_data = {
1983
+ "title": "Drink selection",
1984
+ "content": "Some content",
1985
+ "slug": "drink-selection",
1986
+ "custom_form_fields-TOTAL_FORMS": "3",
1987
+ "custom_form_fields-INITIAL_FORMS": "3",
1988
+ "custom_form_fields-MIN_NUM_FORMS": "0",
1989
+ "custom_form_fields-MAX_NUM_FORMS": "1000",
1990
+ # Duplicate field labels
1991
+ "custom_form_fields-0-id": "",
1992
+ "custom_form_fields-0-label": "chocolate",
1993
+ "custom_form_fields-0-field_type": "singleline",
1994
+ "custom_form_fields-1-id": "",
1995
+ "custom_form_fields-1-label": "chocolate",
1996
+ "custom_form_fields-1-field_type": "singleline",
1997
+ # Unique field label
1998
+ "custom_form_fields-2-id": "",
1999
+ "custom_form_fields-2-label": "coffee",
2000
+ "custom_form_fields-2-field_type": "singleline",
2001
+ }
2002
+
2003
+ response = self.client.post(
2004
+ reverse(
2005
+ "wagtailadmin_pages:add",
2006
+ args=(
2007
+ "tests",
2008
+ "formpagewithcustomsubmission",
2009
+ self.root_page.id,
2010
+ ),
2011
+ ),
2012
+ post_data,
2013
+ )
2014
+
2015
+ self.assertEqual(response.status_code, 200)
2016
+
2017
+ self.assertContains(
2018
+ response,
2019
+ text="There is another field with the label chocolate, please change one of them.",
2020
+ )
2021
+
1976
2022
 
1977
2023
  class TestPreview(WagtailTestUtils, TestCase):
1978
2024
  post_data = {
@@ -2085,6 +2131,35 @@ class TestFormPageCreate(WagtailTestUtils, TestCase):
2085
2131
  response, reverse("wagtailadmin_pages:edit", args=(page.id,))
2086
2132
  )
2087
2133
 
2134
+ def test_form_page_creation_error_with_custom_clean_method(self):
2135
+ """
2136
+ CustomFormPageSubmission uses a custom `base_form_class` with a clean method
2137
+ that will raise a ValidationError if the from email contains 'example.com'.
2138
+ """
2139
+
2140
+ post_data = {
2141
+ "title": "Drink selection",
2142
+ "slug": "drink-selection",
2143
+ "from_address": "bad@example.com",
2144
+ }
2145
+
2146
+ response = self.client.post(
2147
+ reverse(
2148
+ "wagtailadmin_pages:add",
2149
+ args=("tests", "formpagewithcustomsubmission", self.root_page.id),
2150
+ ),
2151
+ post_data,
2152
+ )
2153
+
2154
+ self.assertEqual(response.status_code, 200)
2155
+
2156
+ self.assertContains(
2157
+ response, "The page could not be created due to validation errors"
2158
+ )
2159
+ self.assertContains(
2160
+ response, "<li>Email cannot be from example.com</li>", count=1
2161
+ )
2162
+
2088
2163
 
2089
2164
  class TestFormPageEdit(WagtailTestUtils, TestCase):
2090
2165
  def setUp(self):
@@ -0,0 +1,83 @@
1
+ import logging
2
+ import re
3
+ from collections import defaultdict
4
+ from urllib.parse import urlsplit, urlunsplit
5
+
6
+ from django.conf import settings
7
+ from django_tasks import task
8
+
9
+ from wagtail.coreutils import get_content_languages
10
+
11
+ from .utils import get_backends
12
+
13
+ logger = logging.getLogger("wagtail.frontendcache")
14
+
15
+
16
+ @task()
17
+ def purge_urls_from_cache_task(urls, backend_settings=None, backends=None):
18
+ if not urls:
19
+ return
20
+
21
+ backends = get_backends(backend_settings, backends)
22
+
23
+ # If no backends are configured, there's nothing to do
24
+ if not backends:
25
+ return
26
+
27
+ # Convert each url to urls one for each managed language (WAGTAILFRONTENDCACHE_LANGUAGES setting).
28
+ # The managed languages are common to all the defined backends.
29
+ # This depends on settings.USE_I18N
30
+ # If WAGTAIL_I18N_ENABLED is True, this defaults to WAGTAIL_CONTENT_LANGUAGES
31
+ wagtail_i18n_enabled = getattr(settings, "WAGTAIL_I18N_ENABLED", False)
32
+ content_languages = get_content_languages() if wagtail_i18n_enabled else {}
33
+ languages = getattr(
34
+ settings, "WAGTAILFRONTENDCACHE_LANGUAGES", list(content_languages.keys())
35
+ )
36
+ if settings.USE_I18N and languages:
37
+ langs_regex = "^/(%s)/" % "|".join(languages)
38
+ new_urls = []
39
+
40
+ # Purge the given url for each managed language
41
+ for isocode in languages:
42
+ for url in urls:
43
+ up = urlsplit(url)
44
+ new_url = urlunsplit(
45
+ (
46
+ up.scheme,
47
+ up.netloc,
48
+ re.sub(langs_regex, "/%s/" % isocode, up.path),
49
+ up.query,
50
+ up.fragment,
51
+ )
52
+ )
53
+
54
+ # Check for best performance. True if re.sub found no match
55
+ # It happens when i18n_patterns was not used in urls.py to serve content for different languages from different URLs
56
+ if new_url in new_urls:
57
+ continue
58
+
59
+ new_urls.append(new_url)
60
+
61
+ urls = new_urls
62
+
63
+ urls_by_hostname = defaultdict(list)
64
+
65
+ for url in urls:
66
+ urls_by_hostname[urlsplit(url).netloc].append(url)
67
+
68
+ for hostname, urls in urls_by_hostname.items():
69
+ backends_for_hostname = {
70
+ backend_name: backend
71
+ for backend_name, backend in backends.items()
72
+ if backend.invalidates_hostname(hostname)
73
+ }
74
+
75
+ if not backends_for_hostname:
76
+ logger.info("Unable to find purge backend for %s", hostname)
77
+ continue
78
+
79
+ for backend_name, backend in backends_for_hostname.items():
80
+ for url in urls:
81
+ logger.info("[%s] Purging URL: %s", backend_name, url)
82
+
83
+ backend.purge_batch(urls)
@@ -447,32 +447,37 @@ class TestCachePurgingFunctions(TestCase):
447
447
  PURGED_URLS.clear()
448
448
 
449
449
  def test_purge_url_from_cache(self):
450
- purge_url_from_cache("http://localhost/foo")
450
+ with self.captureOnCommitCallbacks(execute=True):
451
+ purge_url_from_cache("http://localhost/foo")
451
452
  self.assertEqual(PURGED_URLS, {"http://localhost/foo"})
452
453
 
453
454
  def test_purge_urls_from_cache(self):
454
- purge_urls_from_cache(["http://localhost/foo", "http://localhost/bar"])
455
+ with self.captureOnCommitCallbacks(execute=True):
456
+ purge_urls_from_cache(["http://localhost/foo", "http://localhost/bar"])
455
457
  self.assertEqual(PURGED_URLS, {"http://localhost/foo", "http://localhost/bar"})
456
458
 
457
459
  def test_purge_page_from_cache(self):
458
- page = EventIndex.objects.get(url_path="/home/events/")
459
- purge_page_from_cache(page)
460
+ with self.captureOnCommitCallbacks(execute=True):
461
+ page = EventIndex.objects.get(url_path="/home/events/")
462
+ purge_page_from_cache(page)
460
463
  self.assertEqual(
461
464
  PURGED_URLS, {"http://localhost/events/", "http://localhost/events/past/"}
462
465
  )
463
466
 
464
467
  def test_purge_pages_from_cache(self):
465
- purge_pages_from_cache(EventIndex.objects.all())
468
+ with self.captureOnCommitCallbacks(execute=True):
469
+ purge_pages_from_cache(EventIndex.objects.all())
466
470
  self.assertEqual(
467
471
  PURGED_URLS, {"http://localhost/events/", "http://localhost/events/past/"}
468
472
  )
469
473
 
470
474
  def test_purge_batch(self):
471
- batch = PurgeBatch()
472
- page = EventIndex.objects.get(url_path="/home/events/")
473
- batch.add_page(page)
474
- batch.add_url("http://localhost/foo")
475
- batch.purge()
475
+ with self.captureOnCommitCallbacks(execute=True):
476
+ batch = PurgeBatch()
477
+ page = EventIndex.objects.get(url_path="/home/events/")
478
+ batch.add_page(page)
479
+ batch.add_url("http://localhost/foo")
480
+ batch.purge()
476
481
 
477
482
  self.assertEqual(
478
483
  PURGED_URLS,
@@ -493,7 +498,8 @@ class TestCachePurgingFunctions(TestCase):
493
498
  )
494
499
  def test_invalidate_specific_location(self):
495
500
  with self.assertLogs(level="INFO") as log_output:
496
- purge_url_from_cache("http://localhost/foo")
501
+ with self.captureOnCommitCallbacks(execute=True):
502
+ purge_url_from_cache("http://localhost/foo")
497
503
 
498
504
  self.assertEqual(PURGED_URLS, set())
499
505
  self.assertIn(
@@ -501,7 +507,8 @@ class TestCachePurgingFunctions(TestCase):
501
507
  log_output.output[0],
502
508
  )
503
509
 
504
- purge_url_from_cache("http://example.com/foo")
510
+ with self.captureOnCommitCallbacks(execute=True):
511
+ purge_url_from_cache("http://example.com/foo")
505
512
  self.assertEqual(PURGED_URLS, {"http://example.com/foo"})
506
513
 
507
514
 
@@ -520,10 +527,11 @@ class TestCloudflareCachePurgingFunctions(TestCase):
520
527
  PURGED_URLS.clear()
521
528
 
522
529
  def test_cloudflare_purge_batch_chunked(self):
523
- batch = PurgeBatch()
524
- urls = [f"https://localhost/foo{i}" for i in range(1, 65)]
525
- batch.add_urls(urls)
526
- batch.purge()
530
+ with self.captureOnCommitCallbacks(execute=True):
531
+ batch = PurgeBatch()
532
+ urls = [f"https://localhost/foo{i}" for i in range(1, 65)]
533
+ batch.add_urls(urls)
534
+ batch.purge()
527
535
 
528
536
  self.assertCountEqual(PURGED_URLS, set(urls))
529
537
 
@@ -543,24 +551,27 @@ class TestCachePurgingSignals(TestCase):
543
551
  PURGED_URLS.clear()
544
552
 
545
553
  def test_purge_on_publish(self):
546
- page = EventIndex.objects.get(url_path="/home/events/")
547
- page.save_revision().publish()
554
+ with self.captureOnCommitCallbacks(execute=True):
555
+ page = EventIndex.objects.get(url_path="/home/events/")
556
+ page.save_revision().publish()
548
557
  self.assertEqual(
549
558
  PURGED_URLS, {"http://localhost/events/", "http://localhost/events/past/"}
550
559
  )
551
560
 
552
561
  def test_purge_on_unpublish(self):
553
- page = EventIndex.objects.get(url_path="/home/events/")
554
- page.unpublish()
562
+ with self.captureOnCommitCallbacks(execute=True):
563
+ page = EventIndex.objects.get(url_path="/home/events/")
564
+ page.unpublish()
555
565
  self.assertEqual(
556
566
  PURGED_URLS, {"http://localhost/events/", "http://localhost/events/past/"}
557
567
  )
558
568
 
559
569
  def test_purge_with_unroutable_page(self):
560
- root = Page.objects.get(url_path="/")
561
- page = EventIndex(title="new top-level page")
562
- root.add_child(instance=page)
563
- page.save_revision().publish()
570
+ with self.captureOnCommitCallbacks(execute=True):
571
+ root = Page.objects.get(url_path="/")
572
+ page = EventIndex(title="new top-level page")
573
+ root.add_child(instance=page)
574
+ page.save_revision().publish()
564
575
  self.assertEqual(PURGED_URLS, set())
565
576
 
566
577
  @override_settings(
@@ -569,8 +580,9 @@ class TestCachePurgingSignals(TestCase):
569
580
  WAGTAILFRONTENDCACHE_LANGUAGES=["en", "fr", "pt-br"],
570
581
  )
571
582
  def test_purge_on_publish_in_multilang_env(self):
572
- page = EventIndex.objects.get(url_path="/home/events/")
573
- page.save_revision().publish()
583
+ with self.captureOnCommitCallbacks(execute=True):
584
+ page = EventIndex.objects.get(url_path="/home/events/")
585
+ page.save_revision().publish()
574
586
 
575
587
  self.assertEqual(
576
588
  PURGED_URLS,
@@ -591,8 +603,9 @@ class TestCachePurgingSignals(TestCase):
591
603
  WAGTAIL_CONTENT_LANGUAGES=[("en", "English"), ("fr", "French")],
592
604
  )
593
605
  def test_purge_on_publish_with_i18n_enabled(self):
594
- page = EventIndex.objects.get(url_path="/home/events/")
595
- page.save_revision().publish()
606
+ with self.captureOnCommitCallbacks(execute=True):
607
+ page = EventIndex.objects.get(url_path="/home/events/")
608
+ page.save_revision().publish()
596
609
 
597
610
  self.assertEqual(
598
611
  PURGED_URLS,
@@ -610,9 +623,10 @@ class TestCachePurgingSignals(TestCase):
610
623
  WAGTAIL_CONTENT_LANGUAGES=[("en", "English"), ("fr", "French")],
611
624
  )
612
625
  def test_purge_on_publish_without_i18n_enabled(self):
613
- # It should ignore WAGTAIL_CONTENT_LANGUAGES as WAGTAIL_I18N_ENABLED isn't set
614
- page = EventIndex.objects.get(url_path="/home/events/")
615
- page.save_revision().publish()
626
+ with self.captureOnCommitCallbacks(execute=True):
627
+ # It should ignore WAGTAIL_CONTENT_LANGUAGES as WAGTAIL_I18N_ENABLED isn't set
628
+ page = EventIndex.objects.get(url_path="/home/events/")
629
+ page.save_revision().publish()
616
630
  self.assertEqual(
617
631
  PURGED_URLS,
618
632
  {"http://localhost/en/events/", "http://localhost/en/events/past/"},
@@ -696,7 +710,8 @@ class TestPurgeBatchClass(TestCase):
696
710
  batch.add_url("http://localhost/events/")
697
711
 
698
712
  with self.assertLogs(level="ERROR") as log_output:
699
- batch.purge(backend_settings=backend_settings)
713
+ with self.captureOnCommitCallbacks(execute=True):
714
+ batch.purge(backend_settings=backend_settings)
700
715
 
701
716
  self.assertIn(
702
717
  "Couldn't purge 'http://localhost/events/' from Cloudflare. HTTPError: 500",
@@ -1,14 +1,9 @@
1
1
  import logging
2
- import re
3
- from collections import defaultdict
4
- from urllib.parse import urlsplit, urlunsplit
5
2
 
6
3
  from django.conf import settings
7
4
  from django.core.exceptions import ImproperlyConfigured
8
5
  from django.utils.module_loading import import_string
9
6
 
10
- from wagtail.coreutils import get_content_languages
11
-
12
7
  logger = logging.getLogger("wagtail.frontendcache")
13
8
 
14
9
 
@@ -64,72 +59,9 @@ def purge_url_from_cache(url, backend_settings=None, backends=None):
64
59
 
65
60
 
66
61
  def purge_urls_from_cache(urls, backend_settings=None, backends=None):
67
- if not urls:
68
- return
69
-
70
- backends = get_backends(backend_settings, backends)
71
-
72
- # If no backends are configured, there's nothing to do
73
- if not backends:
74
- return
75
-
76
- # Convert each url to urls one for each managed language (WAGTAILFRONTENDCACHE_LANGUAGES setting).
77
- # The managed languages are common to all the defined backends.
78
- # This depends on settings.USE_I18N
79
- # If WAGTAIL_I18N_ENABLED is True, this defaults to WAGTAIL_CONTENT_LANGUAGES
80
- wagtail_i18n_enabled = getattr(settings, "WAGTAIL_I18N_ENABLED", False)
81
- content_languages = get_content_languages() if wagtail_i18n_enabled else {}
82
- languages = getattr(
83
- settings, "WAGTAILFRONTENDCACHE_LANGUAGES", list(content_languages.keys())
84
- )
85
- if settings.USE_I18N and languages:
86
- langs_regex = "^/(%s)/" % "|".join(languages)
87
- new_urls = []
88
-
89
- # Purge the given url for each managed language
90
- for isocode in languages:
91
- for url in urls:
92
- up = urlsplit(url)
93
- new_url = urlunsplit(
94
- (
95
- up.scheme,
96
- up.netloc,
97
- re.sub(langs_regex, "/%s/" % isocode, up.path),
98
- up.query,
99
- up.fragment,
100
- )
101
- )
102
-
103
- # Check for best performance. True if re.sub found no match
104
- # It happens when i18n_patterns was not used in urls.py to serve content for different languages from different URLs
105
- if new_url in new_urls:
106
- continue
107
-
108
- new_urls.append(new_url)
109
-
110
- urls = new_urls
111
-
112
- urls_by_hostname = defaultdict(list)
113
-
114
- for url in urls:
115
- urls_by_hostname[urlsplit(url).netloc].append(url)
116
-
117
- for hostname, urls in urls_by_hostname.items():
118
- backends_for_hostname = {
119
- backend_name: backend
120
- for backend_name, backend in backends.items()
121
- if backend.invalidates_hostname(hostname)
122
- }
123
-
124
- if not backends_for_hostname:
125
- logger.info("Unable to find purge backend for %s", hostname)
126
- continue
127
-
128
- for backend_name, backend in backends_for_hostname.items():
129
- for url in urls:
130
- logger.info("[%s] Purging URL: %s", backend_name, url)
62
+ from .tasks import purge_urls_from_cache_task
131
63
 
132
- backend.purge_batch(urls)
64
+ purge_urls_from_cache_task.enqueue(list(urls), backend_settings, backends)
133
65
 
134
66
 
135
67
  def _get_page_cached_urls(page):
@@ -1,8 +1,6 @@
1
1
  import csv
2
2
  from io import BytesIO, StringIO
3
3
 
4
- import openpyxl
5
-
6
4
 
7
5
  class Dataset(list):
8
6
  def __init__(self, rows=(), headers=None):
@@ -66,6 +64,8 @@ class XLSX:
66
64
  """
67
65
  Create dataset from the first sheet of a xlsx workbook.
68
66
  """
67
+ import openpyxl
68
+
69
69
  workbook = openpyxl.load_workbook(BytesIO(data), read_only=True, data_only=True)
70
70
  sheet = workbook.worksheets[0]
71
71
  try:
@@ -99,6 +99,9 @@ msgstr "هل أنت متأكد من أنك تريد حذف إعادة التوج
99
99
  msgid "Yes, delete"
100
100
  msgstr "نعم احذف"
101
101
 
102
+ msgid "Cancel"
103
+ msgstr "إلغاء"
104
+
102
105
  msgid "Preview"
103
106
  msgstr "نظر مقدم"
104
107
 
@@ -8,7 +8,7 @@ msgid ""
8
8
  msgstr ""
9
9
  "Project-Id-Version: PACKAGE VERSION\n"
10
10
  "Report-Msgid-Bugs-To: \n"
11
- "POT-Creation-Date: 2024-10-21 17:53+0100\n"
11
+ "POT-Creation-Date: 2025-01-20 17:59+0000\n"
12
12
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -22,7 +22,7 @@ msgstr ""
22
22
  msgid "Wagtail redirects"
23
23
  msgstr ""
24
24
 
25
- #: filters.py:12 views.py:102 views.py:119
25
+ #: filters.py:12 views.py:100 views.py:117
26
26
  msgid "Type"
27
27
  msgstr ""
28
28
 
@@ -123,19 +123,14 @@ msgstr ""
123
123
  msgid "redirects"
124
124
  msgstr ""
125
125
 
126
- #: templates/wagtailredirects/add.html:3 templates/wagtailredirects/add.html:5
127
- #: views.py:70
128
- msgid "Add redirect"
129
- msgstr ""
130
-
131
- #: templates/wagtailredirects/choose_import_file.html:3 views.py:79
126
+ #: templates/wagtailredirects/choose_import_file.html:3 views.py:77
132
127
  #: wagtail_hooks.py:34
133
128
  msgid "Redirects"
134
129
  msgstr ""
135
130
 
136
131
  #: templates/wagtailredirects/choose_import_file.html:6
137
132
  #: templates/wagtailredirects/confirm_import.html:6
138
- #: templates/wagtailredirects/import_summary.html:5 views.py:130
133
+ #: templates/wagtailredirects/import_summary.html:5 views.py:128
139
134
  msgid "Import redirects"
140
135
  msgstr ""
141
136
 
@@ -157,17 +152,9 @@ msgid "Delete redirect %(title)s"
157
152
  msgstr ""
158
153
 
159
154
  #: templates/wagtailredirects/confirm_delete.html:5
160
- msgid "Delete"
161
- msgstr ""
162
-
163
- #: templates/wagtailredirects/confirm_delete.html:9
164
155
  msgid "Are you sure you want to delete this redirect?"
165
156
  msgstr ""
166
157
 
167
- #: templates/wagtailredirects/confirm_delete.html:12
168
- msgid "Yes, delete"
169
- msgstr ""
170
-
171
158
  #: templates/wagtailredirects/confirm_import.html:4
172
159
  #: templates/wagtailredirects/confirm_import.html:7
173
160
  msgid "Confirm import"
@@ -196,11 +183,11 @@ msgid ""
196
183
  "Found %(total)s redirects, created %(successes)s and found %(errors)s errors."
197
184
  msgstr ""
198
185
 
199
- #: templates/wagtailredirects/import_summary.html:17 views.py:84 views.py:116
186
+ #: templates/wagtailredirects/import_summary.html:17 views.py:82 views.py:114
200
187
  msgid "From"
201
188
  msgstr ""
202
189
 
203
- #: templates/wagtailredirects/import_summary.html:18 views.py:96 views.py:118
190
+ #: templates/wagtailredirects/import_summary.html:18 views.py:94 views.py:116
204
191
  msgid "To"
205
192
  msgstr ""
206
193
 
@@ -224,57 +211,57 @@ msgid ""
224
211
  "href=\"%(wagtailredirects_add_redirect_url)s\">add one</a>?"
225
212
  msgstr ""
226
213
 
227
- #: views.py:90 views.py:117
214
+ #: views.py:68
215
+ msgid "Add redirect"
216
+ msgstr ""
217
+
218
+ #: views.py:88 views.py:115
228
219
  msgid "Site"
229
220
  msgstr ""
230
221
 
231
- #: views.py:148
222
+ #: views.py:146
232
223
  msgid "The redirect could not be saved due to errors."
233
224
  msgstr ""
234
225
 
235
- #: views.py:153
226
+ #: views.py:150
236
227
  #, python-format
237
228
  msgid "Redirect '%(redirect_title)s' updated."
238
229
  msgstr ""
239
230
 
240
- #: views.py:190
231
+ #: views.py:183
241
232
  #, python-format
242
233
  msgid "Redirect '%(redirect_title)s' deleted."
243
234
  msgstr ""
244
235
 
245
- #: views.py:217
246
- #, python-format
247
- msgid "Redirect '%(redirect_title)s' added."
248
- msgstr ""
249
-
250
- #: views.py:222
251
- msgid "Edit"
236
+ #: views.py:196
237
+ msgid "The redirect could not be created due to errors."
252
238
  msgstr ""
253
239
 
254
- #: views.py:229
255
- msgid "The redirect could not be created due to errors."
240
+ #: views.py:200
241
+ #, python-format
242
+ msgid "Redirect '%(redirect_title)s' added."
256
243
  msgstr ""
257
244
 
258
- #: views.py:273
245
+ #: views.py:235
259
246
  msgid "Search redirects"
260
247
  msgstr ""
261
248
 
262
- #: views.py:287
249
+ #: views.py:249
263
250
  #, python-format
264
251
  msgid "File format of type \"%(extension)s\" is not supported"
265
252
  msgstr ""
266
253
 
267
- #: views.py:304
254
+ #: views.py:266
268
255
  #, python-format
269
256
  msgid "Imported file has a wrong encoding: %(error_message)s"
270
257
  msgstr ""
271
258
 
272
- #: views.py:311
259
+ #: views.py:273
273
260
  #, python-format
274
261
  msgid "%(error)s encountered while trying to read file: %(filename)s"
275
262
  msgstr ""
276
263
 
277
- #: views.py:402
264
+ #: views.py:364
278
265
  #, python-format
279
266
  msgid "Imported %(total)d redirect"
280
267
  msgid_plural "Imported %(total)d redirects"
@@ -1,24 +1 @@
1
- {% extends "wagtailadmin/generic/form.html" %}
2
- {% load i18n wagtailadmin_tags %}
3
- {% block titletag %}{% trans "Add redirect" %}{% endblock %}
4
- {% block content %}
5
- {% trans "Add redirect" as add_red_str %}
6
- {% include "wagtailadmin/shared/header.html" with title=add_red_str icon="redirect" %}
7
-
8
- {% include "wagtailadmin/shared/non_field_errors.html" %}
9
-
10
- <form action="{% url 'wagtailredirects:add' %}" method="POST" class="nice-padding" novalidate>
11
- {% csrf_token %}
12
-
13
- <ul class="fields">
14
- {% for field in form.visible_fields %}
15
- <li>{% formattedfield field %}</li>
16
- {% endfor %}
17
- </ul>
18
-
19
- {% block footer %}
20
- {{ block.super }}
21
- {% endblock %}
22
- </form>
23
-
24
- {% endblock %}
1
+ {% extends "wagtailadmin/generic/create.html" %}