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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. wagtail/__init__.py +1 -1
  2. wagtail/actions/copy_for_translation.py +15 -1
  3. wagtail/admin/checks.py +20 -30
  4. wagtail/admin/forms/pages.py +10 -0
  5. wagtail/admin/icons.py +43 -0
  6. wagtail/admin/locale/en/LC_MESSAGES/django.po +405 -295
  7. wagtail/admin/locale/en/LC_MESSAGES/djangojs.po +21 -3
  8. wagtail/admin/locale/sl/LC_MESSAGES/django.mo +0 -0
  9. wagtail/admin/locale/sl/LC_MESSAGES/django.po +30 -0
  10. wagtail/admin/menu.py +2 -2
  11. wagtail/admin/migrations/0004_editingsession.py +57 -0
  12. wagtail/admin/migrations/0005_editingsession_is_editing.py +18 -0
  13. wagtail/admin/models.py +36 -3
  14. wagtail/admin/rich_text/editors/draftail/__init__.py +2 -20
  15. wagtail/admin/static/wagtailadmin/css/core.css +1 -1
  16. wagtail/admin/static/wagtailadmin/js/bulk-actions.js +1 -1
  17. wagtail/admin/static/wagtailadmin/js/chooser-modal.js +1 -1
  18. wagtail/admin/static/wagtailadmin/js/chooser-widget-telepath.js +1 -1
  19. wagtail/admin/static/wagtailadmin/js/chooser-widget.js +1 -1
  20. wagtail/admin/static/wagtailadmin/js/comments.js +1 -1
  21. wagtail/admin/static/wagtailadmin/js/core.js +1 -1
  22. wagtail/admin/static/wagtailadmin/js/date-time-chooser.js +1 -1
  23. wagtail/admin/static/wagtailadmin/js/draftail.js +1 -1
  24. wagtail/admin/static/wagtailadmin/js/expanding-formset.js +1 -1
  25. wagtail/admin/static/wagtailadmin/js/filtered-select.js +1 -1
  26. wagtail/admin/static/wagtailadmin/js/modal-workflow.js +1 -1
  27. wagtail/admin/static/wagtailadmin/js/page-chooser-modal.js +1 -1
  28. wagtail/admin/static/wagtailadmin/js/page-chooser-telepath.js +1 -1
  29. wagtail/admin/static/wagtailadmin/js/page-chooser.js +1 -1
  30. wagtail/admin/static/wagtailadmin/js/preview-panel.js +2 -1
  31. wagtail/admin/static/wagtailadmin/js/preview-panel.js.LICENSE.txt +11 -0
  32. wagtail/admin/static/wagtailadmin/js/privacy-switch.js +1 -1
  33. wagtail/admin/static/wagtailadmin/js/sidebar.js +1 -1
  34. wagtail/admin/static/wagtailadmin/js/task-chooser-modal.js +1 -1
  35. wagtail/admin/static/wagtailadmin/js/task-chooser.js +1 -1
  36. wagtail/admin/static/wagtailadmin/js/telepath/blocks.js +1 -1
  37. wagtail/admin/static/wagtailadmin/js/telepath/widgets.js +1 -1
  38. wagtail/admin/static/wagtailadmin/js/userbar.js +2 -1
  39. wagtail/admin/static/wagtailadmin/js/userbar.js.LICENSE.txt +11 -0
  40. wagtail/admin/static/wagtailadmin/js/vendor.js +1 -1
  41. wagtail/admin/static/wagtailadmin/js/vendor.js.LICENSE.txt +0 -12
  42. wagtail/admin/static/wagtailadmin/js/wagtailadmin.js +1 -1
  43. wagtail/admin/static/wagtailadmin/js/workflow-action.js +1 -1
  44. wagtail/admin/templates/wagtailadmin/collection_privacy/ancestor_privacy.html +2 -6
  45. wagtail/admin/templates/wagtailadmin/generic/index_results.html +1 -17
  46. wagtail/admin/templates/wagtailadmin/generic/listing_results.html +20 -1
  47. wagtail/admin/templates/wagtailadmin/home/workflow_objects_to_moderate.html +2 -11
  48. wagtail/admin/templates/wagtailadmin/page_privacy/ancestor_privacy.html +2 -6
  49. wagtail/admin/templates/wagtailadmin/page_privacy/no_privacy.html +2 -0
  50. wagtail/admin/templates/wagtailadmin/pages/_editor_js.html +0 -1
  51. wagtail/admin/templates/wagtailadmin/pages/action_menu/menu.html +1 -1
  52. wagtail/admin/templates/wagtailadmin/reports/aging_pages_results.html +54 -0
  53. wagtail/admin/templates/wagtailadmin/reports/base_page_report.html +1 -17
  54. wagtail/admin/templates/wagtailadmin/reports/base_page_report_results.html +10 -0
  55. wagtail/admin/templates/wagtailadmin/reports/base_report.html +1 -40
  56. wagtail/admin/templates/wagtailadmin/reports/base_report_results.html +1 -0
  57. wagtail/admin/templates/wagtailadmin/reports/listing/_list_page_report.html +21 -27
  58. wagtail/admin/templates/wagtailadmin/reports/listing/_list_page_types_usage.html +48 -54
  59. wagtail/admin/templates/wagtailadmin/reports/{locked_pages.html → locked_pages_results.html} +3 -3
  60. wagtail/admin/templates/wagtailadmin/reports/page_types_usage_results.html +10 -0
  61. wagtail/admin/templates/wagtailadmin/reports/site_history_results.html +53 -0
  62. wagtail/admin/templates/wagtailadmin/reports/workflow_results.html +74 -0
  63. wagtail/admin/templates/wagtailadmin/reports/workflow_tasks_results.html +56 -0
  64. wagtail/admin/templates/wagtailadmin/shared/_workflow_init.html +8 -44
  65. wagtail/admin/templates/wagtailadmin/shared/avatar.html +11 -1
  66. wagtail/admin/templates/wagtailadmin/shared/dialog/dialog.html +5 -4
  67. wagtail/admin/templates/wagtailadmin/shared/dropdown/dropdown_button.html +2 -1
  68. wagtail/admin/templates/wagtailadmin/shared/editing_sessions/list.html +132 -0
  69. wagtail/admin/templates/wagtailadmin/shared/editing_sessions/module.html +44 -0
  70. wagtail/admin/templates/wagtailadmin/shared/headers/slim_header.html +7 -1
  71. wagtail/admin/templates/wagtailadmin/shared/page_status_tag_new.html +1 -1
  72. wagtail/admin/templates/wagtailadmin/shared/side_panels/checks.html +32 -16
  73. wagtail/admin/templates/wagtailadmin/skeleton.html +1 -1
  74. wagtail/admin/templates/wagtailadmin/userbar/item_accessibility.html +9 -11
  75. wagtail/admin/templatetags/wagtailadmin_tags.py +13 -2
  76. wagtail/admin/tests/formats/en/__init__.py +0 -0
  77. wagtail/admin/tests/formats/en/formats.py +1 -0
  78. wagtail/admin/tests/pages/test_create_page.py +47 -0
  79. wagtail/admin/tests/pages/test_edit_page.py +10 -8
  80. wagtail/admin/tests/pages/test_parent_page_chooser_view.py +45 -1
  81. wagtail/admin/tests/test_checks.py +53 -3
  82. wagtail/admin/tests/test_collections_views.py +62 -1
  83. wagtail/admin/tests/test_edit_handlers.py +37 -0
  84. wagtail/admin/tests/test_editing_sessions.py +1336 -0
  85. wagtail/admin/tests/test_icon_sprite.py +12 -21
  86. wagtail/admin/tests/test_page_chooser.py +309 -7
  87. wagtail/admin/tests/test_privacy.py +82 -0
  88. wagtail/admin/tests/test_reports_views.py +464 -70
  89. wagtail/admin/tests/test_userbar.py +93 -6
  90. wagtail/admin/tests/test_workflows.py +223 -33
  91. wagtail/admin/tests/viewsets/test_model_viewset.py +151 -2
  92. wagtail/admin/ui/editing_sessions.py +57 -0
  93. wagtail/admin/urls/__init__.py +9 -15
  94. wagtail/admin/urls/editing_sessions.py +17 -0
  95. wagtail/admin/urls/reports.py +33 -1
  96. wagtail/admin/userbar.py +77 -20
  97. wagtail/admin/views/chooser.py +49 -22
  98. wagtail/admin/views/collections.py +0 -11
  99. wagtail/admin/views/editing_sessions.py +193 -0
  100. wagtail/admin/views/generic/__init__.py +1 -0
  101. wagtail/admin/views/generic/history.py +9 -3
  102. wagtail/admin/views/generic/mixins.py +44 -3
  103. wagtail/admin/views/generic/models.py +46 -72
  104. wagtail/admin/views/generic/permissions.py +20 -10
  105. wagtail/admin/views/home.py +2 -31
  106. wagtail/admin/views/page_privacy.py +20 -5
  107. wagtail/admin/views/pages/choose_parent.py +62 -0
  108. wagtail/admin/views/pages/edit.py +28 -0
  109. wagtail/admin/views/reports/aging_pages.py +6 -10
  110. wagtail/admin/views/reports/audit_logging.py +13 -42
  111. wagtail/admin/views/reports/base.py +31 -4
  112. wagtail/admin/views/reports/locked_pages.py +5 -8
  113. wagtail/admin/views/reports/page_types_usage.py +6 -10
  114. wagtail/admin/views/reports/workflows.py +36 -12
  115. wagtail/admin/viewsets/base.py +8 -3
  116. wagtail/admin/viewsets/chooser.py +1 -1
  117. wagtail/admin/viewsets/model.py +26 -1
  118. wagtail/admin/wagtail_hooks.py +2 -1
  119. wagtail/api/v2/filters.py +6 -0
  120. wagtail/api/v2/tests/test_documents.py +1 -1
  121. wagtail/api/v2/tests/test_images.py +1 -1
  122. wagtail/api/v2/tests/test_pages.py +11 -1
  123. wagtail/api/v2/utils.py +2 -2
  124. wagtail/blocks/base.py +35 -12
  125. wagtail/blocks/definition_lookup.py +85 -0
  126. wagtail/blocks/list_block.py +12 -0
  127. wagtail/blocks/migrations/migrate_operation.py +2 -0
  128. wagtail/blocks/stream_block.py +19 -0
  129. wagtail/blocks/struct_block.py +19 -0
  130. wagtail/contrib/forms/locale/en/LC_MESSAGES/django.po +1 -1
  131. wagtail/contrib/frontend_cache/backends/__init__.py +5 -0
  132. wagtail/contrib/frontend_cache/backends/azure.py +179 -0
  133. wagtail/contrib/frontend_cache/backends/base.py +28 -0
  134. wagtail/contrib/frontend_cache/backends/cloudflare.py +114 -0
  135. wagtail/contrib/frontend_cache/backends/cloudfront.py +99 -0
  136. wagtail/contrib/frontend_cache/backends/http.py +64 -0
  137. wagtail/contrib/frontend_cache/tests.py +59 -17
  138. wagtail/contrib/frontend_cache/utils.py +26 -8
  139. wagtail/contrib/redirects/filters.py +15 -1
  140. wagtail/contrib/redirects/locale/en/LC_MESSAGES/django.po +37 -72
  141. wagtail/contrib/redirects/models.py +6 -5
  142. wagtail/contrib/redirects/templates/wagtailredirects/edit.html +1 -38
  143. wagtail/contrib/redirects/tests/test_redirects.py +141 -1
  144. wagtail/contrib/redirects/urls.py +1 -2
  145. wagtail/contrib/redirects/views.py +39 -80
  146. wagtail/contrib/routable_page/models.py +6 -4
  147. wagtail/contrib/routable_page/tests.py +11 -0
  148. wagtail/contrib/search_promotions/locale/en/LC_MESSAGES/django.po +1 -1
  149. wagtail/contrib/settings/locale/en/LC_MESSAGES/django.po +4 -4
  150. wagtail/contrib/simple_translation/locale/en/LC_MESSAGES/django.po +5 -1
  151. wagtail/contrib/simple_translation/models.py +2 -1
  152. wagtail/contrib/styleguide/locale/en/LC_MESSAGES/django.po +7 -7
  153. wagtail/contrib/table_block/locale/en/LC_MESSAGES/django.po +1 -1
  154. wagtail/contrib/table_block/static/table_block/js/table.js +1 -1
  155. wagtail/contrib/typed_table_block/blocks.py +19 -0
  156. wagtail/contrib/typed_table_block/locale/en/LC_MESSAGES/django.po +10 -10
  157. wagtail/contrib/typed_table_block/static/typed_table_block/js/typed_table_block.js +1 -1
  158. wagtail/contrib/typed_table_block/tests.py +38 -0
  159. wagtail/coreutils.py +1 -1
  160. wagtail/documents/__init__.py +1 -1
  161. wagtail/documents/locale/en/LC_MESSAGES/django.po +8 -8
  162. wagtail/documents/locale/sl/LC_MESSAGES/django.mo +0 -0
  163. wagtail/documents/locale/sl/LC_MESSAGES/django.po +20 -0
  164. wagtail/documents/models.py +5 -1
  165. wagtail/documents/static/wagtaildocs/js/document-chooser-modal.js +1 -1
  166. wagtail/documents/static/wagtaildocs/js/document-chooser-telepath.js +1 -1
  167. wagtail/documents/static/wagtaildocs/js/document-chooser.js +1 -1
  168. wagtail/documents/tests/test_models.py +5 -1
  169. wagtail/embeds/apps.py +2 -0
  170. wagtail/embeds/embeds.py +12 -14
  171. wagtail/embeds/finders/__init__.py +2 -0
  172. wagtail/embeds/finders/facebook.py +17 -33
  173. wagtail/embeds/finders/instagram.py +19 -16
  174. wagtail/embeds/locale/en/LC_MESSAGES/django.po +1 -1
  175. wagtail/embeds/signal_handlers.py +13 -0
  176. wagtail/embeds/tests/test_embeds.py +7 -7
  177. wagtail/fields.py +58 -14
  178. wagtail/images/__init__.py +1 -1
  179. wagtail/images/locale/en/LC_MESSAGES/django.po +34 -34
  180. wagtail/images/locale/sl/LC_MESSAGES/django.mo +0 -0
  181. wagtail/images/locale/sl/LC_MESSAGES/django.po +20 -0
  182. wagtail/images/models.py +2 -0
  183. wagtail/images/static/wagtailimages/js/image-chooser-modal.js +1 -1
  184. wagtail/images/static/wagtailimages/js/image-chooser-telepath.js +1 -1
  185. wagtail/images/static/wagtailimages/js/image-chooser.js +1 -1
  186. wagtail/images/templates/wagtailimages/images/edit.html +4 -4
  187. wagtail/images/tests/test_admin_views.py +26 -2
  188. wagtail/images/views/chooser.py +6 -1
  189. wagtail/locale/en/LC_MESSAGES/django.po +84 -80
  190. wagtail/locales/locale/en/LC_MESSAGES/django.po +2 -2
  191. wagtail/locales/tests.py +16 -0
  192. wagtail/locales/wagtail_hooks.py +0 -9
  193. wagtail/migrations/0094_alter_page_locale.py +19 -0
  194. wagtail/models/__init__.py +11 -5
  195. wagtail/models/i18n.py +6 -1
  196. wagtail/project_template/requirements.txt +1 -1
  197. wagtail/search/locale/en/LC_MESSAGES/django.po +1 -1
  198. wagtail/signals.py +4 -0
  199. wagtail/sites/locale/en/LC_MESSAGES/django.po +2 -2
  200. wagtail/sites/tests.py +15 -0
  201. wagtail/sites/wagtail_hooks.py +0 -9
  202. wagtail/snippets/locale/en/LC_MESSAGES/django.po +9 -9
  203. wagtail/snippets/permissions.py +5 -3
  204. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser-telepath.js +1 -1
  205. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser.js +1 -1
  206. wagtail/snippets/templates/wagtailsnippets/snippets/action_menu/menu.html +1 -1
  207. wagtail/snippets/tests/test_snippets.py +78 -12
  208. wagtail/snippets/tests/test_viewset.py +22 -0
  209. wagtail/snippets/views/snippets.py +19 -14
  210. wagtail/snippets/wagtail_hooks.py +2 -10
  211. wagtail/templatetags/wagtailcore_tags.py +3 -0
  212. wagtail/test/dummy_external_storage.py +1 -1
  213. wagtail/test/i18n/migrations/0003_alter_clusterabletestmodel_locale_and_more.py +40 -0
  214. wagtail/test/routablepage/models.py +4 -0
  215. wagtail/test/snippets/migrations/0012_alter_translatablesnippet_locale.py +20 -0
  216. wagtail/test/testapp/migrations/0038_sociallink.py +52 -0
  217. wagtail/test/testapp/migrations/0039_alter_eventcategory_locale_and_more.py +45 -0
  218. wagtail/test/testapp/models.py +24 -0
  219. wagtail/test/testapp/views.py +1 -0
  220. wagtail/test/testapp/wagtail_hooks.py +9 -0
  221. wagtail/test/urls_multilang.py +6 -1
  222. wagtail/test/urls_multilang_non_root.py +11 -0
  223. wagtail/tests/streamfield_migrations/test_migrations.py +53 -12
  224. wagtail/tests/test_audit_log.py +72 -2
  225. wagtail/tests/test_blocks.py +103 -0
  226. wagtail/tests/test_signals.py +49 -2
  227. wagtail/tests/test_streamfield.py +153 -0
  228. wagtail/tests/test_utils.py +14 -0
  229. wagtail/tests/tests.py +5 -0
  230. wagtail/users/apps.py +1 -0
  231. wagtail/users/forms.py +7 -0
  232. wagtail/users/locale/en/LC_MESSAGES/django.po +55 -50
  233. wagtail/users/models.py +1 -0
  234. wagtail/users/templates/wagtailusers/groups/includes/formatted_permissions.html +3 -2
  235. wagtail/users/templatetags/wagtailusers_tags.py +9 -0
  236. wagtail/users/tests/__init__.py +7 -1
  237. wagtail/users/tests/test_admin_views.py +117 -32
  238. wagtail/users/views/groups.py +4 -0
  239. wagtail/users/views/users.py +58 -14
  240. wagtail/users/wagtail_hooks.py +7 -123
  241. wagtail/utils/utils.py +1 -0
  242. wagtail/utils/version.py +5 -2
  243. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/METADATA +3 -3
  244. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/RECORD +248 -222
  245. wagtail/admin/templates/wagtailadmin/reports/aging_pages.html +0 -58
  246. wagtail/admin/templates/wagtailadmin/reports/page_types_usage.html +0 -18
  247. wagtail/admin/templates/wagtailadmin/reports/site_history.html +0 -57
  248. wagtail/admin/templates/wagtailadmin/reports/workflow.html +0 -81
  249. wagtail/admin/templates/wagtailadmin/reports/workflow_tasks.html +0 -63
  250. wagtail/contrib/frontend_cache/backends.py +0 -400
  251. wagtail/contrib/redirects/templates/wagtailredirects/list.html +0 -43
  252. wagtail/contrib/redirects/templates/wagtailredirects/reports/redirects_report.html +0 -14
  253. wagtail/contrib/redirects/tests/test_reports_view.py +0 -82
  254. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/LICENSE +0 -0
  255. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/WHEEL +0 -0
  256. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/entry_points.txt +0 -0
  257. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  import os
