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
@@ -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]={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)})();
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 aria-hidden="true"></th>\n <th class="control-cell">\n <button type="button" class="button button-small button-secondary append-column" aria-expanded="false" 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(this.addColumnMenuTrigger)}));const n=$("<li></li>").append(e);this.addColumnMenu.append(n)})),this.addColumnMenuTrigger=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.addColumnMenuTrigger?.attr("aria-expanded","false"),this.addColumnMenuTrigger=t,t.after(this.addColumnMenu),t.attr("aria-expanded","true"),this.addColumnMenu.show(),this.addColumnCallback=e}hideAddColumnMenu(t){t.attr("aria-expanded","false"),this.addColumnMenu.hide(),this.addColumnMenuTrigger=null}toggleAddColumnMenu(t,e){this.addColumnMenuTrigger===t?this.hideAddColumnMenu(t):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 aria-expanded="false"\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).attr("aria-expanded","false").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)})();
@@ -9,6 +9,7 @@ from wagtail.blocks.struct_block import StructBlockValidationError
9
9
  from wagtail.contrib.typed_table_block.blocks import (
10
10
  TypedTable,
11
11
  TypedTableBlock,
12
+ TypedTableBlockAdapter,
12
13
  TypedTableBlockValidationError,
13
14
  )
14
15
 
@@ -223,6 +224,38 @@ class TestTableBlock(TestCase):
223
224
  # rendering should use the block renderings of the child blocks ('FR' not 'fr')
224
225
  self.assertIn("<td>FR</td>", html)
225
226
 
227
+ def test_adapt(self):
228
+ block = TypedTableBlock(description="A table of countries and their food")
229
+
230
+ block.set_name("test_typedtableblock")
231
+ js_args = TypedTableBlockAdapter().js_args(block)
232
+
233
+ self.assertEqual(js_args[0], "test_typedtableblock")
234
+ self.assertEqual(
235
+ js_args[-1],
236
+ {
237
+ "label": "Test typedtableblock",
238
+ "description": "A table of countries and their food",
239
+ "required": False,
240
+ "icon": "table",
241
+ "blockDefId": block.definition_prefix,
242
+ "isPreviewable": block.is_previewable,
243
+ "strings": {
244
+ "CAPTION": "Caption",
245
+ "CAPTION_HELP_TEXT": (
246
+ "A heading that identifies the overall topic of the table, and is useful for screen reader users."
247
+ ),
248
+ "ADD_COLUMN": "Add column",
249
+ "ADD_ROW": "Add row",
250
+ "COLUMN_HEADING": "Column heading",
251
+ "INSERT_COLUMN": "Insert column",
252
+ "DELETE_COLUMN": "Delete column",
253
+ "INSERT_ROW": "Insert row",
254
+ "DELETE_ROW": "Delete row",
255
+ },
256
+ },
257
+ )
258
+
226
259
  def test_validation_error_as_json(self):
