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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. wagtail/__init__.py +1 -1
  2. wagtail/actions/copy_for_translation.py +15 -1
  3. wagtail/admin/checks.py +20 -30
  4. wagtail/admin/forms/pages.py +10 -0
  5. wagtail/admin/icons.py +43 -0
  6. wagtail/admin/locale/en/LC_MESSAGES/django.po +405 -295
  7. wagtail/admin/locale/en/LC_MESSAGES/djangojs.po +21 -3
  8. wagtail/admin/locale/sl/LC_MESSAGES/django.mo +0 -0
  9. wagtail/admin/locale/sl/LC_MESSAGES/django.po +30 -0
  10. wagtail/admin/menu.py +2 -2
  11. wagtail/admin/migrations/0004_editingsession.py +57 -0
  12. wagtail/admin/migrations/0005_editingsession_is_editing.py +18 -0
  13. wagtail/admin/models.py +36 -3
  14. wagtail/admin/rich_text/editors/draftail/__init__.py +2 -20
  15. wagtail/admin/static/wagtailadmin/css/core.css +1 -1
  16. wagtail/admin/static/wagtailadmin/js/bulk-actions.js +1 -1
  17. wagtail/admin/static/wagtailadmin/js/chooser-modal.js +1 -1
  18. wagtail/admin/static/wagtailadmin/js/chooser-widget-telepath.js +1 -1
  19. wagtail/admin/static/wagtailadmin/js/chooser-widget.js +1 -1
  20. wagtail/admin/static/wagtailadmin/js/comments.js +1 -1
  21. wagtail/admin/static/wagtailadmin/js/core.js +1 -1
  22. wagtail/admin/static/wagtailadmin/js/date-time-chooser.js +1 -1
  23. wagtail/admin/static/wagtailadmin/js/draftail.js +1 -1
  24. wagtail/admin/static/wagtailadmin/js/expanding-formset.js +1 -1
  25. wagtail/admin/static/wagtailadmin/js/filtered-select.js +1 -1
  26. wagtail/admin/static/wagtailadmin/js/modal-workflow.js +1 -1
  27. wagtail/admin/static/wagtailadmin/js/page-chooser-modal.js +1 -1
  28. wagtail/admin/static/wagtailadmin/js/page-chooser-telepath.js +1 -1
  29. wagtail/admin/static/wagtailadmin/js/page-chooser.js +1 -1
  30. wagtail/admin/static/wagtailadmin/js/preview-panel.js +2 -1
  31. wagtail/admin/static/wagtailadmin/js/preview-panel.js.LICENSE.txt +11 -0
  32. wagtail/admin/static/wagtailadmin/js/privacy-switch.js +1 -1
  33. wagtail/admin/static/wagtailadmin/js/sidebar.js +1 -1
  34. wagtail/admin/static/wagtailadmin/js/task-chooser-modal.js +1 -1
  35. wagtail/admin/static/wagtailadmin/js/task-chooser.js +1 -1
  36. wagtail/admin/static/wagtailadmin/js/telepath/blocks.js +1 -1
  37. wagtail/admin/static/wagtailadmin/js/telepath/widgets.js +1 -1
  38. wagtail/admin/static/wagtailadmin/js/userbar.js +2 -1
  39. wagtail/admin/static/wagtailadmin/js/userbar.js.LICENSE.txt +11 -0
  40. wagtail/admin/static/wagtailadmin/js/vendor.js +1 -1
  41. wagtail/admin/static/wagtailadmin/js/vendor.js.LICENSE.txt +0 -12
  42. wagtail/admin/static/wagtailadmin/js/wagtailadmin.js +1 -1
  43. wagtail/admin/static/wagtailadmin/js/workflow-action.js +1 -1
  44. wagtail/admin/templates/wagtailadmin/collection_privacy/ancestor_privacy.html +2 -6
  45. wagtail/admin/templates/wagtailadmin/generic/index_results.html +1 -17
  46. wagtail/admin/templates/wagtailadmin/generic/listing_results.html +20 -1
  47. wagtail/admin/templates/wagtailadmin/home/workflow_objects_to_moderate.html +2 -11
  48. wagtail/admin/templates/wagtailadmin/page_privacy/ancestor_privacy.html +2 -6
  49. wagtail/admin/templates/wagtailadmin/page_privacy/no_privacy.html +2 -0
  50. wagtail/admin/templates/wagtailadmin/pages/_editor_js.html +0 -1
  51. wagtail/admin/templates/wagtailadmin/pages/action_menu/menu.html +1 -1
  52. wagtail/admin/templates/wagtailadmin/reports/aging_pages_results.html +54 -0
  53. wagtail/admin/templates/wagtailadmin/reports/base_page_report.html +1 -17
  54. wagtail/admin/templates/wagtailadmin/reports/base_page_report_results.html +10 -0
  55. wagtail/admin/templates/wagtailadmin/reports/base_report.html +1 -40
  56. wagtail/admin/templates/wagtailadmin/reports/base_report_results.html +1 -0
  57. wagtail/admin/templates/wagtailadmin/reports/listing/_list_page_report.html +21 -27
  58. wagtail/admin/templates/wagtailadmin/reports/listing/_list_page_types_usage.html +48 -54
  59. wagtail/admin/templates/wagtailadmin/reports/{locked_pages.html → locked_pages_results.html} +3 -3
  60. wagtail/admin/templates/wagtailadmin/reports/page_types_usage_results.html +10 -0
  61. wagtail/admin/templates/wagtailadmin/reports/site_history_results.html +53 -0
  62. wagtail/admin/templates/wagtailadmin/reports/workflow_results.html +74 -0
  63. wagtail/admin/templates/wagtailadmin/reports/workflow_tasks_results.html +56 -0
  64. wagtail/admin/templates/wagtailadmin/shared/_workflow_init.html +8 -44
  65. wagtail/admin/templates/wagtailadmin/shared/avatar.html +11 -1
  66. wagtail/admin/templates/wagtailadmin/shared/dialog/dialog.html +5 -4
  67. wagtail/admin/templates/wagtailadmin/shared/dropdown/dropdown_button.html +2 -1
  68. wagtail/admin/templates/wagtailadmin/shared/editing_sessions/list.html +132 -0
  69. wagtail/admin/templates/wagtailadmin/shared/editing_sessions/module.html +44 -0
  70. wagtail/admin/templates/wagtailadmin/shared/headers/slim_header.html +7 -1
  71. wagtail/admin/templates/wagtailadmin/shared/page_status_tag_new.html +1 -1
  72. wagtail/admin/templates/wagtailadmin/shared/side_panels/checks.html +32 -16
  73. wagtail/admin/templates/wagtailadmin/skeleton.html +1 -1
  74. wagtail/admin/templates/wagtailadmin/userbar/item_accessibility.html +9 -11
  75. wagtail/admin/templatetags/wagtailadmin_tags.py +13 -2
  76. wagtail/admin/tests/formats/en/__init__.py +0 -0
  77. wagtail/admin/tests/formats/en/formats.py +1 -0
  78. wagtail/admin/tests/pages/test_create_page.py +47 -0
  79. wagtail/admin/tests/pages/test_edit_page.py +10 -8
  80. wagtail/admin/tests/pages/test_parent_page_chooser_view.py +45 -1
  81. wagtail/admin/tests/test_checks.py +53 -3
  82. wagtail/admin/tests/test_collections_views.py +62 -1
  83. wagtail/admin/tests/test_edit_handlers.py +37 -0
  84. wagtail/admin/tests/test_editing_sessions.py +1336 -0
  85. wagtail/admin/tests/test_icon_sprite.py +12 -21
  86. wagtail/admin/tests/test_page_chooser.py +309 -7
  87. wagtail/admin/tests/test_privacy.py +82 -0
  88. wagtail/admin/tests/test_reports_views.py +464 -70
  89. wagtail/admin/tests/test_userbar.py +93 -6
  90. wagtail/admin/tests/test_workflows.py +223 -33
  91. wagtail/admin/tests/viewsets/test_model_viewset.py +151 -2
  92. wagtail/admin/ui/editing_sessions.py +57 -0
  93. wagtail/admin/urls/__init__.py +9 -15
  94. wagtail/admin/urls/editing_sessions.py +17 -0
  95. wagtail/admin/urls/reports.py +33 -1
  96. wagtail/admin/userbar.py +77 -20
  97. wagtail/admin/views/chooser.py +49 -22
  98. wagtail/admin/views/collections.py +0 -11
  99. wagtail/admin/views/editing_sessions.py +193 -0
  100. wagtail/admin/views/generic/__init__.py +1 -0
  101. wagtail/admin/views/generic/history.py +9 -3
  102. wagtail/admin/views/generic/mixins.py +44 -3
  103. wagtail/admin/views/generic/models.py +46 -72
  104. wagtail/admin/views/generic/permissions.py +20 -10
  105. wagtail/admin/views/home.py +2 -31
  106. wagtail/admin/views/page_privacy.py +20 -5
  107. wagtail/admin/views/pages/choose_parent.py +62 -0
  108. wagtail/admin/views/pages/edit.py +28 -0
  109. wagtail/admin/views/reports/aging_pages.py +6 -10
  110. wagtail/admin/views/reports/audit_logging.py +13 -42
  111. wagtail/admin/views/reports/base.py +31 -4
  112. wagtail/admin/views/reports/locked_pages.py +5 -8
  113. wagtail/admin/views/reports/page_types_usage.py +6 -10
  114. wagtail/admin/views/reports/workflows.py +36 -12
  115. wagtail/admin/viewsets/base.py +8 -3
  116. wagtail/admin/viewsets/chooser.py +1 -1
  117. wagtail/admin/viewsets/model.py +26 -1
  118. wagtail/admin/wagtail_hooks.py +2 -1
  119. wagtail/api/v2/filters.py +6 -0
  120. wagtail/api/v2/tests/test_documents.py +1 -1
  121. wagtail/api/v2/tests/test_images.py +1 -1
  122. wagtail/api/v2/tests/test_pages.py +11 -1
  123. wagtail/api/v2/utils.py +2 -2
  124. wagtail/blocks/base.py +35 -12
  125. wagtail/blocks/definition_lookup.py +85 -0
  126. wagtail/blocks/list_block.py +12 -0
  127. wagtail/blocks/migrations/migrate_operation.py +2 -0
  128. wagtail/blocks/stream_block.py +19 -0
  129. wagtail/blocks/struct_block.py +19 -0
  130. wagtail/contrib/forms/locale/en/LC_MESSAGES/django.po +1 -1
  131. wagtail/contrib/frontend_cache/backends/__init__.py +5 -0
  132. wagtail/contrib/frontend_cache/backends/azure.py +179 -0
  133. wagtail/contrib/frontend_cache/backends/base.py +28 -0
  134. wagtail/contrib/frontend_cache/backends/cloudflare.py +114 -0
  135. wagtail/contrib/frontend_cache/backends/cloudfront.py +99 -0
  136. wagtail/contrib/frontend_cache/backends/http.py +64 -0
  137. wagtail/contrib/frontend_cache/tests.py +59 -17
  138. wagtail/contrib/frontend_cache/utils.py +26 -8
  139. wagtail/contrib/redirects/filters.py +15 -1
  140. wagtail/contrib/redirects/locale/en/LC_MESSAGES/django.po +37 -72
  141. wagtail/contrib/redirects/models.py +6 -5
  142. wagtail/contrib/redirects/templates/wagtailredirects/edit.html +1 -38
  143. wagtail/contrib/redirects/tests/test_redirects.py +141 -1
  144. wagtail/contrib/redirects/urls.py +1 -2
  145. wagtail/contrib/redirects/views.py +39 -80
  146. wagtail/contrib/routable_page/models.py +6 -4
  147. wagtail/contrib/routable_page/tests.py +11 -0
  148. wagtail/contrib/search_promotions/locale/en/LC_MESSAGES/django.po +1 -1
  149. wagtail/contrib/settings/locale/en/LC_MESSAGES/django.po +4 -4
  150. wagtail/contrib/simple_translation/locale/en/LC_MESSAGES/django.po +5 -1
  151. wagtail/contrib/simple_translation/models.py +2 -1
  152. wagtail/contrib/styleguide/locale/en/LC_MESSAGES/django.po +7 -7
  153. wagtail/contrib/table_block/locale/en/LC_MESSAGES/django.po +1 -1
  154. wagtail/contrib/table_block/static/table_block/js/table.js +1 -1
  155. wagtail/contrib/typed_table_block/blocks.py +19 -0
  156. wagtail/contrib/typed_table_block/locale/en/LC_MESSAGES/django.po +10 -10
  157. wagtail/contrib/typed_table_block/static/typed_table_block/js/typed_table_block.js +1 -1
  158. wagtail/contrib/typed_table_block/tests.py +38 -0
  159. wagtail/coreutils.py +1 -1
  160. wagtail/documents/__init__.py +1 -1
  161. wagtail/documents/locale/en/LC_MESSAGES/django.po +8 -8
  162. wagtail/documents/locale/sl/LC_MESSAGES/django.mo +0 -0
  163. wagtail/documents/locale/sl/LC_MESSAGES/django.po +20 -0
  164. wagtail/documents/models.py +5 -1
  165. wagtail/documents/static/wagtaildocs/js/document-chooser-modal.js +1 -1
  166. wagtail/documents/static/wagtaildocs/js/document-chooser-telepath.js +1 -1
  167. wagtail/documents/static/wagtaildocs/js/document-chooser.js +1 -1
  168. wagtail/documents/tests/test_models.py +5 -1
  169. wagtail/embeds/apps.py +2 -0
  170. wagtail/embeds/embeds.py +12 -14
  171. wagtail/embeds/finders/__init__.py +2 -0
  172. wagtail/embeds/finders/facebook.py +17 -33
  173. wagtail/embeds/finders/instagram.py +19 -16
  174. wagtail/embeds/locale/en/LC_MESSAGES/django.po +1 -1
  175. wagtail/embeds/signal_handlers.py +13 -0
  176. wagtail/embeds/tests/test_embeds.py +7 -7
  177. wagtail/fields.py +58 -14
  178. wagtail/images/__init__.py +1 -1
  179. wagtail/images/locale/en/LC_MESSAGES/django.po +34 -34
  180. wagtail/images/locale/sl/LC_MESSAGES/django.mo +0 -0
  181. wagtail/images/locale/sl/LC_MESSAGES/django.po +20 -0
  182. wagtail/images/models.py +2 -0
  183. wagtail/images/static/wagtailimages/js/image-chooser-modal.js +1 -1
  184. wagtail/images/static/wagtailimages/js/image-chooser-telepath.js +1 -1
  185. wagtail/images/static/wagtailimages/js/image-chooser.js +1 -1
  186. wagtail/images/templates/wagtailimages/images/edit.html +4 -4
  187. wagtail/images/tests/test_admin_views.py +26 -2
  188. wagtail/images/views/chooser.py +6 -1
  189. wagtail/locale/en/LC_MESSAGES/django.po +84 -80
  190. wagtail/locales/locale/en/LC_MESSAGES/django.po +2 -2
  191. wagtail/locales/tests.py +16 -0
  192. wagtail/locales/wagtail_hooks.py +0 -9
  193. wagtail/migrations/0094_alter_page_locale.py +19 -0
  194. wagtail/models/__init__.py +11 -5
  195. wagtail/models/i18n.py +6 -1
  196. wagtail/project_template/requirements.txt +1 -1
  197. wagtail/search/locale/en/LC_MESSAGES/django.po +1 -1
  198. wagtail/signals.py +4 -0
  199. wagtail/sites/locale/en/LC_MESSAGES/django.po +2 -2
  200. wagtail/sites/tests.py +15 -0
  201. wagtail/sites/wagtail_hooks.py +0 -9
  202. wagtail/snippets/locale/en/LC_MESSAGES/django.po +9 -9
  203. wagtail/snippets/permissions.py +5 -3
  204. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser-telepath.js +1 -1
  205. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser.js +1 -1
  206. wagtail/snippets/templates/wagtailsnippets/snippets/action_menu/menu.html +1 -1
  207. wagtail/snippets/tests/test_snippets.py +78 -12
  208. wagtail/snippets/tests/test_viewset.py +22 -0
  209. wagtail/snippets/views/snippets.py +19 -14
  210. wagtail/snippets/wagtail_hooks.py +2 -10
  211. wagtail/templatetags/wagtailcore_tags.py +3 -0
  212. wagtail/test/dummy_external_storage.py +1 -1
  213. wagtail/test/i18n/migrations/0003_alter_clusterabletestmodel_locale_and_more.py +40 -0
  214. wagtail/test/routablepage/models.py +4 -0
  215. wagtail/test/snippets/migrations/0012_alter_translatablesnippet_locale.py +20 -0
  216. wagtail/test/testapp/migrations/0038_sociallink.py +52 -0
  217. wagtail/test/testapp/migrations/0039_alter_eventcategory_locale_and_more.py +45 -0
  218. wagtail/test/testapp/models.py +24 -0
  219. wagtail/test/testapp/views.py +1 -0
  220. wagtail/test/testapp/wagtail_hooks.py +9 -0
  221. wagtail/test/urls_multilang.py +6 -1
  222. wagtail/test/urls_multilang_non_root.py +11 -0
  223. wagtail/tests/streamfield_migrations/test_migrations.py +53 -12
  224. wagtail/tests/test_audit_log.py +72 -2
  225. wagtail/tests/test_blocks.py +103 -0
  226. wagtail/tests/test_signals.py +49 -2
  227. wagtail/tests/test_streamfield.py +153 -0
  228. wagtail/tests/test_utils.py +14 -0
  229. wagtail/tests/tests.py +5 -0
  230. wagtail/users/apps.py +1 -0
  231. wagtail/users/forms.py +7 -0
  232. wagtail/users/locale/en/LC_MESSAGES/django.po +55 -50
  233. wagtail/users/models.py +1 -0
  234. wagtail/users/templates/wagtailusers/groups/includes/formatted_permissions.html +3 -2
  235. wagtail/users/templatetags/wagtailusers_tags.py +9 -0
  236. wagtail/users/tests/__init__.py +7 -1
  237. wagtail/users/tests/test_admin_views.py +117 -32
  238. wagtail/users/views/groups.py +4 -0
  239. wagtail/users/views/users.py +58 -14
  240. wagtail/users/wagtail_hooks.py +7 -123
  241. wagtail/utils/utils.py +1 -0
  242. wagtail/utils/version.py +5 -2
  243. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/METADATA +3 -3
  244. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/RECORD +248 -222
  245. wagtail/admin/templates/wagtailadmin/reports/aging_pages.html +0 -58
  246. wagtail/admin/templates/wagtailadmin/reports/page_types_usage.html +0 -18
  247. wagtail/admin/templates/wagtailadmin/reports/site_history.html +0 -57
  248. wagtail/admin/templates/wagtailadmin/reports/workflow.html +0 -81
  249. wagtail/admin/templates/wagtailadmin/reports/workflow_tasks.html +0 -63
  250. wagtail/contrib/frontend_cache/backends.py +0 -400
  251. wagtail/contrib/redirects/templates/wagtailredirects/list.html +0 -43
  252. wagtail/contrib/redirects/templates/wagtailredirects/reports/redirects_report.html +0 -14
  253. wagtail/contrib/redirects/tests/test_reports_view.py +0 -82
  254. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/LICENSE +0 -0
  255. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/WHEEL +0 -0
  256. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/entry_points.txt +0 -0
  257. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/top_level.txt +0 -0