2
+ from typing import List
2
3
 
3
4
  from django.core.exceptions import PermissionDenied, SuspiciousOperation
4
5
  from django.db import transaction
@@ -16,7 +17,6 @@ from wagtail.admin.auth import PermissionPolicyChecker
16
17
  from wagtail.admin.forms.search import SearchForm
17
18
  from wagtail.admin.ui.tables import Column, StatusTagColumn, TitleColumn
18
19
  from wagtail.admin.views import generic
19
- from wagtail.admin.views.reports import ReportView
20
20
  from wagtail.admin.widgets.button import Button
21
21
  from wagtail.contrib.redirects import models
22
22
  from wagtail.contrib.redirects.filters import RedirectsReportFilterSet
@@ -45,12 +45,12 @@ class RedirectTargetColumn(Column):
45
45
  url_name = "wagtailadmin_pages:edit"
46
46
 
47
47
  def get_value(self, instance):
48
- if instance.redirect_page:
48
+ if instance.redirect_page_id:
49
49
  return instance.redirect_page.get_admin_display_title()
50
50
  return instance.redirect_link
51
51
 
52
52
  def get_url(self, instance):
53
- if instance.redirect_page:
53
+ if instance.redirect_page_id:
54
54
  return reverse(self.url_name, args=[instance.redirect_page_id])
