wagtail 7.2.1__py3-none-any.whl → 7.3rc1__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.
- wagtail/__init__.py +1 -1
- wagtail/actions/copy_for_translation.py +4 -2
- wagtail/admin/action_menu.py +4 -1
- wagtail/admin/api/actions/convert_alias.py +2 -2
- wagtail/admin/api/actions/copy.py +2 -2
- wagtail/admin/api/actions/copy_for_translation.py +2 -2
- wagtail/admin/api/actions/create_alias.py +2 -2
- wagtail/admin/api/actions/delete.py +1 -1
- wagtail/admin/api/actions/move.py +1 -1
- wagtail/admin/api/actions/publish.py +2 -2
- wagtail/admin/api/actions/revert_to_page_revision.py +2 -2
- wagtail/admin/api/actions/unpublish.py +1 -1
- wagtail/admin/api/filters.py +2 -2
- wagtail/admin/compare.py +22 -0
- wagtail/admin/forms/account.py +52 -1
- wagtail/admin/forms/comments.py +53 -0
- wagtail/admin/forms/models.py +36 -0
- wagtail/admin/forms/pages.py +7 -0
- wagtail/admin/forms/workflows.py +5 -2
- wagtail/admin/icons.py +4 -3
- wagtail/admin/locale/ar/LC_MESSAGES/django.mo +0 -0
- wagtail/admin/locale/ar/LC_MESSAGES/django.po +35 -0
- wagtail/admin/locale/en/LC_MESSAGES/django.po +262 -234
- wagtail/admin/locale/en/LC_MESSAGES/djangojs.po +72 -43
- wagtail/admin/locale/it/LC_MESSAGES/django.mo +0 -0
- wagtail/admin/locale/it/LC_MESSAGES/django.po +566 -6
- wagtail/admin/locale/it/LC_MESSAGES/djangojs.mo +0 -0
- wagtail/admin/locale/it/LC_MESSAGES/djangojs.po +40 -2
- wagtail/admin/locale/nl/LC_MESSAGES/django.mo +0 -0
- wagtail/admin/locale/nl/LC_MESSAGES/django.po +2 -2
- wagtail/admin/locale/sv/LC_MESSAGES/django.mo +0 -0
- wagtail/admin/locale/sv/LC_MESSAGES/django.po +330 -15
- wagtail/admin/locale/sv/LC_MESSAGES/djangojs.mo +0 -0
- wagtail/admin/locale/sv/LC_MESSAGES/djangojs.po +3 -2
- wagtail/admin/panels/comment_panel.py +1 -51
- wagtail/admin/panels/title_field_panel.py +3 -1
- wagtail/admin/static/wagtailadmin/css/core.css +1 -1
- wagtail/admin/static/wagtailadmin/js/bulk-actions.js +1 -1
- wagtail/admin/static/wagtailadmin/js/comments.js +1 -1
- wagtail/admin/static/wagtailadmin/js/core.js +1 -1
- wagtail/admin/static/wagtailadmin/js/core.js.LICENSE.txt +1 -1
- wagtail/admin/static/wagtailadmin/js/draftail.js +1 -1
- wagtail/admin/static/wagtailadmin/js/privacy-switch.js +1 -1
- wagtail/admin/static/wagtailadmin/js/sidebar.js +1 -1
- wagtail/admin/static/wagtailadmin/js/telepath/blocks.js +1 -1
- wagtail/admin/static/wagtailadmin/js/userbar.js +1 -1
- wagtail/admin/static/wagtailadmin/js/userbar.js.LICENSE.txt +1 -1
- wagtail/admin/static/wagtailadmin/js/vendor.js +1 -1
- wagtail/admin/static/wagtailadmin/js/wagtailadmin.js +1 -1
- wagtail/admin/templates/wagtailadmin/base.html +1 -1
- wagtail/admin/templates/wagtailadmin/generic/edit_partials.html +100 -0
- wagtail/admin/templates/wagtailadmin/generic/form.html +26 -5
- wagtail/admin/templates/wagtailadmin/generic/includes/_loaded_revision_inputs.html +3 -0
- wagtail/admin/templates/wagtailadmin/generic/listing_results.html +1 -17
- wagtail/admin/templates/wagtailadmin/pages/create.html +14 -4
- wagtail/admin/templates/wagtailadmin/pages/edit.html +16 -3
- wagtail/admin/templates/wagtailadmin/pages/edit_partials.html +31 -0
- wagtail/admin/templates/wagtailadmin/pages/index_results.html +1 -9
- wagtail/admin/templates/wagtailadmin/shared/autosave/indicator.html +36 -0
- wagtail/admin/templates/wagtailadmin/shared/breadcrumbs.html +1 -1
- wagtail/admin/templates/wagtailadmin/shared/editing_sessions/list.html +2 -2
- wagtail/admin/templates/wagtailadmin/shared/editing_sessions/module.html +19 -3
- wagtail/admin/templates/wagtailadmin/shared/headers/_actions.html +5 -0
- wagtail/admin/templates/wagtailadmin/shared/headers/_history_icon_link.html +2 -2
- wagtail/admin/templates/wagtailadmin/shared/headers/slim_header.html +8 -9
- wagtail/admin/templates/wagtailadmin/shared/listing/filter_partials.html +19 -0
- wagtail/admin/templates/wagtailadmin/shared/side_panel_toggle.html +3 -3
- wagtail/admin/templates/wagtailadmin/shared/side_panels/includes/side_panel.html +3 -0
- wagtail/admin/templates/wagtailadmin/shared/side_panels/includes/status/privacy.html +18 -6
- wagtail/admin/templates/wagtailadmin/shared/side_panels/includes/status/usage.html +11 -4
- wagtail/admin/templates/wagtailadmin/shared/side_panels/includes/status/workflow.html +22 -1
- wagtail/admin/templates/wagtailadmin/shared/side_panels/preview.html +1 -0
- wagtail/admin/templates/wagtailadmin/shared/side_panels.html +1 -2
- wagtail/admin/templates/wagtailadmin/shared/unsaved_changes_warning.html +20 -20
- wagtail/admin/templates/wagtailadmin/userbar/base.html +2 -0
- wagtail/admin/templates/wagtailadmin/userbar/item_accessibility.html +1 -1
- wagtail/admin/templatetags/wagtailadmin_tags.py +6 -14
- wagtail/admin/tests/pages/test_create_page.py +298 -1
- wagtail/admin/tests/pages/test_custom_listing.py +48 -2
- wagtail/admin/tests/pages/test_edit_page.py +721 -7
- wagtail/admin/tests/pages/test_explorer_view.py +9 -5
- wagtail/admin/tests/pages/test_page_search.py +15 -0
- wagtail/admin/tests/pages/test_revisions.py +4 -0
- wagtail/admin/tests/test_account_management.py +88 -2
- wagtail/admin/tests/test_collections_views.py +15 -15
- wagtail/admin/tests/test_compare.py +34 -0
- wagtail/admin/tests/test_editing_sessions.py +230 -8
- wagtail/admin/tests/test_forms.py +192 -1
- wagtail/admin/tests/test_icon_sprite.py +22 -2
- wagtail/admin/tests/test_page_chooser.py +13 -13
- wagtail/admin/tests/test_reports_views.py +4 -1
- wagtail/admin/tests/test_userbar.py +69 -5
- wagtail/admin/tests/test_views_generic.py +5 -5
- wagtail/admin/tests/test_workflows.py +14 -12
- wagtail/admin/tests/viewsets/test_model_viewset.py +13 -0
- wagtail/admin/ui/autosave.py +5 -0
- wagtail/admin/ui/editing_sessions.py +3 -0
- wagtail/admin/ui/side_panels.py +19 -20
- wagtail/admin/userbar.py +48 -27
- wagtail/admin/views/bulk_action/dispatcher.py +2 -2
- wagtail/admin/views/chooser.py +6 -6
- wagtail/admin/views/editing_sessions.py +20 -7
- wagtail/admin/views/generic/__init__.py +1 -0
- wagtail/admin/views/generic/chooser.py +5 -5
- wagtail/admin/views/generic/mixins.py +143 -26
- wagtail/admin/views/generic/models.py +157 -27
- wagtail/admin/views/generic/ordering.py +1 -1
- wagtail/admin/views/generic/preview.py +4 -4
- wagtail/admin/views/home.py +3 -1
- wagtail/admin/views/pages/create.py +77 -29
- wagtail/admin/views/pages/edit.py +160 -34
- wagtail/admin/views/pages/preview.py +4 -4
- wagtail/admin/views/pages/revisions.py +3 -0
- wagtail/admin/views/pages/search.py +4 -4
- wagtail/admin/views/pages/usage.py +2 -2
- wagtail/admin/views/tags.py +2 -2
- wagtail/admin/views/workflows.py +73 -54
- wagtail/admin/viewsets/model.py +34 -8
- wagtail/admin/widgets/button.py +11 -4
- wagtail/admin/widgets/chooser.py +6 -4
- wagtail/admin/widgets/slug.py +1 -1
- wagtail/api/v2/filters.py +27 -21
- wagtail/api/v2/pagination.py +4 -4
- wagtail/api/v2/views.py +7 -7
- wagtail/blocks/list_block.py +0 -8
- wagtail/blocks/migrations/migrate_operation.py +8 -1
- wagtail/blocks/stream_block.py +2 -10
- wagtail/blocks/struct_block.py +192 -12
- wagtail/compat.py +2 -2
- wagtail/contrib/forms/locale/en/LC_MESSAGES/django.po +1 -1
- wagtail/contrib/forms/locale/it/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/forms/locale/it/LC_MESSAGES/django.po +40 -2
- wagtail/contrib/frontend_cache/backends/azure.py +6 -6
- wagtail/contrib/frontend_cache/backends/cloudfront.py +2 -2
- wagtail/contrib/frontend_cache/utils.py +1 -1
- wagtail/contrib/redirects/forms.py +1 -1
- wagtail/contrib/redirects/locale/en/LC_MESSAGES/django.po +14 -14
- wagtail/contrib/redirects/locale/it/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/redirects/locale/it/LC_MESSAGES/django.po +15 -2
- wagtail/contrib/redirects/locale/sv/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/redirects/locale/sv/LC_MESSAGES/django.po +16 -3
- wagtail/contrib/redirects/middleware.py +11 -7
- wagtail/contrib/redirects/models.py +17 -1
- wagtail/contrib/redirects/tests/test_import_admin_views.py +3 -3
- wagtail/contrib/redirects/tests/test_redirects.py +56 -8
- wagtail/contrib/search_promotions/locale/en/LC_MESSAGES/django.po +6 -6
- wagtail/contrib/search_promotions/locale/it/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/search_promotions/locale/it/LC_MESSAGES/django.po +43 -2
- wagtail/contrib/search_promotions/tests.py +1 -1
- wagtail/contrib/search_promotions/views/settings.py +24 -25
- wagtail/contrib/settings/jinja2tags.py +2 -2
- wagtail/contrib/settings/locale/en/LC_MESSAGES/django.po +2 -2
- wagtail/contrib/settings/locale/it/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/settings/locale/it/LC_MESSAGES/django.po +6 -2
- wagtail/contrib/settings/locale/sv/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/settings/locale/sv/LC_MESSAGES/django.po +3 -2
- wagtail/contrib/settings/tests/generic/test_admin.py +118 -2
- wagtail/contrib/settings/tests/site_specific/test_admin.py +78 -15
- wagtail/contrib/settings/tests/site_specific/test_model.py +2 -2
- wagtail/contrib/settings/views.py +7 -0
- wagtail/contrib/simple_translation/locale/en/LC_MESSAGES/django.po +1 -1
- wagtail/contrib/simple_translation/locale/it/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/simple_translation/locale/it/LC_MESSAGES/django.po +5 -2
- wagtail/contrib/styleguide/locale/en/LC_MESSAGES/django.po +7 -7
- wagtail/contrib/styleguide/tests.py +2 -0
- wagtail/contrib/styleguide/views.py +4 -5
- wagtail/contrib/table_block/locale/ar/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/table_block/locale/ar/LC_MESSAGES/django.po +5 -1
- wagtail/contrib/table_block/locale/en/LC_MESSAGES/django.po +1 -1
- wagtail/contrib/table_block/locale/it/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/table_block/locale/it/LC_MESSAGES/django.po +5 -2
- wagtail/contrib/typed_table_block/blocks.py +37 -0
- wagtail/contrib/typed_table_block/locale/en/LC_MESSAGES/django.po +10 -10
- wagtail/contrib/typed_table_block/locale/sv/LC_MESSAGES/django.mo +0 -0
- wagtail/contrib/typed_table_block/locale/sv/LC_MESSAGES/django.po +4 -3
- wagtail/contrib/typed_table_block/tests.py +108 -0
- wagtail/coreutils.py +4 -4
- wagtail/documents/__init__.py +4 -4
- wagtail/documents/locale/en/LC_MESSAGES/django.po +15 -15
- wagtail/documents/locale/it/LC_MESSAGES/django.mo +0 -0
- wagtail/documents/locale/it/LC_MESSAGES/django.po +32 -2
- wagtail/documents/locale/sv/LC_MESSAGES/django.mo +0 -0
- wagtail/documents/locale/sv/LC_MESSAGES/django.po +32 -4
- wagtail/documents/models.py +1 -1
- wagtail/documents/tests/test_admin_views.py +19 -4
- wagtail/documents/tests/test_search.py +0 -2
- wagtail/documents/tests/test_views.py +6 -4
- wagtail/documents/views/bulk_actions/add_tags.py +1 -1
- wagtail/embeds/finders/facebook.py +3 -3
- wagtail/embeds/finders/instagram.py +3 -3
- wagtail/embeds/finders/oembed.py +7 -2
- wagtail/embeds/locale/en/LC_MESSAGES/django.po +1 -1
- wagtail/embeds/oembed_providers.py +8 -0
- wagtail/embeds/tests/test_embeds.py +35 -0
- wagtail/images/__init__.py +4 -4
- wagtail/images/admin_urls.py +3 -3
- wagtail/images/blocks.py +1 -1
- wagtail/images/formats.py +2 -2
- wagtail/images/image_operations.py +2 -2
- wagtail/images/locale/en/LC_MESSAGES/django.po +44 -40
- wagtail/images/locale/it/LC_MESSAGES/django.mo +0 -0
- wagtail/images/locale/it/LC_MESSAGES/django.po +50 -4
- wagtail/images/locale/nl/LC_MESSAGES/django.mo +0 -0
- wagtail/images/locale/nl/LC_MESSAGES/django.po +3 -3
- wagtail/images/locale/sv/LC_MESSAGES/django.mo +0 -0
- wagtail/images/locale/sv/LC_MESSAGES/django.po +49 -6
- wagtail/images/models.py +11 -10
- wagtail/images/templates/wagtailimages/images/index_results.html +1 -1
- wagtail/images/templates/wagtailimages/images/url_generator.html +17 -38
- wagtail/images/templates/wagtailimages/images/url_generator_output.html +28 -0
- wagtail/images/templatetags/wagtailimages_tags.py +4 -4
- wagtail/images/tests/test_admin_views.py +432 -59
- wagtail/images/tests/test_image_operations.py +2 -2
- wagtail/images/tests/test_models.py +0 -2
- wagtail/images/views/bulk_actions/add_tags.py +1 -1
- wagtail/images/views/images.py +72 -39
- wagtail/locale/en/LC_MESSAGES/django.po +103 -105
- wagtail/locale/it/LC_MESSAGES/django.mo +0 -0
- wagtail/locale/it/LC_MESSAGES/django.po +105 -2
- wagtail/locale/sv/LC_MESSAGES/django.mo +0 -0
- wagtail/locale/sv/LC_MESSAGES/django.po +95 -12
- wagtail/locales/locale/en/LC_MESSAGES/django.po +1 -1
- wagtail/locales/locale/it/LC_MESSAGES/django.mo +0 -0
- wagtail/locales/locale/it/LC_MESSAGES/django.po +13 -2
- wagtail/locales/locale/sv/LC_MESSAGES/django.mo +0 -0
- wagtail/locales/locale/sv/LC_MESSAGES/django.po +4 -3
- wagtail/locales/tests.py +5 -5
- wagtail/models/i18n.py +4 -6
- wagtail/models/media.py +18 -0
- wagtail/models/pages.py +65 -11
- wagtail/models/reference_index.py +15 -0
- wagtail/models/revisions.py +40 -12
- wagtail/models/workflows.py +0 -3
- wagtail/permission_policies/base.py +2 -2
- wagtail/permission_policies/collections.py +2 -2
- wagtail/project_template/requirements.txt +2 -2
- wagtail/search/locale/en/LC_MESSAGES/django.po +1 -1
- wagtail/signal_handlers.py +1 -0
- wagtail/sites/locale/en/LC_MESSAGES/django.po +1 -1
- wagtail/sites/locale/sv/LC_MESSAGES/django.mo +0 -0
- wagtail/sites/locale/sv/LC_MESSAGES/django.po +3 -2
- wagtail/sites/tests.py +7 -7
- wagtail/snippets/action_menu.py +2 -1
- wagtail/snippets/locale/en/LC_MESSAGES/django.po +23 -13
- wagtail/snippets/locale/sv/LC_MESSAGES/django.mo +0 -0
- wagtail/snippets/locale/sv/LC_MESSAGES/django.po +12 -1
- wagtail/snippets/tests/test_chooser_block.py +197 -0
- wagtail/snippets/tests/test_chooser_panel.py +149 -0
- wagtail/snippets/tests/test_chooser_views.py +375 -0
- wagtail/snippets/tests/test_chooser_widget.py +22 -0
- wagtail/snippets/tests/test_compare_revisions_view.py +167 -0
- wagtail/snippets/tests/test_copy_view.py +38 -0
- wagtail/snippets/tests/test_create_view.py +1159 -0
- wagtail/snippets/tests/test_delete_view.py +225 -0
- wagtail/snippets/tests/test_edit_view.py +2882 -0
- wagtail/snippets/tests/test_history_view.py +182 -0
- wagtail/snippets/tests/test_index_view.py +105 -0
- wagtail/snippets/tests/test_list_view.py +786 -0
- wagtail/snippets/tests/test_locking.py +28 -0
- wagtail/snippets/tests/test_permissions.py +167 -0
- wagtail/snippets/tests/test_preview.py +3 -3
- wagtail/snippets/tests/test_revert_view.py +343 -0
- wagtail/snippets/tests/test_snippet_models.py +112 -0
- wagtail/snippets/tests/test_unpublish_view.py +228 -0
- wagtail/snippets/tests/test_unschedule_view.py +151 -0
- wagtail/snippets/tests/test_viewset.py +38 -5
- wagtail/snippets/views/snippets.py +78 -30
- wagtail/snippets/widgets.py +2 -2
- wagtail/templatetags/wagtailcore_tags.py +2 -2
- wagtail/test/dummy_external_storage.py +1 -1
- wagtail/test/testapp/fixtures/test.json +22 -0
- wagtail/test/testapp/fixtures/test_empty.json +2 -0
- wagtail/test/testapp/fixtures/test_explorable_pages.json +10 -0
- wagtail/test/testapp/fixtures/test_specific.json +9 -0
- wagtail/test/testapp/migrations/0059_nopromotepage.py +25 -0
- wagtail/test/testapp/models.py +7 -0
- wagtail/test/testapp/views.py +5 -0
- wagtail/test/utils/page_tests.py +7 -7
- wagtail/test/utils/wagtail_factories/builder.py +2 -2
- wagtail/tests/streamfield_migrations/test_migrations.py +35 -0
- wagtail/tests/test_blocks.py +321 -61
- wagtail/tests/test_collection_model.py +12 -0
- wagtail/tests/test_page_model.py +190 -1
- wagtail/tests/test_page_privacy.py +5 -0
- wagtail/tests/test_reference_index.py +42 -0
- wagtail/tests/test_revision_model.py +118 -1
- wagtail/tests/test_utils.py +2 -2
- wagtail/users/locale/en/LC_MESSAGES/django.po +14 -14
- wagtail/users/locale/id_ID/LC_MESSAGES/django.mo +0 -0
- wagtail/users/locale/id_ID/LC_MESSAGES/django.po +6 -2
- wagtail/users/locale/it/LC_MESSAGES/django.mo +0 -0
- wagtail/users/locale/it/LC_MESSAGES/django.po +14 -2
- wagtail/users/locale/sv/LC_MESSAGES/django.mo +0 -0
- wagtail/users/locale/sv/LC_MESSAGES/django.po +48 -17
- wagtail/users/tests/test_admin_views.py +39 -25
- wagtail/users/utils.py +4 -1
- wagtail/users/views/groups.py +19 -5
- wagtail/users/wagtail_hooks.py +1 -1
- wagtail/utils/loading.py +2 -2
- wagtail-7.3rc1.data/data/AGENTS.md +21 -0
- wagtail-7.3rc1.data/data/CHANGELOG.txt +4941 -0
- wagtail-7.3rc1.data/data/CODE_OF_CONDUCT.md +71 -0
- wagtail-7.3rc1.data/data/CONTRIBUTORS.md +999 -0
- wagtail-7.3rc1.data/data/SPONSORS.md +55 -0
- {wagtail-7.2.1.dist-info → wagtail-7.3rc1.dist-info}/METADATA +6 -6
- {wagtail-7.2.1.dist-info → wagtail-7.3rc1.dist-info}/RECORD +310 -280
- {wagtail-7.2.1.dist-info → wagtail-7.3rc1.dist-info}/WHEEL +1 -1
- wagtail/images/static/wagtailimages/js/image-url-generator.js +0 -1
- wagtail/snippets/tests/test_snippets.py +0 -6377
- {wagtail-7.2.1.dist-info → wagtail-7.3rc1.dist-info}/entry_points.txt +0 -0
- {wagtail-7.2.1.dist-info → wagtail-7.3rc1.dist-info}/licenses/LICENSE +0 -0
- {wagtail-7.2.1.dist-info → wagtail-7.3rc1.dist-info}/top_level.txt +0 -0
wagtail/admin/ui/side_panels.py
CHANGED
|
@@ -22,13 +22,7 @@ class BaseSidePanel(Component):
|
|
|
22
22
|
self.panel = panel
|
|
23
23
|
|
|
24
24
|
def get_context_data(self, parent_context):
|
|
25
|
-
|
|
26
|
-
inherit = {
|
|
27
|
-
"nav_icon_button_classes",
|
|
28
|
-
"nav_icon_classes",
|
|
29
|
-
"nav_icon_counter_classes",
|
|
30
|
-
}
|
|
31
|
-
context = {key: parent_context.get(key) for key in inherit}
|
|
25
|
+
context = {}
|
|
32
26
|
context["toggle"] = self
|
|
33
27
|
context["panel"] = self.panel
|
|
34
28
|
context["count"] = 0
|
|
@@ -98,16 +92,15 @@ class StatusSidePanel(BaseSidePanel):
|
|
|
98
92
|
"wagtailadmin/shared/side_panels/includes/status/locale.html"
|
|
99
93
|
)
|
|
100
94
|
|
|
101
|
-
if self.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
)
|
|
95
|
+
if self.locking_enabled:
|
|
96
|
+
templates.append(
|
|
97
|
+
"wagtailadmin/shared/side_panels/includes/status/locked.html"
|
|
98
|
+
)
|
|
106
99
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
100
|
+
if self.usage_url is not None:
|
|
101
|
+
templates.append(
|
|
102
|
+
"wagtailadmin/shared/side_panels/includes/status/usage.html"
|
|
103
|
+
)
|
|
111
104
|
|
|
112
105
|
return templates
|
|
113
106
|
|
|
@@ -211,10 +204,11 @@ class StatusSidePanel(BaseSidePanel):
|
|
|
211
204
|
}
|
|
212
205
|
|
|
213
206
|
def get_usage_context(self):
|
|
207
|
+
usage_count = 0
|
|
208
|
+
if self.object.pk:
|
|
209
|
+
usage_count = ReferenceIndex.get_grouped_references_to(self.object).count()
|
|
214
210
|
return {
|
|
215
|
-
"usage_count":
|
|
216
|
-
self.object
|
|
217
|
-
).count(),
|
|
211
|
+
"usage_count": usage_count,
|
|
218
212
|
"usage_url": self.usage_url,
|
|
219
213
|
}
|
|
220
214
|
|
|
@@ -229,9 +223,13 @@ class StatusSidePanel(BaseSidePanel):
|
|
|
229
223
|
context["history_url"] = self.history_url
|
|
230
224
|
context["status_templates"] = self.get_status_templates(context)
|
|
231
225
|
context["last_updated_info"] = self.last_updated_info
|
|
226
|
+
context["is_partial"] = parent_context.get("is_partial", False)
|
|
227
|
+
context["hydrate_create_view"] = parent_context.get(
|
|
228
|
+
"hydrate_create_view", False
|
|
229
|
+
)
|
|
232
230
|
context.update(self.get_scheduled_publishing_context(parent_context))
|
|
233
231
|
context.update(self.get_lock_context(parent_context))
|
|
234
|
-
if self.
|
|
232
|
+
if self.usage_url is not None:
|
|
235
233
|
context.update(self.get_usage_context())
|
|
236
234
|
return context
|
|
237
235
|
|
|
@@ -240,6 +238,7 @@ class PageStatusSidePanel(StatusSidePanel):
|
|
|
240
238
|
def __init__(self, *args, **kwargs):
|
|
241
239
|
self.parent_page = kwargs.pop("parent_page", None)
|
|
242
240
|
super().__init__(*args, **kwargs)
|
|
241
|
+
self.usage_url = ""
|
|
243
242
|
if self.object.pk:
|
|
244
243
|
self.usage_url = reverse("wagtailadmin_pages:usage", args=(self.object.pk,))
|
|
245
244
|
|
wagtail/admin/userbar.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from warnings import warn
|
|
2
2
|
|
|
3
|
-
from django.
|
|
3
|
+
from django.forms import Media
|
|
4
4
|
from django.utils import translation
|
|
5
5
|
from django.utils.translation import gettext_lazy as _
|
|
6
6
|
|
|
@@ -14,18 +14,18 @@ from wagtail.users.models import UserProfile
|
|
|
14
14
|
from wagtail.utils.deprecation import RemovedInWagtail80Warning
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
class BaseItem:
|
|
18
|
-
|
|
17
|
+
class BaseItem(Component):
|
|
18
|
+
template_name = "wagtailadmin/userbar/item_base.html"
|
|
19
19
|
|
|
20
|
-
def get_context_data(self,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return
|
|
20
|
+
def get_context_data(self, parent_context):
|
|
21
|
+
context = super().get_context_data(parent_context)
|
|
22
|
+
context["self"] = self
|
|
23
|
+
context["request"] = parent_context.get("request")
|
|
24
|
+
return context
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
class AdminItem(BaseItem):
|
|
28
|
-
|
|
28
|
+
template_name = "wagtailadmin/userbar/item_admin.html"
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
class AccessibilityItem(BaseItem):
|
|
@@ -37,7 +37,7 @@ class AccessibilityItem(BaseItem):
|
|
|
37
37
|
"""Whether the accessibility checker is being run in the page editor."""
|
|
38
38
|
|
|
39
39
|
#: The template to use for rendering the item.
|
|
40
|
-
|
|
40
|
+
template_name = "wagtailadmin/userbar/item_accessibility.html"
|
|
41
41
|
|
|
42
42
|
#: A list of CSS selector(s) to test specific parts of the page.
|
|
43
43
|
#: For more details, see `Axe documentation <https://github.com/dequelabs/axe-core/blob/master/doc/context.md#the-include-property>`__.
|
|
@@ -212,21 +212,23 @@ class AccessibilityItem(BaseItem):
|
|
|
212
212
|
"spec": self.get_axe_spec(request),
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
def get_context_data(self,
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
215
|
+
def get_context_data(self, parent_context):
|
|
216
|
+
context = super().get_context_data(parent_context)
|
|
217
|
+
context["axe_configuration"] = self.get_axe_configuration(
|
|
218
|
+
parent_context.get("request")
|
|
219
|
+
)
|
|
220
|
+
return context
|
|
220
221
|
|
|
221
222
|
|
|
222
223
|
class AddPageItem(BaseItem):
|
|
223
|
-
|
|
224
|
+
template_name = "wagtailadmin/userbar/item_page_add.html"
|
|
224
225
|
|
|
225
226
|
def __init__(self, page):
|
|
226
227
|
self.page = page
|
|
227
228
|
self.parent_page = page.get_parent()
|
|
228
229
|
|
|
229
|
-
def
|
|
230
|
+
def render_html(self, parent_context):
|
|
231
|
+
request = parent_context.get("request")
|
|
230
232
|
# Don't render if the page doesn't have an id
|
|
231
233
|
if not self.page.id:
|
|
232
234
|
return ""
|
|
@@ -236,17 +238,18 @@ class AddPageItem(BaseItem):
|
|
|
236
238
|
if not permission_checker.can_add_subpage():
|
|
237
239
|
return ""
|
|
238
240
|
|
|
239
|
-
return super().
|
|
241
|
+
return super().render_html(parent_context)
|
|
240
242
|
|
|
241
243
|
|
|
242
244
|
class ExplorePageItem(BaseItem):
|
|
243
|
-
|
|
245
|
+
template_name = "wagtailadmin/userbar/item_page_explore.html"
|
|
244
246
|
|
|
245
247
|
def __init__(self, page):
|
|
246
248
|
self.page = page
|
|
247
249
|
self.parent_page = page.get_parent()
|
|
248
250
|
|
|
249
|
-
def
|
|
251
|
+
def render_html(self, parent_context):
|
|
252
|
+
request = parent_context.get("request")
|
|
250
253
|
# Don't render if the page doesn't have an id
|
|
251
254
|
if not self.page.id:
|
|
252
255
|
return ""
|
|
@@ -259,16 +262,17 @@ class ExplorePageItem(BaseItem):
|
|
|
259
262
|
):
|
|
260
263
|
return ""
|
|
261
264
|
|
|
262
|
-
return super().
|
|
265
|
+
return super().render_html(parent_context)
|
|
263
266
|
|
|
264
267
|
|
|
265
268
|
class EditPageItem(BaseItem):
|
|
266
|
-
|
|
269
|
+
template_name = "wagtailadmin/userbar/item_page_edit.html"
|
|
267
270
|
|
|
268
271
|
def __init__(self, page):
|
|
269
272
|
self.page = page
|
|
270
273
|
|
|
271
|
-
def
|
|
274
|
+
def render_html(self, parent_context):
|
|
275
|
+
request = parent_context.get("request")
|
|
272
276
|
# Don't render if the page doesn't have an id
|
|
273
277
|
if not self.page.id:
|
|
274
278
|
return ""
|
|
@@ -286,7 +290,7 @@ class EditPageItem(BaseItem):
|
|
|
286
290
|
if not permission_checker.can_edit():
|
|
287
291
|
return ""
|
|
288
292
|
|
|
289
|
-
return super().
|
|
293
|
+
return super().render_html(parent_context)
|
|
290
294
|
|
|
291
295
|
|
|
292
296
|
def apply_userbar_hooks(request, items, page):
|
|
@@ -359,10 +363,26 @@ class Userbar(Component):
|
|
|
359
363
|
apply_userbar_hooks(request, items, self.object)
|
|
360
364
|
|
|
361
365
|
# Render the items
|
|
362
|
-
rendered_items = [
|
|
366
|
+
rendered_items = []
|
|
367
|
+
media = Media()
|
|
368
|
+
for item in items:
|
|
369
|
+
# Backwards compatibility for legacy items with a render method.
|
|
370
|
+
if hasattr(item, "render"):
|
|
371
|
+
warn(
|
|
372
|
+
"Userbar items now use the `render_html(parent_context)` method instead of `render(request)` -"
|
|
373
|
+
f" view the `construct_wagtail_userbar` docs to update {item.__class__.__name__}",
|
|
374
|
+
RemovedInWagtail80Warning,
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
content = item.render(request)
|
|
378
|
+
else:
|
|
379
|
+
content = item.render_html(parent_context)
|
|
380
|
+
if content:
|
|
381
|
+
rendered_items.append(content)
|
|
363
382
|
|
|
364
|
-
|
|
365
|
-
|
|
383
|
+
# Always append media even if the item might render nothing.
|
|
384
|
+
if hasattr(item, "media"):
|
|
385
|
+
media += item.media
|
|
366
386
|
|
|
367
387
|
if request:
|
|
368
388
|
origin = f"{request.scheme}://{request.get_host()}"
|
|
@@ -374,6 +394,7 @@ class Userbar(Component):
|
|
|
374
394
|
"request": request,
|
|
375
395
|
"origin": origin,
|
|
376
396
|
"items": rendered_items,
|
|
397
|
+
"media": media,
|
|
377
398
|
"position": self.position,
|
|
378
399
|
"page": self.object,
|
|
379
400
|
"revision_id": revision_id,
|
|
@@ -7,8 +7,8 @@ from wagtail.admin.views.bulk_action.registry import bulk_action_registry as reg
|
|
|
7
7
|
def index(request, app_label, model_name, action):
|
|
8
8
|
try:
|
|
9
9
|
model = apps.get_model(app_label, model_name)
|
|
10
|
-
except LookupError:
|
|
11
|
-
raise Http404
|
|
10
|
+
except LookupError as e:
|
|
11
|
+
raise Http404 from e
|
|
12
12
|
action_class = registry.get_bulk_action_class(app_label, model_name, action)
|
|
13
13
|
if action_class is not None:
|
|
14
14
|
return action_class(request, model).dispatch(request)
|
wagtail/admin/views/chooser.py
CHANGED
|
@@ -243,8 +243,8 @@ class BrowseView(View):
|
|
|
243
243
|
|
|
244
244
|
try:
|
|
245
245
|
self.desired_classes = page_models_from_string(page_type_string)
|
|
246
|
-
except (ValueError, LookupError):
|
|
247
|
-
raise Http404
|
|
246
|
+
except (ValueError, LookupError) as e:
|
|
247
|
+
raise Http404 from e
|
|
248
248
|
|
|
249
249
|
# Find parent page
|
|
250
250
|
if parent_page_id:
|
|
@@ -350,8 +350,8 @@ class BrowseView(View):
|
|
|
350
350
|
paginator = Paginator(pages, per_page=25)
|
|
351
351
|
try:
|
|
352
352
|
pages = paginator.page(request.GET.get("p", 1))
|
|
353
|
-
except InvalidPage:
|
|
354
|
-
raise Http404
|
|
353
|
+
except InvalidPage as e:
|
|
354
|
+
raise Http404 from e
|
|
355
355
|
|
|
356
356
|
# Annotate each page with can_choose/can_descend flags
|
|
357
357
|
for page in pages:
|
|
@@ -445,8 +445,8 @@ class SearchView(View):
|
|
|
445
445
|
|
|
446
446
|
try:
|
|
447
447
|
desired_classes = page_models_from_string(page_type_string)
|
|
448
|
-
except (ValueError, LookupError):
|
|
449
|
-
raise Http404
|
|
448
|
+
except (ValueError, LookupError) as e:
|
|
449
|
+
raise Http404 from e
|
|
450
450
|
|
|
451
451
|
pages = Page.objects.all()
|
|
452
452
|
if self.i18n_enabled:
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
1
3
|
from django.apps import apps
|
|
2
4
|
from django.contrib.admin.utils import unquote
|
|
3
5
|
from django.contrib.contenttypes.models import ContentType
|
|
@@ -18,8 +20,8 @@ from wagtail.models import Page, Revision, RevisionMixin, WorkflowMixin
|
|
|
18
20
|
def ping(request, app_label, model_name, object_id, session_id):
|
|
19
21
|
try:
|
|
20
22
|
model = apps.get_model(app_label, model_name)
|
|
21
|
-
except LookupError:
|
|
22
|
-
raise Http404
|
|
23
|
+
except LookupError as e:
|
|
24
|
+
raise Http404 from e
|
|
23
25
|
|
|
24
26
|
unquoted_object_id = unquote(object_id)
|
|
25
27
|
|
|
@@ -31,9 +33,9 @@ def ping(request, app_label, model_name, object_id, session_id):
|
|
|
31
33
|
else:
|
|
32
34
|
try:
|
|
33
35
|
permission_policy = model.snippet_viewset.permission_policy
|
|
34
|
-
except AttributeError:
|
|
36
|
+
except AttributeError as e:
|
|
35
37
|
# model is neither a Page nor a snippet
|
|
36
|
-
raise Http404
|
|
38
|
+
raise Http404 from e
|
|
37
39
|
|
|
38
40
|
can_edit = permission_policy.user_has_permission_for_instance(
|
|
39
41
|
request.user, "change", obj
|
|
@@ -108,11 +110,22 @@ def ping(request, app_label, model_name, object_id, session_id):
|
|
|
108
110
|
all_revisions = obj.revisions.defer("content")
|
|
109
111
|
try:
|
|
110
112
|
original_revision = all_revisions.get(id=revision_id)
|
|
111
|
-
except Revision.DoesNotExist:
|
|
112
|
-
raise Http404
|
|
113
|
+
except Revision.DoesNotExist as e:
|
|
114
|
+
raise Http404 from e
|
|
113
115
|
|
|
116
|
+
try:
|
|
117
|
+
# The created_at of the latest revision when the session was created
|
|
118
|
+
created_at = datetime.fromisoformat(
|
|
119
|
+
request.POST.get("revision_created_at", "")
|
|
120
|
+
)
|
|
121
|
+
except ValueError:
|
|
122
|
+
# Invalid or missing, assume it's the same as the real created_at
|
|
123
|
+
created_at = original_revision.created_at
|
|
124
|
+
|
|
125
|
+
# The newest revision may have been updated via autosave after the
|
|
126
|
+
# session was created, so filter by created_at rather than the pk.
|
|
114
127
|
newest_revision = (
|
|
115
|
-
all_revisions.filter(created_at__gt=
|
|
128
|
+
all_revisions.filter(created_at__gt=created_at)
|
|
116
129
|
.order_by("-created_at", "-pk")
|
|
117
130
|
.select_related("user")
|
|
118
131
|
.first()
|
|
@@ -257,8 +257,8 @@ class BaseChooseView(
|
|
|
257
257
|
self.paginator = self.paginator_class(objects, per_page=self.per_page)
|
|
258
258
|
try:
|
|
259
259
|
return self.paginator.page(request.GET.get("p", 1))
|
|
260
|
-
except InvalidPage:
|
|
261
|
-
raise Http404
|
|
260
|
+
except InvalidPage as e:
|
|
261
|
+
raise Http404 from e
|
|
262
262
|
|
|
263
263
|
def get(self, request):
|
|
264
264
|
self.filter_form = self.get_filter_form()
|
|
@@ -516,9 +516,9 @@ class ChosenViewMixin(ModelLookupMixin):
|
|
|
516
516
|
|
|
517
517
|
def get(self, request, pk):
|
|
518
518
|
try:
|
|
519
|
-
item = self.get_object(unquote(pk))
|
|
520
|
-
except ObjectDoesNotExist:
|
|
521
|
-
raise Http404
|
|
519
|
+
item = self.get_object(unquote(str(pk)))
|
|
520
|
+
except ObjectDoesNotExist as e:
|
|
521
|
+
raise Http404 from e
|
|
522
522
|
|
|
523
523
|
return self.get_chosen_response(item)
|
|
524
524
|
|
|
@@ -3,8 +3,10 @@ import json
|
|
|
3
3
|
from django.conf import settings
|
|
4
4
|
from django.contrib.admin.utils import quote
|
|
5
5
|
from django.contrib.contenttypes.models import ContentType
|
|
6
|
+
from django.core.exceptions import PermissionDenied
|
|
6
7
|
from django.db import models, transaction
|
|
7
8
|
from django.forms import Media
|
|
9
|
+
from django.http import JsonResponse
|
|
8
10
|
from django.shortcuts import get_object_or_404
|
|
9
11
|
from django.template.loader import render_to_string
|
|
10
12
|
from django.urls import reverse
|
|
@@ -21,6 +23,7 @@ from wagtail.admin.forms.models import WagtailAdminModelForm
|
|
|
21
23
|
from wagtail.admin.models import EditingSession
|
|
22
24
|
from wagtail.admin.telepath import JSContext
|
|
23
25
|
from wagtail.admin.templatetags.wagtailadmin_tags import user_display_name
|
|
26
|
+
from wagtail.admin.ui.autosave import AutosaveIndicator
|
|
24
27
|
from wagtail.admin.ui.editing_sessions import EditingSessionsModule
|
|
25
28
|
from wagtail.admin.ui.tables import LiveStatusTagColumn, TitleColumn
|
|
26
29
|
from wagtail.admin.utils import get_latest_str, set_query_params
|
|
@@ -32,6 +35,7 @@ from wagtail.models import (
|
|
|
32
35
|
Locale,
|
|
33
36
|
LockableMixin,
|
|
34
37
|
PreviewableMixin,
|
|
38
|
+
Revision,
|
|
35
39
|
RevisionMixin,
|
|
36
40
|
TranslatableMixin,
|
|
37
41
|
WorkflowMixin,
|
|
@@ -279,6 +283,8 @@ class CreateEditViewOptionalFeaturesMixin:
|
|
|
279
283
|
and issubclass(self.model, LockableMixin)
|
|
280
284
|
and self.view_name != "create"
|
|
281
285
|
)
|
|
286
|
+
self.autosave_interval = getattr(settings, "WAGTAIL_AUTOSAVE_INTERVAL", 500)
|
|
287
|
+
self.autosave_enabled = self.revision_enabled and self.autosave_interval > 0
|
|
282
288
|
|
|
283
289
|
# Set the object before super().setup() as LocaleMixin.setup() needs it
|
|
284
290
|
self.object = self.get_object()
|
|
@@ -446,16 +452,6 @@ class CreateEditViewOptionalFeaturesMixin:
|
|
|
446
452
|
self.confirm_workflow_cancellation_url_name, args=[quote(self.object.pk)]
|
|
447
453
|
)
|
|
448
454
|
|
|
449
|
-
def get_error_message(self):
|
|
450
|
-
if self.action == "cancel-workflow":
|
|
451
|
-
return None
|
|
452
|
-
if self.locked_for_user:
|
|
453
|
-
return capfirst(
|
|
454
|
-
_("The %(model_name)s could not be saved as it is locked")
|
|
455
|
-
% {"model_name": self.model._meta.verbose_name}
|
|
456
|
-
)
|
|
457
|
-
return super().get_error_message()
|
|
458
|
-
|
|
459
455
|
def get_success_message(self, instance=None):
|
|
460
456
|
object = instance or self.object
|
|
461
457
|
|
|
@@ -547,8 +543,23 @@ class CreateEditViewOptionalFeaturesMixin:
|
|
|
547
543
|
# Save revision if the model inherits from RevisionMixin
|
|
548
544
|
self.new_revision = None
|
|
549
545
|
if self.revision_enabled:
|
|
546
|
+
overwrite_revision_id = self.request.POST.get("overwrite_revision_id")
|
|
547
|
+
if overwrite_revision_id:
|
|
548
|
+
try:
|
|
549
|
+
overwrite_revision = instance.revisions.get(
|
|
550
|
+
pk=overwrite_revision_id
|
|
551
|
+
)
|
|
552
|
+
except Revision.DoesNotExist as e:
|
|
553
|
+
raise PermissionDenied(
|
|
554
|
+
"Cannot overwrite a revision that does not exist"
|
|
555
|
+
) from e
|
|
556
|
+
else:
|
|
557
|
+
overwrite_revision = None
|
|
558
|
+
|
|
550
559
|
self.new_revision = instance.save_revision(
|
|
551
|
-
user=self.request.user,
|
|
560
|
+
user=self.request.user,
|
|
561
|
+
clean=not self.saving_as_draft,
|
|
562
|
+
overwrite_revision=overwrite_revision,
|
|
552
563
|
)
|
|
553
564
|
|
|
554
565
|
log(
|
|
@@ -617,8 +628,14 @@ class CreateEditViewOptionalFeaturesMixin:
|
|
|
617
628
|
|
|
618
629
|
def form_valid(self, form):
|
|
619
630
|
self.form = form
|
|
620
|
-
|
|
621
|
-
|
|
631
|
+
try:
|
|
632
|
+
with transaction.atomic():
|
|
633
|
+
self.object = self.save_instance()
|
|
634
|
+
except PermissionDenied as e:
|
|
635
|
+
# The revision passed to overwrite_revision was not valid
|
|
636
|
+
self.produced_error_code = "invalid_revision"
|
|
637
|
+
self.produced_error_message = str(e)
|
|
638
|
+
return self.form_invalid(form)
|
|
622
639
|
|
|
623
640
|
response = self.run_action_method()
|
|
624
641
|
if response is not None:
|
|
@@ -632,9 +649,18 @@ class CreateEditViewOptionalFeaturesMixin:
|
|
|
632
649
|
|
|
633
650
|
return response
|
|
634
651
|
|
|
652
|
+
def get_success_json(self):
|
|
653
|
+
data = super().get_success_json()
|
|
654
|
+
if self.revision_enabled and self.new_revision:
|
|
655
|
+
data["revision_id"] = self.new_revision.id
|
|
656
|
+
data["revision_created_at"] = self.new_revision.created_at.isoformat()
|
|
657
|
+
return data
|
|
658
|
+
|
|
635
659
|
def form_invalid(self, form):
|
|
636
|
-
# Even if the
|
|
637
|
-
# the
|
|
660
|
+
# Even if the form is invalid, a cancel-workflow action can still proceed. This accommodates
|
|
661
|
+
# the typical case for a lockable model, where the object is locked to the submitter at the
|
|
662
|
+
# point of submission (and thus the lock would make the form submission invalid) - but also
|
|
663
|
+
# applies to other error conditions such as actual validation errors.
|
|
638
664
|
if self.action == "cancel-workflow":
|
|
639
665
|
self.cancel_workflow_action()
|
|
640
666
|
messages.success(
|
|
@@ -645,6 +671,10 @@ class CreateEditViewOptionalFeaturesMixin:
|
|
|
645
671
|
# Refresh the lock object as now WorkflowLock no longer applies
|
|
646
672
|
self.lock = self.get_lock()
|
|
647
673
|
self.locked_for_user = self.lock and self.lock.for_user(self.request.user)
|
|
674
|
+
# Unset whichever error message was set by is_valid() (most likely "this X could not be
|
|
675
|
+
# saved as it is locked") so that we just show the success message.
|
|
676
|
+
self.produced_error_message = None
|
|
677
|
+
|
|
648
678
|
return super().form_invalid(form)
|
|
649
679
|
|
|
650
680
|
def get_last_updated_info(self):
|
|
@@ -743,7 +773,6 @@ class CreateEditViewOptionalFeaturesMixin:
|
|
|
743
773
|
object_id=self.object.pk,
|
|
744
774
|
last_seen_at=timezone.now(),
|
|
745
775
|
)
|
|
746
|
-
revision_id = self.object.latest_revision_id if self.revision_enabled else None
|
|
747
776
|
return EditingSessionsModule(
|
|
748
777
|
session,
|
|
749
778
|
reverse(
|
|
@@ -760,15 +789,56 @@ class CreateEditViewOptionalFeaturesMixin:
|
|
|
760
789
|
args=(session.id,),
|
|
761
790
|
),
|
|
762
791
|
[],
|
|
763
|
-
|
|
792
|
+
self.latest_revision and self.latest_revision.pk,
|
|
793
|
+
self.latest_revision_created_at,
|
|
764
794
|
)
|
|
765
795
|
|
|
796
|
+
@cached_property
|
|
797
|
+
def latest_revision(self):
|
|
798
|
+
return self.revision_enabled and self.object and self.object.latest_revision
|
|
799
|
+
|
|
800
|
+
@cached_property
|
|
801
|
+
def latest_revision_created_at(self):
|
|
802
|
+
return self.latest_revision and self.latest_revision.created_at.isoformat()
|
|
803
|
+
|
|
804
|
+
@cached_property
|
|
805
|
+
def is_out_of_date(self):
|
|
806
|
+
if not self.latest_revision:
|
|
807
|
+
return False
|
|
808
|
+
|
|
809
|
+
latest_revision_id = str(self.latest_revision.pk)
|
|
810
|
+
|
|
811
|
+
# Two different sessions cannot have the same autosave revision, so if
|
|
812
|
+
# autosave revision is present, it is either the latest or it is not.
|
|
813
|
+
if overwrite_revision_id := self.request.POST.get("overwrite_revision_id"):
|
|
814
|
+
return overwrite_revision_id != latest_revision_id
|
|
815
|
+
|
|
816
|
+
# Client has not made an autosave revision, check the loaded revision.
|
|
817
|
+
if loaded_revision_id := self.request.POST.get("loaded_revision_id"):
|
|
818
|
+
# If the loaded revision is not the latest revision, it is outdated.
|
|
819
|
+
if loaded_revision_id != latest_revision_id:
|
|
820
|
+
return True
|
|
821
|
+
|
|
822
|
+
# It's pointing to the latest revision, but that revision may have
|
|
823
|
+
# been overwritten by another session (via autosave) since the editor
|
|
824
|
+
# loaded it. The created_at is strictly increasing, so assume the
|
|
825
|
+
# loaded revision outdated if it doesn't match the latest created_at.
|
|
826
|
+
return (
|
|
827
|
+
self.request.POST.get("loaded_revision_created_at", "")
|
|
828
|
+
!= self.latest_revision_created_at
|
|
829
|
+
)
|
|
830
|
+
|
|
831
|
+
# Not enough information to deduce, assume it's up to date.
|
|
832
|
+
return False
|
|
833
|
+
|
|
766
834
|
def get_context_data(self, **kwargs):
|
|
767
835
|
context = super().get_context_data(**kwargs)
|
|
768
836
|
context.update(self.get_lock_context())
|
|
769
837
|
context["revision_enabled"] = self.revision_enabled
|
|
770
838
|
context["draftstate_enabled"] = self.draftstate_enabled
|
|
771
839
|
context["workflow_enabled"] = self.workflow_enabled
|
|
840
|
+
context["autosave_enabled"] = self.autosave_enabled
|
|
841
|
+
context["autosave_interval"] = self.autosave_interval
|
|
772
842
|
context["workflow_history_url"] = self.get_workflow_history_url()
|
|
773
843
|
context["confirm_workflow_cancellation_url"] = (
|
|
774
844
|
self.get_confirm_workflow_cancellation_url()
|
|
@@ -778,27 +848,36 @@ class CreateEditViewOptionalFeaturesMixin:
|
|
|
778
848
|
) and bool(self.workflow_tasks)
|
|
779
849
|
context["revisions_compare_url_name"] = self.revisions_compare_url_name
|
|
780
850
|
context["editing_sessions"] = self.get_editing_sessions()
|
|
851
|
+
context["loaded_revision_created_at"] = self.latest_revision_created_at
|
|
852
|
+
if self.autosave_enabled:
|
|
853
|
+
context["autosave_indicator"] = AutosaveIndicator()
|
|
781
854
|
return context
|
|
782
855
|
|
|
783
|
-
def
|
|
784
|
-
|
|
856
|
+
def is_valid(self, form):
|
|
857
|
+
# For autosave, we only want to save the page if there are no conflicts
|
|
858
|
+
if self.expects_json_response and self.is_out_of_date:
|
|
859
|
+
self.produced_error_code = "invalid_revision"
|
|
860
|
+
self.produced_error_message = _("Saving will overwrite a newer version.")
|
|
861
|
+
return False
|
|
785
862
|
|
|
786
863
|
# Make sure object is not locked
|
|
787
864
|
if self.locked_for_user:
|
|
788
|
-
|
|
865
|
+
self.produced_error_code = "locked"
|
|
866
|
+
self.produced_error_message = capfirst(
|
|
867
|
+
_("The %(model_name)s could not be saved as it is locked")
|
|
868
|
+
% {"model_name": self.model._meta.verbose_name}
|
|
869
|
+
)
|
|
870
|
+
return False
|
|
789
871
|
|
|
790
872
|
# If saving as draft, do not enforce full validation
|
|
791
873
|
if self.saving_as_draft and isinstance(form, WagtailAdminModelForm):
|
|
792
874
|
form.defer_required_fields()
|
|
793
|
-
|
|
875
|
+
result = super().is_valid(form)
|
|
794
876
|
form.restore_required_fields()
|
|
795
877
|
else:
|
|
796
|
-
|
|
878
|
+
result = super().is_valid(form)
|
|
797
879
|
|
|
798
|
-
|
|
799
|
-
return self.form_valid(form)
|
|
800
|
-
else:
|
|
801
|
-
return self.form_invalid(form)
|
|
880
|
+
return result
|
|
802
881
|
|
|
803
882
|
|
|
804
883
|
class RevisionsRevertMixin:
|
|
@@ -880,4 +959,42 @@ class RevisionsRevertMixin:
|
|
|
880
959
|
context = super().get_context_data(**kwargs)
|
|
881
960
|
context["revision"] = self.revision
|
|
882
961
|
context["action_url"] = self.get_revisions_revert_url()
|
|
962
|
+
# Autosave does not make much sense in this view, we want the user to
|
|
963
|
+
# explicitly confirm they want to revert to the previous revision
|
|
964
|
+
context["autosave_enabled"] = False
|
|
883
965
|
return context
|
|
966
|
+
|
|
967
|
+
|
|
968
|
+
class JsonPostResponseMixin:
|
|
969
|
+
"""
|
|
970
|
+
Helper methods for create/edit views that return JSON responses when POSTed to with an
|
|
971
|
+
Accepts: application/json header, rather than the usual "redirect on success, HTML form
|
|
972
|
+
on failure" behaviour.
|
|
973
|
+
"""
|
|
974
|
+
|
|
975
|
+
partials_template_name = None
|
|
976
|
+
|
|
977
|
+
@cached_property
|
|
978
|
+
def expects_json_response(self):
|
|
979
|
+
return not self.request.accepts("text/html")
|
|
980
|
+
|
|
981
|
+
def json_error_response(self, error_code, error_message):
|
|
982
|
+
return JsonResponse(
|
|
983
|
+
{
|
|
984
|
+
"success": False,
|
|
985
|
+
"error_code": error_code,
|
|
986
|
+
"error_message": error_message,
|
|
987
|
+
},
|
|
988
|
+
status=400,
|
|
989
|
+
)
|
|
990
|
+
|
|
991
|
+
@staticmethod
|
|
992
|
+
def response_is_json(response):
|
|
993
|
+
return response.headers.get("Content-Type") == "application/json"
|
|
994
|
+
|
|
995
|
+
def render_partials(self):
|
|
996
|
+
return render_to_string(
|
|
997
|
+
self.partials_template_name,
|
|
998
|
+
self.get_context_data(),
|
|
999
|
+
request=self.request,
|
|
1000
|
+
)
|