@@ -8,7 +8,7 @@ msgid ""
8
8
  msgstr ""
9
9
  "Project-Id-Version: PACKAGE VERSION\n"
10
10
  "Report-Msgid-Bugs-To: \n"
11
- "POT-Creation-Date: 2024-04-18 17:28+0100\n"
11
+ "POT-Creation-Date: 2024-07-19 16:26+0100\n"
12
12
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -51,31 +51,31 @@ msgstr ""
51
51
  msgid "Select documents in choosers"
52
52
  msgstr ""
53
53
 
54
- #: models.py:25
54
+ #: models.py:26
55
55
  msgid "title"
56
56
  msgstr ""
57
57
 
58
- #: models.py:26
58
+ #: models.py:27
59
59
  msgid "file"
60
60
  msgstr ""
61
61
 
62
- #: models.py:27
62
+ #: models.py:28
63
63
  msgid "created at"
64
64
  msgstr ""
65
65
 
66
- #: models.py:30
66
+ #: models.py:31
67
67
  msgid "uploaded by user"
68
68
  msgstr ""
69
69
 
70
- #: models.py:38
70
+ #: models.py:39
71
71
  msgid "tags"
72
72
  msgstr ""
73
73
 
74
- #: models.py:204
74
+ #: models.py:208
75
75
  msgid "document"