55
55
  return None
56
56
 
@@ -105,71 +105,54 @@ class IndexView(generic.IndexView):
105
105
  primary=lambda r: r.is_permanent,
106
106
  ),
107
107
  ]
108
+ filterset_class = RedirectsReportFilterSet
109
+ list_export = [
110
+ "old_path",
111
+ "link",
112
+ "get_is_permanent_display",
113
+ "site",
114
+ ]
115
+ export_headings = {
116
+ "old_path": _("From"),
117
+ "site": _("Site"),
118
+ "link": _("To"),
119
+ "get_is_permanent_display": _("Type"),
120
+ }
108
121
 
109
122
  def get_base_queryset(self):
110
123
  return super().get_base_queryset().select_related("redirect_page", "site")
111
124
 
112
125
  @cached_property
113
- def header_more_buttons(self):
114
- return [
126
+ def header_more_buttons(self) -> List[Button]:
127
+ buttons = super().header_more_buttons.copy()
128
+ buttons.append(
115
129
  Button(
116
130
  _("Import redirects"),
117
131
  url=reverse("wagtailredirects:start_import"),
118
132
  icon_name="doc-full-inverse",
119
- priority=90,
120
- ),
121
- Button(
122
- _("Export redirects"),
123
- url=reverse("wagtailredirects:report"),
124
- icon_name="download",
125
- priority=100,
126
- ),
127
- ]
128
-
129
-
130
- @permission_checker.require("change")
131
- def edit(request, redirect_id):
132
- theredirect = get_object_or_404(models.Redirect, id=redirect_id)
133
+ priority=50,
134
+ )
135
+ )
136
+ return buttons
133
137
 
134
- if not permission_policy.user_has_permission_for_instance(
135
- request.user, "change", theredirect
136
- ):
137
- raise PermissionDenied
138
138
 
139
- if request.method == "POST":
140
- form = RedirectForm(request.POST, request.FILES, instance=theredirect)
141
- if form.is_valid():
142
- with transaction.atomic():
143
- form.save()
144
- log(instance=theredirect, action="wagtail.edit")
145
- messages.success(
146
- request,
147
- _("Redirect '%(redirect_title)s' updated.")
148
- % {"redirect_title": theredirect.title},
149
- buttons=[
150
- messages.button(
151
- reverse("wagtailredirects:edit", args=(theredirect.id,)),
152
- _("Edit"),
153
- )
154
- ],
155
- )
156
- return redirect("wagtailredirects:index")
157
- else:
158
- messages.error(request, _("The redirect could not be saved due to errors."))
159
- else:
160
- form = RedirectForm(instance=theredirect)
139
+ class EditView(generic.EditView):
140
+ model = Redirect
141
+ form_class = RedirectForm
142
+ permission_policy = permission_policy
143
+ template_name = "wagtailredirects/edit.html"
144
+ index_url_name = "wagtailredirects:index"
145
+ edit_url_name = "wagtailredirects:edit"
146
+ delete_url_name = "wagtailredirects:delete"
147
+ pk_url_kwarg = "redirect_id"
148
+ error_message = gettext_lazy("The redirect could not be saved due to errors.")
149
+ header_icon = "redirect"
150
+ _show_breadcrumbs = True
161
151
 
162
- return TemplateResponse(
163
- request,
164
- "wagtailredirects/edit.html",
165
- {
166
- "redirect": theredirect,
167
- "form": form,
168
- "user_can_delete": permission_policy.user_has_permission(
169
- request.user, "delete"
170
- ),
171
- },
172
- )
152
+ def get_success_message(self):
153
+ return _("Redirect '%(redirect_title)s' updated.") % {
154
+ "redirect_title": self.object.title
155
+ }
173
156
 
174
157
 
175
158
  @permission_checker.require("delete")
@@ -444,27 +427,3 @@ def to_readable_errors(error):
444
427
  errors = [x.lstrip("* ") for x in errors]
445
428
  errors = ", ".join(errors)
446
429
  return errors
447
-
448
-
449
- class RedirectsReportView(ReportView):
450
- header_icon = "redirect"
451
- title = _("Export Redirects")
452
- template_name = "wagtailredirects/reports/redirects_report.html"
453
- filterset_class = RedirectsReportFilterSet
454
-
455
- list_export = [
456
- "old_path",
457
- "link",
458
- "get_is_permanent_display",
459
- "site",
460
- ]
461
-
462
- export_headings = {
463
- "old_path": _("From"),
464
- "site": _("Site"),
465
- "link": _("To"),
466
- "get_is_permanent_display": _("Type"),
467
- }
468
-
469
- def get_queryset(self):
470
- return models.Redirect.objects.all().order_by("old_path")
@@ -137,12 +137,12 @@ class RoutablePageMixin:
137
137
  """
138
138
  This method takes a URL path and finds the view to call.
139
139
  """
140
- view, args, kwargs = self.get_resolver().resolve(path)
140
+ resolver_match = self.get_resolver().resolve(path)
141
141
 
142
142
  # Bind the method
143
- view = view.__get__(self, type(self))
143
+ resolver_match.func = resolver_match.func.__get__(self, type(self))
144
144
 
145
- return view, args, kwargs
145
+ return resolver_match
146
146
 
147
147
  def route(self, request, path_components):