227
260
  error = TypedTableBlockValidationError(
228
261
  cell_errors={
@@ -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"
@@ -71,11 +71,11 @@ msgstr ""
71
71
  msgid "tags"
72
72
  msgstr ""
73
73
 
74
- #: models.py:208
74
+ #: models.py:209
75
75
  msgid "document"
76
76
  msgstr ""
77
77
 
78
- #: models.py:209
78
+ #: models.py:210
79
79
  msgid "documents"
80
80
  msgstr ""
81
81
 
@@ -151,14 +151,14 @@ msgid "Delete documents"
151
151
  msgstr ""
152
152
 
153
153
  #: templates/wagtaildocs/bulk_actions/confirm_bulk_delete.html:17
154
- #: views/documents.py:282
154
+ #: views/documents.py:281
155
155
  msgid "Are you sure you want to delete this document?"
156
156
  msgid_plural "Are you sure you want to delete these documents?"
157
157
  msgstr[0] ""
158
158
  msgstr[1] ""
159
159
 
160
160
  #: templates/wagtaildocs/bulk_actions/confirm_bulk_delete.html:28
161
- #: templates/wagtaildocs/documents/edit.html:28
161
+ #: templates/wagtaildocs/documents/edit.html:27
162
162
  #, python-format
163
163
  msgid "Used %(usage_count)s time"
164
164
  msgid_plural "Used %(usage_count)s times"
@@ -210,11 +210,11 @@ msgstr ""
210
210
  msgid "Add a document"
211
211
  msgstr ""
212
212
 
213
- #: templates/wagtaildocs/documents/add.html:45 views/chooser.py:186
213
+ #: templates/wagtaildocs/documents/add.html:45 views/chooser.py:187
214
214
  msgid "Uploading…"
215
215
  msgstr ""
216
216
 
217
- #: templates/wagtaildocs/documents/add.html:48 views/chooser.py:185
217
+ #: templates/wagtaildocs/documents/add.html:48 views/chooser.py:186
218
218
  msgid "Upload"
219
219
  msgstr ""
220
220
 
@@ -266,30 +266,30 @@ msgstr[1] ""
266
266
  msgid "Add multiple documents"
267
267
  msgstr ""
268
268
 
269
- #: templates/wagtaildocs/multiple/add.html:14
269
+ #: templates/wagtaildocs/multiple/add.html:20
270
270
  msgid "Drag and drop documents into this area to upload immediately."
271
271
  msgstr ""
272
272
 
273
- #: templates/wagtaildocs/multiple/add.html:19
273
+ #: templates/wagtaildocs/multiple/add.html:25
274
274
  msgid "Or choose from your computer"
275
275
  msgstr ""
276
276
 
277
- #: templates/wagtaildocs/multiple/add.html:31
277
+ #: templates/wagtaildocs/multiple/add.html:37
278
278
  msgid "Add to collection:"
279
279
  msgstr ""
280
280
 
281
- #: templates/wagtaildocs/multiple/add.html:61
281
+ #: templates/wagtaildocs/multiple/add.html:67
282
282
  msgid ""
283
283
  "Upload successful. Please update this document with a more appropriate "
284
284
  "title, if necessary. You may also delete the document completely if the "
285
285
  "upload wasn't required."
286
286
  msgstr ""
287
287
 
288
- #: templates/wagtaildocs/multiple/add.html:62
288
+ #: templates/wagtaildocs/multiple/add.html:68
289
289
  msgid "Sorry, upload failed."
290
290
  msgstr ""
291
291
 
292
- #: templates/wagtaildocs/multiple/add.html:63
292
+ #: templates/wagtaildocs/multiple/add.html:69
293
293
  msgid "Document updated."
294
294
  msgstr ""
295
295
 
@@ -340,7 +340,7 @@ msgid_plural ""
340
340
  msgstr[0] ""
341
341
  msgstr[1] ""
342
342
 
343
- #: views/bulk_actions/delete.py:8 views/documents.py:249
343
+ #: views/bulk_actions/delete.py:8 views/documents.py:247
344
344
  msgid "Delete"
345
345
  msgstr ""
346
346
 
@@ -363,19 +363,19 @@ msgstr ""
363
363
  msgid "Created"
364
364
  msgstr ""
365
365
 
366
- #: views/chooser.py:184
366
+ #: views/chooser.py:185
367
367
  msgid "Choose a document"
368
368
  msgstr ""
369
369
 
370
- #: views/chooser.py:187
370
+ #: views/chooser.py:188
371
371
  msgid "Choose another document"
372
372
  msgstr ""
373
373
 
374
- #: views/chooser.py:188
374
+ #: views/chooser.py:189
375
375
  msgid "Edit this document"
376
376
  msgstr ""
377
377
 
378
- #: views/documents.py:63 wagtail_hooks.py:61 wagtail_hooks.py:134
378
+ #: views/documents.py:63 wagtail_hooks.py:62 wagtail_hooks.py:135
379
379
  msgid "Documents"
380
380
  msgstr ""
381
381
 
@@ -387,30 +387,30 @@ msgstr ""
387
387
  msgid "The document could not be created due to errors."
388
388
  msgstr ""
389
389
 
390
- #: views/documents.py:185
390
+ #: views/documents.py:184
391
391
  #, python-format
392
392
  msgid "Document '%(document_title)s' added."
393
393
  msgstr ""
394
394
 
395
- #: views/documents.py:193
395
+ #: views/documents.py:192
396
396
  msgid "The document could not be saved due to errors."
397
397
  msgstr ""
398
398
 
399
- #: views/documents.py:223
399
+ #: views/documents.py:221
400
400
  #, python-format
401
401
  msgid "Document '%(document_title)s' updated"
402
402
  msgstr ""
403
403
 
404
- #: views/documents.py:247
404
+ #: views/documents.py:245
405
405
  msgid ""
406
406
  "The file could not be found. Please change the source or delete the document"
407
407
  msgstr ""
408
408
 
409
- #: views/documents.py:270
409
+ #: views/documents.py:269
410
410
  msgid "Delete document"
411
411
  msgstr ""
412
412
 
413
- #: views/documents.py:288
413
+ #: views/documents.py:287
414
414
  #, python-format
415
415
  msgid "Document '%(document_title)s' deleted."
416
416
  msgstr ""
@@ -419,11 +419,11 @@ msgstr ""
419
419
  msgid "Add documents"
420
420
  msgstr ""
421
421
 
422
- #: wagtail_hooks.py:80
422
+ #: wagtail_hooks.py:81
423
423
  msgid "Document"
424
424
  msgstr ""
425
425
 
426
- #: wagtail_hooks.py:155
426
+ #: wagtail_hooks.py:156
427
427
  #, python-format
428
428
  msgid "%(count)s document"
429
429
  msgid_plural "%(count)s documents"
@@ -80,6 +80,7 @@ class Migration(migrations.Migration):
80
80
 
81
81
  dependencies = [
82
82
  ("wagtaildocs", "0010_document_file_hash"),
83
+ ("wagtailadmin", "0001_create_admin_access_permissions"),
83
84
  ]
84
85
 
85
86
  operations = [
@@ -57,6 +57,7 @@ class AbstractDocument(CollectionMember, index.Indexed, models.Model):
57
57
  ],
58
58
  ),
59
59
  index.FilterField("uploaded_by_user"),
60
+ index.FilterField("created_at"),
60
61
  ]