76
76
  msgstr ""
77
77
 
78
- #: models.py:205
78
+ #: models.py:209
79
79
  msgid "documents"
80
80
  msgstr ""
81
81
 
@@ -252,6 +252,26 @@ msgstr ""
252
252
  "Naložen ni še noben dokument. Zakaj ga ne bi <a "
253
253
  "href=\"%(wagtaildocs_add_document_url)s\">naložili</a> sedaj?"
254
254
 
255
+ #, python-format
256
+ msgid ""
257
+ "<span>%(total)s</span> Document <span class=\"w-sr-only\">created in "
258
+ "%(site_name)s</span>"
259
+ msgid_plural ""
260
+ "<span>%(total)s</span> Documents <span class=\"w-sr-only\">created in "
261
+ "%(site_name)s</span>"
262
+ msgstr[0] ""
263
+ "<span>%(total)s</span> dokument <span class=\"w-sr-only\"> ustvarjen v "
264
+ "%(site_name)s</span>"
265
+ msgstr[1] ""
266
+ "<span>%(total)s</span> dokumenta <span class=\"w-sr-only\">ustvarjena na "
267
+ "%(site_name)s</span>"
268
+ msgstr[2] ""
269
+ "<span>%(total)s</span> dokumenti <span class=\"w-sr-only\">ustvarjeni na "
270
+ "%(site_name)s</span>"
271
+ msgstr[3] ""
272
+ "<span>%(total)s</span> dokumentov <span class=\"w-sr-only\">narejenih v "
273
+ "%(site_name)s</span>"
274
+
255
275
  msgid "Add multiple documents"
