wagtail 6.2.2__py3-none-any.whl → 6.3rc2__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 (386) hide show
  1. wagtail/__init__.py +1 -1
  2. wagtail/actions/copy_for_translation.py +6 -0
  3. wagtail/actions/publish_revision.py +3 -3
  4. wagtail/admin/action_menu.py +5 -3
  5. wagtail/admin/forms/account.py +1 -1
  6. wagtail/admin/icons.py +2 -6
  7. wagtail/admin/locale/cy/LC_MESSAGES/django.mo +0 -0
  8. wagtail/admin/locale/cy/LC_MESSAGES/django.po +32 -0
  9. wagtail/admin/locale/dv/LC_MESSAGES/django.mo +0 -0
  10. wagtail/admin/locale/dv/LC_MESSAGES/django.po +28 -0
  11. wagtail/admin/locale/en/LC_MESSAGES/django.po +451 -485
  12. wagtail/admin/locale/en/LC_MESSAGES/djangojs.po +7 -7
  13. wagtail/admin/locale/sl/LC_MESSAGES/django.mo +0 -0
  14. wagtail/admin/locale/sl/LC_MESSAGES/django.po +150 -0
  15. wagtail/admin/locale/sl/LC_MESSAGES/djangojs.mo +0 -0
  16. wagtail/admin/locale/sl/LC_MESSAGES/djangojs.po +9 -2
  17. wagtail/admin/locale/ug/LC_MESSAGES/django.mo +0 -0
  18. wagtail/admin/locale/ug/LC_MESSAGES/django.po +3250 -196
  19. wagtail/admin/localization.py +12 -2
  20. wagtail/admin/panels/model_utils.py +1 -1
  21. wagtail/admin/site_summary.py +0 -2
  22. wagtail/admin/static/wagtailadmin/css/core.css +1 -1
  23. wagtail/admin/static/wagtailadmin/css/panels/draftail.css +1 -1
  24. wagtail/admin/static/wagtailadmin/images/email-header.jpg +0 -0
  25. wagtail/admin/static/wagtailadmin/js/bulk-actions.js +1 -1
  26. wagtail/admin/static/wagtailadmin/js/core.js +1 -1
  27. wagtail/admin/static/wagtailadmin/js/core.js.LICENSE.txt +12 -0
  28. wagtail/admin/static/wagtailadmin/js/draftail.js +1 -1
  29. wagtail/admin/static/wagtailadmin/js/sidebar.js +1 -1
  30. wagtail/admin/static/wagtailadmin/js/telepath/telepath.js +1 -1
  31. wagtail/admin/static/wagtailadmin/js/telepath/widgets.js +1 -1
  32. wagtail/admin/static/wagtailadmin/js/userbar.js +1 -1
  33. wagtail/admin/static/wagtailadmin/js/userbar.js.LICENSE.txt +1 -1
  34. wagtail/admin/static/wagtailadmin/js/vendor.js +1 -1
  35. wagtail/admin/static/wagtailadmin/js/wagtailadmin.js +1 -1
  36. wagtail/admin/static/wagtailadmin/js/workflow-action.js +1 -1
  37. wagtail/admin/staticfiles.py +6 -4
  38. wagtail/admin/templates/wagtailadmin/account/account.html +42 -59
  39. wagtail/admin/templates/wagtailadmin/admin_base.html +0 -28
  40. wagtail/admin/templates/wagtailadmin/bulk_actions/footer.html +1 -1
  41. wagtail/admin/templates/wagtailadmin/chooser/_search_results.html +7 -5
  42. wagtail/admin/templates/wagtailadmin/chooser/tables/page_navigate_to_children_cell.html +1 -1
  43. wagtail/admin/templates/wagtailadmin/generic/chooser/results.html +7 -5
  44. wagtail/admin/templates/wagtailadmin/generic/edit.html +0 -8
  45. wagtail/admin/templates/wagtailadmin/generic/form.html +60 -17
  46. wagtail/admin/templates/wagtailadmin/generic/index.html +17 -0
  47. wagtail/admin/templates/wagtailadmin/generic/index_results.html +9 -35
  48. wagtail/admin/templates/wagtailadmin/generic/inspect.html +2 -21
  49. wagtail/admin/templates/wagtailadmin/generic/listing.html +11 -17
  50. wagtail/admin/templates/wagtailadmin/generic/listing_results.html +19 -1
  51. wagtail/admin/templates/wagtailadmin/home/account_summary.html +26 -0
  52. wagtail/admin/templates/wagtailadmin/home/locked_pages.html +28 -18
  53. wagtail/admin/templates/wagtailadmin/home/recent_edits.html +28 -17
  54. wagtail/admin/templates/wagtailadmin/home/site_summary_pages.html +2 -2
  55. wagtail/admin/templates/wagtailadmin/home/upgrade_notification.html +35 -13
  56. wagtail/admin/templates/wagtailadmin/home/user_objects_in_workflow_moderation.html +28 -18
  57. wagtail/admin/templates/wagtailadmin/home/workflow_objects_to_moderate.html +43 -32
  58. wagtail/admin/templates/wagtailadmin/home.html +22 -5
  59. wagtail/admin/templates/wagtailadmin/pages/_editor_js.html +0 -1
  60. wagtail/admin/templates/wagtailadmin/pages/action_menu/menu.html +1 -11
  61. wagtail/admin/templates/wagtailadmin/pages/action_menu/menu_item.html +1 -6
  62. wagtail/admin/templates/wagtailadmin/pages/action_menu/page_locked.html +1 -1
  63. wagtail/admin/templates/wagtailadmin/pages/action_menu/publish.html +1 -1
  64. wagtail/admin/templates/wagtailadmin/pages/action_menu/save_draft.html +1 -1
  65. wagtail/admin/templates/wagtailadmin/pages/bulk_actions/confirm_bulk_delete.html +12 -6
  66. wagtail/admin/templates/wagtailadmin/pages/bulk_actions/confirm_bulk_move.html +12 -7
  67. wagtail/admin/templates/wagtailadmin/pages/bulk_actions/confirm_bulk_publish.html +17 -11
  68. wagtail/admin/templates/wagtailadmin/pages/bulk_actions/confirm_bulk_unpublish.html +15 -9
  69. wagtail/admin/templates/wagtailadmin/pages/confirm_delete.html +9 -9
  70. wagtail/admin/templates/wagtailadmin/pages/confirm_move.html +2 -2
  71. wagtail/admin/templates/wagtailadmin/pages/confirm_unpublish.html +4 -4
  72. wagtail/admin/templates/wagtailadmin/pages/create.html +15 -34
  73. wagtail/admin/templates/wagtailadmin/pages/edit.html +15 -30
  74. wagtail/admin/templates/wagtailadmin/pages/explorable_index.html +6 -0
  75. wagtail/admin/templates/wagtailadmin/pages/explorable_index_results.html +60 -0
  76. wagtail/admin/templates/wagtailadmin/pages/index.html +1 -36
  77. wagtail/admin/templates/wagtailadmin/pages/index_results.html +2 -50
  78. wagtail/admin/templates/wagtailadmin/pages/listing/_locked_indicator.html +1 -1
  79. wagtail/admin/templates/wagtailadmin/pages/listing/_ordering_cell.html +1 -1
  80. wagtail/admin/templates/wagtailadmin/pages/listing/_page_header_buttons.html +1 -1
  81. wagtail/admin/templates/wagtailadmin/pages/listing/_page_title_column_header.html +3 -3
  82. wagtail/admin/templates/wagtailadmin/pages/listing/_pagination.html +1 -1
  83. wagtail/admin/templates/wagtailadmin/pages/listing.html +24 -0
  84. wagtail/admin/templates/wagtailadmin/pages/search.html +1 -20
  85. wagtail/admin/templates/wagtailadmin/pages/search_results.html +27 -25
  86. wagtail/admin/templates/wagtailadmin/permissions/includes/collection_member_permissions_formset.html +0 -1
  87. wagtail/admin/templates/wagtailadmin/reports/listing/_list_page_report.html +2 -2
  88. wagtail/admin/templates/wagtailadmin/reports/listing/_list_page_types_usage.html +6 -6
  89. wagtail/admin/templates/wagtailadmin/reports/workflow_tasks_results.html +1 -1
  90. wagtail/admin/templates/wagtailadmin/shared/action_menu/menu.html +14 -0
  91. wagtail/admin/templates/wagtailadmin/shared/action_menu/menu_item.html +6 -0
  92. wagtail/admin/templates/wagtailadmin/shared/avatar.html +13 -3
  93. wagtail/admin/templates/wagtailadmin/shared/header.html +0 -5
  94. wagtail/admin/templates/wagtailadmin/shared/headers/slim_header.html +1 -1
  95. wagtail/admin/templates/wagtailadmin/shared/page_status_tag_new.html +4 -1
  96. wagtail/admin/templates/wagtailadmin/shared/pagination_nav.html +1 -1
  97. wagtail/admin/templates/wagtailadmin/shared/side_panel_toggle.html +1 -1
  98. wagtail/admin/templates/wagtailadmin/shared/side_panels/includes/status/locale.html +1 -1
  99. wagtail/admin/templates/wagtailadmin/shared/side_panels/includes/status/usage.html +2 -2
  100. wagtail/admin/templates/wagtailadmin/shared/side_panels/preview.html +94 -52
  101. wagtail/admin/templates/wagtailadmin/{pages/_unsaved_changes_warning.html → shared/unsaved_changes_warning.html} +4 -4
  102. wagtail/admin/templates/wagtailadmin/shared/usage_summary.html +2 -2
  103. wagtail/admin/templates/wagtailadmin/shared/workflow_history/detail.html +1 -7
  104. wagtail/admin/templates/wagtailadmin/shared/workflow_history/listing.html +1 -0
  105. wagtail/admin/templates/wagtailadmin/shared/workflow_history/{list.html → listing_results.html} +33 -35
  106. wagtail/admin/templates/wagtailadmin/tables/references_cell.html +1 -1
  107. wagtail/admin/templates/wagtailadmin/workflows/create.html +11 -36
  108. wagtail/admin/templates/wagtailadmin/workflows/create_task.html +0 -38
  109. wagtail/admin/templates/wagtailadmin/workflows/edit.html +44 -74
  110. wagtail/admin/templates/wagtailadmin/workflows/edit_task.html +27 -66
  111. wagtail/admin/templates/wagtailadmin/workflows/includes/task_usage_cell.html +4 -2
  112. wagtail/admin/templates/wagtailadmin/workflows/includes/workflow_tasks_cell.html +4 -4
  113. wagtail/admin/templates/wagtailadmin/workflows/includes/workflow_used_by_cell.html +15 -11
  114. wagtail/admin/templates/wagtailadmin/workflows/task_chooser/includes/results.html +7 -5
  115. wagtail/admin/templatetags/wagtailadmin_tags.py +51 -22
  116. wagtail/admin/tests/pages/test_content_type_use_view.py +82 -2
  117. wagtail/admin/tests/pages/test_edit_page.py +70 -0
  118. wagtail/admin/tests/pages/test_explorer_view.py +65 -4
  119. wagtail/admin/tests/pages/test_page_search.py +8 -7
  120. wagtail/admin/tests/pages/test_page_usage.py +3 -1
  121. wagtail/admin/tests/pages/test_preview.py +208 -63
  122. wagtail/admin/tests/pages/test_reorder_page.py +91 -1
  123. wagtail/admin/tests/pages/test_view_draft.py +32 -1
  124. wagtail/admin/tests/pages/test_workflow_history.py +288 -7
  125. wagtail/admin/tests/test_account_management.py +23 -3
  126. wagtail/admin/tests/test_audit_log.py +24 -2
  127. wagtail/admin/tests/test_collections_views.py +17 -5
  128. wagtail/admin/tests/test_dashboard.py +1 -1
  129. wagtail/admin/tests/test_edit_handlers.py +3 -2
  130. wagtail/admin/tests/test_icon_sprite.py +4 -0
  131. wagtail/admin/tests/test_privacy.py +5 -19
  132. wagtail/admin/tests/test_reports_views.py +1 -1
  133. wagtail/admin/tests/test_site_summary.py +3 -3
  134. wagtail/admin/tests/test_templatetags.py +27 -3
  135. wagtail/admin/tests/test_upgrade_notification.py +71 -18
  136. wagtail/admin/tests/test_views.py +2 -2
  137. wagtail/admin/tests/test_views_generic.py +13 -0
  138. wagtail/admin/tests/test_widgets.py +3 -3
  139. wagtail/admin/tests/test_workflows.py +172 -27
  140. wagtail/admin/tests/tests.py +1 -1
  141. wagtail/admin/tests/viewsets/test_model_viewset.py +55 -3
  142. wagtail/admin/ui/side_panels.py +19 -0
  143. wagtail/admin/ui/sidebar.py +4 -3
  144. wagtail/admin/ui/tables/__init__.py +14 -1
  145. wagtail/admin/ui/tables/pages.py +6 -1
  146. wagtail/admin/urls/pages.py +10 -1
  147. wagtail/admin/urls/workflows.py +6 -1
  148. wagtail/admin/views/account.py +5 -1
  149. wagtail/admin/views/collections.py +0 -2
  150. wagtail/admin/views/generic/base.py +118 -1
  151. wagtail/admin/views/generic/history.py +158 -26
  152. wagtail/admin/views/generic/models.py +113 -99
  153. wagtail/admin/views/home.py +24 -9
  154. wagtail/admin/views/page_privacy.py +54 -30
  155. wagtail/admin/views/pages/history.py +11 -4
  156. wagtail/admin/views/pages/listing.py +76 -89
  157. wagtail/admin/views/pages/preview.py +1 -1
  158. wagtail/admin/views/pages/search.py +27 -71
  159. wagtail/admin/views/pages/usage.py +63 -24
  160. wagtail/admin/views/pages/utils.py +4 -3
  161. wagtail/admin/views/reports/base.py +0 -6
  162. wagtail/admin/views/workflows.py +67 -25
  163. wagtail/admin/viewsets/model.py +4 -3
  164. wagtail/admin/widgets/chooser.py +2 -1
  165. wagtail/api/v2/tests/test_pages.py +15 -2
  166. wagtail/blocks/field_block.py +15 -3
  167. wagtail/blocks/static_block.py +3 -0
  168. wagtail/contrib/forms/locale/cy/LC_MESSAGES/django.mo +0 -0
  169. wagtail/contrib/forms/locale/cy/LC_MESSAGES/django.po +29 -1
  170. wagtail/contrib/forms/locale/en/LC_MESSAGES/django.po +15 -11
  171. wagtail/contrib/forms/templates/wagtailforms/confirm_delete.html +1 -0
  172. wagtail/contrib/forms/templates/wagtailforms/index.html +1 -1
  173. wagtail/contrib/forms/templates/wagtailforms/index_results.html +1 -1
  174. wagtail/contrib/forms/templates/wagtailforms/list_submissions.html +2 -1
  175. wagtail/contrib/forms/templates/wagtailforms/panels/form_responses_panel.html +2 -2
  176. wagtail/contrib/forms/tests/test_views.py +234 -35
  177. wagtail/contrib/forms/utils.py +8 -14
  178. wagtail/contrib/forms/views.py +72 -27
  179. wagtail/contrib/frontend_cache/backends/azure.py +1 -1
  180. wagtail/contrib/frontend_cache/backends/cloudfront.py +2 -2
  181. wagtail/contrib/frontend_cache/backends/dummy.py +11 -0
  182. wagtail/contrib/frontend_cache/tests.py +31 -37
  183. wagtail/contrib/frontend_cache/utils.py +12 -5
  184. wagtail/contrib/redirects/locale/cy/LC_MESSAGES/django.mo +0 -0
  185. wagtail/contrib/redirects/locale/cy/LC_MESSAGES/django.po +16 -1
  186. wagtail/contrib/redirects/locale/en/LC_MESSAGES/django.po +20 -24
  187. wagtail/contrib/redirects/models.py +16 -1
  188. wagtail/contrib/redirects/signal_handlers.py +18 -3
  189. wagtail/contrib/redirects/templates/wagtailredirects/add.html +5 -16
  190. wagtail/contrib/redirects/templates/wagtailredirects/import_summary.html +1 -1
  191. wagtail/contrib/redirects/tests/test_redirects.py +49 -6
  192. wagtail/contrib/redirects/tests/test_signal_handlers.py +42 -1
  193. wagtail/contrib/redirects/views.py +27 -3
  194. wagtail/contrib/search_promotions/locale/cy/LC_MESSAGES/django.mo +0 -0
  195. wagtail/contrib/search_promotions/locale/cy/LC_MESSAGES/django.po +60 -1
  196. wagtail/contrib/search_promotions/locale/en/LC_MESSAGES/django.po +12 -18
  197. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/add.html +5 -8
  198. wagtail/contrib/search_promotions/templates/wagtailsearchpromotions/edit.html +15 -10
  199. wagtail/contrib/search_promotions/tests.py +13 -2
  200. wagtail/contrib/search_promotions/views.py +15 -3
  201. wagtail/contrib/settings/locale/cy/LC_MESSAGES/django.mo +0 -0
  202. wagtail/contrib/settings/locale/cy/LC_MESSAGES/django.po +6 -1
  203. wagtail/contrib/settings/locale/en/LC_MESSAGES/django.po +2 -10
  204. wagtail/contrib/settings/templates/wagtailsettings/edit.html +12 -45
  205. wagtail/contrib/simple_translation/locale/cy/LC_MESSAGES/django.mo +0 -0
  206. wagtail/contrib/simple_translation/locale/cy/LC_MESSAGES/django.po +13 -1
  207. wagtail/contrib/simple_translation/locale/dv/LC_MESSAGES/django.mo +0 -0
  208. wagtail/contrib/simple_translation/locale/dv/LC_MESSAGES/django.po +3 -0
  209. wagtail/contrib/simple_translation/locale/en/LC_MESSAGES/django.po +4 -4
  210. wagtail/contrib/simple_translation/locale/sl/LC_MESSAGES/django.mo +0 -0
  211. wagtail/contrib/simple_translation/locale/sl/LC_MESSAGES/django.po +5 -2
  212. wagtail/contrib/simple_translation/tests/test_views.py +51 -0
  213. wagtail/contrib/simple_translation/wagtail_hooks.py +16 -11
  214. wagtail/contrib/styleguide/locale/cy/LC_MESSAGES/django.mo +0 -0
  215. wagtail/contrib/styleguide/locale/cy/LC_MESSAGES/django.po +5 -1
  216. wagtail/contrib/styleguide/locale/en/LC_MESSAGES/django.po +1 -1
  217. wagtail/contrib/table_block/locale/cy/LC_MESSAGES/django.mo +0 -0
  218. wagtail/contrib/table_block/locale/cy/LC_MESSAGES/django.po +27 -1
  219. wagtail/contrib/table_block/locale/en/LC_MESSAGES/django.po +1 -1
  220. wagtail/contrib/typed_table_block/blocks.py +25 -0
  221. wagtail/contrib/typed_table_block/locale/cy/LC_MESSAGES/django.mo +0 -0
  222. wagtail/contrib/typed_table_block/locale/cy/LC_MESSAGES/django.po +12 -1
  223. wagtail/contrib/typed_table_block/locale/en/LC_MESSAGES/django.po +10 -10
  224. wagtail/contrib/typed_table_block/tests.py +24 -1
  225. wagtail/coreutils.py +5 -11
  226. wagtail/documents/admin_urls.py +2 -2
  227. wagtail/documents/locale/cy/LC_MESSAGES/django.mo +0 -0
  228. wagtail/documents/locale/cy/LC_MESSAGES/django.po +10 -0
  229. wagtail/documents/locale/en/LC_MESSAGES/django.po +61 -76
  230. wagtail/documents/migrations/0014_alter_document_file_size.py +18 -0
  231. wagtail/documents/models.py +1 -1
  232. wagtail/documents/rich_text/__init__.py +1 -3
  233. wagtail/documents/static/wagtaildocs/js/add-multiple.js +1 -1
  234. wagtail/documents/templates/wagtaildocs/bulk_actions/confirm_bulk_add_tags.html +5 -1
  235. wagtail/documents/templates/wagtaildocs/bulk_actions/confirm_bulk_add_to_collection.html +5 -1
  236. wagtail/documents/templates/wagtaildocs/bulk_actions/confirm_bulk_delete.html +11 -5
  237. wagtail/documents/templates/wagtaildocs/documents/add.html +13 -41
  238. wagtail/documents/templates/wagtaildocs/documents/edit.html +28 -56
  239. wagtail/documents/templates/wagtaildocs/documents/index.html +1 -4
  240. wagtail/documents/templates/wagtaildocs/homepage/site_summary_documents.html +2 -2
  241. wagtail/documents/templates/wagtaildocs/multiple/add.html +36 -41
  242. wagtail/documents/tests/test_admin_views.py +64 -6
  243. wagtail/documents/tests/test_site_summary.py +3 -3
  244. wagtail/documents/views/documents.py +103 -113
  245. wagtail/documents/views/multiple.py +19 -1
  246. wagtail/embeds/locale/en/LC_MESSAGES/django.po +1 -1
  247. wagtail/embeds/oembed_providers.py +9 -19
  248. wagtail/fields.py +43 -27
  249. wagtail/images/admin_urls.py +2 -2
  250. wagtail/images/blocks.py +230 -2
  251. wagtail/images/fields.py +17 -29
  252. wagtail/images/image_operations.py +1 -1
  253. wagtail/images/locale/cy/LC_MESSAGES/django.mo +0 -0
  254. wagtail/images/locale/cy/LC_MESSAGES/django.po +128 -1
  255. wagtail/images/locale/en/LC_MESSAGES/django.po +119 -129
  256. wagtail/images/migrations/0025_alter_image_file_alter_rendition_file.py +36 -43
  257. wagtail/images/migrations/0027_image_description.py +20 -0
  258. wagtail/images/models.py +120 -45
  259. wagtail/images/rich_text/__init__.py +1 -3
  260. wagtail/images/static/wagtailimages/js/add-multiple.js +1 -1
  261. wagtail/images/static/wagtailimages/js/image-block.js +1 -0
  262. wagtail/images/templates/wagtailimages/bulk_actions/confirm_bulk_add_tags.html +5 -1
  263. wagtail/images/templates/wagtailimages/bulk_actions/confirm_bulk_add_to_collection.html +5 -1
  264. wagtail/images/templates/wagtailimages/bulk_actions/confirm_bulk_delete.html +11 -5
  265. wagtail/images/templates/wagtailimages/chooser/results.html +2 -2
  266. wagtail/images/templates/wagtailimages/homepage/site_summary_images.html +2 -2
  267. wagtail/images/templates/wagtailimages/images/_file_field.html +2 -2
  268. wagtail/images/templates/wagtailimages/images/add.html +13 -31
  269. wagtail/images/templates/wagtailimages/images/edit.html +53 -82
  270. wagtail/images/templates/wagtailimages/images/index.html +1 -4
  271. wagtail/images/templates/wagtailimages/images/url_generator.html +1 -1
  272. wagtail/images/templates/wagtailimages/multiple/add.html +40 -47
  273. wagtail/images/templates/wagtailimages/widgets/image.html +5 -0
  274. wagtail/images/templates/wagtailimages/widgets/image_chooser.html +1 -1
  275. wagtail/images/tests/test_admin_views.py +70 -10
  276. wagtail/images/tests/test_blocks.py +367 -1
  277. wagtail/images/tests/test_image_operations.py +23 -0
  278. wagtail/images/tests/test_models.py +20 -0
  279. wagtail/images/tests/test_signal_handlers.py +99 -95
  280. wagtail/images/tests/test_site_summary.py +3 -3
  281. wagtail/images/tests/test_templatetags.py +11 -7
  282. wagtail/images/tests/tests.py +4 -0
  283. wagtail/images/views/images.py +103 -104
  284. wagtail/images/views/multiple.py +17 -1
  285. wagtail/locale/cy/LC_MESSAGES/django.mo +0 -0
  286. wagtail/locale/cy/LC_MESSAGES/django.po +3 -0
  287. wagtail/locale/en/LC_MESSAGES/django.po +137 -125
  288. wagtail/locale/sl/LC_MESSAGES/django.mo +0 -0
  289. wagtail/locale/sl/LC_MESSAGES/django.po +3 -0
  290. wagtail/locales/locale/en/LC_MESSAGES/django.po +8 -8
  291. wagtail/locales/views.py +4 -1
  292. wagtail/migrations/0089_log_entry_data_json_null_to_object.py +1 -7
  293. wagtail/models/__init__.py +122 -14
  294. wagtail/models/audit_log.py +1 -3
  295. wagtail/models/i18n.py +1 -2
  296. wagtail/project_template/Dockerfile +3 -3
  297. wagtail/project_template/requirements.txt +2 -2
  298. wagtail/query.py +17 -4
  299. wagtail/rich_text/__init__.py +2 -3
  300. wagtail/rich_text/pages.py +2 -4
  301. wagtail/rich_text/rewriters.py +2 -2
  302. wagtail/search/backends/database/mysql/query.py +3 -3
  303. wagtail/search/backends/database/sqlite/query.py +6 -6
  304. wagtail/search/locale/en/LC_MESSAGES/django.po +1 -1
  305. wagtail/sites/locale/en/LC_MESSAGES/django.po +6 -6
  306. wagtail/sites/views.py +0 -1
  307. wagtail/snippets/action_menu.py +14 -4
  308. wagtail/snippets/locale/cy/LC_MESSAGES/django.mo +0 -0
  309. wagtail/snippets/locale/cy/LC_MESSAGES/django.po +73 -1
  310. wagtail/snippets/locale/en/LC_MESSAGES/django.po +27 -33
  311. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser-telepath.js +1 -1
  312. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser.js +1 -1
  313. wagtail/snippets/templates/wagtailsnippets/bulk_actions/confirm_bulk_delete.html +5 -3
  314. wagtail/snippets/templates/wagtailsnippets/snippets/action_menu/locked.html +1 -1
  315. wagtail/snippets/templates/wagtailsnippets/snippets/action_menu/menu.html +1 -11
  316. wagtail/snippets/templates/wagtailsnippets/snippets/action_menu/menu_item.html +1 -6
  317. wagtail/snippets/templates/wagtailsnippets/snippets/action_menu/publish.html +1 -1
  318. wagtail/snippets/templates/wagtailsnippets/snippets/action_menu/save.html +1 -1
  319. wagtail/snippets/templates/wagtailsnippets/snippets/create.html +1 -18
  320. wagtail/snippets/templates/wagtailsnippets/snippets/edit.html +1 -14
  321. wagtail/snippets/templates/wagtailsnippets/snippets/index.html +2 -5
  322. wagtail/snippets/tests/test_preview.py +193 -61
  323. wagtail/snippets/tests/test_snippets.py +247 -38
  324. wagtail/snippets/tests/test_usage.py +5 -0
  325. wagtail/snippets/tests/test_viewset.py +25 -9
  326. wagtail/snippets/tests/test_workflows.py +232 -19
  327. wagtail/snippets/views/snippets.py +1 -10
  328. wagtail/test/numberformat.py +104 -0
  329. wagtail/test/settings.py +10 -0
  330. wagtail/test/settings_ui.py +2 -0
  331. wagtail/test/testapp/migrations/0010_alter_customimage_file_and_more.py +71 -78
  332. wagtail/test/testapp/migrations/0040_nocreatablesubpagetypespage_nosubpagetypespage.py +54 -0
  333. wagtail/test/testapp/migrations/0041_alter_jsonstreammodel_options.py +17 -0
  334. wagtail/test/testapp/migrations/0042_alter_customdocument_file_size_and_more.py +28 -0
  335. wagtail/test/testapp/migrations/0043_customimage_description.py +41 -0
  336. wagtail/test/testapp/migrations/0044_custompreviewsizesmodel_custompreviewsizespage.py +52 -0
  337. wagtail/test/testapp/migrations/0045_alter_streampage_body.py +52 -0
  338. wagtail/test/testapp/models.py +62 -4
  339. wagtail/test/testapp/rich_text.py +2 -2
  340. wagtail/test/testapp/templates/tests/form_page_landing.html +2 -1
  341. wagtail/test/testapp/urls.py +5 -0
  342. wagtail/test/testapp/views.py +5 -0
  343. wagtail/test/utils/page_tests.py +5 -5
  344. wagtail/test/utils/template_tests.py +2 -2
  345. wagtail/tests/test_blocks.py +15 -0
  346. wagtail/tests/test_page_permissions.py +109 -0
  347. wagtail/tests/test_revision_model.py +27 -0
  348. wagtail/tests/test_signals.py +21 -2
  349. wagtail/tests/test_tests.py +30 -0
  350. wagtail/users/locale/cy/LC_MESSAGES/django.mo +0 -0
  351. wagtail/users/locale/cy/LC_MESSAGES/django.po +118 -1
  352. wagtail/users/locale/dv/LC_MESSAGES/django.mo +0 -0
  353. wagtail/users/locale/dv/LC_MESSAGES/django.po +3 -0
  354. wagtail/users/locale/en/LC_MESSAGES/django.po +89 -113
  355. wagtail/users/locale/sl/LC_MESSAGES/django.mo +0 -0
  356. wagtail/users/locale/sl/LC_MESSAGES/django.po +3 -0
  357. wagtail/users/migrations/0014_userprofile_contrast.py +23 -0
  358. wagtail/users/models.py +11 -0
  359. wagtail/users/templates/wagtailusers/bulk_actions/confirm_bulk_assign_role.html +5 -1
  360. wagtail/users/templates/wagtailusers/bulk_actions/confirm_bulk_delete.html +5 -1
  361. wagtail/users/templates/wagtailusers/bulk_actions/confirm_bulk_set_active_state.html +5 -1
  362. wagtail/users/templates/wagtailusers/groups/create.html +19 -17
  363. wagtail/users/templates/wagtailusers/groups/edit.html +2 -40
  364. wagtail/users/templates/wagtailusers/groups/includes/formatted_permissions.html +1 -62
  365. wagtail/users/templates/wagtailusers/groups/includes/page_permissions_formset.html +0 -1
  366. wagtail/users/templates/wagtailusers/users/create.html +46 -50
  367. wagtail/users/templates/wagtailusers/users/edit.html +45 -62
  368. wagtail/users/templates/wagtailusers/users/index.html +1 -4
  369. wagtail/users/templatetags/wagtailusers_tags.py +1 -5
  370. wagtail/users/tests/test_admin_views.py +85 -66
  371. wagtail/users/views/groups.py +4 -1
  372. wagtail/users/views/users.py +2 -0
  373. {wagtail-6.2.2.dist-info → wagtail-6.3rc2.dist-info}/METADATA +6 -6
  374. {wagtail-6.2.2.dist-info → wagtail-6.3rc2.dist-info}/RECORD +378 -367
  375. wagtail/admin/static/wagtailadmin/js/preview-panel.js +0 -2
  376. wagtail/admin/static/wagtailadmin/js/preview-panel.js.LICENSE.txt +0 -11
  377. wagtail/admin/templates/wagtailadmin/generic/history_results.html +0 -1
  378. wagtail/admin/templates/wagtailadmin/page_privacy/ancestor_privacy.html +0 -3
  379. wagtail/admin/templates/wagtailadmin/pages/usage_results.html +0 -6
  380. wagtail/admin/templates/wagtailadmin/shared/workflow_history/index.html +0 -17
  381. wagtail/admin/templates/wagtailadmin/shared/workflow_history/results.html +0 -17
  382. wagtail/admin/templates/wagtailadmin/workflows/usage.html +0 -44
  383. {wagtail-6.2.2.dist-info → wagtail-6.3rc2.dist-info}/LICENSE +0 -0
  384. {wagtail-6.2.2.dist-info → wagtail-6.3rc2.dist-info}/WHEEL +0 -0
  385. {wagtail-6.2.2.dist-info → wagtail-6.3rc2.dist-info}/entry_points.txt +0 -0
  386. {wagtail-6.2.2.dist-info → wagtail-6.3rc2.dist-info}/top_level.txt +0 -0