61
62
 
62
63
  def clean(self):
@@ -2,11 +2,15 @@ from django.db import transaction
2
2
  from django.db.models.signals import post_delete
3
3
 
4
4
  from wagtail.documents import get_document_model
5
+ from wagtail.tasks import delete_file_from_storage_task
5
6
 
6
7
 
7
8
  def post_delete_file_cleanup(instance, **kwargs):
8
- # Pass false so FileField doesn't save the model.
9
- transaction.on_commit(lambda: instance.file.delete(False))
9
+ transaction.on_commit(
10
+ lambda: delete_file_from_storage_task.enqueue(
11
+ instance.file.storage.deconstruct(), instance.file.name
12
+ )
13
+ )
10
14
 
11
15
 
12
16
  def register_signal_handlers():
@@ -1 +1 @@
1
- $((function(){$(document).on("drop dragover",(function(e){e.preventDefault()})),$("#fileupload").fileupload({dataType:"html",sequentialUploads:!0,dropZone:$(".drop-zone"),add:function(e,t){var a=$(this),s=a.data("blueimp-fileupload")||a.data("fileupload"),o=$($("#upload-list-item").html()).addClass("upload-uploading"),l=s.options;$("#upload-list").append(o),t.context=o,t.process((function(){return a.fileupload("process",t)})).always((function(){t.context.removeClass("processing"),t.context.find(".left").each((function(e,a){$(a).append(escapeHtml(t.files[e].name))}))})).done((function(){t.context.find(".start").prop("disabled",!1),!1!==s._trigger("added",e,t)&&(l.autoUpload||t.autoUpload)&&!1!==t.autoUpload&&t.submit()})).fail((function(){t.files.error&&t.context.each((function(e){var a=t.files[e].error;a&&$(this).find(".error_messages").text(a)}))}))},processfail:function(e,t){$(t.context).removeClass("upload-uploading").addClass("upload-failure")},progress:function(e,t){if(e.isDefaultPrevented())return!1;var a=Math.floor(t.loaded/t.total*100);t.context.each((function(){$(this).find(".progress").addClass("active").attr("aria-valuenow",a).find(".bar").css("width",a+"%").html(a+"%")}))},progressall:function(e,t){var a=parseInt(t.loaded/t.total*100,10);$("#overall-progress").addClass("active").attr("aria-valuenow",a).find(".bar").css("width",a+"%").html(a+"%"),a>=100&&$("#overall-progress").removeClass("active").find(".bar").css("width","0%")},formData:function(e){var t=this.files[0].name,a={title:t.replace(/\.[^.]+$/,"")};return e.get(0).dispatchEvent(new CustomEvent("wagtail:documents-upload",{bubbles:!0,cancelable:!0,detail:{data:a,filename:t,maxTitleLength:this.maxTitleLength}}))?e.serializeArray().concat({name:"title",value:a.title}):e.serializeArray()},done:function(e,t){var a=$(t.context),s=JSON.parse(t.result);s.success?(a.addClass("upload-success"),$(".right",a).append(s.form)):(a.addClass("upload-failure"),$(".right .error_messages",a).append(s.error_message))},fail:function(e,t){$(t.context).addClass("upload-failure")},always:function(e,t){$(t.context).removeClass("upload-uploading").addClass("upload-complete")}}),$("#upload-list").on("submit","form",(function(e){var t=$(this),a=new FormData(this),s=t.closest("#upload-list > li");e.preventDefault(),$.ajax({contentType:!1,data:a,processData:!1,type:"POST",url:this.action}).done((function(e){if(e.success){var a=$(".status-msg.update-success").first().text();document.dispatchEvent(new CustomEvent("w-messages:add",{detail:{clear:!0,text:a,type:"success"}})),s.slideUp((function(){$(this).remove()}))}else t.replaceWith(e.form)}))})),$("#upload-list").on("click",".delete",(function(e){var t=$(this).closest("form"),a=t.closest("#upload-list > li");e.preventDefault();var s=$('input[name="csrfmiddlewaretoken"]',t).val();$.post(this.href,{csrfmiddlewaretoken:s},(function(e){e.success&&a.slideUp((function(){$(this).remove()}))}))}))}));
1
+ $((function(){$("#fileupload").fileupload({dataType:"html",sequentialUploads:!0,dropZone:$(".drop-zone"),add:function(e,a){var t=$(this),s=t.data("blueimp-fileupload")||t.data("fileupload"),o=$($("#upload-list-item").html()).addClass("upload-uploading"),l=s.options;$("#upload-list").append(o),a.context=o,a.process((function(){return t.fileupload("process",a)})).always((function(){a.context.removeClass("processing"),a.context.find(".left").each((function(e,t){$(t).append(escapeHtml(a.files[e].name))}))})).done((function(){a.context.find(".start").prop("disabled",!1),!1!==s._trigger("added",e,a)&&(l.autoUpload||a.autoUpload)&&!1!==a.autoUpload&&a.submit()})).fail((function(){a.files.error&&a.context.each((function(e){var t=a.files[e].error;t&&$(this).find(".error_messages").text(t)}))}))},processfail:function(e,a){$(a.context).removeClass("upload-uploading").addClass("upload-failure")},progress:function(e,a){if(e.isDefaultPrevented())return!1;var t=Math.floor(a.loaded/a.total*100);a.context.each((function(){$(this).find(".progress").addClass("active").attr("aria-valuenow",t).find(".bar").css("width",t+"%").html(t+"%")}))},progressall:function(e,a){var t=parseInt(a.loaded/a.total*100,10);$("#overall-progress").addClass("active").attr("aria-valuenow",t).find(".bar").css("width",t+"%").html(t+"%"),t>=100&&$("#overall-progress").removeClass("active").find(".bar").css("width","0%")},formData:function(e){var a=this.files[0].name,t={title:a.replace(/\.[^.]+$/,"")};return e.get(0).dispatchEvent(new CustomEvent("wagtail:documents-upload",{bubbles:!0,cancelable:!0,detail:{data:t,filename:a,maxTitleLength:this.maxTitleLength}}))?e.serializeArray().concat({name:"title",value:t.title}):e.serializeArray()},done:function(e,a){var t=$(a.context),s=JSON.parse(a.result);s.success?(t.addClass("upload-success"),$(".right",t).append(s.form)):(t.addClass("upload-failure"),$(".right .error_messages",t).append(s.error_message))},fail:function(e,a){$(a.context).addClass("upload-failure")},always:function(e,a){$(a.context).removeClass("upload-uploading").addClass("upload-complete")}}),$("#upload-list").on("submit","form",(function(e){var a=$(this),t=new FormData(this),s=a.closest("#upload-list > li");e.preventDefault(),$.ajax({contentType:!1,data:t,processData:!1,type:"POST",url:this.action}).done((function(e){if(e.success){var t=$(".status-msg.update-success").first().text();document.dispatchEvent(new CustomEvent("w-messages:add",{detail:{clear:!0,text:t,type:"success"}})),s.slideUp((function(){$(this).remove()}))}else a.replaceWith(e.form)}))})),$("#upload-list").on("click",".delete",(function(e){var a=$(this).closest("form"),t=a.closest("#upload-list > li");e.preventDefault();var s=$('input[name="csrfmiddlewaretoken"]',a).val();$.post(this.href,{csrfmiddlewaretoken:s},(function(e){e.success&&t.slideUp((function(){$(this).remove()}))}))}))}));
@@ -24,9 +24,7 @@
24
24
 