256
276
  msgstr "Dodaj več dokumentov"
257
277
 
@@ -4,6 +4,7 @@ from contextlib import contextmanager
4
4
  from mimetypes import guess_type
5
5
 
6
6
  from django.conf import settings
7
+ from django.core.exceptions import ValidationError
7
8
  from django.core.validators import FileExtensionValidator
8
9
  from django.db import models
9
10
  from django.dispatch import Signal
@@ -71,7 +72,10 @@ class AbstractDocument(CollectionMember, index.Indexed, models.Model):
71
72
  allowed_extensions = getattr(settings, "WAGTAILDOCS_EXTENSIONS", None)
72
73
  if allowed_extensions:
73
74
  validate = FileExtensionValidator(allowed_extensions)
74
- validate(self.file)
75
+ try:
76
+ validate(self.file)
77
+ except ValidationError as e:
78
+ raise ValidationError({"file": e.messages[0]})
75
79
 
76
80
  def is_stored_locally(self):
77
81
  """
@@ -1 +1 @@
1
- (()=>{"use strict";var e,o={4186:(e,o,r)=>{var t=r(1669),n=r.n(t),a=r(2614);class l extends a.C4{ajaxifyLinks(e,o){super.ajaxifyLinks(e,o),n()("a.upload-one-now").on("click",(e=>{const o=n()("#id_collection_id").val();o&&n()("#id_document-chooser-upload-collection").val(o),e.preventDefault()}))}}window.DOCUMENT_CHOOSER_MODAL_ONLOAD_HANDLERS=new l({searchInputDelay:50,creationFormFileFieldSelector:"#id_document-chooser-upload-file",creationFormTitleFieldSelector:"#id_document-chooser-upload-title",creationFormTabSelector:"#tab-upload",creationFormEventName:"wagtail:documents-upload"}).getOnLoadHandlers();class i extends a.ZZ{onloadHandlers=window.DOCUMENT_CHOOSER_MODAL_ONLOAD_HANDLERS}window.DocumentChooserModal=i},1669:e=>{e.exports=jQuery}},r={};function t(e){var n=r[e];if(void 0!==n)return n.exports;var a=r[e]={id:e,loaded:!1,exports:{}};return o[e].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}t.m=o,e=[],t.O=(o,r,n,a)=>{if(!r){var l=1/0;for(s=0;s<e.length;s++){for(var[r,n,a]=e[s],i=!0,d=0;d<r.length;d++)(!1&a||l>=a)&&Object.keys(t.O).every((e=>t.O[e](r[d])))?r.splice(d--,1):(i=!1,a<l&&(l=a));if(i){e.splice(s--,1);var c=n();void 0!==c&&(o=c)}}return o}a=a||0;for(var s=e.length;s>0&&e[s-1][2]>a;s--)e[s]=e[s-1];e[s]=[r,n,a]},t.n=e=>{var o=e&&e.__esModule?()=>e.default:()=>e;return t.d(o,{a:o}),o},t.d=(e,o)=>{for(var r in o)t.o(o,r)&&!t.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:o[r]})},t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),t.o=(e,o)=>Object.prototype.hasOwnProperty.call(e,o),t.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),t.j=884,(()=>{var e={884:0};t.O.j=o=>0===e[o];var o=(o,r)=>{var n,a,[l,i,d]=r,c=0;if(l.some((o=>0!==e[o]))){for(n in i)t.o(i,n)&&(t.m[n]=i[n]);if(d)var s=d(t)}for(o&&o(r);c<l.length;c++)a=l[c],t.o(e,a)&&e[a]&&e[a][0](),e[a]=0;return t.O(s)},r=globalThis.webpackChunkwagtail=globalThis.webpackChunkwagtail||[];r.forEach(o.bind(null,0)),r.push=o.bind(null,r.push.bind(r))})();var n=t.O(void 0,[321],(()=>t(4186)));n=t.O(n)})();
1
+ (()=>{"use strict";var e,o={4186:(e,o,r)=>{var t=r(1669),n=r.n(t),a=r(2614);class i extends a.C4{ajaxifyLinks(e,o){super.ajaxifyLinks(e,o),n()("a.upload-one-now").on("click",(e=>{const o=n()("#id_collection_id").val();o&&n()("#id_document-chooser-upload-collection").val(o),e.preventDefault()}))}}window.DOCUMENT_CHOOSER_MODAL_ONLOAD_HANDLERS=new i({searchInputDelay:50,creationFormFileFieldSelector:"#id_document-chooser-upload-file",creationFormTitleFieldSelector:"#id_document-chooser-upload-title",creationFormTabSelector:"#tab-upload",creationFormEventName:"wagtail:documents-upload"}).getOnLoadHandlers();class l extends a.ZZ{onloadHandlers=window.DOCUMENT_CHOOSER_MODAL_ONLOAD_HANDLERS}window.DocumentChooserModal=l},1669:e=>{e.exports=jQuery}},r={};function t(e){var n=r[e];if(void 0!==n)return n.exports;var a=r[e]={exports:{}};return o[e](a,a.exports,t),a.exports}t.m=o,e=[],t.O=(o,r,n,a)=>{if(!r){var i=1/0;for(u=0;u<e.length;u++){for(var[r,n,a]=e[u],l=!0,d=0;d<r.length;d++)(!1&a||i>=a)&&Object.keys(t.O).every((e=>t.O[e](r[d])))?r.splice(d--,1):(l=!1,a<i&&(i=a));if(l){e.splice(u--,1);var c=n();void 0!==c&&(o=c)}}return o}a=a||0;for(var u=e.length;u>0&&e[u-1][2]>a;u--)e[u]=e[u-1];e[u]=[r,n,a]},t.n=e=>{var o=e&&e.__esModule?()=>e.default:()=>e;return t.d(o,{a:o}),o},t.d=(e,o)=>{for(var r in o)t.o(o,r)&&!t.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:o[r]})},t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),t.o=(e,o)=>Object.prototype.hasOwnProperty.call(e,o),t.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.j=884,(()=>{var e={884:0};t.O.j=o=>0===e[o];var o=(o,r)=>{var n,a,[i,l,d]=r,c=0;if(i.some((o=>0!==e[o]))){for(n in l)t.o(l,n)&&(t.m[n]=l[n]);if(d)var u=d(t)}for(o&&o(r);c<i.length;c++)a=i[c],t.o(e,a)&&e[a]&&e[a][0](),e[a]=0;return t.O(u)},r=globalThis.webpackChunkwagtail=globalThis.webpackChunkwagtail||[];r.forEach(o.bind(null,0)),r.push=o.bind(null,r.push.bind(r))})();var n=t.O(void 0,[321],(()=>t(4186)));n=t.O(n)})();
@@ -1 +1 @@
1
- (()=>{"use strict";var e,o={3819:(e,o,r)=>{var t=r(9465);class n extends t.y{chooserModalClass=DocumentChooserModal}window.DocumentChooser=n;class a extends t._{widgetClass=n;chooserModalClass=DocumentChooserModal}window.telepath.register("wagtail.documents.widgets.DocumentChooser",a)},1669:e=>{e.exports=jQuery}},r={};function t(e){var n=r[e];if(void 0!==n)return n.exports;var a=r[e]={id:e,loaded:!1,exports:{}};return o[e].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}t.m=o,e=[],t.O=(o,r,n,a)=>{if(!r){var l=1/0;for(u=0;u<e.length;u++){for(var[r,n,a]=e[u],i=!0,s=0;s<r.length;s++)(!1&a||l>=a)&&Object.keys(t.O).every((e=>t.O[e](r[s])))?r.splice(s--,1):(i=!1,a<l&&(l=a));if(i){e.splice(u--,1);var d=n();void 0!==d&&(o=d)}}return o}a=a||0;for(var u=e.length;u>0&&e[u-1][2]>a;u--)e[u]=e[u-1];e[u]=[r,n,a]},t.n=e=>{var o=e&&e.__esModule?()=>e.default:()=>e;return t.d(o,{a:o}),o},t.d=(e,o)=>{for(var r in o)t.o(o,r)&&!t.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:o[r]})},t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),t.o=(e,o)=>Object.prototype.hasOwnProperty.call(e,o),t.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),t.j=556,(()=>{var e={556:0};t.O.j=o=>0===e[o];var o=(o,r)=>{var n,a,[l,i,s]=r,d=0;if(l.some((o=>0!==e[o]))){for(n in i)t.o(i,n)&&(t.m[n]=i[n]);if(s)var u=s(t)}for(o&&o(r);d<l.length;d++)a=l[d],t.o(e,a)&&e[a]&&e[a][0](),e[a]=0;return t.O(u)},r=globalThis.webpackChunkwagtail=globalThis.webpackChunkwagtail||[];r.forEach(o.bind(null,0)),r.push=o.bind(null,r.push.bind(r))})();var n=t.O(void 0,[321],(()=>t(3819)));n=t.O(n)})();
1
+ (()=>{"use strict";var e,o={3819:(e,o,r)=>{var t=r(9465);class n extends t.y{chooserModalClass=DocumentChooserModal}window.DocumentChooser=n;class a extends t._{widgetClass=n;chooserModalClass=DocumentChooserModal}window.telepath.register("wagtail.documents.widgets.DocumentChooser",a)},1669:e=>{e.exports=jQuery}},r={};function t(e){var n=r[e];if(void 0!==n)return n.exports;var a=r[e]={exports:{}};return o[e](a,a.exports,t),a.exports}t.m=o,e=[],t.O=(o,r,n,a)=>{if(!r){var i=1/0;for(d=0;d<e.length;d++){for(var[r,n,a]=e[d],l=!0,s=0;s<r.length;s++)(!1&a||i>=a)&&Object.keys(t.O).every((e=>t.O[e](r[s])))?r.splice(s--,1):(l=!1,a<i&&(i=a));if(l){e.splice(d--,1);var u=n();void 0!==u&&(o=u)}}return o}a=a||0;for(var d=e.length;d>0&&e[d-1][2]>a;d--)e[d]=e[d-1];e[d]=[r,n,a]},t.n=e=>{var o=e&&e.__esModule?()=>e.default:()=>e;return t.d(o,{a:o}),o},t.d=(e,o)=>{for(var r in o)t.o(o,r)&&!t.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:o[r]})},t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),t.o=(e,o)=>Object.prototype.hasOwnProperty.call(e,o),t.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.j=556,(()=>{var e={556:0};t.O.j=o=>0===e[o];var o=(o,r)=>{var n,a,[i,l,s]=r,u=0;if(i.some((o=>0!==e[o]))){for(n in l)t.o(l,n)&&(t.m[n]=l[n]);if(s)var d=s(t)}for(o&&o(r);u<i.length;u++)a=i[u],t.o(e,a)&&e[a]&&e[a][0](),e[a]=0;return t.O(d)},r=globalThis.webpackChunkwagtail=globalThis.webpackChunkwagtail||[];r.forEach(o.bind(null,0)),r.push=o.bind(null,r.push.bind(r))})();var n=t.O(void 0,[321],(()=>t(3819)));n=t.O(n)})();
@@ -1 +1 @@
1
- (()=>{"use strict";var e,r={7023:(e,r,o)=>{var t=o(9465);class n extends t.y{chooserModalClass=DocumentChooserModal}window.DocumentChooser=n,window.DocumentChooser=n},1669:e=>{e.exports=jQuery}},o={};function t(e){var n=o[e];if(void 0!==n)return n.exports;var a=o[e]={id:e,loaded:!1,exports:{}};return r[e].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}t.m=r,e=[],t.O=(r,o,n,a)=>{if(!o){var i=1/0;for(u=0;u<e.length;u++){for(var[o,n,a]=e[u],l=!0,s=0;s<o.length;s++)(!1&a||i>=a)&&Object.keys(t.O).every((e=>t.O[e](o[s])))?o.splice(s--,1):(l=!1,a<i&&(i=a));if(l){e.splice(u--,1);var d=n();void 0!==d&&(r=d)}}return r}a=a||0;for(var u=e.length;u>0&&e[u-1][2]>a;u--)e[u]=e[u-1];e[u]=[o,n,a]},t.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return t.d(r,{a:r}),r},t.d=(e,r)=>{for(var o in r)t.o(r,o)&&!t.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:r[o]})},t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),t.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),t.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),t.j=844,(()=>{var e={844:0};t.O.j=r=>0===e[r];var r=(r,o)=>{var n,a,[i,l,s]=o,d=0;if(i.some((r=>0!==e[r]))){for(n in l)t.o(l,n)&&(t.m[n]=l[n]);if(s)var u=s(t)}for(r&&r(o);d<i.length;d++)a=i[d],t.o(e,a)&&e[a]&&e[a][0](),e[a]=0;return t.O(u)},o=globalThis.webpackChunkwagtail=globalThis.webpackChunkwagtail||[];o.forEach(r.bind(null,0)),o.push=r.bind(null,o.push.bind(o))})();var n=t.O(void 0,[321],(()=>t(7023)));n=t.O(n)})();
1
+ (()=>{"use strict";var e,r={7023:(e,r,o)=>{var t=o(9465);class n extends t.y{chooserModalClass=DocumentChooserModal}window.DocumentChooser=n,window.DocumentChooser=n},1669:e=>{e.exports=jQuery}},o={};function t(e){var n=o[e];if(void 0!==n)return n.exports;var a=o[e]={exports:{}};return r[e](a,a.exports,t),a.exports}t.m=r,e=[],t.O=(r,o,n,a)=>{if(!o){var i=1/0;for(c=0;c<e.length;c++){for(var[o,n,a]=e[c],l=!0,s=0;s<o.length;s++)(!1&a||i>=a)&&Object.keys(t.O).every((e=>t.O[e](o[s])))?o.splice(s--,1):(l=!1,a<i&&(i=a));if(l){e.splice(c--,1);var u=n();void 0!==u&&(r=u)}}return r}a=a||0;for(var c=e.length;c>0&&e[c-1][2]>a;c--)e[c]=e[c-1];e[c]=[o,n,a]},t.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return t.d(r,{a:r}),r},t.d=(e,r)=>{for(var o in r)t.o(r,o)&&!t.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:r[o]})},t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),t.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),t.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.j=844,(()=>{var e={844:0};t.O.j=r=>0===e[r];var r=(r,o)=>{var n,a,[i,l,s]=o,u=0;if(i.some((r=>0!==e[r]))){for(n in l)t.o(l,n)&&(t.m[n]=l[n]);if(s)var c=s(t)}for(r&&r(o);u<i.length;u++)a=i[u],t.o(e,a)&&e[a]&&e[a][0](),e[a]=0;return t.O(c)},o=globalThis.webpackChunkwagtail=globalThis.webpackChunkwagtail||[];o.forEach(r.bind(null,0)),o.push=r.bind(null,o.push.bind(o))})();var n=t.O(void 0,[321],(()=>t(7023)));n=t.O(n)})();
@@ -217,8 +217,12 @@ class TestDocumentValidateExtensions(TestCase):
217
217
  creation when called full_clean. This specific testcase invalid
218
218
  file extension is passed
219
219
  """