wagtail/images/models.py CHANGED
@@ -7,11 +7,12 @@ import logging
7
7
  import os.path
8
8
  import re
9
9
  import time
10
- from collections import OrderedDict, defaultdict
10
+ from collections import OrderedDict, defaultdict, namedtuple
11
+ from collections.abc import Iterable
11
12
  from contextlib import contextmanager
12
13
  from io import BytesIO
13
14
  from tempfile import SpooledTemporaryFile
14
- from typing import Any, Dict, Iterable, List, Optional, Union
15
+ from typing import Any
15
16
 
16
17
  import willow
17
18
  from django.apps import apps
@@ -38,7 +39,6 @@ from wagtail.images.exceptions import (
38
39
  InvalidFilterSpecError,
39
40
  UnknownOutputImageFormatError,
40
41
  )
41
- from wagtail.images.fields import image_format_name_to_content_type
42
42
  from wagtail.images.image_operations import (
43
43
  FilterOperation,
44
44
  FormatOperation,
@@ -62,6 +62,7 @@ IMAGE_FORMAT_EXTENSIONS = {
62
62
  "webp": ".webp",
63
63
  "svg": ".svg",
64
64
  "ico": ".ico",
65
+ "heic": ".heic",
65
66
  }
66
67
 
67
68
 
@@ -262,6 +263,12 @@ class AbstractImage(ImageFileMixin, CollectionMember, index.Indexed, models.Mode
262
263
  width_field="width",
263
264
  height_field="height",
264
265
  )
266
+ description = models.CharField(
267
+ blank=True,
268
+ max_length=255,
269
+ verbose_name=_("description"),
270
+ default="",
271
+ )
265
272
  width = models.IntegerField(verbose_name=_("width"), editable=False)
266
273
  height = models.IntegerField(verbose_name=_("height"), editable=False)
267
274
  created_at = models.DateTimeField(
@@ -292,6 +299,11 @@ class AbstractImage(ImageFileMixin, CollectionMember, index.Indexed, models.Mode
292
299
 
293
300
  objects = ImageQuerySet.as_manager()
294
301
 
302
+ def __init__(self, *args, **kwargs):
303
+ super().__init__(*args, **kwargs)
304
+ self.decorative = False
305
+ self.contextual_alt_text = None
306
+
295
307
  def _set_file_hash(self):
296
308
  with self.open_file() as f:
297
309
  self.file_hash = hash_filelike(f)
@@ -365,6 +377,36 @@ class AbstractImage(ImageFileMixin, CollectionMember, index.Indexed, models.Mode
365
377
  def __str__(self):
366
378
  return self.title
367
379
 
380
+ def __eq__(self, other):
381
+ """
382
+ Customise the definition of equality so that two Image instances referring to the same
383
+ image but different contextual alt text or decorative status are considered different.
384
+ All other aspects are copied from Django's base `Model` implementation.
385
+ """
386
+ if not isinstance(other, models.Model):
387
+ return NotImplemented
388
+
389
+ if self._meta.concrete_model != other._meta.concrete_model:
390
+ return False
391
+
392
+ my_pk = self.pk
393
+ if my_pk is None:
394
+ return self is other
395
+
396
+ return (
397
+ my_pk == other.pk
398
+ and other.contextual_alt_text == self.contextual_alt_text
399
+ and other.decorative == self.decorative
400
+ )
401
+
402
+ def __hash__(self):
403
+ """
404
+ Match the semantics of the custom equality definition.
405
+ """
406
+ if self.pk is None:
407
+ raise TypeError("Model instances without primary key value are unhashable")
408
+ return hash((self.pk, self.contextual_alt_text, self.decorative))
409
+
368
410
  def get_rect(self):
369
411
  return Rect(0, 0, self.width, self.height)
370
412
 
@@ -442,12 +484,12 @@ class AbstractImage(ImageFileMixin, CollectionMember, index.Indexed, models.Mode
442
484
  """Get the Rendition model for this Image model"""
443
485
  return cls.renditions.rel.related_model
444
486
 
445
- def _get_prefetched_renditions(self) -> Union[Iterable["AbstractRendition"], None]:
487
+ def _get_prefetched_renditions(self) -> Iterable[AbstractRendition] | None:
446
488
  if "renditions" in getattr(self, "_prefetched_objects_cache", {}):
447
489
  return self.renditions.all()
448
490
  return getattr(self, "prefetched_renditions", None)
449
491
 
450
- def _add_to_prefetched_renditions(self, rendition: "AbstractRendition") -> None:
492
+ def _add_to_prefetched_renditions(self, rendition: AbstractRendition) -> None:
451
493
  # Reuse this rendition if requested again from this object
452
494
  try:
453
495
  self._prefetched_objects_cache["renditions"]._result_cache.append(rendition)
@@ -458,7 +500,7 @@ class AbstractImage(ImageFileMixin, CollectionMember, index.Indexed, models.Mode
458
500
  except AttributeError:
459
501
  pass
460
502
 
461
- def get_rendition(self, filter: Union["Filter", str]) -> "AbstractRendition":
503
+ def get_rendition(self, filter: Filter | str) -> AbstractRendition:
462
504
  """
463
505
  Returns a ``Rendition`` instance with a ``file`` field value (an
464
506
  image) reflecting the supplied ``filter`` value and focal point values
@@ -486,7 +528,7 @@ class AbstractImage(ImageFileMixin, CollectionMember, index.Indexed, models.Mode
486
528
 
487
529
  return rendition
488
530
 
489
- def find_existing_rendition(self, filter: "Filter") -> "AbstractRendition":
531
+ def find_existing_rendition(self, filter: Filter) -> AbstractRendition:
490
532
  """
491
533
  Returns an existing ``Rendition`` instance with a ``file`` field value
492
534
  (an image) reflecting the supplied ``filter`` value and focal point
@@ -505,7 +547,7 @@ class AbstractImage(ImageFileMixin, CollectionMember, index.Indexed, models.Mode
505
547
  except KeyError:
506
548
  raise Rendition.DoesNotExist
507
549
 
508
- def create_rendition(self, filter: "Filter") -> "AbstractRendition":
550
+ def create_rendition(self, filter: Filter) -> AbstractRendition:
509
551
  """
510
552
  Creates and returns a ``Rendition`` instance with a ``file`` field
511
553
  value (an image) reflecting the supplied ``filter`` value and focal
@@ -526,9 +568,7 @@ class AbstractImage(ImageFileMixin, CollectionMember, index.Indexed, models.Mode
526
568
  )
527
569
  return rendition
528
570
 
529
- def get_renditions(
530
- self, *filters: Union["Filter", str]
531
- ) -> Dict[str, "AbstractRendition"]:
571
+ def get_renditions(self, *filters: Filter | str) -> dict[str, AbstractRendition]:
532
572
  """
533
573
  Returns a ``dict`` of ``Rendition`` instances with image files reflecting
534
574
  the supplied ``filters``, keyed by filter spec patterns.
@@ -566,8 +606,8 @@ class AbstractImage(ImageFileMixin, CollectionMember, index.Indexed, models.Mode
566
606
  return {filter.spec: renditions[filter] for filter in filters}
567
607
 
568
608
  def find_existing_renditions(
569
- self, *filters: "Filter"
570
- ) -> Dict["Filter", "AbstractRendition"]:
609
+ self, *filters: Filter
610
+ ) -> dict[Filter, AbstractRendition]:
571
611
  """
572
612
  Returns a dictionary of existing ``Rendition`` instances with ``file``
573
613
  values (images) reflecting the supplied ``filters`` and the focal point
@@ -578,8 +618,8 @@ class AbstractImage(ImageFileMixin, CollectionMember, index.Indexed, models.Mode
578
618
  created before, the return value will be an empty dict.
579
619
  """
580
620
  Rendition = self.get_rendition_model()
581
- filters_by_spec: Dict[str, Filter] = {f.spec: f for f in filters}
582
- found: Dict[Filter, AbstractRendition] = {}
621
+ filters_by_spec: dict[str, Filter] = {f.spec: f for f in filters}
622
+ found: dict[Filter, AbstractRendition] = {}
583
623
 
584
624
  # Interrogate prefetched values first (where available)
585
625
  prefetched_renditions = self._get_prefetched_renditions()
@@ -589,7 +629,7 @@ class AbstractImage(ImageFileMixin, CollectionMember, index.Indexed, models.Mode
589
629
  # prefetched value, and further cache/database lookups are avoided.
590
630
 
591
631
  # group renditions by the filters of interest
592
- potential_matches: Dict[Filter, List[AbstractRendition]] = defaultdict(list)
632
+ potential_matches: dict[Filter, list[AbstractRendition]] = defaultdict(list)
593
633
  for rendition in prefetched_renditions:
594
634
  try:
595
635
  filter = filters_by_spec[rendition.filter_spec]
@@ -621,6 +661,9 @@ class AbstractImage(ImageFileMixin, CollectionMember, index.Indexed, models.Mode
621
661
  ]
622
662
  for rendition in Rendition.cache_backend.get_many(cache_keys).values():
623
663
  filter = filters_by_spec[rendition.filter_spec]
664
+ # The retrieved rendition needs to be associated with the current image instance, so that any
665
+ # locally-set properties such as contextual_alt_text are respected
666
+ rendition.image = self
624
667
  found[filter] = rendition
625
668
 
626
669
  # For items not found in the cache, look in the database
@@ -637,9 +680,7 @@ class AbstractImage(ImageFileMixin, CollectionMember, index.Indexed, models.Mode
637
680
  found[filter] = rendition
638
681
  return found
639
682
 
640
- def create_renditions(
641
- self, *filters: "Filter"
642
- ) -> Dict["Filter", "AbstractRendition"]:
683
+ def create_renditions(self, *filters: Filter) -> dict[Filter, AbstractRendition]:
643
684
  """
644
685
  Creates multiple ``Rendition`` instances with image files reflecting the supplied
645
686
  ``filters``, and returns them as a ``dict`` keyed by the relevant ``Filter`` instance.
@@ -664,8 +705,8 @@ class AbstractImage(ImageFileMixin, CollectionMember, index.Indexed, models.Mode
664
705
  filter = filters[0]
665
706
  return {filter: self.create_rendition(filter)}
666
707
 
667
- return_value: Dict[Filter, AbstractRendition] = {}
668
- filter_map: Dict[str, Filter] = {f.spec: f for f in filters}
708
+ return_value: dict[Filter, AbstractRendition] = {}
709
+ filter_map: dict[str, Filter] = {f.spec: f for f in filters}
669
710
 
670
711
  # Read file contents into memory
671
712
  with self.open_file() as file:
@@ -688,7 +729,7 @@ class AbstractImage(ImageFileMixin, CollectionMember, index.Indexed, models.Mode
688
729
  # identical renditions in the meantime, we should find them to avoid clashes.
689
730
  # NB: Clashes can still occur, because there is no get_or_create() equivalent
690
731
  # for multiple objects. However, this will reduce that risk considerably.
691
- files_for_deletion: List[File] = []
732
+ files_for_deletion: list[File] = []
692
733
 
693
734
  # Assemble Q() to identify potential clashes
694
735
  lookup_q = Q()
@@ -724,8 +765,8 @@ class AbstractImage(ImageFileMixin, CollectionMember, index.Indexed, models.Mode
724
765
  return return_value
725
766
 
726
767
  def generate_rendition_instance(
727
- self, filter: "Filter", source: BytesIO
728
- ) -> "AbstractRendition":
768
+ self, filter: Filter, source: BytesIO
769
+ ) -> AbstractRendition:
729
770
  """
730
771
  Use the supplied ``source`` image to create and return an
731
772
  **unsaved** ``Rendition`` instance, with a ``file`` value reflecting
@@ -740,7 +781,7 @@ class AbstractImage(ImageFileMixin, CollectionMember, index.Indexed, models.Mode
740
781
  ),
741
782
  )
742
783
 
743
- def generate_rendition_file(self, filter: "Filter", *, source: File = None) -> File:
784
+ def generate_rendition_file(self, filter: Filter, *, source: File = None) -> File:
744
785
  """
745
786
  Generates an in-memory image matching the supplied ``filter`` value
746
787
  and focal point value from this object, wraps it in a ``File`` object
@@ -827,9 +868,9 @@ class AbstractImage(ImageFileMixin, CollectionMember, index.Indexed, models.Mode
827
868
  @property
828
869
  def default_alt_text(self):
829
870
  # by default the alt text field (used in rich text insertion) is populated
830
- # from the title. Subclasses might provide a separate alt field, and
831
- # override this
832
- return self.title
871
+ # from the description. In the absence of that, it is populated from the title.
872
+ # Subclasses might provide a separate alt field, and override this
873
+ return getattr(self, "description", None) or self.title
833
874
 
834
875
  def is_editable_by_user(self, user):
835
876
  from wagtail.images.permissions import permission_policy
@@ -844,6 +885,7 @@ class Image(AbstractImage):
844
885
  admin_form_fields = (
845
886
  "title",
846
887
  "file",
888
+ "description",
847
889
  "collection",
848
890
  "tags",
849
891
  "focal_point_x",
@@ -877,7 +919,7 @@ class Filter:
877
919
  self.spec = spec
878
920
 
879
921
  @classmethod
880
- def expand_spec(self, spec: Union["str", Iterable["str"]]) -> List["str"]:
922
+ def expand_spec(self, spec: str | Iterable[str]) -> list[str]:
881
923
  """
882
924
  Converts a spec pattern with brace-expansions, into a list of spec patterns.
883
925
  For example, "width-{100,200}" becomes ["width-100", "width-200"].
@@ -995,11 +1037,12 @@ class Filter:
995
1037
  # Developer specified an output format
996
1038
  output_format = env["output-format"]
997
1039
  else:
998
- # Convert bmp and webp to png by default
1040
+ # Convert avif, bmp and webp to png, and heic to jpg, by default
999
1041
  default_conversions = {
1000
1042
  "avif": "png",
1001
1043
  "bmp": "png",
1002
1044
  "webp": "png",
1045
+ "heic": "jpeg",
1003
1046
  }
1004
1047
 
1005
1048
  # Convert unanimated GIFs to PNG as well
@@ -1058,6 +1101,20 @@ class Filter:
1058
1101
  else:
1059
1102
  quality = getattr(settings, "WAGTAILIMAGES_AVIF_QUALITY", 80)
1060
1103
  return willow.save_as_avif(output, quality=quality)
1104
+ elif output_format == "heic":
1105
+ # Allow changing of HEIC compression quality. Safari is the only browser that supports HEIC,
1106
+ # so there is little value in outputting it - for that reason, we make it work if someone
1107
+ # explicitly requests it, but these settings are not documented.
1108
+ if (
1109
+ "output-format-options" in env
1110
+ and "lossless" in env["output-format-options"]
1111
+ ):
1112
+ return willow.save_as_heic(output, lossless=True)
1113
+ elif "heic-quality" in env:
1114
+ quality = env["heic-quality"]
1115
+ else:
1116
+ quality = getattr(settings, "WAGTAILIMAGES_HEIC_QUALITY", 80)
1117
+ return willow.save_as_heic(output, quality=quality)
1061
1118
  elif output_format == "svg":
1062
1119
  return willow.save_as_svg(output)
1063
1120
  elif output_format == "ico":
@@ -1092,14 +1149,14 @@ class ResponsiveImage:
1092
1149
 
1093
1150
  def __init__(
1094
1151
  self,
1095
- renditions: Dict[str, "AbstractRendition"],
1096
- attrs: Optional[Dict[str, Any]] = None,
1152
+ renditions: dict[str, AbstractRendition],
1153
+ attrs: dict[str, Any] | None = None,
1097
1154
  ):
1098
1155
  self.renditions = list(renditions.values())
1099
1156
  self.attrs = attrs
1100
1157
 
1101
1158
  @classmethod
1102
- def get_width_srcset(cls, renditions_list: List["AbstractRendition"]):
1159
+ def get_width_srcset(cls, renditions_list: list[AbstractRendition]):
1103
1160
  if len(renditions_list) == 1:
1104
1161
  # No point in using width descriptors if there is a single image.
1105
1162
  return renditions_list[0].url
@@ -1122,32 +1179,41 @@ class ResponsiveImage:
1122
1179
  def __bool__(self):
1123
1180
  return bool(self.renditions)
1124
1181
 
1125
- def __eq__(self, other: "ResponsiveImage"):
1182
+ def __eq__(self, other: ResponsiveImage):
1126
1183
  if isinstance(other, ResponsiveImage):
1127
1184
  return self.renditions == other.renditions and self.attrs == other.attrs
1128
1185
  return False
1129
1186
 
1130
1187
 
1188
+ FileFormat = namedtuple("FileFormat", ["name", "mime_type"])
1189
+
1190
+
1131
1191
  class Picture(ResponsiveImage):
1132
1192
  # Keep this separate from FormatOperation.supported_formats,
1133
1193
  # as the order our formats are defined in is essential for the picture tag.
1134
1194
  # Defines the order of <source> elements in the tag when format operations
1135
1195
  # are in use, and the priority order to identify the "fallback" format.
1136
1196
  # The browser will pick the first supported format in this list.
1137
- source_format_order = ["avif", "webp", "jpeg", "png", "gif"]
1197
+ source_format_order = [
1198
+ FileFormat("avif", "image/avif"),
1199
+ FileFormat("webp", "image/webp"),
1200
+ FileFormat("jpeg", "image/jpeg"),
1201
+ FileFormat("png", "image/png"),
1202
+ FileFormat("gif", "image/gif"),
1203
+ ]
1138
1204
 
1139
1205
  def __init__(
1140
1206
  self,
1141
- renditions: Dict[str, "AbstractRendition"],
1142
- attrs: Optional[Dict[str, Any]] = None,
1207
+ renditions: dict[str, AbstractRendition],
1208
+ attrs: dict[str, Any] | None = None,
1143
1209
  ):
1144
1210
  super().__init__(renditions, attrs)
1145
1211
  # Store renditions grouped by format separately for access from templates.
1146
1212
  self.formats = self.get_formats(renditions)
1147
1213
 
1148
1214
  def get_formats(
1149
- self, renditions: Dict[str, "AbstractRendition"]
1150
- ) -> Dict[str, List["AbstractRendition"]]:
1215
+ self, renditions: dict[str, AbstractRendition]
1216
+ ) -> dict[str, list[AbstractRendition]]:
1151
1217
  """
1152
1218
  Group renditions by the format they are for, if any.
1153
1219
  If there is only one format, no grouping is required.
@@ -1167,8 +1233,8 @@ class Picture(ResponsiveImage):
1167
1233
 
1168
1234
  def get_fallback_format(self):
1169
1235
  for fmt in reversed(self.source_format_order):
1170
- if fmt in self.formats:
1171
- return fmt
1236
+ if fmt.name in self.formats:
1237
+ return fmt.name
1172
1238
 
1173
1239
  def __html__(self):
1174
1240
  # If there aren’t multiple formats, render a vanilla img tag with srcset.
@@ -1184,9 +1250,9 @@ class Picture(ResponsiveImage):
1184
1250
  sources = []
1185
1251
 
1186
1252
  for fmt in self.source_format_order:
1187
- if fmt != fallback_format and fmt in self.formats:
1188
- srcset = self.get_width_srcset(self.formats[fmt])
1189
- mime = image_format_name_to_content_type(fmt)
1253
+ if fmt.name != fallback_format and fmt.name in self.formats:
1254
+ srcset = self.get_width_srcset(self.formats[fmt.name])
1255
+ mime = fmt.mime_type
1190
1256
  sources.append(f'<source srcset="{srcset}" {sizes}type="{mime}">')
1191
1257
 
1192
1258
  if len(fallback_renditions) > 1:
@@ -1221,7 +1287,16 @@ class AbstractRendition(ImageFileMixin, models.Model):
1221
1287
 
1222
1288
  @property
1223
1289
  def alt(self):
1224
- return self.image.default_alt_text
1290
+ # 'decorative' and 'contextual_alt_text' exist only for ImageBlock
1291
+ if hasattr(self.image, "decorative") and self.image.decorative:
1292
+ return ""
1293
+ elif (
1294
+ hasattr(self.image, "contextual_alt_text")
1295
+ and self.image.contextual_alt_text
1296
+ ):
1297
+ return self.image.contextual_alt_text
1298
+ else:
1299
+ return self.image.default_alt_text
1225
1300
 
1226
1301
  @property
1227
1302
  def attrs(self):
@@ -1,5 +1,3 @@
1
- from typing import List
2
-
3
1
  from wagtail.images import get_image_model
4
2
  from wagtail.images.formats import get_image_format
5
3
  from wagtail.rich_text import EmbedHandler
@@ -19,7 +17,7 @@ class ImageEmbedHandler(EmbedHandler):
19
17
  return cls.expand_db_attributes_many([attrs])[0]
20
18
 
21
19
  @classmethod
22
- def expand_db_attributes_many(cls, attrs_list: List[dict]) -> List[str]:
20
+ def expand_db_attributes_many(cls, attrs_list: list[dict]) -> list[str]:
23
21
  """
24
22
  Given a dict of attributes from the <embed> tag, return the real HTML
25
23
  representation for use on the front-end.
@@ -1 +1 @@
1
- $((function(){$(document).on("drop dragover",(function(e){e.preventDefault()})),$("#fileupload").fileupload({dataType:"html",sequentialUploads:!0,dropZone:$(".drop-zone"),acceptFileTypes:window.fileupload_opts.accepted_file_types,maxFileSize:window.fileupload_opts.max_file_size,previewMinWidth:150,previewMaxWidth:150,previewMinHeight:150,previewMaxHeight:150,messages:{acceptFileTypes:window.fileupload_opts.errormessages.accepted_file_types,maxFileSize:window.fileupload_opts.errormessages.max_file_size},add:function(e,t){var a=$(this),o=a.data("blueimp-fileupload")||a.data("fileupload"),s=$($("#upload-list-item").html()).addClass("upload-uploading"),i=o.options;$("#upload-list").append(s),t.context=s,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))})),t.context.find(".preview .thumb").each((function(e,a){$(a).find(".icon").remove(),$(a).append(t.files[e].preview)}))})).done((function(){t.context.find(".start").prop("disabled",!1),!1!==o._trigger("added",e,t)&&(i.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").html(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(/\.[^.]+$/,"")},o=window.fileupload_opts.max_title_length;return e.get(0).dispatchEvent(new CustomEvent("wagtail:images-upload",{bubbles:!0,cancelable:!0,detail:{data:a,filename:t,maxTitleLength:o}}))?e.serializeArray().concat({name:"title",value:a.title}):e.serializeArray()},done:function(e,t){var a=$(t.context),o=JSON.parse(t.result);o.success?o.duplicate?(a.addClass("upload-duplicate"),$(".right",a).append(o.confirm_duplicate_upload),$(".confirm-duplicate-upload",a).on("click",".confirm-upload",(function(e){e.preventDefault(),$(this).closest(".confirm-duplicate-upload").remove(),$(".right",a).append(o.form)}))):(a.addClass("upload-success"),$(".right",a).append(o.form)):(a.addClass("upload-failure"),$(".right .error_messages",a).append(o.error_message))},fail:function(e,t){var a=$(t.context),o=$(".server-error",a);$(".error-text",o).text(t.errorThrown),$(".error-code",o).text(t.jqXHR.status),a.addClass("upload-server-error")},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),o=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"}})),o.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 o=$('input[name="csrfmiddlewaretoken"]',t).val();$.post(this.href,{csrfmiddlewaretoken:o},(function(e){e.success&&a.slideUp((function(){$(this).remove()}))}))}))}));
1
+ $((function(){$(document).on("drop dragover",(function(e){e.preventDefault()})),$("#fileupload").fileupload({dataType:"html",sequentialUploads:!0,dropZone:$(".drop-zone"),previewMinWidth:150,previewMaxWidth:150,previewMinHeight:150,previewMaxHeight:150,add:function(e,t){var a=$(this),o=a.data("blueimp-fileupload")||a.data("fileupload"),s=$($("#upload-list-item").html()).addClass("upload-uploading"),i=o.options;$("#upload-list").append(s),t.context=s,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))})),t.context.find(".preview .thumb").each((function(e,a){$(a).find(".icon").remove(),$(a).append(t.files[e].preview)}))})).done((function(){t.context.find(".start").prop("disabled",!1),!1!==o._trigger("added",e,t)&&(i.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").html(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:images-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),o=JSON.parse(t.result);o.success?o.duplicate?(a.addClass("upload-duplicate"),$(".right",a).append(o.confirm_duplicate_upload),$(".confirm-duplicate-upload",a).on("click",".confirm-upload",(function(e){e.preventDefault(),$(this).closest(".confirm-duplicate-upload").remove(),$(".right",a).append(o.form)}))):(a.addClass("upload-success"),$(".right",a).append(o.form)):(a.addClass("upload-failure"),$(".right .error_messages",a).append(o.error_message))},fail:function(e,t){var a=$(t.context),o=$(".server-error",a);$(".error-text",o).text(t.errorThrown),$(".error-code",o).text(t.jqXHR.status),a.addClass("upload-server-error")},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),o=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"}})),o.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 o=$('input[name="csrfmiddlewaretoken"]',t).val();$.post(this.href,{csrfmiddlewaretoken:o},(function(e){e.success&&a.slideUp((function(){$(this).remove()}))}))}))}));
@@ -0,0 +1 @@
1
+ (()=>{class e extends window.wagtailStreamField.blocks.StructBlockDefinition{render(e,t,d,i){const n=super.render(e,t,d,i),r=document.getElementById(`${t}-alt_text`),a=document.getElementById(`${t}-decorative`),l=()=>{a.checked?r.setAttribute("disabled",!0):r.removeAttribute("disabled")};return l(),a.addEventListener("change",l),n}}window.telepath.register("wagtail.images.blocks.ImageBlock",e)})();
@@ -1,7 +1,11 @@
1
1
  {% extends 'wagtailadmin/bulk_actions/confirmation/base.html' %}