25
25
  <dt>{% trans "Usage" %}</dt>
26
26
  <dd>
27
- {% with usage_count_val=document.get_usage.count %}
28
- <a href="{{ document.usage_url }}">{% blocktrans trimmed with usage_count=usage_count_val|intcomma count usage_count_val=usage_count_val %}Used {{ usage_count }} time{% plural %}Used {{ usage_count }} times{% endblocktrans %}</a>
29
- {% endwith %}
27
+ <a href="{{ document.usage_url }}">{% blocktrans trimmed with usage_count=usage_count_val|intcomma count usage_count_val=usage_count_val %}Used {{ usage_count }} time{% plural %}Used {{ usage_count }} times{% endblocktrans %}</a>
30
28
  </dd>
31
29
  </dl>
32
30
  </div>
@@ -10,7 +10,13 @@
10
10
  {% endblock %}
11
11
 
12
12
  {% block main_content %}
13
- <div class="drop-zone w-mt-8">
13
+ <div
14
+ class="drop-zone w-mt-8"
15
+ data-controller="w-zone"
16
+ data-action="dragover@document->w-zone#noop:prevent drop@document->w-zone#noop:prevent dragover->w-zone#activate drop->w-zone#deactivate dragleave->w-zone#deactivate dragend->w-zone#deactivate"
17
+ data-w-zone-active-class="hovered"
18
+ data-w-zone-delay-value="10"
19
+ >
14
20
  <p>{% trans "Drag and drop documents into this area to upload immediately." %}</p>