220
- with self.assertRaises(ValidationError):
220
+ with self.assertRaises(ValidationError) as e:
221
221
  self.document_invalid.full_clean()
222
+ expected_message = (
223
+ "File extension “doc” is not allowed. Allowed extensions are: pdf."
224
+ )
225
+ self.assertEqual(e.exception.message_dict["file"][0], expected_message)
222
226
 
223
227
  def test_create_doc_valid_extension(self):
224
228
  """
wagtail/embeds/apps.py CHANGED
@@ -11,5 +11,7 @@ class WagtailEmbedsAppConfig(AppConfig):
11
11
  default_auto_field = "django.db.models.AutoField"
12
12
 
13
13
  def ready(self):
14
+ from . import signal_handlers # noqa
15
+
14
16
  # Check configuration on startup
15
17
  get_finders()
wagtail/embeds/embeds.py CHANGED
@@ -9,7 +9,18 @@ from .finders import get_finders
9
9
  from .models import Embed
10
10
 
11
11
 
12
- def get_embed(url, max_width=None, max_height=None, finder=None):
12
+ def get_finder_for_embed(url, max_width=None, max_height=None):
13
+ for finder in get_finders():
14
+ if finder.accept(url):
15
+ kwargs = {}
16
+ if accepts_kwarg(finder.find_embed, "max_height"):
17
+ kwargs["max_height"] = max_height
18
+ return finder.find_embed(url, max_width=max_width, **kwargs)
19
+
20
+ raise EmbedUnsupportedProviderException
21
+
22
+
23
+ def get_embed(url, max_width=None, max_height=None, finder=get_finder_for_embed):
13
24
  embed_hash = get_embed_hash(url, max_width, max_height)
14
25
 
15
26
  # Check database
@@ -18,19 +29,6 @@ def get_embed(url, max_width=None, max_height=None, finder=None):
18
29
  except Embed.DoesNotExist:
19
30
  pass
20
31
 
21
- # Get/Call finder
22
- if not finder:
23
-
24
- def finder(url, max_width=None, max_height=None):
25
- for finder in get_finders():
26
- if finder.accept(url):
27
- kwargs = {}
28
- if accepts_kwarg(finder.find_embed, "max_height"):
29
- kwargs["max_height"] = max_height
30
- return finder.find_embed(url, max_width=max_width, **kwargs)
31
-
32
- raise EmbedUnsupportedProviderException
33
-
34
32
  embed_dict = finder(url, max_width, max_height)
35
33
 
36
34
  # Make sure width and height are valid integers before inserting into database
@@ -1,3 +1,4 @@
1
+ from functools import lru_cache
1
2
  from importlib import import_module
2
3
 
3
4
  from django.conf import settings
@@ -34,6 +35,7 @@ def _get_config_from_settings():
34
35
  ]
35
36
 
36
37
 
38
+ @lru_cache(maxsize=None)
37
39
  def get_finders():
38
40
  finders = []
39
41
 
@@ -1,5 +1,4 @@
1
1
  import json
2
- import re
3
2
  from urllib import request as urllib_request
4
3
  from urllib.error import HTTPError, URLError
5
4
  from urllib.parse import urlencode
@@ -7,29 +6,25 @@ from urllib.request import Request
7
6
 
8
7
  from wagtail.embeds.exceptions import EmbedException, EmbedNotFoundException
9
8
 
10
- from .base import EmbedFinder
9
+ from .oembed import OEmbedFinder
11
10
 
12
11
 
13
12
  class AccessDeniedFacebookOEmbedException(EmbedException):
14
13
  pass
15
14
 
16
15
 
17
- class FacebookOEmbedFinder(EmbedFinder):
18
- """
19
- An embed finder that supports the authenticated Facebook oEmbed Endpoint.
20
- https://developers.facebook.com/docs/plugins/oembed
21
- """
22
-
23
- facebook_video = {
16
+ FACEBOOK_PROVIDERS = [
17
+ # Videos
18
+ {
24
19
  "endpoint": "https://graph.facebook.com/v11.0/oembed_video",
25
20
  "urls": [
26
21
  r"^https://(?:www\.)?facebook\.com/.+?/videos/.+$",
27
22
  r"^https://(?:www\.)?facebook\.com/video\.php\?(?:v|id)=.+$",
28
23
  r"^https://fb.watch/.+$",
29
24
  ],
30
- }
31
-
32
- facebook_post = {
25
+ },
26
+ # Posts
27
+ {
33
28
  "endpoint": "https://graph.facebook.com/v11.0/oembed_post",
34
29
  "urls": [
35
30
  r"^https://(?:www\.)?facebook\.com/.+?/(?:posts|activity)/.+$",
@@ -42,7 +37,15 @@ class FacebookOEmbedFinder(EmbedFinder):
42
37
  # Works for posts with a single photo
43
38
  r"^https://(?:www\.)?facebook\.com/.+?/photos/.+$",
44
39
  ],
45
- }
40
+ },
41
+ ]
42
+
43
+
44
+ class FacebookOEmbedFinder(OEmbedFinder):
45
+ """
46
+ An embed finder that supports the authenticated Facebook oEmbed Endpoint.
47
+ https://developers.facebook.com/docs/plugins/oembed
48
+ """
46
49
 
47
50
  def __init__(self, omitscript=False, app_id=None, app_secret=None):
48
51
  # {settings.facebook_APP_ID}|{settings.facebook_APP_SECRET}
@@ -50,26 +53,7 @@ class FacebookOEmbedFinder(EmbedFinder):
50
53
  self.app_secret = app_secret
51
54
  self.omitscript = omitscript
52
55
 
53
- self._endpoints = {}
54
-
55
- for provider in [self.facebook_video, self.facebook_post]:
56
- patterns = []
57
-
58
- endpoint = provider["endpoint"].replace("{format}", "json")
59
-
60
- for url in provider["urls"]:
61
- patterns.append(re.compile(url))
62
-
63
- self._endpoints[endpoint] = patterns
64
-
65
- def _get_endpoint(self, url):
66
- for endpoint, patterns in self._endpoints.items():
67
- for pattern in patterns:
68
- if re.match(pattern, url):
69
- return endpoint
70
-
71
- def accept(self, url):
72
- return self._get_endpoint(url) is not None
56
+ super().__init__(providers=FACEBOOK_PROVIDERS)
73
57
 
74
58
  def find_embed(self, url, max_width=None, max_height=None):
75
59
  # Find provider
@@ -1,5 +1,4 @@
1
1
  import json
2
- import re
3
2
  from urllib import request as urllib_request
4
3
  from urllib.error import HTTPError, URLError
5
4
  from urllib.parse import urlencode
@@ -7,39 +6,43 @@ from urllib.request import Request
7
6
 
8
7
  from wagtail.embeds.exceptions import EmbedException, EmbedNotFoundException
9
8
 
10
- from .base import EmbedFinder
9
+ from .oembed import OEmbedFinder
11
10
 
12
11
 
13
12
  class AccessDeniedInstagramOEmbedException(EmbedException):
14
13
  pass
15
14
 
16
15
 
17
- class InstagramOEmbedFinder(EmbedFinder):
16
+ INSTAGRAM_PROVIDER = {
17
+ "endpoint": "https://graph.facebook.com/v11.0/instagram_oembed",
18
+ "urls": [
19
+ r"^https?://(?:www\.)?instagram\.com/p/.+$",
20
+ r"^https?://(?:www\.)?instagram\.com/tv/.+$",
21
+ r"^https?://(?:www\.)?instagram\.com/reel/.+$",
22
+ ],
23
+ }
24
+
25
+
26
+ class InstagramOEmbedFinder(OEmbedFinder):
18
27
  """