2
2
  {% load i18n %}
3
3
  {% load wagtailimages_tags wagtailadmin_tags %}
4
- {% block titletag %}{% blocktrans trimmed count counter=items|length %}Add tags to 1 image {% plural %}Add tags to {{ counter }} images{% endblocktrans %}{% endblock %}
4
+ {% block titletag %}
5
+ {% with counter_val=items|length %}
6
+ {% blocktrans trimmed with counter=counter_val|intcomma count counter_val=counter_val %}Add tags to 1 image {% plural %}Add tags to {{ counter }} images{% endblocktrans %}
7
+ {% endwith %}
8
+ {% endblock %}
5
9
 
6
10
  {% block header %}
7
11
  {% trans "Add tags to images" as add_str %}
@@ -1,7 +1,11 @@
1
1
  {% extends 'wagtailadmin/bulk_actions/confirmation/base.html' %}
2
2
  {% load i18n %}
3
3
  {% load wagtailimages_tags wagtailadmin_tags %}
4
- {% block titletag %}{% blocktrans trimmed count counter=items|length %}Add 1 image to new collection {% plural %}Add {{ counter }} images to new collection{% endblocktrans %}{% endblock %}
4
+ {% block titletag %}
5
+ {% with counter_val=items|length %}
6
+ {% blocktrans trimmed with counter=counter_val|intcomma count counter_val=counter_val %}Add 1 image to new collection {% plural %}Add {{ counter }} images to new collection{% endblocktrans %}
7
+ {% endwith %}
8
+ {% endblock %}
5
9
 