15
21
  <p>{{ help_text }}</p>
16
22
 
@@ -30,6 +30,7 @@ from wagtail.test.testapp.models import (
30
30
  )
31
31
  from wagtail.test.utils import WagtailTestUtils
32
32
  from wagtail.test.utils.template_tests import AdminTemplateTestUtils
33
+ from wagtail.test.utils.timestamps import local_datetime
33
34
 
34
35
 
35
36
  class TestDocumentIndexView(WagtailTestUtils, TestCase):
@@ -95,7 +96,7 @@ class TestDocumentIndexView(WagtailTestUtils, TestCase):
95
96
  )
96
97
 
97
98
  def test_ordering(self):
98
- orderings = ["title", "-created_at"]
99
+ orderings = ["title", "created_at", "-created_at"]
99
100
  for ordering in orderings:
100
101
  response = self.get({"ordering": ordering})
101
102
  self.assertEqual(response.status_code, 200)
@@ -371,6 +372,39 @@ class TestDocumentIndexViewSearch(WagtailTestUtils, TransactionTestCase):
371
372
  response = self.get({"tag": "one", "q": "test"})
372
373
  self.assertEqual(response.context["page_obj"].paginator.count, 2)
373
374
 
375
+ def test_search_and_order_by_created_at(self):
376
+ # Create Documents, change their created_at dates after creation as
377
+ # the field has auto_now_add=True
378
+ doc1 = models.Document.objects.create(title="recent good Document")
379
+ doc1.created_at = local_datetime(2024, 1, 1)
380
+ doc1.save()
381
+
382
+ doc2 = models.Document.objects.create(title="latest ok Document")
383
+ doc2.created_at = local_datetime(2025, 1, 1)
384
+ doc2.save()
385
+
386
+ doc3 = models.Document.objects.create(title="oldest good document")
387
+ doc3.created_at = local_datetime(2023, 1, 1)
388
+ doc3.save()
389
+
390
+ cases = [
391
+ ("created_at", [doc3, doc1]),
392
+ ("-created_at", [doc1, doc3]),
393
+ ]
394
+
395
+ for ordering, expected_docs in cases:
396
+ with self.subTest(ordering=ordering):
397
+ response = self.get({"q": "good", "ordering": ordering})
398
+ self.assertEqual(response.status_code, 200)
399
+ self.assertEqual(response.context["query_string"], "good")
400
+
401
+ # Check that the documents are filtered by the search query
402
+ # and are in the correct order
403
+ documents = list(response.context["page_obj"].object_list)
404
+ self.assertEqual(documents, expected_docs)
405
+ self.assertIn("ordering", response.context)
406
+ self.assertEqual(response.context["ordering"], ordering)
407
+
374
408
 