148
148
  """
@@ -154,7 +154,9 @@ class RoutablePageMixin:
154
154
  if path_components:
155
155
  path += "/".join(path_components) + "/"
156
156
 
157
- view, args, kwargs = self.resolve_subpage(path)
157
+ resolver_match = self.resolve_subpage(path)
158
+ request.routable_resolver_match = resolver_match
159
+ view, args, kwargs = resolver_match
158
160
  return RouteResult(self, args=(view, args, kwargs))
159
161
  except Http404:
160
162
  pass
@@ -134,6 +134,9 @@ class TestRoutablePage(TestCase):
134
134
  (context["page"], context["self"], context.get("foo")),
135
135
  (self.routable_page, self.routable_page, None),
136
136
  )
137
+ self.assertEqual(
138
+ context["request"].routable_resolver_match.url_name, "index_route"
139
+ )
137
140
 
138
141
  def test_get_render_method_route_view(self):
139
142
  with self.assertTemplateUsed("routablepagetests/routable_page_test.html"):
@@ -157,6 +160,14 @@ class TestRoutablePage(TestCase):
157
160
  (self.routable_page, 1, "fighters"),
158
161
  )
159
162
 
163
+ def test_get_render_method_route_view_with_arg(self):
164
+ response = self.client.get(
165
+ self.routable_page.url + "render-method-with-arg/foo/"
166
+ )
167
+ resolver_match = response.context_data["request"].routable_resolver_match
168
+ self.assertEqual(resolver_match.url_name, "render_method_test_with_arg")
169
+ self.assertEqual(resolver_match.kwargs, {"slug": "foo"})
170
+
160
171
  def test_get_routable_page_with_overridden_index_route(self):
161
172
  page = self.home_page.add_child(
162
173
  instance=RoutablePageWithOverriddenIndexRouteTest(
@@ -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-04-18 17:28+0100\n"
11
+ "POT-Creation-Date: 2024-07-19 16:26+0100\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"
@@ -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-04-18 17:28+0100\n"
11
+ "POT-Creation-Date: 2024-07-19 16:26+0100\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"
@@ -44,15 +44,15 @@ msgstr ""
44
44
  msgid "Settings"
45
45
  msgstr ""
46
46
 
47
- #: views.py:61
47
+ #: views.py:62
48
48
  msgid "This setting could not be opened because there is no site defined."
49
49
  msgstr ""
50
50
 
51
- #: views.py:83
51
+ #: views.py:84
52
52
  msgid "The setting could not be saved due to errors."
53
53
  msgstr ""
54
54
 
55
- #: views.py:149
55
+ #: views.py:152
56
56
  #, python-format
57
57
  msgid "%(setting_type)s updated."
58
58
  msgstr ""
@@ -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-04-18 17:28+0100\n"
11
+ "POT-Creation-Date: 2024-07-19 16:26+0100\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"
@@ -54,6 +54,10 @@ msgid_plural "Translate the parent pages."
54
54
  msgstr[0] ""
55
55
  msgstr[1] ""
56
56
 
57
+ #: models.py:21
58
+ msgid "Can submit translations"
59
+ msgstr ""
60
+
57
61
  #: templates/simple_translation/admin/submit_translation.html:26
58
62
  msgid "Submit"
59
63
  msgstr ""
@@ -1,5 +1,6 @@
1
1
  from django.conf import settings
2
2
  from django.db.models import Model
3
+ from django.utils.translation import gettext_lazy as _
3
4
 
4
5
  from wagtail import hooks
5
6
  from wagtail.models import Locale
@@ -17,7 +18,7 @@ class SimpleTranslation(Model):
17
18
  class Meta:
18
19
  default_permissions = []
19
20
  permissions = [
20
- ("submit_translation", "Can submit translations"),
21
+ ("submit_translation", _("Can submit translations")),
21
22
  ]
22
23
 
23
24
 
@@ -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-04-18 17:28+0100\n"
11
+ "POT-Creation-Date: 2024-07-19 16:26+0100\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"
@@ -34,26 +34,26 @@ msgstr ""
34
34
  msgid "Delete image"
35
35
  msgstr ""
36
36
 
37
- #: views.py:110 wagtail_hooks.py:20
37
+ #: views.py:113 wagtail_hooks.py:20
38
38
  msgid "Styleguide"
39
39
  msgstr ""
40
40
 
41
- #: views.py:118
41
+ #: views.py:121
42
42
  msgid "Success message"
43
43
  msgstr ""
44
44
 
45
- #: views.py:120 views.py:128 views.py:136
45
+ #: views.py:123 views.py:131 views.py:139
46
46
  msgid "View live"
47
47
  msgstr ""
48
48
 
49
- #: views.py:121 views.py:129 views.py:137
49
+ #: views.py:124 views.py:132 views.py:140
50
50
  msgid "Edit"
51
51
  msgstr ""
52
52
 
53
- #: views.py:126
53
+ #: views.py:129
54
54
  msgid "Warning message"
55
55
  msgstr ""
56
56
 
57
- #: views.py:134
57
+ #: views.py:137
58
58
  msgid "Error message"
59
59
  msgstr ""
@@ -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-04-18 17:28+0100\n"
11
+ "POT-Creation-Date: 2024-07-19 16:26+0100\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"
@@ -1 +1 @@
1
- (()=>{"use strict";var e,t={6865:(e,t,a)=>{var n=a(1669),l=a.n(n),i=a(4188);const o={contenteditable:"true","plaintext-only":"true",tabindex:"0"};function r(e,t){const a=e+"-handsontable-container";var n=e+"-handsontable-header",r=e+"-handsontable-col-header",s=e+"-table-header-choice";const d=e+"-handsontable-col-caption",c=l()("#"+e);var h=l()("#"+n),p=l()("#"+r),f=l()("#"+s);const u=l()("#"+d),b={};let g=null,v=null,w=!1;const m=l()("#"+e).parent(),_=function(){let e=0;return m.find(".htCore").each((function(){e+=l()(this).height()})),e+m.find("[data-field]").first().height()},y=[`#${a}`,".wtHider",".wtHolder"],$=function(t){const a=l()("#"+e);l().each(y,(function(){a.closest("[data-field]").find(this).height(t)}))};try{v=JSON.parse(c.val())}catch(e){}null!==v&&((0,i.$)(v,"table_caption")&&u.prop("value",v.table_caption),(0,i.$)(v,"table_header_choice")&&f.prop("value",v.table_header_choice)),(0,i.$)(t,"width")&&(0,i.$)(t,"height")||l()(window).on("resize",(()=>{var e;g.updateSettings({width:l()(".w-field--table_input").closest(".w-panel").width(),height:_()}),e="100%",l().each(y,(function(){l()(this).width(e)})),l()(".w-field--table_input").width(e)}));const C=function(){const e=[],t=[];g.getCellsMeta().forEach((t=>{let a,n;(0,i.$)(t,"className")&&(a=t.className),(0,i.$)(t,"hidden")&&(n=!0),(void 0!==a||n)&&e.push({row:t.row,col:t.col,className:a,hidden:n})})),g.getPlugin("mergeCells").isEnabled()&&g.getPlugin("mergeCells").mergedCellsCollection.mergedCells.forEach((e=>{t.push({row:e.row,col:e.col,rowspan:e.rowspan,colspan:e.colspan})})),c.val(JSON.stringify({data:g.getData(),cell:e,mergeCells:t,first_row_is_table_header:h.val(),first_col_is_header:p.val(),table_header_choice:f.val(),table_caption:u.val()}))},O=function(e,t){$(_()),C(),l()((()=>{l()(m).find("td, th").attr(o)}))};f.on("change",(()=>{C()})),u.on("change",(()=>{C()}));const S={afterChange:function(e,t){w&&"loadData"!==t&&"MergeCells"!==t&&C()},afterCreateCol:O,afterCreateRow:O,afterRemoveCol:O,afterRemoveRow:O,afterSetCellMeta:function(e,t,a,n){w&&"className"===a&&C()},afterMergeCells:function(e,t,a){w&&C()},afterUnmergeCells:function(e,t){w&&C()},afterInit:function(){w=!0}};null!==v&&((0,i.$)(v,"data")&&(S.data=v.data),(0,i.$)(v,"cell")&&(S.cell=v.cell)),Object.keys(S).forEach((e=>{b[e]=S[e]})),Object.keys(t).forEach((e=>{b[e]=t[e]})),(0,i.$)(b,"mergeCells")&&!0===b.mergeCells&&null!==v&&(0,i.$)(v,"mergeCells")&&(b.mergeCells=v.mergeCells),g=new Handsontable(document.getElementById(a),b),window.addEventListener("load",(()=>{g.render(),$(_()),m.find("td, th").attr(o),window.dispatchEvent(new Event("resize"))}))}window.initTable=r,window.telepath.register("wagtail.widgets.TableInput",class{constructor(e,t){this.options=e,this.strings=t}render(e,t,a,n){const i=document.createElement("div");i.innerHTML=`\n <div class="w-field__wrapper" data-field-wrapper>\n <label class="w-field__label" for="${a}-table-header-choice">${this.strings["Table headers"]}</label>\n <select id="${a}-table-header-choice" name="table-header-choice">\n <option value="">Select a header option</option>\n <option value="row">\n ${this.strings["Display the first row as a header"]}\n </option>\n <option value="column">\n ${this.strings["Display the first column as a header"]}\n </option>\n <option value="both">\n ${this.strings["Display the first row AND first column as headers"]}\n </option>\n <option value="neither">\n ${this.strings["No headers"]}\n </option>\n </select>\n <p class="help">${this.strings["Which cells should be displayed as headers?"]}</p>\n </div>\n <div class="w-field__wrapper" data-field-wrapper>\n <label class="w-field__label" for="${a}-handsontable-col-caption">${this.strings["Table caption"]}</label>\n <div class="w-field w-field--char_field w-field--text_input" data-field>\n <div class="w-field__help" id="${a}-handsontable-col-caption-helptext" data-field-help>\n <div class="help">${this.strings["A heading that identifies the overall topic of the table, and is useful for screen reader users."]}</div>\n </div>\n <div class="w-field__input" data-field-input>\n <input type="text" id="${a}-handsontable-col-caption" name="handsontable-col-caption" aria-describedby="${a}-handsontable-col-caption-helptext" />\n </div>\n </div>\n </div>\n <div id="${a}-handsontable-container"></div>\n <input type="hidden" name="${t}" id="${a}" placeholder="${this.strings.Table}">\n `,l()((()=>{const e=document.getElementById(`${a}-handsontable-container`);l()(e).find("td, th").attr(o)})),e.replaceWith(i);const s=i.querySelector(`input[name="${t}"]`),d=this.options,c={getValue:()=>JSON.parse(s.value),getState:()=>JSON.parse(s.value),setState(e){s.value=JSON.stringify(e),r(a,d)},focus(){}};return c.setState(n),c}})},1669:e=>{e.exports=jQuery}},a={};function n(e){var l=a[e];if(void 0!==l)return l.exports;var i=a[e]={id:e,loaded:!1,exports:{}};return t[e].call(i.exports,i,i.exports,n),i.loaded=!0,i.exports}n.m=t,e=[],n.O=(t,a,l,i)=>{if(!a){var o=1/0;for(c=0;c<e.length;c++){for(var[a,l,i]=e[c],r=!0,s=0;s<a.length;s++)(!1&i||o>=i)&&Object.keys(n.O).every((e=>n.O[e](a[s])))?a.splice(s--,1):(r=!1,i<o&&(o=i));if(r){e.splice(c--,1);var d=l();void 0!==d&&(t=d)}}return t}i=i||0;for(var c=e.length;c>0&&e[c-1][2]>i;c--)e[c]=e[c-1];e[c]=[a,l,i]},n.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},n.d=(e,t)=>{for(var a in t)n.o(t,a)&&!n.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),n.j=853,(()=>{var e={853:0};n.O.j=t=>0===e[t];var t=(t,a)=>{var l,i,[o,r,s]=a,d=0;if(o.some((t=>0!==e[t]))){for(l in r)n.o(r,l)&&(n.m[l]=r[l]);if(s)var c=s(n)}for(t&&t(a);d<o.length;d++)i=o[d],n.o(e,i)&&e[i]&&e[i][0](),e[i]=0;return n.O(c)},a=globalThis.webpackChunkwagtail=globalThis.webpackChunkwagtail||[];a.forEach(t.bind(null,0)),a.push=t.bind(null,a.push.bind(a))})();var l=n.O(void 0,[321],(()=>n(6865)));l=n.O(l)})();
1
+ (()=>{"use strict";var e,t={6865:(e,t,n)=>{var a=n(1669),l=n.n(a),i=n(4188);const o={contenteditable:"true","plaintext-only":"true",tabindex:"0"};function r(e,t){const n=e+"-handsontable-container";var a=e+"-handsontable-header",r=e+"-handsontable-col-header",s=e+"-table-header-choice";const d=e+"-handsontable-col-caption",c=l()("#"+e);var h=l()("#"+a),p=l()("#"+r),f=l()("#"+s);const u=l()("#"+d),b={};let g=null,v=null,w=!1;const m=l()("#"+e).parent(),_=function(){let e=0;return m.find(".htCore").each((function(){e+=l()(this).height()})),e+m.find("[data-field]").first().height()},y=[`#${n}`,".wtHider",".wtHolder"],$=function(t){const n=l()("#"+e);l().each(y,(function(){n.closest("[data-field]").find(this).height(t)}))};try{v=JSON.parse(c.val())}catch(e){}null!==v&&((0,i.$)(v,"table_caption")&&u.prop("value",v.table_caption),(0,i.$)(v,"table_header_choice")&&f.prop("value",v.table_header_choice)),(0,i.$)(t,"width")&&(0,i.$)(t,"height")||l()(window).on("resize",(()=>{var e;g.updateSettings({width:l()(".w-field--table_input").closest(".w-panel").width(),height:_()}),e="100%",l().each(y,(function(){l()(this).width(e)})),l()(".w-field--table_input").width(e)}));const C=function(){const e=[],t=[];g.getCellsMeta().forEach((t=>{let n,a;(0,i.$)(t,"className")&&(n=t.className),(0,i.$)(t,"hidden")&&(a=!0),(void 0!==n||a)&&e.push({row:t.row,col:t.col,className:n,hidden:a})})),g.getPlugin("mergeCells").isEnabled()&&g.getPlugin("mergeCells").mergedCellsCollection.mergedCells.forEach((e=>{t.push({row:e.row,col:e.col,rowspan:e.rowspan,colspan:e.colspan})})),c.val(JSON.stringify({data:g.getData(),cell:e,mergeCells:t,first_row_is_table_header:h.val(),first_col_is_header:p.val(),table_header_choice:f.val(),table_caption:u.val()}))},O=function(e,t){$(_()),C(),l()((()=>{l()(m).find("td, th").attr(o)}))};f.on("change",(()=>{C()})),u.on("change",(()=>{C()}));const S={afterChange:function(e,t){w&&"loadData"!==t&&"MergeCells"!==t&&C()},afterCreateCol:O,afterCreateRow:O,afterRemoveCol:O,afterRemoveRow:O,afterSetCellMeta:function(e,t,n,a){w&&"className"===n&&C()},afterMergeCells:function(e,t,n){w&&C()},afterUnmergeCells:function(e,t){w&&C()},afterInit:function(){w=!0}};null!==v&&((0,i.$)(v,"data")&&(S.data=v.data),(0,i.$)(v,"cell")&&(S.cell=v.cell)),Object.keys(S).forEach((e=>{b[e]=S[e]})),Object.keys(t).forEach((e=>{b[e]=t[e]})),(0,i.$)(b,"mergeCells")&&!0===b.mergeCells&&null!==v&&(0,i.$)(v,"mergeCells")&&(b.mergeCells=v.mergeCells),g=new Handsontable(document.getElementById(n),b),window.addEventListener("load",(()=>{g.render(),$(_()),m.find("td, th").attr(o),window.dispatchEvent(new Event("resize"))}))}window.initTable=r,window.telepath.register("wagtail.widgets.TableInput",class{constructor(e,t){this.options=e,this.strings=t}render(e,t,n,a){const i=document.createElement("div");i.innerHTML=`\n <div class="w-field__wrapper" data-field-wrapper>\n <label class="w-field__label" for="${n}-table-header-choice">${this.strings["Table headers"]}</label>\n <select id="${n}-table-header-choice" name="table-header-choice">\n <option value="">Select a header option</option>\n <option value="row">\n ${this.strings["Display the first row as a header"]}\n </option>\n <option value="column">\n ${this.strings["Display the first column as a header"]}\n </option>\n <option value="both">\n ${this.strings["Display the first row AND first column as headers"]}\n </option>\n <option value="neither">\n ${this.strings["No headers"]}\n </option>\n </select>\n <p class="help">${this.strings["Which cells should be displayed as headers?"]}</p>\n </div>\n <div class="w-field__wrapper" data-field-wrapper>\n <label class="w-field__label" for="${n}-handsontable-col-caption">${this.strings["Table caption"]}</label>\n <div class="w-field w-field--char_field w-field--text_input" data-field>\n <div class="w-field__help" id="${n}-handsontable-col-caption-helptext" data-field-help>\n <div class="help">${this.strings["A heading that identifies the overall topic of the table, and is useful for screen reader users."]}</div>\n </div>\n <div class="w-field__input" data-field-input>\n <input type="text" id="${n}-handsontable-col-caption" name="handsontable-col-caption" aria-describedby="${n}-handsontable-col-caption-helptext" />\n </div>\n </div>\n </div>\n <div id="${n}-handsontable-container"></div>\n <input type="hidden" name="${t}" id="${n}" placeholder="${this.strings.Table}">\n `,l()((()=>{const e=document.getElementById(`${n}-handsontable-container`);l()(e).find("td, th").attr(o)})),e.replaceWith(i);const s=i.querySelector(`input[name="${t}"]`),d=this.options,c={getValue:()=>JSON.parse(s.value),getState:()=>JSON.parse(s.value),setState(e){s.value=JSON.stringify(e),r(n,d)},focus(){}};return c.setState(a),c}})},1669:e=>{e.exports=jQuery}},n={};function a(e){var l=n[e];if(void 0!==l)return l.exports;var i=n[e]={exports:{}};return t[e](i,i.exports,a),i.exports}a.m=t,e=[],a.O=(t,n,l,i)=>{if(!n){var o=1/0;for(c=0;c<e.length;c++){for(var[n,l,i]=e[c],r=!0,s=0;s<n.length;s++)(!1&i||o>=i)&&Object.keys(a.O).every((e=>a.O[e](n[s])))?n.splice(s--,1):(r=!1,i<o&&(o=i));if(r){e.splice(c--,1);var d=l();void 0!==d&&(t=d)}}return t}i=i||0;for(var c=e.length;c>0&&e[c-1][2]>i;c--)e[c]=e[c-1];e[c]=[n,l,i]},a.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return a.d(t,{a:t}),t},a.d=(e,t)=>{for(var n in t)a.o(t,n)&&!a.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},a.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),a.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),a.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.j=853,(()=>{var e={853:0};a.O.j=t=>0===e[t];var t=(t,n)=>{var l,i,[o,r,s]=n,d=0;if(o.some((t=>0!==e[t]))){for(l in r)a.o(r,l)&&(a.m[l]=r[l]);if(s)var c=s(a)}for(t&&t(n);d<o.length;d++)i=o[d],a.o(e,i)&&e[i]&&e[i][0](),e[i]=0;return a.O(c)},n=globalThis.webpackChunkwagtail=globalThis.webpackChunkwagtail||[];n.forEach(t.bind(null,0)),n.push=t.bind(null,n.push.bind(n))})();var l=a.O(void 0,[321],(()=>a(6865)));l=a.O(l)})();
@@ -87,6 +87,14 @@ class BaseTypedTableBlock(Block):
87
87
  block.set_name(name)
88
88
  self.child_blocks[name] = block
89
89
 
90
+ @classmethod
91
+ def construct_from_lookup(cls, lookup, child_blocks, **kwargs):
92
+ if child_blocks:
93
+ child_blocks = [
94
+ (name, lookup.get_block(index)) for name, index in child_blocks
95
+ ]
96
+ return cls(child_blocks, **kwargs)
97
+
90
98
  def value_from_datadict(self, data, files, prefix):
91
99
  caption = data["%s-caption" % prefix]
92
100
 
@@ -259,6 +267,17 @@ class BaseTypedTableBlock(Block):
259
267
  kwargs = self._constructor_kwargs
260
268
  return (path, args, kwargs)
261
269
 
270
+ def deconstruct_with_lookup(self, lookup):
271
+ path = "wagtail.contrib.typed_table_block.blocks.TypedTableBlock"
272
+ args = [
273
+ [
274
+ (name, lookup.add_block(block))
275
+ for name, block in self.child_blocks.items()
276
+ ]
277
+ ]
278
+ kwargs = self._constructor_kwargs
279
+ return (path, args, kwargs)
280
+
262
281
  def check(self, **kwargs):
263
282
  errors = super().check(**kwargs)
264
283
  for name, child_block in self.child_blocks.items():
@@ -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-04-18 17:28+0100\n"
11
+ "POT-Creation-Date: 2024-07-19 16:26+0100\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"
@@ -18,40 +18,40 @@ msgstr ""
18
18
  "Content-Transfer-Encoding: 8bit\n"
19
19
  "Plural-Forms: nplurals=2; plural=(n != 1);\n"
20
20
 
21
- #: blocks.py:294
21
+ #: blocks.py:313
22
22
  msgid "Caption"
23
23
  msgstr ""
24
24
 
25
- #: blocks.py:296
25
+ #: blocks.py:315
26
26
  msgid ""
27
27
  "A heading that identifies the overall topic of the table, and is useful for "
28
28
  "screen reader users."
29
29
  msgstr ""
30
30
 
31
- #: blocks.py:298
31
+ #: blocks.py:317
32
32
  msgid "Add column"
33
33
  msgstr ""
34
34
 
35
- #: blocks.py:299
35
+ #: blocks.py:318
36
36
  msgid "Add row"
37
37
  msgstr ""
38
38
 
39
- #: blocks.py:300
39
+ #: blocks.py:319
40
40
  msgid "Column heading"
41
41
  msgstr ""
42
42
 
43
- #: blocks.py:301
43
+ #: blocks.py:320
44
44
  msgid "Insert column"
45
45
  msgstr ""
46
46
 
47
- #: blocks.py:302
47
+ #: blocks.py:321
48
48
  msgid "Delete column"
49
49
  msgstr ""
50
50
 
51
- #: blocks.py:303
51
+ #: blocks.py:322
52
52
  msgid "Insert row"
53
53
  msgstr ""
54
54
 
55
- #: blocks.py:304
55
+ #: blocks.py:323
56
56
  msgid "Delete row"
57
57
  msgstr ""
@@ -1 +1 @@
1
- (()=>{"use strict";var t,e={7595:(t,e,n)=>{var i=n(4327),o=n(9675),s=n(4545);class l{constructor(t,e,n,o,s){this.blockDef=t,this.type=t.name,this.caption="",this.columns=[],this.rows=[],this.columnCountIncludingDeleted=0,this.rowCountIncludingDeleted=0,this.prefix=n,this.childBlockDefsByName={},this.blockDef.childBlockDefs.forEach((t=>{this.childBlockDefsByName[t.name]=t}));const l=this.blockDef.meta.strings,a=`${(0,i.Z)(n)}-caption`,d=$(`\n <div class="typed-table-block ${(0,i.Z)(this.blockDef.meta.classname||"")}">\n <div class="w-field__wrapper" data-field-wrapper>\n <label class="w-field__label" for="${a}">\n ${l.CAPTION}\n </label>\n <div class="w-field w-field--char_field w-field--text_input" data-field>\n <div class="w-field__help" data-field-help>\n <div class="help">\n ${l.CAPTION_HELP_TEXT}\n </div>\n </div>\n <div class="w-field__input" data-field-input>\n <input type="text" id="${a}" name="${a}" value="" />\n <span></span>\n </div>\n </div>\n </div>\n <input type="hidden" name="${(0,i.Z)(n)}-column-count" data-column-count value="0">\n <input type="hidden" name="${(0,i.Z)(n)}-row-count" data-row-count value="0">\n <div data-deleted-fields></div>\n <div class="typed-table-block__wrapper">\n <table>\n <thead>\n <tr>\n <th></th>\n <th class="control-cell">\n <button type="button" class="button button-small button-secondary append-column" data-append-column>\n ${(0,i.Z)(l.ADD_COLUMN)}\n </button>\n </th>\n </tr>\n </thead>\n <tbody>\n </tbody>\n <tfoot>\n <tr>\n <td class="control-cell">\n <button\n type="button"\n class="button button-small button-secondary button--icon text-replace prepend-row"\n data-add-row\n aria-label="${(0,i.Z)(l.ADD_ROW)}"\n title="${(0,i.Z)(l.ADD_ROW)}"\n >\n <svg class="icon icon-plus icon" aria-hidden="true">\n <use href="#icon-plus"></use>\n </svg>\n </button></td>\n </tr>\n </tfoot>\n </table>\n </div>\n </div>\n `);$(e).replaceWith(d),this.container=d,this.captionInput=d.find(`#${a}`).get(0),this.thead=d.find("table > thead").get(0),this.tbody=d.find("table > tbody").get(0),this.columnCountInput=d.find("input[data-column-count]").get(0),this.rowCountInput=d.find("input[data-row-count]").get(0),this.deletedFieldsContainer=d.find("[data-deleted-fields]").get(0),this.appendColumnButton=d.find("button[data-append-column]"),this.addRowButton=d.find("button[data-add-row]"),this.addRowButton.hide(),this.blockDef.meta.helpText&&d.append(`\n <div class="c-sf-help">\n <div class="help">\n ${this.blockDef.meta.helpText}\n </div>\n </div>\n `),this.addColumnCallback=null,this.addColumnMenu=$('<ul class="add-column-menu"></ul>'),this.blockDef.childBlockDefs.forEach((t=>{const e=$('<button type="button" class="button button-small"></button>').text(t.meta.label);e.on("click",(()=>{this.addColumnCallback&&this.addColumnCallback(t),this.hideAddColumnMenu()}));const n=$("<li></li>").append(e);this.addColumnMenu.append(n)})),this.addColumnMenuBaseElement=null,this.appendColumnButton.on("click",(()=>{this.toggleAddColumnMenu(this.appendColumnButton,(t=>{this.insertColumn(this.columns.length,t,{addInitialRow:!0})}))})),this.addRowButton.on("click",(()=>{this.insertRow(this.rows.length)})),this.setState(o),s&&this.setError(s)}showAddColumnMenu(t,e){this.addColumnMenuBaseElement=t,t.after(this.addColumnMenu),this.addColumnMenu.show(),this.addColumnCallback=e}hideAddColumnMenu(){this.addColumnMenu.hide(),this.addColumnMenuBaseElement=null}toggleAddColumnMenu(t,e){this.addColumnMenuBaseElement===t?this.hideAddColumnMenu():this.showAddColumnMenu(t,e)}clear(){this.setCaption(""),this.columns=[],this.rows=[],this.columnCountIncludingDeleted=0,this.columnCountInput.value=0,this.rowCountIncludingDeleted=0,this.rowCountInput.value=0,this.deletedFieldsContainer.replaceChildren();const t=this.thead.children[0];t.replaceChildren(t.firstElementChild,t.lastElementChild),this.appendColumnButton.text(this.blockDef.meta.strings.ADD_COLUMN).removeClass("button--icon text-replace white").removeAttr("aria-label").removeAttr("title"),this.tbody.replaceChildren(),this.addRowButton.hide()}setCaption(t){this.caption=t,this.captionInput.value=t}insertColumn(t,e,n){const s={blockDef:e,position:t,id:this.columnCountIncludingDeleted};this.columnCountIncludingDeleted+=1,(0,o.y)(t,this.columns.length).forEach((t=>{this.columns[t].position+=1,this.columns[t].positionInput.value=this.columns[t].position})),this.columns.splice(t,0,s),this.columnCountInput.value=this.columnCountIncludingDeleted;const l=this.thead.children[0],a=l.children,d=document.createElement("th");l.insertBefore(d,a[t+1]),s.typeInput=document.createElement("input"),s.typeInput.type="hidden",s.typeInput.name=this.prefix+"-column-"+s.id+"-type",s.typeInput.value=e.name,d.appendChild(s.typeInput),s.positionInput=document.createElement("input"),s.positionInput.type="hidden",s.positionInput.name=this.prefix+"-column-"+s.id+"-order",s.positionInput.value=t,d.appendChild(s.positionInput),s.deletedInput=document.createElement("input"),s.deletedInput.type="hidden",s.deletedInput.name=this.prefix+"-column-"+s.id+"-deleted",s.deletedInput.value="",this.deletedFieldsContainer.appendChild(s.deletedInput);const u=$(`<button type="button"\n class="button button-secondary button-small button--icon text-replace prepend-column"\n aria-label="${(0,i.Z)(this.blockDef.meta.strings.INSERT_COLUMN)}"\n title="${(0,i.Z)(this.blockDef.meta.strings.INSERT_COLUMN)}">\n <svg class="icon icon-plus icon" aria-hidden="true"><use href="#icon-plus"></use></svg>\n </button>`);$(d).append(u),u.on("click",(()=>{this.toggleAddColumnMenu(u,(t=>{this.insertColumn(s.position,t,{addInitialRow:!0})}))})),s.headingInput=document.createElement("input"),s.headingInput.type="text",s.headingInput.name=this.prefix+"-column-"+s.id+"-heading",s.headingInput.className="column-heading",s.headingInput.placeholder=this.blockDef.meta.strings.COLUMN_HEADING,d.appendChild(s.headingInput);const c=$(`<button type="button"\n class="button button-secondary button-small button--icon text-replace no delete-column"\n aria-label="${(0,i.Z)(this.blockDef.meta.strings.DELETE_COLUMN)}"\n title="${(0,i.Z)(this.blockDef.meta.strings.DELETE_COLUMN)}">\n <svg class="icon icon-bin icon" aria-hidden="true"><use href="#icon-bin"></use></svg>\n </button>`);$(d).append(c),c.on("click",(()=>{this.deleteColumn(s.position)}));const r=this.blockDef.childBlockDefaultStates[e.name];return Array.from(this.tbody.children).forEach(((e,n)=>{const i=this.rows[n],o=e.children,l=document.createElement("td");e.insertBefore(l,o[t+1]);const a=this.initCell(l,s,i,r);i.blocks.splice(t,0,a)})),this.addRowButton.show(),this.appendColumnButton.html('<svg class="icon icon-plus icon" aria-hidden="true"><use href="#icon-plus"></use></svg>').addClass("button--icon text-replace white").attr("aria-label",this.blockDef.meta.strings.ADD_COLUMN).addClass("button--icon text-replace white").attr("aria-label",this.blockDef.meta.strings.ADD_COLUMN).attr("title",this.blockDef.meta.strings.ADD_COLUMN),n&&n.addInitialRow&&0===this.tbody.children.length&&this.insertRow(0),s}deleteColumn(t){this.columns[t].deletedInput.value="1";const e=this.thead.children[0],n=e.children;e.removeChild(n[t+1]),Array.from(this.tbody.children).forEach(((e,n)=>{const i=e.children;e.removeChild(i[t+1]),this.rows[n].blocks.splice(t,1)})),this.columns.splice(t,1),(0,o.y)(t,this.columns.length).forEach((t=>{this.columns[t].position-=1,this.columns[t].positionInput.value=this.columns[t].position})),0===this.columns.length&&this.clear()}insertRow(t,e){const n=document.createElement("tr"),s={blocks:[],position:t,id:this.rowCountIncludingDeleted};if(t<this.rows.length){const e=this.tbody.children[t];this.tbody.insertBefore(n,e)}else this.tbody.appendChild(n);this.rows.splice(t,0,s),this.rowCountIncludingDeleted+=1,this.rowCountInput.value=this.rowCountIncludingDeleted;const l=document.createElement("td");l.className="control-cell",n.appendChild(l);const a=$(`<button type="button"\n class="button button-secondary button-small button--icon text-replace prepend-row"\n aria-label="${(0,i.Z)(this.blockDef.meta.strings.INSERT_ROW)}"\n title="${(0,i.Z)(this.blockDef.meta.strings.INSERT_ROW)}">\n <svg class="icon icon-plus icon" aria-hidden="true"><use href="#icon-plus"></use></svg>\n </button>`);$(l).append(a),a.on("click",(()=>{this.insertRow(s.position)})),this.columns.forEach(((t,i)=>{let o;o=e?e[i]:this.blockDef.childBlockDefaultStates[t.blockDef.name];const l=document.createElement("td");n.appendChild(l),s.blocks[i]=this.initCell(l,t,s,o)}));const d=document.createElement("td");d.className="control-cell",n.appendChild(d),s.positionInput=document.createElement("input"),s.positionInput.type="hidden",s.positionInput.name=this.prefix+"-row-"+s.id+"-order",s.positionInput.value=s.position,d.appendChild(s.positionInput);const u=$(`<button type="button"\n class="button button-secondary button-small button--icon text-replace no delete-row"\n aria-label="${(0,i.Z)(this.blockDef.meta.strings.DELETE_ROW)}"\n title="${(0,i.Z)(this.blockDef.meta.strings.DELETE_ROW)}">\n <svg class="icon icon-bin icon" aria-hidden="true"><use href="#icon-bin"></use></svg>\n </button>`);return $(d).append(u),u.on("click",(()=>{this.deleteRow(s.position)})),s.deletedInput=document.createElement("input"),s.deletedInput.type="hidden",s.deletedInput.name=this.prefix+"-row-"+s.id+"-deleted",s.deletedInput.value="",this.deletedFieldsContainer.appendChild(s.deletedInput),(0,o.y)(t+1,this.rows.length).forEach((t=>{this.rows[t].position+=1,this.rows[t].positionInput.value=this.rows[t].position})),s}deleteRow(t){this.rows[t].deletedInput.value="1";const e=this.tbody.children[t];this.tbody.removeChild(e),this.rows.splice(t,1),(0,o.y)(t,this.rows.length).forEach((t=>{this.rows[t].position-=1,this.rows[t].positionInput.value=this.rows[t].position}))}initCell(t,e,n,i){const o=document.createElement("div");t.appendChild(o);const s=this.prefix+"-cell-"+n.id+"-"+e.id;return e.blockDef.render(o,s,i,null)}setState(t){this.clear(),t&&(t.columns.forEach(((t,e)=>{const n=this.childBlockDefsByName[t.type];this.insertColumn(e,n).headingInput.value=t.heading})),t.rows.forEach(((t,e)=>{this.insertRow(e,t.values)})),this.setCaption(t.caption))}setError(t){if(!t)return;const e=this.container[0];if((0,s.$)(e),t.messages&&(0,s.U)(e,t.messages),t.blockErrors)for(const[e,n]of Object.entries(t.blockErrors))for(const[t,i]of Object.entries(n))this.rows[e].blocks[t].setError(i)}getState(){return{columns:this.getColumnStates(),rows:this.rows.map((t=>({values:t.blocks.map((t=>t.getState()))}))),caption:this.caption}}getDuplicatedState(){return{columns:this.getColumnStates(),rows:this.rows.map((t=>({values:t.blocks.map((t=>void 0===t.getDuplicatedState?t.getState():t.getDuplicatedState()))})))}}getValue(){return{columns:this.getColumnStates(),rows:this.rows.map((t=>({values:t.blocks.map((t=>t.getValue()))}))),caption:this.caption}}getColumnStates(){return this.columns.map((t=>({type:t.blockDef.name,heading:t.headingInput.value})))}getTextLabel(t){const e=t&&t.maxLength;let n="";for(const t of this.rows)for(const i of t.blocks)if(i.getTextLabel){const t=i.getTextLabel({maxLength:e});if(t)if(n){const i=n+", "+t;if(e&&i.length>e-1)return n.endsWith("…")||(n+="…"),n;n=i}else n=t}return n}focus(t){this.columns.length?this.rows.length?this.rows[0].blocks[0].focus(t):this.addRowButton.focus():this.appendColumnButton.focus()}}window.telepath.register("wagtail.contrib.typed_table_block.blocks.TypedTableBlock",class{constructor(t,e,n,i){this.name=t,this.childBlockDefs=e,this.childBlockDefaultStates=n,this.meta=i}render(t,e,n,i){return new l(this,t,e,n,i)}})}},n={};function i(t){var o=n[t];if(void 0!==o)return o.exports;var s=n[t]={id:t,loaded:!1,exports:{}};return e[t].call(s.exports,s,s.exports,i),s.loaded=!0,s.exports}i.m=e,t=[],i.O=(e,n,o,s)=>{if(!n){var l=1/0;for(c=0;c<t.length;c++){for(var[n,o,s]=t[c],a=!0,d=0;d<n.length;d++)(!1&s||l>=s)&&Object.keys(i.O).every((t=>i.O[t](n[d])))?n.splice(d--,1):(a=!1,s<l&&(l=s));if(a){t.splice(c--,1);var u=o();void 0!==u&&(e=u)}}return e}s=s||0;for(var c=t.length;c>0&&t[c-1][2]>s;c--)t[c]=t[c-1];t[c]=[n,o,s]},i.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return i.d(e,{a:e}),e},i.d=(t,e)=>{for(var n in e)i.o(e,n)&&!i.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},i.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),i.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),i.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},i.nmd=t=>(t.paths=[],t.children||(t.children=[]),t),i.j=702,(()=>{var t={702:0};i.O.j=e=>0===t[e];var e=(e,n)=>{var o,s,[l,a,d]=n,u=0;if(l.some((e=>0!==t[e]))){for(o in a)i.o(a,o)&&(i.m[o]=a[o]);if(d)var c=d(i)}for(e&&e(n);u<l.length;u++)s=l[u],i.o(t,s)&&t[s]&&t[s][0](),t[s]=0;return i.O(c)},n=globalThis.webpackChunkwagtail=globalThis.webpackChunkwagtail||[];n.forEach(e.bind(null,0)),n.push=e.bind(null,n.push.bind(n))})();var o=i.O(void 0,[321],(()=>i(7595)));o=i.O(o)})();
1
+ (()=>{"use strict";var t,e={7595:(t,e,n)=>{var i=n(4327),o=n(9675),s=n(4545);class l{constructor(t,e,n,o,s){this.blockDef=t,this.type=t.name,this.caption="",this.columns=[],this.rows=[],this.columnCountIncludingDeleted=0,this.rowCountIncludingDeleted=0,this.prefix=n,this.childBlockDefsByName={},this.blockDef.childBlockDefs.forEach((t=>{this.childBlockDefsByName[t.name]=t}));const l=this.blockDef.meta.strings,a=`${(0,i.Z)(n)}-caption`,d=$(`\n <div class="typed-table-block ${(0,i.Z)(this.blockDef.meta.classname||"")}">\n <div class="w-field__wrapper" data-field-wrapper>\n <label class="w-field__label" for="${a}">\n ${l.CAPTION}\n </label>\n <div class="w-field w-field--char_field w-field--text_input" data-field>\n <div class="w-field__help" data-field-help>\n <div class="help">\n ${l.CAPTION_HELP_TEXT}\n </div>\n </div>\n <div class="w-field__input" data-field-input>\n <input type="text" id="${a}" name="${a}" value="" />\n <span></span>\n </div>\n </div>\n </div>\n <input type="hidden" name="${(0,i.Z)(n)}-column-count" data-column-count value="0">\n <input type="hidden" name="${(0,i.Z)(n)}-row-count" data-row-count value="0">\n <div data-deleted-fields></div>\n <div class="typed-table-block__wrapper">\n <table>\n <thead>\n <tr>\n <th></th>\n <th class="control-cell">\n <button type="button" class="button button-small button-secondary append-column" data-append-column>\n ${(0,i.Z)(l.ADD_COLUMN)}\n </button>\n </th>\n </tr>\n </thead>\n <tbody>\n </tbody>\n <tfoot>\n <tr>\n <td class="control-cell">\n <button\n type="button"\n class="button button-small button-secondary button--icon text-replace prepend-row"\n data-add-row\n aria-label="${(0,i.Z)(l.ADD_ROW)}"\n title="${(0,i.Z)(l.ADD_ROW)}"\n >\n <svg class="icon icon-plus icon" aria-hidden="true">\n <use href="#icon-plus"></use>\n </svg>\n </button></td>\n </tr>\n </tfoot>\n </table>\n </div>\n </div>\n `);$(e).replaceWith(d),this.container=d,this.captionInput=d.find(`#${a}`).get(0),this.thead=d.find("table > thead").get(0),this.tbody=d.find("table > tbody").get(0),this.columnCountInput=d.find("input[data-column-count]").get(0),this.rowCountInput=d.find("input[data-row-count]").get(0),this.deletedFieldsContainer=d.find("[data-deleted-fields]").get(0),this.appendColumnButton=d.find("button[data-append-column]"),this.addRowButton=d.find("button[data-add-row]"),this.addRowButton.hide(),this.blockDef.meta.helpText&&d.append(`\n <div class="c-sf-help">\n <div class="help">\n ${this.blockDef.meta.helpText}\n </div>\n </div>\n `),this.addColumnCallback=null,this.addColumnMenu=$('<ul class="add-column-menu"></ul>'),this.blockDef.childBlockDefs.forEach((t=>{const e=$('<button type="button" class="button button-small"></button>').text(t.meta.label);e.on("click",(()=>{this.addColumnCallback&&this.addColumnCallback(t),this.hideAddColumnMenu()}));const n=$("<li></li>").append(e);this.addColumnMenu.append(n)})),this.addColumnMenuBaseElement=null,this.appendColumnButton.on("click",(()=>{this.toggleAddColumnMenu(this.appendColumnButton,(t=>{this.insertColumn(this.columns.length,t,{addInitialRow:!0})}))})),this.addRowButton.on("click",(()=>{this.insertRow(this.rows.length)})),this.setState(o),s&&this.setError(s)}showAddColumnMenu(t,e){this.addColumnMenuBaseElement=t,t.after(this.addColumnMenu),this.addColumnMenu.show(),this.addColumnCallback=e}hideAddColumnMenu(){this.addColumnMenu.hide(),this.addColumnMenuBaseElement=null}toggleAddColumnMenu(t,e){this.addColumnMenuBaseElement===t?this.hideAddColumnMenu():this.showAddColumnMenu(t,e)}clear(){this.setCaption(""),this.columns=[],this.rows=[],this.columnCountIncludingDeleted=0,this.columnCountInput.value=0,this.rowCountIncludingDeleted=0,this.rowCountInput.value=0,this.deletedFieldsContainer.replaceChildren();const t=this.thead.children[0];t.replaceChildren(t.firstElementChild,t.lastElementChild),this.appendColumnButton.text(this.blockDef.meta.strings.ADD_COLUMN).removeClass("button--icon text-replace white").removeAttr("aria-label").removeAttr("title"),this.tbody.replaceChildren(),this.addRowButton.hide()}setCaption(t){this.caption=t,this.captionInput.value=t}insertColumn(t,e,n){const s={blockDef:e,position:t,id:this.columnCountIncludingDeleted};this.columnCountIncludingDeleted+=1,(0,o.y)(t,this.columns.length).forEach((t=>{this.columns[t].position+=1,this.columns[t].positionInput.value=this.columns[t].position})),this.columns.splice(t,0,s),this.columnCountInput.value=this.columnCountIncludingDeleted;const l=this.thead.children[0],a=l.children,d=document.createElement("th");l.insertBefore(d,a[t+1]),s.typeInput=document.createElement("input"),s.typeInput.type="hidden",s.typeInput.name=this.prefix+"-column-"+s.id+"-type",s.typeInput.value=e.name,d.appendChild(s.typeInput),s.positionInput=document.createElement("input"),s.positionInput.type="hidden",s.positionInput.name=this.prefix+"-column-"+s.id+"-order",s.positionInput.value=t,d.appendChild(s.positionInput),s.deletedInput=document.createElement("input"),s.deletedInput.type="hidden",s.deletedInput.name=this.prefix+"-column-"+s.id+"-deleted",s.deletedInput.value="",this.deletedFieldsContainer.appendChild(s.deletedInput);const u=$(`<button type="button"\n class="button button-secondary button-small button--icon text-replace prepend-column"\n aria-label="${(0,i.Z)(this.blockDef.meta.strings.INSERT_COLUMN)}"\n title="${(0,i.Z)(this.blockDef.meta.strings.INSERT_COLUMN)}">\n <svg class="icon icon-plus icon" aria-hidden="true"><use href="#icon-plus"></use></svg>\n </button>`);$(d).append(u),u.on("click",(()=>{this.toggleAddColumnMenu(u,(t=>{this.insertColumn(s.position,t,{addInitialRow:!0})}))})),s.headingInput=document.createElement("input"),s.headingInput.type="text",s.headingInput.name=this.prefix+"-column-"+s.id+"-heading",s.headingInput.className="column-heading",s.headingInput.placeholder=this.blockDef.meta.strings.COLUMN_HEADING,d.appendChild(s.headingInput);const c=$(`<button type="button"\n class="button button-secondary button-small button--icon text-replace no delete-column"\n aria-label="${(0,i.Z)(this.blockDef.meta.strings.DELETE_COLUMN)}"\n title="${(0,i.Z)(this.blockDef.meta.strings.DELETE_COLUMN)}">\n <svg class="icon icon-bin icon" aria-hidden="true"><use href="#icon-bin"></use></svg>\n </button>`);$(d).append(c),c.on("click",(()=>{this.deleteColumn(s.position)}));const r=this.blockDef.childBlockDefaultStates[e.name];return Array.from(this.tbody.children).forEach(((e,n)=>{const i=this.rows[n],o=e.children,l=document.createElement("td");e.insertBefore(l,o[t+1]);const a=this.initCell(l,s,i,r);i.blocks.splice(t,0,a)})),this.addRowButton.show(),this.appendColumnButton.html('<svg class="icon icon-plus icon" aria-hidden="true"><use href="#icon-plus"></use></svg>').addClass("button--icon text-replace white").attr("aria-label",this.blockDef.meta.strings.ADD_COLUMN).addClass("button--icon text-replace white").attr("aria-label",this.blockDef.meta.strings.ADD_COLUMN).attr("title",this.blockDef.meta.strings.ADD_COLUMN),n&&n.addInitialRow&&0===this.tbody.children.length&&this.insertRow(0),s}deleteColumn(t){this.columns[t].deletedInput.value="1";const e=this.thead.children[0],n=e.children;e.removeChild(n[t+1]),Array.from(this.tbody.children).forEach(((e,n)=>{const i=e.children;e.removeChild(i[t+1]),this.rows[n].blocks.splice(t,1)})),this.columns.splice(t,1),(0,o.y)(t,this.columns.length).forEach((t=>{this.columns[t].position-=1,this.columns[t].positionInput.value=this.columns[t].position})),0===this.columns.length&&this.clear()}insertRow(t,e){const n=document.createElement("tr"),s={blocks:[],position:t,id:this.rowCountIncludingDeleted};if(t<this.rows.length){const e=this.tbody.children[t];this.tbody.insertBefore(n,e)}else this.tbody.appendChild(n);this.rows.splice(t,0,s),this.rowCountIncludingDeleted+=1,this.rowCountInput.value=this.rowCountIncludingDeleted;const l=document.createElement("td");l.className="control-cell",n.appendChild(l);const a=$(`<button type="button"\n class="button button-secondary button-small button--icon text-replace prepend-row"\n aria-label="${(0,i.Z)(this.blockDef.meta.strings.INSERT_ROW)}"\n title="${(0,i.Z)(this.blockDef.meta.strings.INSERT_ROW)}">\n <svg class="icon icon-plus icon" aria-hidden="true"><use href="#icon-plus"></use></svg>\n </button>`);$(l).append(a),a.on("click",(()=>{this.insertRow(s.position)})),this.columns.forEach(((t,i)=>{let o;o=e?e[i]:this.blockDef.childBlockDefaultStates[t.blockDef.name];const l=document.createElement("td");n.appendChild(l),s.blocks[i]=this.initCell(l,t,s,o)}));const d=document.createElement("td");d.className="control-cell",n.appendChild(d),s.positionInput=document.createElement("input"),s.positionInput.type="hidden",s.positionInput.name=this.prefix+"-row-"+s.id+"-order",s.positionInput.value=s.position,d.appendChild(s.positionInput);const u=$(`<button type="button"\n class="button button-secondary button-small button--icon text-replace no delete-row"\n aria-label="${(0,i.Z)(this.blockDef.meta.strings.DELETE_ROW)}"\n title="${(0,i.Z)(this.blockDef.meta.strings.DELETE_ROW)}">\n <svg class="icon icon-bin icon" aria-hidden="true"><use href="#icon-bin"></use></svg>\n </button>`);return $(d).append(u),u.on("click",(()=>{this.deleteRow(s.position)})),s.deletedInput=document.createElement("input"),s.deletedInput.type="hidden",s.deletedInput.name=this.prefix+"-row-"+s.id+"-deleted",s.deletedInput.value="",this.deletedFieldsContainer.appendChild(s.deletedInput),(0,o.y)(t+1,this.rows.length).forEach((t=>{this.rows[t].position+=1,this.rows[t].positionInput.value=this.rows[t].position})),s}deleteRow(t){this.rows[t].deletedInput.value="1";const e=this.tbody.children[t];this.tbody.removeChild(e),this.rows.splice(t,1),(0,o.y)(t,this.rows.length).forEach((t=>{this.rows[t].position-=1,this.rows[t].positionInput.value=this.rows[t].position}))}initCell(t,e,n,i){const o=document.createElement("div");t.appendChild(o);const s=this.prefix+"-cell-"+n.id+"-"+e.id;return e.blockDef.render(o,s,i,null)}setState(t){this.clear(),t&&(t.columns.forEach(((t,e)=>{const n=this.childBlockDefsByName[t.type];this.insertColumn(e,n).headingInput.value=t.heading})),t.rows.forEach(((t,e)=>{this.insertRow(e,t.values)})),this.setCaption(t.caption))}setError(t){if(!t)return;const e=this.container[0];if((0,s.$)(e),t.messages&&(0,s.U)(e,t.messages),t.blockErrors)for(const[e,n]of Object.entries(t.blockErrors))for(const[t,i]of Object.entries(n))this.rows[e].blocks[t].setError(i)}getState(){return{columns:this.getColumnStates(),rows:this.rows.map((t=>({values:t.blocks.map((t=>t.getState()))}))),caption:this.caption}}getDuplicatedState(){return{columns:this.getColumnStates(),rows:this.rows.map((t=>({values:t.blocks.map((t=>void 0===t.getDuplicatedState?t.getState():t.getDuplicatedState()))})))}}getValue(){return{columns:this.getColumnStates(),rows:this.rows.map((t=>({values:t.blocks.map((t=>t.getValue()))}))),caption:this.caption}}getColumnStates(){return this.columns.map((t=>({type:t.blockDef.name,heading:t.headingInput.value})))}getTextLabel(t){const e=t&&t.maxLength;let n="";for(const t of this.rows)for(const i of t.blocks)if(i.getTextLabel){const t=i.getTextLabel({maxLength:e});if(t)if(n){const i=n+", "+t;if(e&&i.length>e-1)return n.endsWith("…")||(n+="…"),n;n=i}else n=t}return n}focus(t){this.columns.length?this.rows.length?this.rows[0].blocks[0].focus(t):this.addRowButton.focus():this.appendColumnButton.focus()}}window.telepath.register("wagtail.contrib.typed_table_block.blocks.TypedTableBlock",class{constructor(t,e,n,i){this.name=t,this.childBlockDefs=e,this.childBlockDefaultStates=n,this.meta=i}render(t,e,n,i){return new l(this,t,e,n,i)}})}},n={};function i(t){var o=n[t];if(void 0!==o)return o.exports;var s=n[t]={exports:{}};return e[t](s,s.exports,i),s.exports}i.m=e,t=[],i.O=(e,n,o,s)=>{if(!n){var l=1/0;for(c=0;c<t.length;c++){for(var[n,o,s]=t[c],a=!0,d=0;d<n.length;d++)(!1&s||l>=s)&&Object.keys(i.O).every((t=>i.O[t](n[d])))?n.splice(d--,1):(a=!1,s<l&&(l=s));if(a){t.splice(c--,1);var u=o();void 0!==u&&(e=u)}}return e}s=s||0;for(var c=t.length;c>0&&t[c-1][2]>s;c--)t[c]=t[c-1];t[c]=[n,o,s]},i.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return i.d(e,{a:e}),e},i.d=(t,e)=>{for(var n in e)i.o(e,n)&&!i.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},i.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),i.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),i.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},i.j=702,(()=>{var t={702:0};i.O.j=e=>0===t[e];var e=(e,n)=>{var o,s,[l,a,d]=n,u=0;if(l.some((e=>0!==t[e]))){for(o in a)i.o(a,o)&&(i.m[o]=a[o]);if(d)var c=d(i)}for(e&&e(n);u<l.length;u++)s=l[u],i.o(t,s)&&t[s]&&t[s][0](),t[s]=0;return i.O(c)},n=globalThis.webpackChunkwagtail=globalThis.webpackChunkwagtail||[];n.forEach(e.bind(null,0)),n.push=e.bind(null,n.push.bind(n))})();var o=i.O(void 0,[321],(()=>i(7595)));o=i.O(o)})();
@@ -4,6 +4,7 @@ from django.test import TestCase
4
4
 
5
5
  from wagtail import blocks
6
6
  from wagtail.blocks.base import get_error_json_data
7
+ from wagtail.blocks.definition_lookup import BlockDefinitionLookup
7
8
  from wagtail.blocks.struct_block import StructBlockValidationError
8
9
  from wagtail.contrib.typed_table_block.blocks import (
9
10
  TypedTable,
@@ -257,3 +258,40 @@ class TestTableBlock(TestCase):
257
258
  "blockErrors": {1: {2: {"messages": ["This field is required."]}}},
258
259
  },
259
260
  )
261
+
262
+
263
+ class TestBlockDefinitionLookup(TestCase):
264
+ def test_block_lookup(self):
265
+ lookup = BlockDefinitionLookup(
266
+ {
267
+ 0: ("wagtail.blocks.CharBlock", [], {"required": True}),
268
+ 1: (
269
+ "wagtail.blocks.ChoiceBlock",
270
+ [],
271
+ {
272
+ "choices": [
273
+ ("be", "Belgium"),
274
+ ("fr", "France"),
275
+ ("nl", "Netherlands"),
276
+ ]
277
+ },
278
+ ),
279
+ 2: (
280
+ "wagtail.contrib.typed_table_block.blocks.TypedTableBlock",
281
+ [
282
+ [
283
+ ("text", 0),
284
+ ("country", 1),
285
+ ],
286
+ ],
287
+ {},
288
+ ),
289
+ }
290
+ )
291
+ struct_block = lookup.get_block(2)
292
+ self.assertIsInstance(struct_block, TypedTableBlock)
293
+ text_block = struct_block.child_blocks["text"]
294
+ self.assertIsInstance(text_block, blocks.CharBlock)
295
+ self.assertTrue(text_block.required)
296
+ country_block = struct_block.child_blocks["country"]
297
+ self.assertIsInstance(country_block, blocks.ChoiceBlock)
wagtail/coreutils.py CHANGED
@@ -146,7 +146,7 @@ def cautious_slugify(value):
146
146
 
147
147
  def safe_snake_case(value):
148
148
  """
149
- Convert a string to ASCII similar to Django's slugify, with catious handling of
149
+ Convert a string to ASCII similar to Django's slugify, with cautious handling of
150
150
  non-ASCII alphanumeric characters. See `cautious_slugify`.
151
151
 
152
152
  Any inner whitespace, hyphens or dashes will be converted to underscores and
@@ -14,7 +14,7 @@ def get_document_model_string():
14
14
  def get_document_model():
15
15
  """
16
16
  Get the document model from the ``WAGTAILDOCS_DOCUMENT_MODEL`` setting.
17
- Defaults to the standard :class:`~wagtail.documents.models.Document` model
17
+ Defaults to the standard ``wagtail.documents.models.Document`` model
18
18
  if no custom model is defined.
19
19
  """
20
20
  from django.apps import apps