6
10
  {% block header %}
7
11
  {% trans "Add images to collection" as add_str %}
@@ -1,6 +1,10 @@
1
1
  {% extends 'wagtailadmin/bulk_actions/confirmation/base.html' %}
2
2
  {% load wagtailadmin_tags i18n %}
3
- {% block titletag %}{% blocktrans trimmed count counter=items|length %}Delete 1 image{% plural %}Delete {{ counter }} images{% endblocktrans %}{% endblock %}
3
+ {% block titletag %}
4
+ {% with counter_val=items|length %}
5
+ {% blocktrans trimmed with counter=counter_val|intcomma count counter_val=counter_val %}Delete 1 image{% plural %}Delete {{ counter }} images{% endblocktrans %}
6
+ {% endwith %}
7
+ {% endblock %}
4
8
 
5
9
  {% block header %}
6
10
  {% trans "Delete images" as del_str %}
@@ -18,10 +22,12 @@
18
22
  </p>
19
23
  <ul>
20
24
  {% for image in items %}
21
- <li>
22
- <a href="{% url 'wagtailimages:edit' image.item.id %}" target="_blank" rel="noreferrer">{{image.item.title}}</a>
23
- (<a href="{{ image.item.usage_url }}">{% blocktrans trimmed count usage_count=image.item.get_usage.count %}Used {{ usage_count }} time{% plural %}Used {{ usage_count }} times{% endblocktrans %}</a>)
24
- </li>
25
+ {% with usage_count_val=image.item.get_usage.count %}
26
+ <li>
27
+ <a href="{% url 'wagtailimages:edit' image.item.id %}" target="_blank" rel="noreferrer">{{image.item.title}}</a>
28
+ (<a href="{{ image.item.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
+ </li>
30
+ {% endwith %}
25
31
  {% endfor %}
26
32
  </ul>
27
33
  {% endif %}
@@ -1,5 +1,5 @@
1
1
  {% extends "wagtailadmin/generic/chooser/results.html" %}
2
- {% load i18n wagtailimages_tags wagtailadmin_tags l10n %}
2
+ {% load i18n wagtailimages_tags wagtailadmin_tags %}
3
3
 
4
4
  {% block listing_title %}<h2>{% trans "Latest images" %}</h2>{% endblock %}
5
5
 
@@ -11,7 +11,7 @@
11
11
  <label title="Select {% if collections %}{{ image.collection.name }} » {% endif %}{{ image.title }}">
12
12
  <div class="image">{% image image max-165x165 class="show-transparency" %}</div>
13
13
  <h3>
14
- <input type="checkbox" data-multiple-choice-select name="id" value="{{ image.id|unlocalize }}" title="{% blocktrans trimmed with title=image.title %}Select {{ title }}{% endblocktrans %}">
14
+ <input type="checkbox" data-multiple-choice-select name="id" value="{{ image.id }}" title="{% blocktrans trimmed with title=image.title %}Select {{ title }}{% endblocktrans %}">
15
15
  {{ image.title|ellipsistrim:60 }}
16
16
  </h3>
17
17
  </label>
@@ -4,9 +4,9 @@
4
4
  {% icon name="image" %}
5
5
  <a href="{% url 'wagtailimages:index' %}">
6
6
  {% blocktrans trimmed count counter=total_images with total_images|intcomma as total %}
7
- <span>{{ total }}</span> Image <span class="w-sr-only">created in {{ site_name }}</span>
7
+ {{ total }} Image <span class="w-sr-only">created in {{ site_name }}</span>
8
8
  {% plural %}
9
- <span>{{ total }}</span> Images <span class="w-sr-only">created in {{ site_name }}</span>
9
+ {{ total }} Images <span class="w-sr-only">created in {{ site_name }}</span>
10
10
  {% endblocktrans %}
11
11
  </a>
12
12
  </li>
@@ -1,8 +1,8 @@
1
- {% load i18n wagtailadmin_tags wagtailimages_tags %}
1
+ {% load i18n wagtailadmin_tags wagtailimages_tags l10n %}
2
2
  {% rawformattedfield field=field %}
3
3
  {% image image original as original_image %}
4
4
 
5
- <a href="{{ original_image.url }}">{% icon name="image" classname="default" %}{{ image.filename }}</a> ({{ original_image.width }}x{{ original_image.height}})<br /><br />
5
+ <a href="{{ original_image.url }}">{% icon name="image" classname="default" %}{{ image.filename }}</a> ({{ original_image.width|unlocalize }}x{{ original_image.height|unlocalize }})<br /><br />
6
6
 
7
7
  {% trans "Change image file:" %}
8
8
  {{ field }}
@@ -1,13 +1,10 @@
1
- {% extends "wagtailadmin/base.html" %}
2
- {% load wagtailimages_tags wagtailadmin_tags %}
3
- {% load i18n %}
1
+ {% extends "wagtailadmin/generic/form.html" %}
2
+ {% load wagtailimages_tags wagtailadmin_tags i18n %}
4
3
  {% block titletag %}{% trans "Add an image" %}{% endblock %}
5
4
 
6
5
  {% block extra_js %}
7
6
  {{ block.super }}
8
7
 
9
- {{ form.media.js }}
10
-
11
8
  <script>
12
9
  $(function() {
13
10
  $('#id_file').on(
@@ -39,30 +36,15 @@
39
36
  </script>
40
37
  {% endblock %}
41
38
 
42
- {% block extra_css %}
43
- {{ block.super }}
44
- {{ form.media.css }}
45
- {% endblock %}
46
-
47
- {% block content %}
48
- {% trans "Add image" as add_str %}
49
- {% include "wagtailadmin/shared/header.html" with title=add_str icon="image" %}
50
-
51
- <div class="nice-padding">
52
- {% include "wagtailadmin/shared/non_field_errors.html" %}
53
-
54
- <form action="{% url 'wagtailimages:add' %}" method="POST" enctype="multipart/form-data" novalidate>
55
- {% csrf_token %}
56
- <ul class="fields">
57
- {% for field in form %}
58
- {% if field.is_hidden %}
59
- {{ field }}
60
- {% else %}
61
- <li>{% formattedfield field %}</li>
62
- {% endif %}
63
- {% endfor %}
64
- <li><input type="submit" value="{% trans 'Save' %}" class="button" /></li>
65
- </ul>
66
- </form>
67
- </div>
39
+ {% block actions %}
40
+ <button
41
+ type="submit"
42
+ class="button button-longrunning"
43
+ data-controller="w-progress"
44
+ data-action="w-progress#activate"
45
+ data-w-progress-active-value="{% trans 'Uploading…' %}"
46
+ >
47
+ {% icon name="spinner" %}
48
+ <em data-w-progress-target="label">{% trans 'Upload' %}</em>
49
+ </button>
68
50
  {% endblock %}