375
409
  class TestDocumentIndexResultsView(WagtailTestUtils, TransactionTestCase):
376
410
  def setUp(self):
@@ -969,7 +1003,8 @@ class TestDocumentDeleteView(WagtailTestUtils, TestCase):
969
1003
  )
970
1004
 
971
1005
  def test_delete_get_with_protected_reference(self):
972
- VariousOnDeleteModel.objects.create(protected_document=self.document)
1006
+ with self.captureOnCommitCallbacks(execute=True):
1007
+ VariousOnDeleteModel.objects.create(protected_document=self.document)
973
1008
  response = self.client.get(self.delete_url)
974
1009
  self.assertEqual(response.status_code, 200)
975
1010
  self.assertTemplateUsed(response, "wagtailadmin/generic/confirm_delete.html")
@@ -991,7 +1026,8 @@ class TestDocumentDeleteView(WagtailTestUtils, TestCase):
991
1026
  )
992
1027
 
993
1028
  def test_delete_post_with_protected_reference(self):
994
- VariousOnDeleteModel.objects.create(protected_document=self.document)
1029
+ with self.captureOnCommitCallbacks(execute=True):
1030
+ VariousOnDeleteModel.objects.create(protected_document=self.document)
995
1031
  response = self.client.post(self.delete_url)
996
1032
  self.assertRedirects(response, reverse("wagtailadmin_home"))
