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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. wagtail/__init__.py +1 -1
  2. wagtail/actions/copy_for_translation.py +15 -1
  3. wagtail/admin/checks.py +20 -30
  4. wagtail/admin/forms/pages.py +10 -0
  5. wagtail/admin/icons.py +43 -0
  6. wagtail/admin/locale/en/LC_MESSAGES/django.po +405 -295
  7. wagtail/admin/locale/en/LC_MESSAGES/djangojs.po +21 -3
  8. wagtail/admin/locale/sl/LC_MESSAGES/django.mo +0 -0
  9. wagtail/admin/locale/sl/LC_MESSAGES/django.po +30 -0
  10. wagtail/admin/menu.py +2 -2
  11. wagtail/admin/migrations/0004_editingsession.py +57 -0
  12. wagtail/admin/migrations/0005_editingsession_is_editing.py +18 -0
  13. wagtail/admin/models.py +36 -3
  14. wagtail/admin/rich_text/editors/draftail/__init__.py +2 -20
  15. wagtail/admin/static/wagtailadmin/css/core.css +1 -1
  16. wagtail/admin/static/wagtailadmin/js/bulk-actions.js +1 -1
  17. wagtail/admin/static/wagtailadmin/js/chooser-modal.js +1 -1
  18. wagtail/admin/static/wagtailadmin/js/chooser-widget-telepath.js +1 -1
  19. wagtail/admin/static/wagtailadmin/js/chooser-widget.js +1 -1
  20. wagtail/admin/static/wagtailadmin/js/comments.js +1 -1
  21. wagtail/admin/static/wagtailadmin/js/core.js +1 -1
  22. wagtail/admin/static/wagtailadmin/js/date-time-chooser.js +1 -1
  23. wagtail/admin/static/wagtailadmin/js/draftail.js +1 -1
  24. wagtail/admin/static/wagtailadmin/js/expanding-formset.js +1 -1
  25. wagtail/admin/static/wagtailadmin/js/filtered-select.js +1 -1
  26. wagtail/admin/static/wagtailadmin/js/modal-workflow.js +1 -1
  27. wagtail/admin/static/wagtailadmin/js/page-chooser-modal.js +1 -1
  28. wagtail/admin/static/wagtailadmin/js/page-chooser-telepath.js +1 -1
  29. wagtail/admin/static/wagtailadmin/js/page-chooser.js +1 -1
  30. wagtail/admin/static/wagtailadmin/js/preview-panel.js +2 -1
  31. wagtail/admin/static/wagtailadmin/js/preview-panel.js.LICENSE.txt +11 -0
  32. wagtail/admin/static/wagtailadmin/js/privacy-switch.js +1 -1
  33. wagtail/admin/static/wagtailadmin/js/sidebar.js +1 -1
  34. wagtail/admin/static/wagtailadmin/js/task-chooser-modal.js +1 -1
  35. wagtail/admin/static/wagtailadmin/js/task-chooser.js +1 -1
  36. wagtail/admin/static/wagtailadmin/js/telepath/blocks.js +1 -1
  37. wagtail/admin/static/wagtailadmin/js/telepath/widgets.js +1 -1
  38. wagtail/admin/static/wagtailadmin/js/userbar.js +2 -1
  39. wagtail/admin/static/wagtailadmin/js/userbar.js.LICENSE.txt +11 -0
  40. wagtail/admin/static/wagtailadmin/js/vendor.js +1 -1
  41. wagtail/admin/static/wagtailadmin/js/vendor.js.LICENSE.txt +0 -12
  42. wagtail/admin/static/wagtailadmin/js/wagtailadmin.js +1 -1
  43. wagtail/admin/static/wagtailadmin/js/workflow-action.js +1 -1
  44. wagtail/admin/templates/wagtailadmin/collection_privacy/ancestor_privacy.html +2 -6
  45. wagtail/admin/templates/wagtailadmin/generic/index_results.html +1 -17
  46. wagtail/admin/templates/wagtailadmin/generic/listing_results.html +20 -1
  47. wagtail/admin/templates/wagtailadmin/home/workflow_objects_to_moderate.html +2 -11
  48. wagtail/admin/templates/wagtailadmin/page_privacy/ancestor_privacy.html +2 -6
  49. wagtail/admin/templates/wagtailadmin/page_privacy/no_privacy.html +2 -0
  50. wagtail/admin/templates/wagtailadmin/pages/_editor_js.html +0 -1
  51. wagtail/admin/templates/wagtailadmin/pages/action_menu/menu.html +1 -1
  52. wagtail/admin/templates/wagtailadmin/reports/aging_pages_results.html +54 -0
  53. wagtail/admin/templates/wagtailadmin/reports/base_page_report.html +1 -17
  54. wagtail/admin/templates/wagtailadmin/reports/base_page_report_results.html +10 -0
  55. wagtail/admin/templates/wagtailadmin/reports/base_report.html +1 -40
  56. wagtail/admin/templates/wagtailadmin/reports/base_report_results.html +1 -0
  57. wagtail/admin/templates/wagtailadmin/reports/listing/_list_page_report.html +21 -27
  58. wagtail/admin/templates/wagtailadmin/reports/listing/_list_page_types_usage.html +48 -54
  59. wagtail/admin/templates/wagtailadmin/reports/{locked_pages.html → locked_pages_results.html} +3 -3
  60. wagtail/admin/templates/wagtailadmin/reports/page_types_usage_results.html +10 -0
  61. wagtail/admin/templates/wagtailadmin/reports/site_history_results.html +53 -0
  62. wagtail/admin/templates/wagtailadmin/reports/workflow_results.html +74 -0
  63. wagtail/admin/templates/wagtailadmin/reports/workflow_tasks_results.html +56 -0
  64. wagtail/admin/templates/wagtailadmin/shared/_workflow_init.html +8 -44
  65. wagtail/admin/templates/wagtailadmin/shared/avatar.html +11 -1
  66. wagtail/admin/templates/wagtailadmin/shared/dialog/dialog.html +5 -4
  67. wagtail/admin/templates/wagtailadmin/shared/dropdown/dropdown_button.html +2 -1
  68. wagtail/admin/templates/wagtailadmin/shared/editing_sessions/list.html +132 -0
  69. wagtail/admin/templates/wagtailadmin/shared/editing_sessions/module.html +44 -0
  70. wagtail/admin/templates/wagtailadmin/shared/headers/slim_header.html +7 -1
  71. wagtail/admin/templates/wagtailadmin/shared/page_status_tag_new.html +1 -1
  72. wagtail/admin/templates/wagtailadmin/shared/side_panels/checks.html +32 -16
  73. wagtail/admin/templates/wagtailadmin/skeleton.html +1 -1
  74. wagtail/admin/templates/wagtailadmin/userbar/item_accessibility.html +9 -11
  75. wagtail/admin/templatetags/wagtailadmin_tags.py +13 -2
  76. wagtail/admin/tests/formats/en/__init__.py +0 -0
  77. wagtail/admin/tests/formats/en/formats.py +1 -0
  78. wagtail/admin/tests/pages/test_create_page.py +47 -0
  79. wagtail/admin/tests/pages/test_edit_page.py +10 -8
  80. wagtail/admin/tests/pages/test_parent_page_chooser_view.py +45 -1
  81. wagtail/admin/tests/test_checks.py +53 -3
  82. wagtail/admin/tests/test_collections_views.py +62 -1
  83. wagtail/admin/tests/test_edit_handlers.py +37 -0
  84. wagtail/admin/tests/test_editing_sessions.py +1336 -0
  85. wagtail/admin/tests/test_icon_sprite.py +12 -21
  86. wagtail/admin/tests/test_page_chooser.py +309 -7
  87. wagtail/admin/tests/test_privacy.py +82 -0
  88. wagtail/admin/tests/test_reports_views.py +464 -70
  89. wagtail/admin/tests/test_userbar.py +93 -6
  90. wagtail/admin/tests/test_workflows.py +223 -33
  91. wagtail/admin/tests/viewsets/test_model_viewset.py +151 -2
  92. wagtail/admin/ui/editing_sessions.py +57 -0
  93. wagtail/admin/urls/__init__.py +9 -15
  94. wagtail/admin/urls/editing_sessions.py +17 -0
  95. wagtail/admin/urls/reports.py +33 -1
  96. wagtail/admin/userbar.py +77 -20
  97. wagtail/admin/views/chooser.py +49 -22
  98. wagtail/admin/views/collections.py +0 -11
  99. wagtail/admin/views/editing_sessions.py +193 -0
  100. wagtail/admin/views/generic/__init__.py +1 -0
  101. wagtail/admin/views/generic/history.py +9 -3
  102. wagtail/admin/views/generic/mixins.py +44 -3
  103. wagtail/admin/views/generic/models.py +46 -72
  104. wagtail/admin/views/generic/permissions.py +20 -10
  105. wagtail/admin/views/home.py +2 -31
  106. wagtail/admin/views/page_privacy.py +20 -5
  107. wagtail/admin/views/pages/choose_parent.py +62 -0
  108. wagtail/admin/views/pages/edit.py +28 -0
  109. wagtail/admin/views/reports/aging_pages.py +6 -10
  110. wagtail/admin/views/reports/audit_logging.py +13 -42
  111. wagtail/admin/views/reports/base.py +31 -4
  112. wagtail/admin/views/reports/locked_pages.py +5 -8
  113. wagtail/admin/views/reports/page_types_usage.py +6 -10
  114. wagtail/admin/views/reports/workflows.py +36 -12
  115. wagtail/admin/viewsets/base.py +8 -3
  116. wagtail/admin/viewsets/chooser.py +1 -1
  117. wagtail/admin/viewsets/model.py +26 -1
  118. wagtail/admin/wagtail_hooks.py +2 -1
  119. wagtail/api/v2/filters.py +6 -0
  120. wagtail/api/v2/tests/test_documents.py +1 -1
  121. wagtail/api/v2/tests/test_images.py +1 -1
  122. wagtail/api/v2/tests/test_pages.py +11 -1
  123. wagtail/api/v2/utils.py +2 -2
  124. wagtail/blocks/base.py +35 -12
  125. wagtail/blocks/definition_lookup.py +85 -0
  126. wagtail/blocks/list_block.py +12 -0
  127. wagtail/blocks/migrations/migrate_operation.py +2 -0
  128. wagtail/blocks/stream_block.py +19 -0
  129. wagtail/blocks/struct_block.py +19 -0
  130. wagtail/contrib/forms/locale/en/LC_MESSAGES/django.po +1 -1
  131. wagtail/contrib/frontend_cache/backends/__init__.py +5 -0
  132. wagtail/contrib/frontend_cache/backends/azure.py +179 -0
  133. wagtail/contrib/frontend_cache/backends/base.py +28 -0
  134. wagtail/contrib/frontend_cache/backends/cloudflare.py +114 -0
  135. wagtail/contrib/frontend_cache/backends/cloudfront.py +99 -0
  136. wagtail/contrib/frontend_cache/backends/http.py +64 -0
  137. wagtail/contrib/frontend_cache/tests.py +59 -17
  138. wagtail/contrib/frontend_cache/utils.py +26 -8
  139. wagtail/contrib/redirects/filters.py +15 -1
  140. wagtail/contrib/redirects/locale/en/LC_MESSAGES/django.po +37 -72
  141. wagtail/contrib/redirects/models.py +6 -5
  142. wagtail/contrib/redirects/templates/wagtailredirects/edit.html +1 -38
  143. wagtail/contrib/redirects/tests/test_redirects.py +141 -1
  144. wagtail/contrib/redirects/urls.py +1 -2
  145. wagtail/contrib/redirects/views.py +39 -80
  146. wagtail/contrib/routable_page/models.py +6 -4
  147. wagtail/contrib/routable_page/tests.py +11 -0
  148. wagtail/contrib/search_promotions/locale/en/LC_MESSAGES/django.po +1 -1
  149. wagtail/contrib/settings/locale/en/LC_MESSAGES/django.po +4 -4
  150. wagtail/contrib/simple_translation/locale/en/LC_MESSAGES/django.po +5 -1
  151. wagtail/contrib/simple_translation/models.py +2 -1
  152. wagtail/contrib/styleguide/locale/en/LC_MESSAGES/django.po +7 -7
  153. wagtail/contrib/table_block/locale/en/LC_MESSAGES/django.po +1 -1
  154. wagtail/contrib/table_block/static/table_block/js/table.js +1 -1
  155. wagtail/contrib/typed_table_block/blocks.py +19 -0
  156. wagtail/contrib/typed_table_block/locale/en/LC_MESSAGES/django.po +10 -10
  157. wagtail/contrib/typed_table_block/static/typed_table_block/js/typed_table_block.js +1 -1
  158. wagtail/contrib/typed_table_block/tests.py +38 -0
  159. wagtail/coreutils.py +1 -1
  160. wagtail/documents/__init__.py +1 -1
  161. wagtail/documents/locale/en/LC_MESSAGES/django.po +8 -8
  162. wagtail/documents/locale/sl/LC_MESSAGES/django.mo +0 -0
  163. wagtail/documents/locale/sl/LC_MESSAGES/django.po +20 -0
  164. wagtail/documents/models.py +5 -1
  165. wagtail/documents/static/wagtaildocs/js/document-chooser-modal.js +1 -1
  166. wagtail/documents/static/wagtaildocs/js/document-chooser-telepath.js +1 -1
  167. wagtail/documents/static/wagtaildocs/js/document-chooser.js +1 -1
  168. wagtail/documents/tests/test_models.py +5 -1
  169. wagtail/embeds/apps.py +2 -0
  170. wagtail/embeds/embeds.py +12 -14
  171. wagtail/embeds/finders/__init__.py +2 -0
  172. wagtail/embeds/finders/facebook.py +17 -33
  173. wagtail/embeds/finders/instagram.py +19 -16
  174. wagtail/embeds/locale/en/LC_MESSAGES/django.po +1 -1
  175. wagtail/embeds/signal_handlers.py +13 -0
  176. wagtail/embeds/tests/test_embeds.py +7 -7
  177. wagtail/fields.py +58 -14
  178. wagtail/images/__init__.py +1 -1
  179. wagtail/images/locale/en/LC_MESSAGES/django.po +34 -34
  180. wagtail/images/locale/sl/LC_MESSAGES/django.mo +0 -0
  181. wagtail/images/locale/sl/LC_MESSAGES/django.po +20 -0
  182. wagtail/images/models.py +2 -0
  183. wagtail/images/static/wagtailimages/js/image-chooser-modal.js +1 -1
  184. wagtail/images/static/wagtailimages/js/image-chooser-telepath.js +1 -1
  185. wagtail/images/static/wagtailimages/js/image-chooser.js +1 -1
  186. wagtail/images/templates/wagtailimages/images/edit.html +4 -4
  187. wagtail/images/tests/test_admin_views.py +26 -2
  188. wagtail/images/views/chooser.py +6 -1
  189. wagtail/locale/en/LC_MESSAGES/django.po +84 -80
  190. wagtail/locales/locale/en/LC_MESSAGES/django.po +2 -2
  191. wagtail/locales/tests.py +16 -0
  192. wagtail/locales/wagtail_hooks.py +0 -9
  193. wagtail/migrations/0094_alter_page_locale.py +19 -0
  194. wagtail/models/__init__.py +11 -5
  195. wagtail/models/i18n.py +6 -1
  196. wagtail/project_template/requirements.txt +1 -1
  197. wagtail/search/locale/en/LC_MESSAGES/django.po +1 -1
  198. wagtail/signals.py +4 -0
  199. wagtail/sites/locale/en/LC_MESSAGES/django.po +2 -2
  200. wagtail/sites/tests.py +15 -0
  201. wagtail/sites/wagtail_hooks.py +0 -9
  202. wagtail/snippets/locale/en/LC_MESSAGES/django.po +9 -9
  203. wagtail/snippets/permissions.py +5 -3
  204. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser-telepath.js +1 -1
  205. wagtail/snippets/static/wagtailsnippets/js/snippet-chooser.js +1 -1
  206. wagtail/snippets/templates/wagtailsnippets/snippets/action_menu/menu.html +1 -1
  207. wagtail/snippets/tests/test_snippets.py +78 -12
  208. wagtail/snippets/tests/test_viewset.py +22 -0
  209. wagtail/snippets/views/snippets.py +19 -14
  210. wagtail/snippets/wagtail_hooks.py +2 -10
  211. wagtail/templatetags/wagtailcore_tags.py +3 -0
  212. wagtail/test/dummy_external_storage.py +1 -1
  213. wagtail/test/i18n/migrations/0003_alter_clusterabletestmodel_locale_and_more.py +40 -0
  214. wagtail/test/routablepage/models.py +4 -0
  215. wagtail/test/snippets/migrations/0012_alter_translatablesnippet_locale.py +20 -0
  216. wagtail/test/testapp/migrations/0038_sociallink.py +52 -0
  217. wagtail/test/testapp/migrations/0039_alter_eventcategory_locale_and_more.py +45 -0
  218. wagtail/test/testapp/models.py +24 -0
  219. wagtail/test/testapp/views.py +1 -0
  220. wagtail/test/testapp/wagtail_hooks.py +9 -0
  221. wagtail/test/urls_multilang.py +6 -1
  222. wagtail/test/urls_multilang_non_root.py +11 -0
  223. wagtail/tests/streamfield_migrations/test_migrations.py +53 -12
  224. wagtail/tests/test_audit_log.py +72 -2
  225. wagtail/tests/test_blocks.py +103 -0
  226. wagtail/tests/test_signals.py +49 -2
  227. wagtail/tests/test_streamfield.py +153 -0
  228. wagtail/tests/test_utils.py +14 -0
  229. wagtail/tests/tests.py +5 -0
  230. wagtail/users/apps.py +1 -0
  231. wagtail/users/forms.py +7 -0
  232. wagtail/users/locale/en/LC_MESSAGES/django.po +55 -50
  233. wagtail/users/models.py +1 -0
  234. wagtail/users/templates/wagtailusers/groups/includes/formatted_permissions.html +3 -2
  235. wagtail/users/templatetags/wagtailusers_tags.py +9 -0
  236. wagtail/users/tests/__init__.py +7 -1
  237. wagtail/users/tests/test_admin_views.py +117 -32
  238. wagtail/users/views/groups.py +4 -0
  239. wagtail/users/views/users.py +58 -14
  240. wagtail/users/wagtail_hooks.py +7 -123
  241. wagtail/utils/utils.py +1 -0
  242. wagtail/utils/version.py +5 -2
  243. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/METADATA +3 -3
  244. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/RECORD +248 -222
  245. wagtail/admin/templates/wagtailadmin/reports/aging_pages.html +0 -58
  246. wagtail/admin/templates/wagtailadmin/reports/page_types_usage.html +0 -18
  247. wagtail/admin/templates/wagtailadmin/reports/site_history.html +0 -57
  248. wagtail/admin/templates/wagtailadmin/reports/workflow.html +0 -81
  249. wagtail/admin/templates/wagtailadmin/reports/workflow_tasks.html +0 -63
  250. wagtail/contrib/frontend_cache/backends.py +0 -400
  251. wagtail/contrib/redirects/templates/wagtailredirects/list.html +0 -43
  252. wagtail/contrib/redirects/templates/wagtailredirects/reports/redirects_report.html +0 -14
  253. wagtail/contrib/redirects/tests/test_reports_view.py +0 -82
  254. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/LICENSE +0 -0
  255. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/WHEEL +0 -0
  256. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/entry_points.txt +0 -0
  257. {wagtail-6.1.3.dist-info → wagtail-6.2rc1.dist-info}/top_level.txt +0 -0