19
28
  An embed finder that supports the authenticated Instagram oEmbed Endpoint.
20
29
  https://developers.facebook.com/docs/instagram/oembed
21
30
  """
22
31
 
23
- INSTAGRAM_ENDPOINT = "https://graph.facebook.com/v11.0/instagram_oembed"
24
- INSTAGRAM_URL_PATTERNS = [
25
- r"^https?://(?:www\.)?instagram\.com/p/.+$",
26
- r"^https?://(?:www\.)?instagram\.com/tv/.+$",
27
- r"^https?://(?:www\.)?instagram\.com/reel/.+$",
28
- ]
29
-
30
32
  def __init__(self, omitscript=False, app_id=None, app_secret=None):
31
33
  # {settings.INSTAGRAM_APP_ID}|{settings.INSTAGRAM_APP_SECRET}
32
34
  self.app_id = app_id
33
35
  self.app_secret = app_secret
34
36
  self.omitscript = omitscript
35
37
 
36
- def accept(self, url):
37
- for pattern in self.INSTAGRAM_URL_PATTERNS:
38
- if re.match(pattern, url):
39
- return True
40
- return False
38
+ super().__init__(providers=[INSTAGRAM_PROVIDER])
41
39
 
42
40
  def find_embed(self, url, max_width=None, max_height=None):
41
+ # Find provider
42
+ endpoint = self._get_endpoint(url)
43
+ if endpoint is None:
44
+ raise EmbedNotFoundException
45
+
43
46
  params = {"url": url, "format": "json"}
44
47
  if max_width:
45
48
  params["maxwidth"] = max_width
@@ -49,7 +52,7 @@ class InstagramOEmbedFinder(EmbedFinder):
49
52
  params["omitscript"] = "true"
50
53
 
51
54
  # Configure request
52
- request = Request(self.INSTAGRAM_ENDPOINT + "?" + urlencode(params))
55
+ request = Request(endpoint + "?" + urlencode(params))
53
56
  request.add_header("Authorization", f"Bearer {self.app_id}|{self.app_secret}")
54
57
 
55
58
  # Perform request
@@ -8,7 +8,7 @@ msgid ""
8
8
  msgstr ""
9
9
  "Project-Id-Version: PACKAGE VERSION\n"
10
10
  "Report-Msgid-Bugs-To: \n"
11
- "POT-Creation-Date: 2024-04-18 17:28+0100\n"
11
+ "POT-Creation-Date: 2024-07-19 16:26+0100\n"
12
12
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -0,0 +1,13 @@
1
+ from django.core.signals import setting_changed
2
+ from django.dispatch import receiver
3
+
4
+ from .finders import get_finders
5
+
6
+
7
+ @receiver(setting_changed)
8
+ def clear_embed_caches(*, setting: str, **kwargs: dict) -> None:
9
+ """
10
+ Clear the embed caches when settings change
11
+ """
12
+ if setting == "WAGTAILEMBEDS_FINDERS":
13
+ get_finders.cache_clear()
@@ -652,7 +652,7 @@ class TestInstagramOEmbed(TestCase):
652
652
  def test_instagram_oembed_return_values(self, urlopen):
653
653
  urlopen.return_value = self.dummy_response
654
654
  result = InstagramOEmbedFinder(app_id="123", app_secret="abc").find_embed(
655
- "https://instagr.am/p/CHeRxmnDSYe/"
655
+ "https://instagram.com/p/CHeRxmnDSYe/"
656
656
  )
657
657
  self.assertEqual(
658
658
  result,
@@ -671,13 +671,13 @@ class TestInstagramOEmbed(TestCase):
671
671
  request = urlopen.call_args[0][0]
672
672
  self.assertEqual(
673
673
  request.get_full_url(),
674
- "https://graph.facebook.com/v11.0/instagram_oembed?url=https%3A%2F%2Finstagr.am%2Fp%2FCHeRxmnDSYe%2F&format=json",
674
+ "https://graph.facebook.com/v11.0/instagram_oembed?url=https%3A%2F%2Finstagram.com%2Fp%2FCHeRxmnDSYe%2F&format=json",
675
675
  )
676
676
  self.assertEqual(request.get_header("Authorization"), "Bearer 123|abc")
677
677
 
678
678
  def test_instagram_request_denied_401(self):
679
679
  err = HTTPError(
680
- "https://instagr.am/p/CHeRxmnDSYe/",
680
+ "https://instagram.com/p/CHeRxmnDSYe/",
681
681
  code=401,
682
682
  msg="invalid credentials",
683
683
  hdrs={},
@@ -688,12 +688,12 @@ class TestInstagramOEmbed(TestCase):
688
688
  self.assertRaises(
689
689
  AccessDeniedInstagramOEmbedException,
690
690
  InstagramOEmbedFinder().find_embed,
691
- "https://instagr.am/p/CHeRxmnDSYe/",
691
+ "https://instagram.com/p/CHeRxmnDSYe/",
692
692
  )
693
693
 
694
694
  def test_instagram_request_not_found(self):
695
695
  err = HTTPError(
696
- "https://instagr.am/p/badrequest/",
696
+ "https://instagram.com/p/badrequest/",
697
697
  code=404,
698
698
  msg="Not Found",
699
699
  hdrs={},
@@ -704,7 +704,7 @@ class TestInstagramOEmbed(TestCase):
704
704
  self.assertRaises(
705
705
  EmbedNotFoundException,
706
706
  InstagramOEmbedFinder().find_embed,
707
- "https://instagr.am/p/CHeRxmnDSYe/",
707
+ "https://instagram.com/p/CHeRxmnDSYe/",
708
708
  )
709
709
 
710
710
  def test_instagram_failed_request(self):
@@ -713,7 +713,7 @@ class TestInstagramOEmbed(TestCase):
713
713
  self.assertRaises(
714
714
  EmbedNotFoundException,
715
715
  InstagramOEmbedFinder().find_embed,
716
- "https://instagr.am/p/CHeRxmnDSYe/",
716
+ "https://instagram.com/p/CHeRxmnDSYe/",
717
717
  )
718
718
 
719
719
 
wagtail/fields.py CHANGED
@@ -5,8 +5,13 @@ from django.core.validators import MaxLengthValidator
5
5
  from django.db import models
6
6
  from django.db.models.fields.json import KeyTransform
7
7
  from django.utils.encoding import force_str
8
+ from django.utils.functional import cached_property
8
9
 
9
10
  from wagtail.blocks import Block, BlockField, StreamBlock, StreamValue
11
+ from wagtail.blocks.definition_lookup import (
12
+ BlockDefinitionLookup,
13
+ BlockDefinitionLookupBuilder,
14
+ )
10
15
  from wagtail.rich_text import (
11
16
  RichTextMaxLengthValidator,
12
17
  extract_references_from_rich_text,
@@ -82,34 +87,68 @@ class Creator:
82
87
 
83
88
 
84
89
  class StreamField(models.Field):
85
- def __init__(self, block_types, use_json_field=True, **kwargs):
86
- # use_json_field no longer has any effect but is recognised to support historical
87
- # migrations
90
+ def __init__(self, block_types, use_json_field=True, block_lookup=None, **kwargs):
91
+ """
92
+ Construct a StreamField.
93
+
94
+ :param block_types: Either a list of block types that are allowed in this StreamField
95
+ (as a list of tuples of block name and block instance) or a StreamBlock to use as
96
+ the top level block (as a block instance or class).
97
+ :param use_json_field: Ignored, but retained for compatibility with historical migrations.
98
+ :param block_lookup: Used in migrations to provide a more compact block definition -
99
+ see ``wagtail.blocks.definition_lookup.BlockDefinitionLookup``. If passed, ``block_types``
100
+ can contain integer indexes into this lookup table, in place of actual block instances.
101
+ """
88
102
 
89
103
  # extract kwargs that are to be passed on to the block, not handled by super
90
- block_opts = {}
104
+ self.block_opts = {}
91
105
  for arg in ["min_num", "max_num", "block_counts", "collapsed"]:
92
106
  if arg in kwargs:
93
- block_opts[arg] = kwargs.pop(arg)
107
+ self.block_opts[arg] = kwargs.pop(arg)
94
108
 
95
109
  # for a top-level block, the 'blank' kwarg (defaulting to False) always overrides the
96
110
  # block's own 'required' meta attribute, even if not passed explicitly; this ensures
97
111
  # that the field and block have consistent definitions
98
- block_opts["required"] = not kwargs.get("blank", False)
112
+ self.block_opts["required"] = not kwargs.get("blank", False)
113
+
114
+ # Store the `block_types` and `block_lookup` arguments to be handled in the `stream_block`
115
+ # property
116
+ self.block_types_arg = block_types
117
+ self.block_lookup = block_lookup
99
118
 
100
119
  super().__init__(**kwargs)
101
120
 
102
- if isinstance(block_types, Block):
121
+ @cached_property
122
+ def stream_block(self):
123
+ has_block_lookup = self.block_lookup is not None
124
+ if has_block_lookup:
125
+ lookup = BlockDefinitionLookup(self.block_lookup)
126
+
127
+ if isinstance(self.block_types_arg, Block):
103
128
  # use the passed block as the top-level block
104
- self.stream_block = block_types
105
- elif isinstance(block_types, type):
129
+ block = self.block_types_arg
130
+ elif isinstance(self.block_types_arg, int) and has_block_lookup:
131
+ # retrieve block from lookup table to use as the top-level block
132
+ block = lookup.get_block(self.block_types_arg)
133
+ elif isinstance(self.block_types_arg, type):
106
134
  # block passed as a class - instantiate it
107
- self.stream_block = block_types()
135
+ block = self.block_types_arg()
108
136
  else:
109
- # construct a top-level StreamBlock from the list of block types
110
- self.stream_block = StreamBlock(block_types)
137
+ # construct a top-level StreamBlock from the list of block types.
138
+ # If an integer is found in place of a block instance, and block_lookup is
139
+ # provided, it will be replaced with the corresponding block definition.
140
+ child_blocks = []
141
+
142
+ for name, child_block in self.block_types_arg:
143
+ if isinstance(child_block, int) and has_block_lookup:
144
+ child_blocks.append((name, lookup.get_block(child_block)))
145
+ else:
146
+ child_blocks.append((name, child_block))
147
+
148
+ block = StreamBlock(child_blocks)
111
149
 
112
- self.stream_block.set_meta_options(block_opts)
150
+ block.set_meta_options(self.block_opts)
151
+ return block
113
152
 
114
153
  @property
115
154
  def json_field(self):
@@ -126,8 +165,13 @@ class StreamField(models.Field):
126
165
 
127
166
  def deconstruct(self):
128
167
  name, path, _, kwargs = super().deconstruct()
129
- block_types = list(self.stream_block.child_blocks.items())
168
+ lookup = BlockDefinitionLookupBuilder()
169
+ block_types = [
170
+ (name, lookup.add_block(block))
171
+ for name, block in self.stream_block.child_blocks.items()
172
+ ]
130
173
  args = [block_types]
174
+ kwargs["block_lookup"] = lookup.get_lookup_as_dict()
131
175
  return name, path, args, kwargs
132
176
 
133
177
  def to_python(self, value):
@@ -15,7 +15,7 @@ def get_image_model():
15
15
  """
16
16
  Get the image model from the ``WAGTAILIMAGES_IMAGE_MODEL`` setting.
17
17
  Useful for developers making Wagtail plugins that need the image model.
18
- Defaults to the standard :class:`~wagtail.images.models.Image` model
18
+ Defaults to the standard ``wagtail.images.models.Image`` model
19
19
  if no custom model is defined.
20
20
  """
21
21
  from django.apps import apps