997
1033
  self.assertTrue(
@@ -2058,21 +2094,23 @@ class TestUsageCount(WagtailTestUtils, TestCase):
2058
2094
  self.assertEqual(doc.get_usage().count(), 0)
2059
2095
 
2060
2096
  def test_used_document_usage_count(self):
2061
- doc = models.Document.objects.get(id=1)
2062
- page = EventPage.objects.get(id=4)
2063
- event_page_related_link = EventPageRelatedLink()
2064
- event_page_related_link.page = page
2065
- event_page_related_link.link_document = doc
2066
- event_page_related_link.save()
2097
+ with self.captureOnCommitCallbacks(execute=True):
2098
+ doc = models.Document.objects.get(id=1)
2099
+ page = EventPage.objects.get(id=4)
2100
+ event_page_related_link = EventPageRelatedLink()
2101
+ event_page_related_link.page = page
2102
+ event_page_related_link.link_document = doc
2103
+ event_page_related_link.save()
2067
2104
  self.assertEqual(doc.get_usage().count(), 1)
2068
2105
 
2069
2106
  def test_usage_count_appears(self):
2070
- doc = models.Document.objects.get(id=1)
2071
- page = EventPage.objects.get(id=4)
2072
- event_page_related_link = EventPageRelatedLink()
2073
- event_page_related_link.page = page
2074
- event_page_related_link.link_document = doc
2075
- event_page_related_link.save()
2107
+ with self.captureOnCommitCallbacks(execute=True):
2108
+ doc = models.Document.objects.get(id=1)
2109
+ page = EventPage.objects.get(id=4)
2110
+ event_page_related_link = EventPageRelatedLink()
2111
+ event_page_related_link.page = page
2112
+ event_page_related_link.link_document = doc
2113
+ event_page_related_link.save()
2076
2114
  response = self.client.get(reverse("wagtaildocs:edit", args=(1,)))
2077
2115
  self.assertContains(response, "Used 1 time")
2078
2116
 
@@ -2092,12 +2130,13 @@ class TestGetUsage(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
2092
2130
  self.assertEqual(list(doc.get_usage()), [])
2093
2131
 
2094
2132
  def test_used_document_get_usage(self):
2095
- doc = models.Document.objects.get(id=1)
2096
- page = EventPage.objects.get(id=4)
2097
- event_page_related_link = EventPageRelatedLink()
2098
- event_page_related_link.page = page
2099
- event_page_related_link.link_document = doc
2100
- event_page_related_link.save()
2133
+ with self.captureOnCommitCallbacks(execute=True):
2134
+ doc = models.Document.objects.get(id=1)
2135
+ page = EventPage.objects.get(id=4)
2136
+ event_page_related_link = EventPageRelatedLink()
2137
+ event_page_related_link.page = page
2138
+ event_page_related_link.link_document = doc
2139
+ event_page_related_link.save()
2101
2140
 
2102
2141
  self.assertIsInstance(doc.get_usage()[0], tuple)
2103
2142
  self.assertIsInstance(doc.get_usage()[0][0], Page)
@@ -2105,12 +2144,13 @@ class TestGetUsage(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
2105
2144
  self.assertIsInstance(doc.get_usage()[0][1][0], ReferenceIndex)
2106
2145
 
2107
2146
  def test_usage_page(self):
2108
- doc = models.Document.objects.get(id=1)
2109
- page = EventPage.objects.get(id=4)
2110
- event_page_related_link = EventPageRelatedLink()
2111
- event_page_related_link.page = page
2112
- event_page_related_link.link_document = doc
2113
- event_page_related_link.save()
2147
+ with self.captureOnCommitCallbacks(execute=True):
2148
+ doc = models.Document.objects.get(id=1)
2149
+ page = EventPage.objects.get(id=4)
2150
+ event_page_related_link = EventPageRelatedLink()
2151
+ event_page_related_link.page = page
2152
+ event_page_related_link.link_document = doc
2153
+ event_page_related_link.save()
2114
2154
  response = self.client.get(reverse("wagtaildocs:document_usage", args=(1,)))
2115
2155
  self.assertContains(response, "Christmas")
2116
2156
  self.assertContains(response, '<table class="listing">')
@@ -2140,12 +2180,13 @@ class TestGetUsage(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
2140
2180
  self.assertNotContains(response, '<table class="listing">')
2141
2181
 
2142
2182
  def test_usage_page_with_only_change_permission(self):
2143
- doc = models.Document.objects.get(id=1)
2144
- page = EventPage.objects.get(id=4)
2145
- event_page_related_link = EventPageRelatedLink()
2146
- event_page_related_link.page = page
2147
- event_page_related_link.link_document = doc
2148
- event_page_related_link.save()
2183
+ with self.captureOnCommitCallbacks(execute=True):
2184
+ doc = models.Document.objects.get(id=1)
2185
+ page = EventPage.objects.get(id=4)
2186
+ event_page_related_link = EventPageRelatedLink()
2187
+ event_page_related_link.page = page
2188
+ event_page_related_link.link_document = doc
2189
+ event_page_related_link.save()
2149
2190
 
2150
2191
  # Create a user with change_document permission but not add_document
2151
2192
  user = self.create_user(
@@ -58,15 +58,18 @@ class TestServeView(TestCase):
58
58
  f'inline; filename="{self.pdf_document.filename}"',
59
59
  )
60
60
 
61
+ def test_content_security_policy(self):
62
+ self.assertEqual(self.get()["Content-Security-Policy"], "default-src 'none'")
63
+
64
+ with self.settings(WAGTAILDOCS_BLOCK_EMBEDDED_CONTENT=False):
65
+ self.assertNotIn("Content-Security-Policy", self.get().headers)
66
+
67
+ def test_no_sniff_content_type(self):
68
+ self.assertEqual(self.get()["X-Content-Type-Options"], "nosniff")
69
+
61
70
  @mock.patch("wagtail.documents.views.serve.hooks")
62
71
  @mock.patch("wagtail.documents.views.serve.get_object_or_404")
63
- def test_non_local_filesystem_content_disposition_header(
64
- self, mock_get_object_or_404, mock_hooks
65
- ):
66
- """
67
- Tests the 'Content-Disposition' header in a response when using a
68
- storage backend that doesn't expose filesystem paths.
69
- """
72
+ def test_non_local_filesystem_headers(self, mock_get_object_or_404, mock_hooks):
70
73
  # Create a mock document with no local file to hit the correct code path
71
74
  mock_doc = mock.Mock()
72
75
  mock_doc.filename = self.document.filename
@@ -90,16 +93,14 @@ class TestServeView(TestCase):
90
93
  urllib.parse.quote(self.document.filename)
91
94
  ),
92
95
  )
96
+ self.assertEqual(response["Content-Security-Policy"], "default-src 'none'")
97
+ self.assertEqual(response["X-Content-Type-Options"], "nosniff")
93
98
 
94
99
  @mock.patch("wagtail.documents.views.serve.hooks")
95
100
  @mock.patch("wagtail.documents.views.serve.get_object_or_404")
96
- def test_non_local_filesystem_inline_content_disposition_header(
101
+ def test_non_local_filesystem_inline_headers(
97
102
  self, mock_get_object_or_404, mock_hooks
98
103
  ):
99
- """
100
- Tests the 'Content-Disposition' header in a response when using a
101
- storage backend that doesn't expose filesystem paths.
102
- """
103
104
  # Create a mock document with no local file to hit the correct code path
104
105
  mock_doc = mock.Mock()
105
106
  mock_doc.filename = self.pdf_document.filename
@@ -118,6 +119,8 @@ class TestServeView(TestCase):
118
119
  self.assertEqual(response.status_code, 200)
119
120
 
120
121
  self.assertEqual(response["Content-Disposition"], "inline")
122
+ self.assertEqual(response["Content-Security-Policy"], "default-src 'none'")
123
+ self.assertEqual(response["X-Content-Type-Options"], "nosniff")
121
124
 
122
125
  def test_content_length_header(self):
123
126
  self.assertEqual(self.get()["Content-Length"], "25")
@@ -346,6 +349,12 @@ class TestServeViewWithSendfile(TestCase):
346
349
  os.path.join(settings.MEDIA_URL, self.document.file.name),
347
350
  )
348
351
 
352
+ def test_content_security_policy(self):
353
+ self.assertEqual(self.get()["Content-Security-Policy"], "default-src 'none'")
354
+
355
+ def test_no_sniff_content_type(self):
356
+ self.assertEqual(self.get()["X-Content-Type-Options"], "nosniff")
357
+
349
358
 
350
359
  @override_settings(WAGTAILDOCS_SERVE_METHOD=None)
351
360
  class TestServeWithUnicodeFilename(TestCase):
@@ -145,6 +145,7 @@ class BaseAdminDocumentChooser(BaseChooser):
145
145
  js=[
146
146
  versioned_static("wagtaildocs/js/document-chooser-modal.js"),
147
147
  versioned_static("wagtaildocs/js/document-chooser.js"),
148
+ versioned_static("wagtaildocs/js/document-chooser-telepath.js"),
148
149
  ]
149
150
  )
150
151