@@ -1,58 +0,0 @@
1
- {% extends 'wagtailadmin/reports/base_report.html' %}
2
- {% load i18n wagtailadmin_tags %}
3
-
4
- {% block results %}
5
- {% if object_list %}
6
- <table class="listing">
7
- <thead>
8
- <tr>
9
- <th class="title">
10
- {% trans 'Title' %}
11
- </th>
12
- <th class="status">
13
- {% trans 'Status' %}
14
- </th>
15
- <th class="updated">
16
- {% trans 'Last published' %}
17
- </th>
18
- <th>{% trans 'Last published by' %}</th>
19
- <th class="type">
20
- {% trans 'Type' %}
21
- </th>
22
- </tr>
23
- </thead>
24
- <tbody>
25
- {% for page in object_list %}
26
- <tr>
27
- <td class="title" valign="top">
28
- <a href="{% url 'wagtailadmin_pages:edit' page.id %}">
29
- {{ page.specific_deferred.get_admin_display_title }}
30
- </a>
31
- {% i18n_enabled as show_locale_labels %}
32
- {% if show_locale_labels %}
33
- {% locale_label_from_id page.locale_id as locale_label %}
34
- {% status locale_label classname="w-status--label" %}
35
- {% endif %}
36
- </td>
37
- <td class="status" valign="top">
38
- {% include "wagtailadmin/shared/page_status_tag.html" with page=page %}
39
- </td>
40
- <td class="updated" valign="top">{% human_readable_date page.last_published_at %}</td>
41
- <td valign="top">
42
- {% include "wagtailadmin/shared/user_avatar.html" with user=page.last_published_by username=page.last_published_by_user %}
43
- </td>
44
- <td valign="top">
45
- {{ page.content_type|format_content_type }}
46
- </td>
47
- </tr>
48
- {% endfor %}
49
- </tbody>
50
- </table>
51
- {% else %}
52
- <p>{% trans "No pages found." %}</p>
53
- {% endif %}
54
- {% endblock %}
55
-
56
- {% block no_results %}
57
- <p>{% trans "No pages found." %}</p>
58
- {% endblock %}
@@ -1,18 +0,0 @@
1
- {% extends "wagtailadmin/reports/base_report.html" %}
2
- {% load i18n wagtailadmin_tags %}
3
-
4
- {% block results %}
5
- {% with object_list as page_types %}
6
- <div id="page-results">
7
- {% if page_types %}
8
- {% block listing %}
9
- {% include "wagtailadmin/reports/listing/_list_page_types_usage.html" %}
10
- {% endblock listing %}
11
- {% else %}
12
- {% block no_results %}
13
- <p>{% trans "No page types found." %}</p>
14
- {% endblock no_results %}
15
- {% endif %}
16
- </div>
17
- {% endwith %}
18
- {% endblock results %}
@@ -1,57 +0,0 @@
1
- {% extends 'wagtailadmin/reports/base_report.html' %}
2
- {% load i18n wagtailadmin_tags %}
3
-
4
- {% block results %}
5
- {% if object_list %}
6
- <table class="listing">
7
- <thead>
8
- <tr>
9
- <th class="title">
10
- {% trans 'Name' %}
11
- </th>
12
- <th>
13
- {% trans 'Type' %}
14
- </th>
15
- <th>
16
- {% trans 'Action' %}
17
- </th>
18
- <th>
19
- {% trans 'User' %}
20
- </th>
21
- <th class="updated">
22
- {% trans 'Date / Time' %}
23
- </th>
24
- </tr>
25
- </thead>
26
- <tbody>
27
- {% for entry in object_list %}
28
- <tr>
29
- <td class="title">
30
- {% if entry.edit_url %}
31
- <a href="{{ entry.edit_url }}" title="{% trans 'Edit this item' %}">{{ entry.label }}</a>
32
- {% else %}
33
- {{ entry.label }}
34
- {% endif %}
35
- </td>
36
- <td>
37
- {{ entry.content_type|format_content_type }}
38
- </td>
39
- <td>
40
- {{ entry.message }}
41
- </td>
42
- <td>
43
- {% include "wagtailadmin/shared/user_avatar.html" with user=entry.user username=entry.user_display_name %}
44
- </td>
45
- <td class="updated">{% human_readable_date entry.timestamp %}</td>
46
- </tr>
47
- {% endfor %}
48
- </tbody>
49
- </table>
50
- {% else %}
51
- <p>{% trans "No log entries found." %}</p>
52
- {% endif %}
53
- {% endblock %}
54
-
55
- {% block no_results %}
56
- <p>{% trans "No log entries found." %}</p>
57
- {% endblock %}
@@ -1,81 +0,0 @@
1
- {% extends 'wagtailadmin/reports/base_report.html' %}
2
- {% load i18n wagtailadmin_tags %}
3
-
4
- {% block actions %}
5
- <div>
6
- <a href="{% url 'wagtailadmin_reports:workflow_tasks' %}" class="button">{% trans "By Task" %}</a>
7
- </div>
8
- {{ block.super }}
9
- {% endblock %}
10
-
11
- {% block results %}
12
- {% if object_list %}
13
- <table class="listing">
14
- <thead>
15
- <tr>
16
- <th>
17
- {% trans 'Workflow' %}
18
- </th>
19
- <th>
20
- {% trans 'Page/Snippet' %}
21
- </th>
22
- <th>
23
- {% trans 'Status' %}
24
- </th>
25
- <th>
26
- {% trans 'Tasks' %}
27
- </th>
28
- <th>
29
- {% trans 'Requested by' %}
30
- </th>
31
- <th>
32
- {% trans 'Started at' %}
33
- </th>
34
- </tr>
35
- </thead>
36
- <tbody>
37
- {% for workflow_state in object_list %}
38
- <tr>
39
- <td>
40
- <b>{{ workflow_state.workflow }}</b>
41
- </td>
42
- <td>
43
- <a href="{% admin_edit_url workflow_state.content_object %}">
44
- {% latest_str workflow_state.content_object %}
45
- </a>
46
- {% i18n_enabled as show_locale_labels %}
47
- {% if show_locale_labels and workflow_state.content_object.locale_id %}
48
- {% locale_label_from_id workflow_state.content_object.locale_id as locale_label %}
49
- {% status locale_label classname="w-status--label" %}
50
- {% endif %}
51
- </td>
52
- <td>
53
- {% admin_url_name workflow_state.content_object 'workflow_history_detail' as workflow_history_detail_url_name %}
54
- {% url workflow_history_detail_url_name workflow_state.content_object.pk|admin_urlquote workflow_state.pk as status_url %}
55
- {% status workflow_state.get_status_display url=status_url classname="w-status--primary" %}
56
- </td>
57
- <td>
58
- <h2>{{ workflow_name }}</h2>
59
- {% trans 'Incomplete task' as incomplete_title %}
60
- {% for task in workflow_state.all_tasks_with_status %}
61
- <span data-controller="w-tooltip" data-w-tooltip-content-value="{{ task.name }}: {{ task.status_display }}">
62
- {% if task.status == 'approved' %}
63
- {% icon "success" title=task.status_display classname="initial" %}
64
- {% elif task.status == 'rejected' %}
65
- {% icon "error" title=task.status_display classname="initial" %}
66
- {% else %}
67
- {% icon "radio-empty" title=incomplete_title classname="initial" %}
68
- {% endif %}
69
- </span>
70
- {% endfor %}
71
- </td>
72
- <td>{{ workflow_state.requested_by|user_display_name }}</td>
73
- <td>{{ workflow_state.created_at }}</td>
74
- </tr>
75
- {% endfor %}
76
- </tbody>
77
- </table>
78
- {% else %}
79
- <p>{% trans "No pages/snippets have been submitted for moderation yet" %}</p>
80
- {% endif %}
81
- {% endblock %}
@@ -1,63 +0,0 @@
1
- {% extends 'wagtailadmin/reports/base_report.html' %}
2
- {% load i18n wagtailadmin_tags %}
3
-
4
- {% block actions %}
5
- <div>
6
- <a href="{% url 'wagtailadmin_reports:workflow' %}" class="button">{% trans 'By Workflow' %}</a>
7
- </div>
8
- {{ block.super }}
9
- {% endblock %}
10
-
11
- {% block results %}
12
- {% if object_list %}
13
- <table class="listing">
14
- <thead>
15
- <tr>
16
- <th>
17
- {% trans 'Task' %}
18
- </th>
19
- <th>
20
- {% trans 'Page/Snippet' %}
21
- </th>
22
- <th>
23
- {% trans 'Status' %}
24
- </th>
25
- <th>
26
- {% trans 'Started at' %}
27
- </th>
28
- <th>
29
- {% trans 'Completed at' %}
30
- </th>
31
- </tr>
32
- </thead>
33
- <tbody>
34
- {% for task_state in object_list %}
35
- <tr>
36
- <td>
37
- <b>{{ task_state.task }}</b>
38
- </td>
39
- <td>
40
- {% with task_state.workflow_state.content_object as object %}
41
- <a href="{% admin_edit_url object %}">
42
- {% latest_str object %}
43
- </a>
44
- {% i18n_enabled as show_locale_labels %}
45
- {% if show_locale_labels and object.locale_id %}
46
- {% locale_label_from_id object.locale_id as locale_label %}
47
- {% status locale_label classname="w-status--label" %}
48
- {% endif %}
49
- {% endwith %}
50
- </td>
51
- <td>
52
- {% status task_state.get_status_display classname="w-status--primary" %}
53
- </td>
54
- <td>{{ task_state.started_at }}</td>
55
- <td>{{ task_state.finished_at }}</td>
56
- </tr>
57
- {% endfor %}
58
- </tbody>
59
- </table>
60
- {% else %}
61
- <p>{% trans "No pages/snippets have been submitted for moderation yet" %}</p>
62
- {% endif %}
63
- {% endblock %}
@@ -1,400 +0,0 @@
1
- import logging
2
- import uuid
3
- from collections import defaultdict
4
- from urllib.error import HTTPError, URLError
5
- from urllib.parse import urlparse, urlsplit, urlunparse, urlunsplit
6
- from urllib.request import Request, urlopen
7
-
8
- import requests
9
- from django.core.exceptions import ImproperlyConfigured
10
-
11
- from wagtail import __version__
12
-
13
- logger = logging.getLogger("wagtail.frontendcache")
14
-
15
-
16
- class PurgeRequest(Request):
17
- def get_method(self):
18
- return "PURGE"
19
-
20
-
21
- class BaseBackend:
22
- def purge(self, url):
23
- raise NotImplementedError
24
-
25
- def purge_batch(self, urls):
26
- # Fallback for backends that do not support batch purging
27
- for url in urls:
28
- self.purge(url)
29
-
30
-
31
- class HTTPBackend(BaseBackend):
32
- def __init__(self, params):
33
- location_url_parsed = urlparse(params.pop("LOCATION"))
34
- self.cache_scheme = location_url_parsed.scheme
35
- self.cache_netloc = location_url_parsed.netloc
36
-
37
- def purge(self, url):
38
- url_parsed = urlparse(url)
39
- host = url_parsed.hostname
40
-
41
- # Append port to host if it is set in the original URL
42
- if url_parsed.port:
43
- host += ":" + str(url_parsed.port)
44
-
45
- request = PurgeRequest(
46
- url=urlunparse(
47
- [
48
- self.cache_scheme,
49
- self.cache_netloc,
50
- url_parsed.path,
51
- url_parsed.params,
52
- url_parsed.query,
53
- url_parsed.fragment,
54
- ]
55
- ),
56
- headers={
57
- "Host": host,
58
- "User-Agent": "Wagtail-frontendcache/" + __version__,
59
- },
60
- )
61
-
62
- try:
63
- urlopen(request)
64
- except HTTPError as e:
65
- logger.error(
66
- "Couldn't purge '%s' from HTTP cache. HTTPError: %d %s",
67
- url,
68
- e.code,
69
- e.reason,
70
- )
71
- except URLError as e:
72
- logger.error(
73
- "Couldn't purge '%s' from HTTP cache. URLError: %s", url, e.reason
74
- )
75
-
76
-
77
- class CloudflareBackend(BaseBackend):
78
- CHUNK_SIZE = 30
79
-
80
- def __init__(self, params):
81
- self.cloudflare_email = params.pop("EMAIL", None)
82
- self.cloudflare_api_key = params.pop("TOKEN", None) or params.pop(
83
- "API_KEY", None
84
- )
85
- self.cloudflare_token = params.pop("BEARER_TOKEN", None)
86
- self.cloudflare_zoneid = params.pop("ZONEID")
87
-
88
- if (
89
- (not self.cloudflare_email and self.cloudflare_api_key)
90
- or (self.cloudflare_email and not self.cloudflare_api_key)
91
- or (
92
- not any(
93
- [
94
- self.cloudflare_email,
95
- self.cloudflare_api_key,
96
- self.cloudflare_token,
97
- ]
98
- )
99
- )
100
- ):
101
- raise ImproperlyConfigured(
102
- "The setting 'WAGTAILFRONTENDCACHE' requires both 'EMAIL' and 'API_KEY', or 'BEARER_TOKEN' to be specified."
103
- )
104
-
105
- def _purge_urls(self, urls):
106
- try:
107
- purge_url = (
108
- "https://api.cloudflare.com/client/v4/zones/{}/purge_cache".format(
109
- self.cloudflare_zoneid
110
- )
111
- )
112
-
113
- headers = {"Content-Type": "application/json"}
114
-
115
- if self.cloudflare_token:
116
- headers["Authorization"] = f"Bearer {self.cloudflare_token}"
117
- else:
118
- headers["X-Auth-Email"] = self.cloudflare_email
119
- headers["X-Auth-Key"] = self.cloudflare_api_key
120
-
121
- data = {"files": urls}
122
-
123
- response = requests.delete(
124
- purge_url,
125
- json=data,
126
- headers=headers,
127
- )
128
-
129
- try:
130
- response_json = response.json()
131
- except ValueError:
132
- if response.status_code != 200:
133
- response.raise_for_status()
134
- else:
135
- for url in urls:
136
- logger.error(
137
- "Couldn't purge '%s' from Cloudflare. Unexpected JSON parse error.",
138
- url,
139
- )
140
-
141
- except requests.exceptions.HTTPError as e:
142
- for url in urls:
143
- logging.exception(
144
- "Couldn't purge '%s' from Cloudflare. HTTPError: %d",
145
- url,
146
- e.response.status_code,
147
- )
148
- return
149
-
150
- if response_json["success"] is False:
151
- error_messages = ", ".join(
152
- [str(err["message"]) for err in response_json["errors"]]
153
- )
154
- for url in urls:
155
- logger.error(
156
- "Couldn't purge '%s' from Cloudflare. Cloudflare errors '%s'",
157
- url,
158
- error_messages,
159
- )
160
- return
161
-
162
- def purge_batch(self, urls):
163
- # Break the batched URLs in to chunks to fit within Cloudflare's maximum size for
164
- # the purge_cache call (https://api.cloudflare.com/#zone-purge-files-by-url)
165
- for i in range(0, len(urls), self.CHUNK_SIZE):
166
- chunk = urls[i : i + self.CHUNK_SIZE]
167
- self._purge_urls(chunk)
168
-
169
- def purge(self, url):
170
- self.purge_batch([url])
171
-
172
-
173
- class CloudfrontBackend(BaseBackend):
174
- def __init__(self, params):
175
- import boto3
176
-
177
- self.client = boto3.client("cloudfront")
178
- try:
179
- self.cloudfront_distribution_id = params.pop("DISTRIBUTION_ID")
180
- except KeyError:
181
- raise ImproperlyConfigured(
182
- "The setting 'WAGTAILFRONTENDCACHE' requires the object 'DISTRIBUTION_ID'."
183
- )
184
-
185
- def purge_batch(self, urls):
186
- paths_by_distribution_id = defaultdict(list)
187
-
188
- for url in urls:
189
- url_parsed = urlparse(url)
190
- distribution_id = None
191
-
192
- if isinstance(self.cloudfront_distribution_id, dict):
193
- host = url_parsed.hostname
194
- if host in self.cloudfront_distribution_id:
195
- distribution_id = self.cloudfront_distribution_id.get(host)
196
- else:
197
- logger.info(
198
- "Couldn't purge '%s' from CloudFront. Hostname '%s' not found in the DISTRIBUTION_ID mapping",
199
- url,
200
- host,
201
- )
202
- else:
203
- distribution_id = self.cloudfront_distribution_id
204
-
205
- if distribution_id:
206
- paths_by_distribution_id[distribution_id].append(url_parsed.path)
207
-
208
- for distribution_id, paths in paths_by_distribution_id.items():
209
- self._create_invalidation(distribution_id, paths)
210
-
211
- def purge(self, url):
212
- self.purge_batch([url])
213
-
214
- def _create_invalidation(self, distribution_id, paths):
215
- import botocore
216
-
217
- try:
218
- self.client.create_invalidation(
219
- DistributionId=distribution_id,
220
- InvalidationBatch={
221
- "Paths": {"Quantity": len(paths), "Items": paths},
222
- "CallerReference": str(uuid.uuid4()),
223
- },
224
- )
225
- except botocore.exceptions.ClientError as e:
226
- for path in paths:
227
- logger.error(
228
- "Couldn't purge path '%s' from CloudFront (DistributionId=%s). ClientError: %s %s",
229
- path,
230
- distribution_id,
231
- e.response["Error"]["Code"],
232
- e.response["Error"]["Message"],
233
- )
234
-
235
-
236
- class AzureBaseBackend(BaseBackend):
237
- def __init__(self, params):
238
- self._credentials = params.pop("CREDENTIALS", None)
239
- self._subscription_id = params.pop("SUBSCRIPTION_ID", None)
240
- try:
241
- self._resource_group_name = params.pop("RESOURCE_GROUP_NAME")
242
- except KeyError:
243
- raise ImproperlyConfigured(
244
- "The setting 'WAGTAILFRONTENDCACHE' requires 'RESOURCE_GROUP_NAME' to be specified."
245
- )
246
- self._custom_headers = params.pop("CUSTOM_HEADERS", None)
247
-
248
- def purge_batch(self, urls):
249
- self._purge_content([self._get_path(url) for url in urls])
250
-
251
- def purge(self, url):
252
- self.purge_batch([url])
253
-
254
- def _get_default_credentials(self):
255
- try:
256
- from azure.identity import DefaultAzureCredential
257
- except ImportError:
258
- return
259
- return DefaultAzureCredential()
260
-
261
- def _get_credentials(self):
262
- """
263
- Use credentials object set by user. If not set, use the one configured
264
- in the current environment.
265
- """
266
- user_credentials = self._credentials
267
- if user_credentials:
268
- return user_credentials
269
- return self._get_default_credentials()
270
-
271
- def _get_default_subscription_id(self):
272
- """
273
- Obtain subscription ID directly from Azure.
274
- """
275
- try:
276
- from azure.mgmt.resource import SubscriptionClient
277
- except ImportError:
278
- return ""
279
- credential = self._get_credentials()
280
- subscription_client = SubscriptionClient(credential)
281
- subscription = next(subscription_client.subscriptions.list())
282
- return subscription.subscription_id
283
-
284
- def _get_subscription_id(self):
285
- """
286
- Use subscription ID set in the user configuration. If not set, try to
287
- retrieve one from Azure directly.
288
- """
289
- user_subscription_id = self._subscription_id
290
- if user_subscription_id:
291
- return user_subscription_id
292
- return self._get_default_subscription_id()
293
-
294
- def _get_client_kwargs(self):
295
- return {
296
- "credential": self._get_credentials(),
297
- "subscription_id": self._get_subscription_id(),
298
- }
299
-
300
- def _get_path(self, url):
301
- """
302
- Split netloc from the URL and return path only.
303
- """
304
- # Delete scheme and netloc from the URL, that will result in only path being
305
- # left.
306
- url_parts = ("",) * 2 + urlsplit(url)[2:]
307
- return urlunsplit(url_parts)
308
-
309
- def _get_client(self):
310
- """
311
- Get Azure client instance.
312
- """
313
- klass = self._get_client_class()
314
- kwargs = self._get_client_kwargs()
315
- return klass(**kwargs)
316
-
317
- def _get_purge_kwargs(self, paths):
318
- """
319
- Get keyword arguments passes to Azure purge content calls.
320
- """
321
- return {
322
- "resource_group_name": self._resource_group_name,
323
- "custom_headers": self._custom_headers,
324
- "content_paths": paths,
325
- }
326
-
327
- def _purge_content(self, paths):
328
- from msrest.exceptions import HttpOperationError
329
-
330
- client = self._get_client()
331
- try:
332
- self._make_purge_call(client, paths)
333
- except HttpOperationError as exception:
334
- for path in paths:
335
- logger.exception(
336
- "Couldn't purge '%s' from %s cache. HttpOperationError: %r",
337
- path,
338
- type(self).__name__,
339
- exception.response,
340
- )
341
-
342
-
343
- class AzureFrontDoorBackend(AzureBaseBackend):
344
- def __init__(self, params):
345
- super().__init__(params)
346
- try:
347
- self._front_door_name = params.pop("FRONT_DOOR_NAME")
348
- except KeyError:
349
- raise ImproperlyConfigured(
350
- "The setting 'WAGTAILFRONTENDCACHE' requires 'FRONT_DOOR_NAME' to be specified."
351
- )
352
- self._front_door_service_url = params.pop("FRONT_DOOR_SERVICE_URL", None)
353
-
354
- def _get_client_class(self):
355
- from azure.mgmt.frontdoor import FrontDoorManagementClient
356
-
357
- return FrontDoorManagementClient
358
-
359
- def _get_client_kwargs(self):
360
- kwargs = super()._get_client_kwargs()
361
- kwargs.setdefault("base_url", self._front_door_service_url)
362
-
363
- return kwargs
364
-
365
- def _make_purge_call(self, client, paths):
366
- return client.endpoints.purge_content(
367
- **self._get_purge_kwargs(paths),
368
- front_door_name=self._front_door_name,
369
- )
370
-
371
-
372
- class AzureCdnBackend(AzureBaseBackend):
373
- def __init__(self, params):
374
- super().__init__(params)
375
- try:
376
- self._cdn_profile_name = params.pop("CDN_PROFILE_NAME")
377
- self._cdn_endpoint_name = params.pop("CDN_ENDPOINT_NAME")
378
- except KeyError:
379
- raise ImproperlyConfigured(
380
- "The setting 'WAGTAILFRONTENDCACHE' requires 'CDN_PROFILE_NAME' and 'CDN_ENDPOINT_NAME' to be specified."
381
- )
382
- self._cdn_service_url = params.pop("CDN_SERVICE_URL", None)
383
-
384
- def _get_client_class(self):
385
- from azure.mgmt.cdn import CdnManagementClient
386
-
387
- return CdnManagementClient
388
-
389
- def _get_client_kwargs(self):
390
- kwargs = super()._get_client_kwargs()
391
- kwargs.setdefault("base_url", self._cdn_service_url)
392
-
393
- return kwargs
394
-
395
- def _make_purge_call(self, client, paths):
396
- return client.endpoints.purge_content(
397
- **self._get_purge_kwargs(paths),
398
- profile_name=self._cdn_profile_name,
399
- endpoint_name=self._cdn_endpoint_name,
400
- )