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
|
@@ -0,0 +1,1159 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
from unittest import mock
|
|
3
|
+
|
|
4
|
+
from django.contrib.admin.utils import quote
|
|
5
|
+
from django.contrib.auth.models import Permission
|
|
6
|
+
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
7
|
+
from django.core.handlers.wsgi import WSGIRequest
|
|
8
|
+
from django.http import HttpRequest, HttpResponse, JsonResponse
|
|
9
|
+
from django.test import TestCase
|
|
10
|
+
from django.test.utils import override_settings
|
|
11
|
+
from django.urls import reverse
|
|
12
|
+
from django.utils.timezone import now
|
|
13
|
+
from freezegun import freeze_time
|
|
14
|
+
from taggit.models import Tag
|
|
15
|
+
|
|
16
|
+
from wagtail.models import Locale, ModelLogEntry, Revision
|
|
17
|
+
from wagtail.signals import published
|
|
18
|
+
from wagtail.snippets.action_menu import (
|
|
19
|
+
ActionMenuItem,
|
|
20
|
+
get_base_snippet_action_menu_items,
|
|
21
|
+
)
|
|
22
|
+
from wagtail.test.snippets.models import (
|
|
23
|
+
FileUploadSnippet,
|
|
24
|
+
)
|
|
25
|
+
from wagtail.test.testapp.models import (
|
|
26
|
+
Advert,
|
|
27
|
+
DraftStateModel,
|
|
28
|
+
RevisableModel,
|
|
29
|
+
)
|
|
30
|
+
from wagtail.test.utils import WagtailTestUtils
|
|
31
|
+
from wagtail.test.utils.timestamps import submittable_timestamp
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TestSnippetCreateView(WagtailTestUtils, TestCase):
|
|
35
|
+
def setUp(self):
|
|
36
|
+
self.user = self.login()
|
|
37
|
+
|
|
38
|
+
def get(self, params=None, model=Advert, headers=None):
|
|
39
|
+
return self.client.get(
|
|
40
|
+
reverse(model.snippet_viewset.get_url_name("add")), params, headers=headers
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def post(self, post_data=None, model=Advert, headers=None):
|
|
44
|
+
return self.client.post(
|
|
45
|
+
reverse(model.snippet_viewset.get_url_name("add")),
|
|
46
|
+
post_data,
|
|
47
|
+
headers=headers,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def test_get_with_limited_permissions(self):
|
|
51
|
+
self.user.is_superuser = False
|
|
52
|
+
self.user.user_permissions.add(
|
|
53
|
+
Permission.objects.get(
|
|
54
|
+
content_type__app_label="wagtailadmin", codename="access_admin"
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
self.user.save()
|
|
58
|
+
|
|
59
|
+
response = self.get()
|
|
60
|
+
self.assertEqual(response.status_code, 302)
|
|
61
|
+
|
|
62
|
+
def test_simple(self):
|
|
63
|
+
response = self.get()
|
|
64
|
+
self.assertEqual(response.status_code, 200)
|
|
65
|
+
self.assertTemplateUsed(response, "wagtailsnippets/snippets/create.html")
|
|
66
|
+
self.assertNotContains(response, 'role="tablist"', html=True)
|
|
67
|
+
|
|
68
|
+
soup = self.get_soup(response.content)
|
|
69
|
+
|
|
70
|
+
# Should have the unsaved controller set up
|
|
71
|
+
editor_form = soup.select_one("#w-editor-form")
|
|
72
|
+
self.assertIsNotNone(editor_form)
|
|
73
|
+
self.assertIn("w-unsaved", editor_form.attrs.get("data-controller").split())
|
|
74
|
+
self.assertTrue(
|
|
75
|
+
{
|
|
76
|
+
"w-unsaved#submit",
|
|
77
|
+
"beforeunload@window->w-unsaved#confirm",
|
|
78
|
+
}.issubset(editor_form.attrs.get("data-action").split())
|
|
79
|
+
)
|
|
80
|
+
self.assertEqual(
|
|
81
|
+
editor_form.attrs.get("data-w-unsaved-confirmation-value"),
|
|
82
|
+
"true",
|
|
83
|
+
)
|
|
84
|
+
self.assertEqual(
|
|
85
|
+
editor_form.attrs.get("data-w-unsaved-force-value"),
|
|
86
|
+
"false",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def test_snippet_with_tabbed_interface(self):
|
|
90
|
+
response = self.client.get(
|
|
91
|
+
reverse("wagtailsnippets_tests_advertwithtabbedinterface:add")
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
self.assertEqual(response.status_code, 200)
|
|
95
|
+
self.assertTemplateUsed(response, "wagtailsnippets/snippets/create.html")
|
|
96
|
+
self.assertContains(response, 'role="tablist"')
|
|
97
|
+
self.assertContains(
|
|
98
|
+
response,
|
|
99
|
+
'<a id="tab-label-advert" href="#tab-advert" class="w-tabs__tab " role="tab" aria-selected="false" tabindex="-1" data-action="w-tabs#select:prevent" data-w-tabs-target="trigger">',
|
|
100
|
+
)
|
|
101
|
+
self.assertContains(
|
|
102
|
+
response,
|
|
103
|
+
'<a id="tab-label-other" href="#tab-other" class="w-tabs__tab " role="tab" aria-selected="false" tabindex="-1" data-action="w-tabs#select:prevent" data-w-tabs-target="trigger">',
|
|
104
|
+
)
|
|
105
|
+
self.assertContains(response, "Other panels help text")
|
|
106
|
+
self.assertContains(response, "Top-level help text")
|
|
107
|
+
|
|
108
|
+
def test_create_with_limited_permissions(self):
|
|
109
|
+
self.user.is_superuser = False
|
|
110
|
+
self.user.user_permissions.add(
|
|
111
|
+
Permission.objects.get(
|
|
112
|
+
content_type__app_label="wagtailadmin", codename="access_admin"
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
self.user.save()
|
|
116
|
+
|
|
117
|
+
response = self.post(
|
|
118
|
+
post_data={"text": "test text", "url": "http://www.example.com/"}
|
|
119
|
+
)
|
|
120
|
+
self.assertEqual(response.status_code, 302)
|
|
121
|
+
|
|
122
|
+
def test_create_invalid(self):
|
|
123
|
+
response = self.post(post_data={"foo": "bar"})
|
|
124
|
+
|
|
125
|
+
soup = self.get_soup(response.content)
|
|
126
|
+
|
|
127
|
+
header_messages = soup.css.select(".messages[role='status'] ul > li")
|
|
128
|
+
|
|
129
|
+
# there should be one header message that indicates the issue and has a go to error button
|
|
130
|
+
self.assertEqual(len(header_messages), 1)
|
|
131
|
+
message = header_messages[0]
|
|
132
|
+
self.assertIn(
|
|
133
|
+
"The advert could not be created due to errors.", message.get_text()
|
|
134
|
+
)
|
|
135
|
+
buttons = message.find_all("button")
|
|
136
|
+
self.assertEqual(len(buttons), 1)
|
|
137
|
+
self.assertEqual(buttons[0].attrs["data-controller"], "w-count w-focus")
|
|
138
|
+
self.assertEqual(
|
|
139
|
+
set(buttons[0].attrs["data-action"].split()),
|
|
140
|
+
{"click->w-focus#focus", "wagtail:panel-init@document->w-count#count"},
|
|
141
|
+
)
|
|
142
|
+
self.assertIn("Go to the first error", buttons[0].get_text())
|
|
143
|
+
|
|
144
|
+
# field specific error should be shown
|
|
145
|
+
error_messages = soup.css.select(".error-message")
|
|
146
|
+
self.assertEqual(len(error_messages), 1)
|
|
147
|
+
error_message = error_messages[0]
|
|
148
|
+
self.assertEqual(error_message.parent["id"], "panel-child-text-errors")
|
|
149
|
+
self.assertIn("This field is required", error_message.get_text())
|
|
150
|
+
|
|
151
|
+
# Should have the unsaved controller set up
|
|
152
|
+
editor_form = soup.select_one("#w-editor-form")
|
|
153
|
+
self.assertIsNotNone(editor_form)
|
|
154
|
+
self.assertIn("w-unsaved", editor_form.attrs.get("data-controller").split())
|
|
155
|
+
self.assertTrue(
|
|
156
|
+
{
|
|
157
|
+
"w-unsaved#submit",
|
|
158
|
+
"beforeunload@window->w-unsaved#confirm",
|
|
159
|
+
}.issubset(editor_form.attrs.get("data-action").split())
|
|
160
|
+
)
|
|
161
|
+
self.assertEqual(
|
|
162
|
+
editor_form.attrs.get("data-w-unsaved-confirmation-value"),
|
|
163
|
+
"true",
|
|
164
|
+
)
|
|
165
|
+
self.assertEqual(
|
|
166
|
+
editor_form.attrs.get("data-w-unsaved-force-value"),
|
|
167
|
+
# The form is invalid, we want to force it to be "dirty" on initial load
|
|
168
|
+
"true",
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def test_create_invalid_with_json_response(self):
|
|
172
|
+
response = self.post(
|
|
173
|
+
post_data={"foo": "bar"}, headers={"Accept": "application/json"}
|
|
174
|
+
)
|
|
175
|
+
self.assertEqual(response.status_code, 400)
|
|
176
|
+
self.assertEqual(response["Content-Type"], "application/json")
|
|
177
|
+
self.assertEqual(
|
|
178
|
+
response.json(),
|
|
179
|
+
{
|
|
180
|
+
"success": False,
|
|
181
|
+
"error_code": "validation_error",
|
|
182
|
+
"error_message": "There are validation errors, click save to highlight them.",
|
|
183
|
+
},
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def test_create(self):
|
|
187
|
+
response = self.post(
|
|
188
|
+
post_data={"text": "test_advert", "url": "http://www.example.com/"}
|
|
189
|
+
)
|
|
190
|
+
self.assertRedirects(response, reverse("wagtailsnippets_tests_advert:list"))
|
|
191
|
+
|
|
192
|
+
snippets = Advert.objects.filter(text="test_advert")
|
|
193
|
+
self.assertEqual(snippets.count(), 1)
|
|
194
|
+
self.assertEqual(snippets.first().url, "http://www.example.com/")
|
|
195
|
+
|
|
196
|
+
def test_create_with_json_response(self):
|
|
197
|
+
response = self.post(
|
|
198
|
+
post_data={"text": "test_advert", "url": "http://www.example.com/"},
|
|
199
|
+
headers={"Accept": "application/json"},
|
|
200
|
+
)
|
|
201
|
+
self.assertEqual(response.status_code, 200)
|
|
202
|
+
self.assertEqual(response["Content-Type"], "application/json")
|
|
203
|
+
|
|
204
|
+
snippets = Advert.objects.filter(text="test_advert")
|
|
205
|
+
self.assertEqual(snippets.count(), 1)
|
|
206
|
+
snippet = snippets.first()
|
|
207
|
+
self.assertEqual(snippet.url, "http://www.example.com/")
|
|
208
|
+
|
|
209
|
+
response_json = response.json()
|
|
210
|
+
self.assertEqual(response_json["success"], True)
|
|
211
|
+
self.assertEqual(response_json["pk"], snippet.pk)
|
|
212
|
+
self.assertEqual(response_json["field_updates"], {})
|
|
213
|
+
self.assertEqual(
|
|
214
|
+
response_json["url"],
|
|
215
|
+
reverse(snippet.snippet_viewset.get_url_name("edit"), args=(snippet.pk,)),
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
def test_create_with_tags(self):
|
|
219
|
+
tags = ["hello", "world"]
|
|
220
|
+
response = self.post(
|
|
221
|
+
post_data={
|
|
222
|
+
"text": "test_advert",
|
|
223
|
+
"url": "http://example.com/",
|
|
224
|
+
"tags": ", ".join(tags),
|
|
225
|
+
}
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
self.assertRedirects(response, reverse("wagtailsnippets_tests_advert:list"))
|
|
229
|
+
|
|
230
|
+
snippet = Advert.objects.get(text="test_advert")
|
|
231
|
+
|
|
232
|
+
expected_tags = list(Tag.objects.order_by("name").filter(name__in=tags))
|
|
233
|
+
self.assertEqual(len(expected_tags), 2)
|
|
234
|
+
self.assertEqual(list(snippet.tags.order_by("name")), expected_tags)
|
|
235
|
+
|
|
236
|
+
def test_create_file_upload_multipart(self):
|
|
237
|
+
response = self.get(model=FileUploadSnippet)
|
|
238
|
+
self.assertContains(response, 'enctype="multipart/form-data"')
|
|
239
|
+
|
|
240
|
+
response = self.post(
|
|
241
|
+
model=FileUploadSnippet,
|
|
242
|
+
post_data={"file": SimpleUploadedFile("test.txt", b"Uploaded file")},
|
|
243
|
+
)
|
|
244
|
+
self.assertRedirects(
|
|
245
|
+
response,
|
|
246
|
+
reverse("wagtailsnippets_snippetstests_fileuploadsnippet:list"),
|
|
247
|
+
)
|
|
248
|
+
snippet = FileUploadSnippet.objects.get()
|
|
249
|
+
self.assertEqual(snippet.file.read(), b"Uploaded file")
|
|
250
|
+
|
|
251
|
+
def test_create_with_revision(self):
|
|
252
|
+
response = self.post(
|
|
253
|
+
model=RevisableModel, post_data={"text": "create_revisable"}
|
|
254
|
+
)
|
|
255
|
+
self.assertRedirects(
|
|
256
|
+
response, reverse("wagtailsnippets_tests_revisablemodel:list")
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
snippets = RevisableModel.objects.filter(text="create_revisable")
|
|
260
|
+
snippet = snippets.first()
|
|
261
|
+
self.assertEqual(snippets.count(), 1)
|
|
262
|
+
|
|
263
|
+
# The revision should be created
|
|
264
|
+
revisions = snippet.revisions
|
|
265
|
+
revision = revisions.first()
|
|
266
|
+
self.assertEqual(revisions.count(), 1)
|
|
267
|
+
self.assertEqual(revision.content["text"], "create_revisable")
|
|
268
|
+
|
|
269
|
+
# The log entry should have the revision attached
|
|
270
|
+
log_entries = ModelLogEntry.objects.for_instance(snippet).filter(
|
|
271
|
+
action="wagtail.create"
|
|
272
|
+
)
|
|
273
|
+
self.assertEqual(log_entries.count(), 1)
|
|
274
|
+
self.assertEqual(log_entries.first().revision, revision)
|
|
275
|
+
|
|
276
|
+
def test_before_create_snippet_hook_get(self):
|
|
277
|
+
def hook_func(request, model):
|
|
278
|
+
self.assertIsInstance(request, HttpRequest)
|
|
279
|
+
self.assertEqual(model, Advert)
|
|
280
|
+
return HttpResponse("Overridden!")
|
|
281
|
+
|
|
282
|
+
with self.register_hook("before_create_snippet", hook_func):
|
|
283
|
+
response = self.get()
|
|
284
|
+
|
|
285
|
+
self.assertEqual(response.status_code, 200)
|
|
286
|
+
self.assertEqual(response.content, b"Overridden!")
|
|
287
|
+
|
|
288
|
+
def test_before_create_snippet_hook_get_with_json_response(self):
|
|
289
|
+
def non_json_hook_func(request, model):
|
|
290
|
+
self.assertIsInstance(request, HttpRequest)
|
|
291
|
+
self.assertEqual(model, Advert)
|
|
292
|
+
return HttpResponse("Overridden!")
|
|
293
|
+
|
|
294
|
+
def json_hook_func(request, model):
|
|
295
|
+
self.assertIsInstance(request, HttpRequest)
|
|
296
|
+
self.assertEqual(model, Advert)
|
|
297
|
+
return JsonResponse({"status": "purple"})
|
|
298
|
+
|
|
299
|
+
with self.register_hook("before_create_snippet", non_json_hook_func):
|
|
300
|
+
response = self.get(headers={"Accept": "application/json"})
|
|
301
|
+
self.assertEqual(
|
|
302
|
+
response.json(),
|
|
303
|
+
{
|
|
304
|
+
"success": False,
|
|
305
|
+
"error_code": "blocked_by_hook",
|
|
306
|
+
"error_message": "Request to create advert was blocked by hook.",
|
|
307
|
+
},
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
with self.register_hook("before_create_snippet", json_hook_func):
|
|
311
|
+
response = self.get(headers={"Accept": "application/json"})
|
|
312
|
+
self.assertEqual(response.json(), {"status": "purple"})
|
|
313
|
+
|
|
314
|
+
def test_before_create_snippet_hook_post(self):
|
|
315
|
+
def hook_func(request, model):
|
|
316
|
+
self.assertIsInstance(request, HttpRequest)
|
|
317
|
+
self.assertEqual(model, Advert)
|
|
318
|
+
return HttpResponse("Overridden!")
|
|
319
|
+
|
|
320
|
+
with self.register_hook("before_create_snippet", hook_func):
|
|
321
|
+
post_data = {"text": "Hook test", "url": "http://www.example.com/"}
|
|
322
|
+
response = self.post(post_data=post_data)
|
|
323
|
+
|
|
324
|
+
self.assertEqual(response.status_code, 200)
|
|
325
|
+
self.assertEqual(response.content, b"Overridden!")
|
|
326
|
+
|
|
327
|
+
# Request intercepted before advert was created
|
|
328
|
+
self.assertFalse(Advert.objects.exists())
|
|
329
|
+
|
|
330
|
+
def test_before_create_snippet_hook_post_with_json_response(self):
|
|
331
|
+
def non_json_hook_func(request, model):
|
|
332
|
+
self.assertIsInstance(request, HttpRequest)
|
|
333
|
+
self.assertEqual(model, Advert)
|
|
334
|
+
return HttpResponse("Overridden!")
|
|
335
|
+
|
|
336
|
+
def json_hook_func(request, model):
|
|
337
|
+
self.assertIsInstance(request, HttpRequest)
|
|
338
|
+
self.assertEqual(model, Advert)
|
|
339
|
+
return JsonResponse({"status": "purple"})
|
|
340
|
+
|
|
341
|
+
with self.register_hook("before_create_snippet", non_json_hook_func):
|
|
342
|
+
post_data = {"text": "Hook test", "url": "http://www.example.com/"}
|
|
343
|
+
response = self.post(
|
|
344
|
+
post_data=post_data,
|
|
345
|
+
headers={"Accept": "application/json"},
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
self.assertEqual(response.status_code, 400)
|
|
349
|
+
self.assertEqual(
|
|
350
|
+
response.json(),
|
|
351
|
+
{
|
|
352
|
+
"success": False,
|
|
353
|
+
"error_code": "blocked_by_hook",
|
|
354
|
+
"error_message": "Request to create advert was blocked by hook.",
|
|
355
|
+
},
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
# Request intercepted before advert was created
|
|
359
|
+
self.assertFalse(Advert.objects.exists())
|
|
360
|
+
|
|
361
|
+
with self.register_hook("before_create_snippet", json_hook_func):
|
|
362
|
+
post_data = {"text": "Hook test", "url": "http://www.example.com/"}
|
|
363
|
+
response = self.post(
|
|
364
|
+
post_data=post_data,
|
|
365
|
+
headers={"Accept": "application/json"},
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
self.assertEqual(response.status_code, 200)
|
|
369
|
+
self.assertEqual(response.json(), {"status": "purple"})
|
|
370
|
+
|
|
371
|
+
# Request intercepted before advert was created
|
|
372
|
+
self.assertFalse(Advert.objects.exists())
|
|
373
|
+
|
|
374
|
+
def test_after_create_snippet_hook(self):
|
|
375
|
+
def hook_func(request, instance):
|
|
376
|
+
self.assertIsInstance(request, HttpRequest)
|
|
377
|
+
self.assertEqual(instance.text, "Hook test")
|
|
378
|
+
self.assertEqual(instance.url, "http://www.example.com/")
|
|
379
|
+
return HttpResponse("Overridden!")
|
|
380
|
+
|
|
381
|
+
with self.register_hook("after_create_snippet", hook_func):
|
|
382
|
+
post_data = {"text": "Hook test", "url": "http://www.example.com/"}
|
|
383
|
+
response = self.post(post_data=post_data)
|
|
384
|
+
|
|
385
|
+
self.assertEqual(response.status_code, 200)
|
|
386
|
+
self.assertEqual(response.content, b"Overridden!")
|
|
387
|
+
|
|
388
|
+
# Request intercepted after advert was created
|
|
389
|
+
self.assertTrue(Advert.objects.exists())
|
|
390
|
+
|
|
391
|
+
def test_after_create_snippet_hook_post_with_json_response(self):
|
|
392
|
+
def non_json_hook_func(request, instance):
|
|
393
|
+
self.assertIsInstance(request, HttpRequest)
|
|
394
|
+
self.assertEqual(instance.text, "Hook test")
|
|
395
|
+
self.assertEqual(instance.url, "http://www.example.com/")
|
|
396
|
+
return HttpResponse("Overridden!")
|
|
397
|
+
|
|
398
|
+
def json_hook_func(request, instance):
|
|
399
|
+
self.assertIsInstance(request, HttpRequest)
|
|
400
|
+
self.assertEqual(instance.text, "Another hook test")
|
|
401
|
+
self.assertEqual(instance.url, "http://www.example.com/")
|
|
402
|
+
return JsonResponse({"status": "purple"})
|
|
403
|
+
|
|
404
|
+
with self.register_hook("after_create_snippet", non_json_hook_func):
|
|
405
|
+
post_data = {"text": "Hook test", "url": "http://www.example.com/"}
|
|
406
|
+
response = self.post(
|
|
407
|
+
post_data=post_data,
|
|
408
|
+
headers={"Accept": "application/json"},
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
self.assertEqual(response.status_code, 200)
|
|
412
|
+
# hook response is ignored, since it's not a JSON response
|
|
413
|
+
self.assertEqual(response.json()["success"], True)
|
|
414
|
+
|
|
415
|
+
# Request intercepted after advert was created
|
|
416
|
+
self.assertTrue(Advert.objects.filter(text="Hook test").exists())
|
|
417
|
+
|
|
418
|
+
with self.register_hook("after_create_snippet", json_hook_func):
|
|
419
|
+
post_data = {"text": "Another hook test", "url": "http://www.example.com/"}
|
|
420
|
+
response = self.post(
|
|
421
|
+
post_data=post_data,
|
|
422
|
+
headers={"Accept": "application/json"},
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
self.assertEqual(response.status_code, 200)
|
|
426
|
+
# hook response is used, since it's a JSON response
|
|
427
|
+
self.assertEqual(response.json(), {"status": "purple"})
|
|
428
|
+
|
|
429
|
+
# Request intercepted after advert was created
|
|
430
|
+
self.assertTrue(Advert.objects.filter(text="Another hook test").exists())
|
|
431
|
+
|
|
432
|
+
def test_register_snippet_action_menu_item(self):
|
|
433
|
+
class TestSnippetActionMenuItem(ActionMenuItem):
|
|
434
|
+
label = "Test"
|
|
435
|
+
name = "test"
|
|
436
|
+
icon_name = "check"
|
|
437
|
+
classname = "custom-class"
|
|
438
|
+
|
|
439
|
+
def is_shown(self, context):
|
|
440
|
+
return True
|
|
441
|
+
|
|
442
|
+
def hook_func(model):
|
|
443
|
+
return TestSnippetActionMenuItem(order=0)
|
|
444
|
+
|
|
445
|
+
with self.register_hook("register_snippet_action_menu_item", hook_func):
|
|
446
|
+
get_base_snippet_action_menu_items.cache_clear()
|
|
447
|
+
|
|
448
|
+
response = self.get()
|
|
449
|
+
|
|
450
|
+
get_base_snippet_action_menu_items.cache_clear()
|
|
451
|
+
|
|
452
|
+
self.assertContains(
|
|
453
|
+
response,
|
|
454
|
+
'<button type="submit" name="test" value="Test" class="button custom-class"><svg class="icon icon-check icon" aria-hidden="true"><use href="#icon-check"></use></svg>Test</button>',
|
|
455
|
+
html=True,
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
def test_register_snippet_action_menu_item_as_none(self):
|
|
459
|
+
def hook_func(model):
|
|
460
|
+
return None
|
|
461
|
+
|
|
462
|
+
with self.register_hook("register_snippet_action_menu_item", hook_func):
|
|
463
|
+
get_base_snippet_action_menu_items.cache_clear()
|
|
464
|
+
|
|
465
|
+
response = self.get()
|
|
466
|
+
|
|
467
|
+
get_base_snippet_action_menu_items.cache_clear()
|
|
468
|
+
self.assertEqual(response.status_code, 200)
|
|
469
|
+
|
|
470
|
+
def test_construct_snippet_action_menu(self):
|
|
471
|
+
class TestSnippetActionMenuItem(ActionMenuItem):
|
|
472
|
+
label = "Test"
|
|
473
|
+
name = "test"
|
|
474
|
+
icon_name = "check"
|
|
475
|
+
classname = "custom-class"
|
|
476
|
+
|
|
477
|
+
def is_shown(self, context):
|
|
478
|
+
return True
|
|
479
|
+
|
|
480
|
+
class Media:
|
|
481
|
+
js = ["js/some-default-item.js"]
|
|
482
|
+
css = {"all": ["css/some-default-item.css"]}
|
|
483
|
+
|
|
484
|
+
def hook_func(menu_items, request, context):
|
|
485
|
+
self.assertIsInstance(menu_items, list)
|
|
486
|
+
self.assertIsInstance(request, WSGIRequest)
|
|
487
|
+
self.assertEqual(context["view"], "create")
|
|
488
|
+
self.assertEqual(context["model"], Advert)
|
|
489
|
+
|
|
490
|
+
# Replace save menu item
|
|
491
|
+
menu_items[:] = [TestSnippetActionMenuItem(order=0)]
|
|
492
|
+
|
|
493
|
+
with self.register_hook("construct_snippet_action_menu", hook_func):
|
|
494
|
+
response = self.get()
|
|
495
|
+
|
|
496
|
+
soup = self.get_soup(response.content)
|
|
497
|
+
custom_action = soup.select_one("form button[name='test']")
|
|
498
|
+
self.assertIsNotNone(custom_action)
|
|
499
|
+
|
|
500
|
+
# We're replacing the save button, so it should not be in a dropdown
|
|
501
|
+
# as it's the main action
|
|
502
|
+
dropdown_parent = custom_action.find_parent(attrs={"class": "w-dropdown"})
|
|
503
|
+
self.assertIsNone(dropdown_parent)
|
|
504
|
+
|
|
505
|
+
self.assertEqual(custom_action.text.strip(), "Test")
|
|
506
|
+
self.assertEqual(custom_action.attrs.get("class"), ["button", "custom-class"])
|
|
507
|
+
icon = custom_action.select_one("svg use[href='#icon-check']")
|
|
508
|
+
self.assertIsNotNone(icon)
|
|
509
|
+
|
|
510
|
+
# Should contain media files
|
|
511
|
+
js = soup.select_one("script[src='/static/js/some-default-item.js']")
|
|
512
|
+
self.assertIsNotNone(js)
|
|
513
|
+
css = soup.select_one("link[href='/static/css/some-default-item.css']")
|
|
514
|
+
self.assertIsNotNone(css)
|
|
515
|
+
|
|
516
|
+
save_item = soup.select_one("form button[name='action-save']")
|
|
517
|
+
self.assertIsNone(save_item)
|
|
518
|
+
|
|
519
|
+
def test_create_shows_status_side_panel_skeleton(self):
|
|
520
|
+
self.user.first_name = "Chrismansyah"
|
|
521
|
+
self.user.last_name = "Rahadi"
|
|
522
|
+
self.user.save()
|
|
523
|
+
response = self.get(model=RevisableModel)
|
|
524
|
+
soup = self.get_soup(response.content)
|
|
525
|
+
panel = soup.select_one('[data-side-panel="status"]')
|
|
526
|
+
self.assertIsNotNone(panel)
|
|
527
|
+
|
|
528
|
+
def assert_panel_section(label_id, label_text, description):
|
|
529
|
+
section = panel.select_one(f'[aria-labelledby="{label_id}"]')
|
|
530
|
+
self.assertIsNotNone(section)
|
|
531
|
+
label = section.select_one(f"#{label_id}")
|
|
532
|
+
self.assertIsNotNone(label)
|
|
533
|
+
self.assertEqual(label.get_text(separator="\n", strip=True), label_text)
|
|
534
|
+
self.assertEqual(
|
|
535
|
+
section.get_text(separator="\n", strip=True),
|
|
536
|
+
f"{label_text}\n{description}",
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
assert_panel_section(
|
|
540
|
+
"status-sidebar-live",
|
|
541
|
+
"Live",
|
|
542
|
+
"To be created by Chrismansyah Rahadi",
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
usage_section = panel.select("section")[-1]
|
|
546
|
+
self.assertIsNotNone(usage_section)
|
|
547
|
+
self.assertEqual(
|
|
548
|
+
usage_section.get_text(separator="\n", strip=True),
|
|
549
|
+
"Usage\nUsed 0 times",
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
@override_settings(WAGTAIL_I18N_ENABLED=True)
|
|
554
|
+
class TestLocaleSelectorOnCreate(WagtailTestUtils, TestCase):
|
|
555
|
+
fixtures = ["test.json"]
|
|
556
|
+
|
|
557
|
+
def setUp(self):
|
|
558
|
+
self.fr_locale = Locale.objects.create(language_code="fr")
|
|
559
|
+
self.user = self.login()
|
|
560
|
+
|
|
561
|
+
def test_locale_selector(self):
|
|
562
|
+
response = self.client.get(
|
|
563
|
+
reverse("wagtailsnippets_snippetstests_translatablesnippet:add")
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
self.assertContains(response, "Switch locales")
|
|
567
|
+
|
|
568
|
+
switch_to_french_url = (
|
|
569
|
+
reverse("wagtailsnippets_snippetstests_translatablesnippet:add")
|
|
570
|
+
+ "?locale=fr"
|
|
571
|
+
)
|
|
572
|
+
self.assertContains(
|
|
573
|
+
response,
|
|
574
|
+
f'<a href="{switch_to_french_url}" lang="fr">',
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
def test_locale_selector_with_existing_locale(self):
|
|
578
|
+
response = self.client.get(
|
|
579
|
+
reverse("wagtailsnippets_snippetstests_translatablesnippet:add")
|
|
580
|
+
+ "?locale=fr"
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
self.assertContains(response, "Switch locales")
|
|
584
|
+
|
|
585
|
+
switch_to_english_url = (
|
|
586
|
+
reverse("wagtailsnippets_snippetstests_translatablesnippet:add")
|
|
587
|
+
+ "?locale=en"
|
|
588
|
+
)
|
|
589
|
+
self.assertContains(
|
|
590
|
+
response,
|
|
591
|
+
f'<a href="{switch_to_english_url}" lang="en">',
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
@override_settings(WAGTAIL_I18N_ENABLED=False)
|
|
595
|
+
def test_locale_selector_not_present_when_i18n_disabled(self):
|
|
596
|
+
response = self.client.get(
|
|
597
|
+
reverse("wagtailsnippets_snippetstests_translatablesnippet:add")
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
self.assertNotContains(response, "Switch locales")
|
|
601
|
+
|
|
602
|
+
switch_to_french_url = (
|
|
603
|
+
reverse("wagtailsnippets_snippetstests_translatablesnippet:add")
|
|
604
|
+
+ "?locale=fr"
|
|
605
|
+
)
|
|
606
|
+
self.assertNotContains(
|
|
607
|
+
response,
|
|
608
|
+
f'<a href="{switch_to_french_url}" lang="fr">',
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
def test_locale_selector_not_present_on_non_translatable_snippet(self):
|
|
612
|
+
response = self.client.get(reverse("wagtailsnippets_tests_advert:add"))
|
|
613
|
+
|
|
614
|
+
self.assertNotContains(response, "Switch locales")
|
|
615
|
+
|
|
616
|
+
switch_to_french_url = (
|
|
617
|
+
reverse("wagtailsnippets_snippetstests_translatablesnippet:add")
|
|
618
|
+
+ "?locale=fr"
|
|
619
|
+
)
|
|
620
|
+
self.assertNotContains(
|
|
621
|
+
response,
|
|
622
|
+
f'<a href="{switch_to_french_url}" lang="fr">',
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
class TestCreateDraftStateSnippet(WagtailTestUtils, TestCase):
|
|
627
|
+
STATUS_TOGGLE_BADGE_REGEX = (
|
|
628
|
+
r'data-side-panel-toggle="status"[^<]+<svg[^<]+<use[^<]+</use[^<]+</svg[^<]+'
|
|
629
|
+
r"<div data-side-panel-toggle-counter[^>]+w-bg-critical-200[^>]+>\s*%(num_errors)s\s*</div>"
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
def setUp(self):
|
|
633
|
+
self.user = self.login()
|
|
634
|
+
|
|
635
|
+
def get(self):
|
|
636
|
+
return self.client.get(reverse("wagtailsnippets_tests_draftstatemodel:add"))
|
|
637
|
+
|
|
638
|
+
def post(self, post_data=None):
|
|
639
|
+
return self.client.post(
|
|
640
|
+
reverse("wagtailsnippets_tests_draftstatemodel:add"),
|
|
641
|
+
post_data,
|
|
642
|
+
)
|
|
643
|
+
|
|
644
|
+
def test_get(self):
|
|
645
|
+
add_url = reverse("wagtailsnippets_tests_draftstatemodel:add")
|
|
646
|
+
response = self.get()
|
|
647
|
+
|
|
648
|
+
self.assertEqual(response.status_code, 200)
|
|
649
|
+
self.assertTemplateUsed(response, "wagtailsnippets/snippets/create.html")
|
|
650
|
+
|
|
651
|
+
# The save button should be labelled "Save draft"
|
|
652
|
+
self.assertContains(response, "Save draft")
|
|
653
|
+
# The publish button should exist
|
|
654
|
+
self.assertContains(response, "Publish")
|
|
655
|
+
# The publish button should have name="action-publish"
|
|
656
|
+
self.assertContains(
|
|
657
|
+
response,
|
|
658
|
+
'<button\n type="submit"\n name="action-publish"\n value="action-publish"\n class="button action-save button-longrunning"\n data-controller="w-progress"\n data-action="w-progress#activate"\n',
|
|
659
|
+
)
|
|
660
|
+
# The status side panel should be rendered so that the
|
|
661
|
+
# publishing schedule can be configured
|
|
662
|
+
self.assertContains(
|
|
663
|
+
response,
|
|
664
|
+
'<div class="form-side__panel" data-side-panel="status" hidden>',
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
# The status side panel should show "No publishing schedule set" info
|
|
668
|
+
self.assertContains(response, "No publishing schedule set")
|
|
669
|
+
|
|
670
|
+
# Should show the "Set schedule" button
|
|
671
|
+
html = response.content.decode()
|
|
672
|
+
self.assertTagInHTML(
|
|
673
|
+
'<button type="button" data-a11y-dialog-show="schedule-publishing-dialog">Set schedule</button>',
|
|
674
|
+
html,
|
|
675
|
+
count=1,
|
|
676
|
+
allow_extra_attrs=True,
|
|
677
|
+
)
|
|
678
|
+
# Should show the dialog template pointing to the [data-edit-form] selector as the root
|
|
679
|
+
soup = self.get_soup(html)
|
|
680
|
+
dialog = soup.select_one(
|
|
681
|
+
"""
|
|
682
|
+
template[data-controller="w-teleport"][data-w-teleport-target-value="[data-edit-form]"]
|
|
683
|
+
#schedule-publishing-dialog
|
|
684
|
+
"""
|
|
685
|
+
)
|
|
686
|
+
self.assertIsNotNone(dialog)
|
|
687
|
+
# Should render the main form with data-edit-form attribute
|
|
688
|
+
self.assertTagInHTML(
|
|
689
|
+
f'<form action="{add_url}" method="POST" data-edit-form>',
|
|
690
|
+
html,
|
|
691
|
+
count=1,
|
|
692
|
+
allow_extra_attrs=True,
|
|
693
|
+
)
|
|
694
|
+
self.assertTagInHTML(
|
|
695
|
+
'<div id="schedule-publishing-dialog" class="w-dialog publishing" data-controller="w-dialog">',
|
|
696
|
+
html,
|
|
697
|
+
count=1,
|
|
698
|
+
allow_extra_attrs=True,
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
# Should show the correct subtitle in the dialog
|
|
702
|
+
self.assertContains(
|
|
703
|
+
response, "Choose when this draft state model should go live and/or expire"
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
# Should not show the Unpublish action menu item
|
|
707
|
+
unpublish_url = "/admin/snippets/tests/draftstatemodel/unpublish/"
|
|
708
|
+
self.assertNotContains(response, unpublish_url)
|
|
709
|
+
self.assertNotContains(response, "Unpublish")
|
|
710
|
+
|
|
711
|
+
def test_save_draft(self):
|
|
712
|
+
response = self.post(post_data={"text": "Draft-enabled Foo"})
|
|
713
|
+
snippet = DraftStateModel.objects.get(text="Draft-enabled Foo")
|
|
714
|
+
|
|
715
|
+
self.assertRedirects(
|
|
716
|
+
response,
|
|
717
|
+
reverse("wagtailsnippets_tests_draftstatemodel:edit", args=[snippet.pk]),
|
|
718
|
+
)
|
|
719
|
+
|
|
720
|
+
# The instance should be created
|
|
721
|
+
self.assertEqual(snippet.text, "Draft-enabled Foo")
|
|
722
|
+
|
|
723
|
+
# The instance should be a draft
|
|
724
|
+
self.assertFalse(snippet.live)
|
|
725
|
+
self.assertTrue(snippet.has_unpublished_changes)
|
|
726
|
+
self.assertIsNone(snippet.first_published_at)
|
|
727
|
+
self.assertIsNone(snippet.last_published_at)
|
|
728
|
+
self.assertIsNone(snippet.live_revision)
|
|
729
|
+
|
|
730
|
+
# A revision should be created and set as latest_revision
|
|
731
|
+
self.assertIsNotNone(snippet.latest_revision)
|
|
732
|
+
|
|
733
|
+
# The revision content should contain the data
|
|
734
|
+
self.assertEqual(snippet.latest_revision.content["text"], "Draft-enabled Foo")
|
|
735
|
+
|
|
736
|
+
# A log entry should be created
|
|
737
|
+
log_entry = ModelLogEntry.objects.for_instance(snippet).get(
|
|
738
|
+
action="wagtail.create"
|
|
739
|
+
)
|
|
740
|
+
self.assertEqual(log_entry.revision, snippet.latest_revision)
|
|
741
|
+
self.assertEqual(log_entry.label, "Draft-enabled Foo")
|
|
742
|
+
|
|
743
|
+
def test_create_skips_validation_when_saving_draft(self):
|
|
744
|
+
response = self.post(post_data={"text": ""})
|
|
745
|
+
snippet = DraftStateModel.objects.get(text="")
|
|
746
|
+
|
|
747
|
+
self.assertRedirects(
|
|
748
|
+
response,
|
|
749
|
+
reverse(
|
|
750
|
+
"wagtailsnippets_tests_draftstatemodel:edit", args=[quote(snippet.pk)]
|
|
751
|
+
),
|
|
752
|
+
)
|
|
753
|
+
|
|
754
|
+
self.assertFalse(snippet.live)
|
|
755
|
+
|
|
756
|
+
# A log entry should be created (with a fallback label)
|
|
757
|
+
log_entry = ModelLogEntry.objects.for_instance(snippet).get(
|
|
758
|
+
action="wagtail.create"
|
|
759
|
+
)
|
|
760
|
+
self.assertEqual(log_entry.revision, snippet.latest_revision)
|
|
761
|
+
self.assertEqual(log_entry.label, f"DraftStateModel object ({snippet.pk})")
|
|
762
|
+
|
|
763
|
+
def test_required_asterisk_on_reshowing_form(self):
|
|
764
|
+
"""
|
|
765
|
+
If a form is reshown due to a validation error elsewhere, fields whose validation
|
|
766
|
+
was deferred should still show the required asterisk.
|
|
767
|
+
"""
|
|
768
|
+
response = self.client.post(
|
|
769
|
+
reverse("some_namespace:add"),
|
|
770
|
+
{"text": "", "country_code": "UK", "some_number": "meef"},
|
|
771
|
+
)
|
|
772
|
+
|
|
773
|
+
self.assertEqual(response.status_code, 200)
|
|
774
|
+
|
|
775
|
+
# The empty text should not cause a validation error, but the invalid number should
|
|
776
|
+
self.assertNotContains(response, "This field is required.")
|
|
777
|
+
self.assertContains(response, "Enter a whole number.", count=1)
|
|
778
|
+
|
|
779
|
+
soup = self.get_soup(response.content)
|
|
780
|
+
self.assertTrue(soup.select_one('label[for="id_text"] > span.w-required-mark'))
|
|
781
|
+
|
|
782
|
+
def test_create_will_not_publish_invalid_snippet(self):
|
|
783
|
+
response = self.post(
|
|
784
|
+
post_data={"text": "", "action-publish": "Publish"},
|
|
785
|
+
)
|
|
786
|
+
self.assertEqual(response.status_code, 200)
|
|
787
|
+
self.assertContains(
|
|
788
|
+
response, "The draft state model could not be created due to errors."
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
snippets = DraftStateModel.objects.filter(text="")
|
|
792
|
+
self.assertEqual(snippets.count(), 0)
|
|
793
|
+
|
|
794
|
+
def test_publish(self):
|
|
795
|
+
# Connect a mock signal handler to published signal
|
|
796
|
+
mock_handler = mock.MagicMock()
|
|
797
|
+
published.connect(mock_handler)
|
|
798
|
+
|
|
799
|
+
try:
|
|
800
|
+
timestamp = now()
|
|
801
|
+
with freeze_time(timestamp):
|
|
802
|
+
response = self.post(
|
|
803
|
+
post_data={
|
|
804
|
+
"text": "Draft-enabled Foo, Published",
|
|
805
|
+
"action-publish": "action-publish",
|
|
806
|
+
}
|
|
807
|
+
)
|
|
808
|
+
snippet = DraftStateModel.objects.get(text="Draft-enabled Foo, Published")
|
|
809
|
+
|
|
810
|
+
self.assertRedirects(
|
|
811
|
+
response, reverse("wagtailsnippets_tests_draftstatemodel:list")
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
# The instance should be created
|
|
815
|
+
self.assertEqual(snippet.text, "Draft-enabled Foo, Published")
|
|
816
|
+
|
|
817
|
+
# The instance should be live
|
|
818
|
+
self.assertTrue(snippet.live)
|
|
819
|
+
self.assertFalse(snippet.has_unpublished_changes)
|
|
820
|
+
self.assertEqual(snippet.first_published_at, timestamp)
|
|
821
|
+
self.assertEqual(snippet.last_published_at, timestamp)
|
|
822
|
+
|
|
823
|
+
# A revision should be created and set as both latest_revision and live_revision
|
|
824
|
+
self.assertIsNotNone(snippet.live_revision)
|
|
825
|
+
self.assertEqual(snippet.live_revision, snippet.latest_revision)
|
|
826
|
+
|
|
827
|
+
# The revision content should contain the new data
|
|
828
|
+
self.assertEqual(
|
|
829
|
+
snippet.live_revision.content["text"],
|
|
830
|
+
"Draft-enabled Foo, Published",
|
|
831
|
+
)
|
|
832
|
+
|
|
833
|
+
# Check that the published signal was fired
|
|
834
|
+
self.assertEqual(mock_handler.call_count, 1)
|
|
835
|
+
mock_call = mock_handler.mock_calls[0][2]
|
|
836
|
+
|
|
837
|
+
self.assertEqual(mock_call["sender"], DraftStateModel)
|
|
838
|
+
self.assertEqual(mock_call["instance"], snippet)
|
|
839
|
+
self.assertIsInstance(mock_call["instance"], DraftStateModel)
|
|
840
|
+
finally:
|
|
841
|
+
published.disconnect(mock_handler)
|
|
842
|
+
|
|
843
|
+
def test_publish_bad_permissions(self):
|
|
844
|
+
# Only add create and edit permission
|
|
845
|
+
self.user.is_superuser = False
|
|
846
|
+
add_permission = Permission.objects.get(
|
|
847
|
+
content_type__app_label="tests",
|
|
848
|
+
codename="add_draftstatemodel",
|
|
849
|
+
)
|
|
850
|
+
edit_permission = Permission.objects.get(
|
|
851
|
+
content_type__app_label="tests",
|
|
852
|
+
codename="change_draftstatemodel",
|
|
853
|
+
)
|
|
854
|
+
admin_permission = Permission.objects.get(
|
|
855
|
+
content_type__app_label="wagtailadmin",
|
|
856
|
+
codename="access_admin",
|
|
857
|
+
)
|
|
858
|
+
self.user.user_permissions.add(
|
|
859
|
+
add_permission,
|
|
860
|
+
edit_permission,
|
|
861
|
+
admin_permission,
|
|
862
|
+
)
|
|
863
|
+
self.user.save()
|
|
864
|
+
|
|
865
|
+
# Connect a mock signal handler to published signal
|
|
866
|
+
mock_handler = mock.MagicMock()
|
|
867
|
+
published.connect(mock_handler)
|
|
868
|
+
|
|
869
|
+
try:
|
|
870
|
+
response = self.post(
|
|
871
|
+
post_data={
|
|
872
|
+
"text": "Draft-enabled Foo",
|
|
873
|
+
"action-publish": "action-publish",
|
|
874
|
+
}
|
|
875
|
+
)
|
|
876
|
+
snippet = DraftStateModel.objects.get(text="Draft-enabled Foo")
|
|
877
|
+
|
|
878
|
+
# Should be taken to the edit page
|
|
879
|
+
self.assertRedirects(
|
|
880
|
+
response,
|
|
881
|
+
reverse(
|
|
882
|
+
"wagtailsnippets_tests_draftstatemodel:edit",
|
|
883
|
+
args=[snippet.pk],
|
|
884
|
+
),
|
|
885
|
+
)
|
|
886
|
+
|
|
887
|
+
# The instance should still be created
|
|
888
|
+
self.assertEqual(snippet.text, "Draft-enabled Foo")
|
|
889
|
+
|
|
890
|
+
# The instance should not be live
|
|
891
|
+
self.assertFalse(snippet.live)
|
|
892
|
+
self.assertTrue(snippet.has_unpublished_changes)
|
|
893
|
+
|
|
894
|
+
# A revision should be created and set as latest_revision, but not live_revision
|
|
895
|
+
self.assertIsNotNone(snippet.latest_revision)
|
|
896
|
+
self.assertIsNone(snippet.live_revision)
|
|
897
|
+
|
|
898
|
+
# The revision content should contain the data
|
|
899
|
+
self.assertEqual(
|
|
900
|
+
snippet.latest_revision.content["text"],
|
|
901
|
+
"Draft-enabled Foo",
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
# Check that the published signal was not fired
|
|
905
|
+
self.assertEqual(mock_handler.call_count, 0)
|
|
906
|
+
finally:
|
|
907
|
+
published.disconnect(mock_handler)
|
|
908
|
+
|
|
909
|
+
def test_publish_with_publish_permission(self):
|
|
910
|
+
# Use create and publish permissions instead of relying on superuser flag
|
|
911
|
+
self.user.is_superuser = False
|
|
912
|
+
add_permission = Permission.objects.get(
|
|
913
|
+
content_type__app_label="tests",
|
|
914
|
+
codename="add_draftstatemodel",
|
|
915
|
+
)
|
|
916
|
+
publish_permission = Permission.objects.get(
|
|
917
|
+
content_type__app_label="tests",
|
|
918
|
+
codename="publish_draftstatemodel",
|
|
919
|
+
)
|
|
920
|
+
admin_permission = Permission.objects.get(
|
|
921
|
+
content_type__app_label="wagtailadmin",
|
|
922
|
+
codename="access_admin",
|
|
923
|
+
)
|
|
924
|
+
self.user.user_permissions.add(
|
|
925
|
+
add_permission,
|
|
926
|
+
publish_permission,
|
|
927
|
+
admin_permission,
|
|
928
|
+
)
|
|
929
|
+
self.user.save()
|
|
930
|
+
|
|
931
|
+
# Connect a mock signal handler to published signal
|
|
932
|
+
mock_handler = mock.MagicMock()
|
|
933
|
+
published.connect(mock_handler)
|
|
934
|
+
|
|
935
|
+
try:
|
|
936
|
+
timestamp = now()
|
|
937
|
+
with freeze_time(timestamp):
|
|
938
|
+
response = self.post(
|
|
939
|
+
post_data={
|
|
940
|
+
"text": "Draft-enabled Foo, Published",
|
|
941
|
+
"action-publish": "action-publish",
|
|
942
|
+
}
|
|
943
|
+
)
|
|
944
|
+
snippet = DraftStateModel.objects.get(text="Draft-enabled Foo, Published")
|
|
945
|
+
|
|
946
|
+
self.assertRedirects(
|
|
947
|
+
response, reverse("wagtailsnippets_tests_draftstatemodel:list")
|
|
948
|
+
)
|
|
949
|
+
|
|
950
|
+
# The instance should be created
|
|
951
|
+
self.assertEqual(snippet.text, "Draft-enabled Foo, Published")
|
|
952
|
+
|
|
953
|
+
# The instance should be live
|
|
954
|
+
self.assertTrue(snippet.live)
|
|
955
|
+
self.assertFalse(snippet.has_unpublished_changes)
|
|
956
|
+
self.assertEqual(snippet.first_published_at, timestamp)
|
|
957
|
+
self.assertEqual(snippet.last_published_at, timestamp)
|
|
958
|
+
|
|
959
|
+
# A revision should be created and set as both latest_revision and live_revision
|
|
960
|
+
self.assertIsNotNone(snippet.live_revision)
|
|
961
|
+
self.assertEqual(snippet.live_revision, snippet.latest_revision)
|
|
962
|
+
|
|
963
|
+
# The revision content should contain the new data
|
|
964
|
+
self.assertEqual(
|
|
965
|
+
snippet.live_revision.content["text"],
|
|
966
|
+
"Draft-enabled Foo, Published",
|
|
967
|
+
)
|
|
968
|
+
|
|
969
|
+
# Check that the published signal was fired
|
|
970
|
+
self.assertEqual(mock_handler.call_count, 1)
|
|
971
|
+
mock_call = mock_handler.mock_calls[0][2]
|
|
972
|
+
|
|
973
|
+
self.assertEqual(mock_call["sender"], DraftStateModel)
|
|
974
|
+
self.assertEqual(mock_call["instance"], snippet)
|
|
975
|
+
self.assertIsInstance(mock_call["instance"], DraftStateModel)
|
|
976
|
+
finally:
|
|
977
|
+
published.disconnect(mock_handler)
|
|
978
|
+
|
|
979
|
+
def test_create_scheduled(self):
|
|
980
|
+
go_live_at = now() + datetime.timedelta(days=1)
|
|
981
|
+
expire_at = now() + datetime.timedelta(days=2)
|
|
982
|
+
response = self.post(
|
|
983
|
+
post_data={
|
|
984
|
+
"text": "Some content",
|
|
985
|
+
"go_live_at": submittable_timestamp(go_live_at),
|
|
986
|
+
"expire_at": submittable_timestamp(expire_at),
|
|
987
|
+
}
|
|
988
|
+
)
|
|
989
|
+
|
|
990
|
+
snippet = DraftStateModel.objects.get(text="Some content")
|
|
991
|
+
|
|
992
|
+
# Should be redirected to the edit page
|
|
993
|
+
self.assertRedirects(
|
|
994
|
+
response,
|
|
995
|
+
reverse("wagtailsnippets_tests_draftstatemodel:edit", args=[snippet.pk]),
|
|
996
|
+
)
|
|
997
|
+
|
|
998
|
+
# Should be saved as draft with the scheduled publishing dates
|
|
999
|
+
self.assertEqual(snippet.go_live_at.date(), go_live_at.date())
|
|
1000
|
+
self.assertEqual(snippet.expire_at.date(), expire_at.date())
|
|
1001
|
+
self.assertIs(snippet.expired, False)
|
|
1002
|
+
self.assertEqual(snippet.status_string, "draft")
|
|
1003
|
+
|
|
1004
|
+
# No revisions with approved_go_live_at
|
|
1005
|
+
self.assertFalse(
|
|
1006
|
+
Revision.objects.for_instance(snippet)
|
|
1007
|
+
.exclude(approved_go_live_at__isnull=True)
|
|
1008
|
+
.exists()
|
|
1009
|
+
)
|
|
1010
|
+
|
|
1011
|
+
def test_create_scheduled_go_live_before_expiry(self):
|
|
1012
|
+
response = self.post(
|
|
1013
|
+
post_data={
|
|
1014
|
+
"text": "Some content",
|
|
1015
|
+
"go_live_at": submittable_timestamp(now() + datetime.timedelta(days=2)),
|
|
1016
|
+
"expire_at": submittable_timestamp(now() + datetime.timedelta(days=1)),
|
|
1017
|
+
}
|
|
1018
|
+
)
|
|
1019
|
+
|
|
1020
|
+
self.assertEqual(response.status_code, 200)
|
|
1021
|
+
|
|
1022
|
+
# Check that a form error was raised
|
|
1023
|
+
self.assertFormError(
|
|
1024
|
+
response.context["form"],
|
|
1025
|
+
"go_live_at",
|
|
1026
|
+
"Go live date/time must be before expiry date/time",
|
|
1027
|
+
)
|
|
1028
|
+
self.assertFormError(
|
|
1029
|
+
response.context["form"],
|
|
1030
|
+
"expire_at",
|
|
1031
|
+
"Go live date/time must be before expiry date/time",
|
|
1032
|
+
)
|
|
1033
|
+
|
|
1034
|
+
self.assertContains(
|
|
1035
|
+
response,
|
|
1036
|
+
'<div class="w-label-3 w-text-primary">Invalid schedule</div>',
|
|
1037
|
+
html=True,
|
|
1038
|
+
)
|
|
1039
|
+
|
|
1040
|
+
num_errors = 2
|
|
1041
|
+
|
|
1042
|
+
# Should show the correct number on the badge of the toggle button
|
|
1043
|
+
self.assertRegex(
|
|
1044
|
+
response.content.decode(),
|
|
1045
|
+
self.STATUS_TOGGLE_BADGE_REGEX % {"num_errors": num_errors},
|
|
1046
|
+
)
|
|
1047
|
+
|
|
1048
|
+
def test_create_scheduled_expire_in_the_past(self):
|
|
1049
|
+
response = self.post(
|
|
1050
|
+
post_data={
|
|
1051
|
+
"text": "Some content",
|
|
1052
|
+
"expire_at": submittable_timestamp(now() + datetime.timedelta(days=-1)),
|
|
1053
|
+
}
|
|
1054
|
+
)
|
|
1055
|
+
|
|
1056
|
+
self.assertEqual(response.status_code, 200)
|
|
1057
|
+
|
|
1058
|
+
# Check that a form error was raised
|
|
1059
|
+
self.assertFormError(
|
|
1060
|
+
response.context["form"],
|
|
1061
|
+
"expire_at",
|
|
1062
|
+
"Expiry date/time must be in the future.",
|
|
1063
|
+
)
|
|
1064
|
+
|
|
1065
|
+
self.assertContains(
|
|
1066
|
+
response,
|
|
1067
|
+
'<div class="w-label-3 w-text-primary">Invalid schedule</div>',
|
|
1068
|
+
html=True,
|
|
1069
|
+
)
|
|
1070
|
+
|
|
1071
|
+
num_errors = 1
|
|
1072
|
+
|
|
1073
|
+
# Should show the correct number on the badge of the toggle button
|
|
1074
|
+
self.assertRegex(
|
|
1075
|
+
response.content.decode(),
|
|
1076
|
+
self.STATUS_TOGGLE_BADGE_REGEX % {"num_errors": num_errors},
|
|
1077
|
+
)
|
|
1078
|
+
|
|
1079
|
+
def test_create_post_publish_scheduled(self):
|
|
1080
|
+
go_live_at = now() + datetime.timedelta(days=1)
|
|
1081
|
+
expire_at = now() + datetime.timedelta(days=2)
|
|
1082
|
+
response = self.post(
|
|
1083
|
+
post_data={
|
|
1084
|
+
"text": "Some content",
|
|
1085
|
+
"action-publish": "Publish",
|
|
1086
|
+
"go_live_at": submittable_timestamp(go_live_at),
|
|
1087
|
+
"expire_at": submittable_timestamp(expire_at),
|
|
1088
|
+
}
|
|
1089
|
+
)
|
|
1090
|
+
|
|
1091
|
+
# Should be redirected to the listing page
|
|
1092
|
+
self.assertRedirects(
|
|
1093
|
+
response, reverse("wagtailsnippets_tests_draftstatemodel:list")
|
|
1094
|
+
)
|
|
1095
|
+
|
|
1096
|
+
# Find the object and check it
|
|
1097
|
+
snippet = DraftStateModel.objects.get(text="Some content")
|
|
1098
|
+
self.assertEqual(snippet.go_live_at.date(), go_live_at.date())
|
|
1099
|
+
self.assertEqual(snippet.expire_at.date(), expire_at.date())
|
|
1100
|
+
self.assertIs(snippet.expired, False)
|
|
1101
|
+
|
|
1102
|
+
# A revision with approved_go_live_at should exist now
|
|
1103
|
+
self.assertTrue(
|
|
1104
|
+
Revision.objects.for_instance(snippet)
|
|
1105
|
+
.exclude(approved_go_live_at__isnull=True)
|
|
1106
|
+
.exists()
|
|
1107
|
+
)
|
|
1108
|
+
# But snippet won't be live
|
|
1109
|
+
self.assertFalse(snippet.live)
|
|
1110
|
+
self.assertFalse(snippet.first_published_at)
|
|
1111
|
+
self.assertEqual(snippet.status_string, "scheduled")
|
|
1112
|
+
|
|
1113
|
+
def test_create_shows_status_side_panel_skeleton(self):
|
|
1114
|
+
self.user.first_name = "Chrismansyah"
|
|
1115
|
+
self.user.last_name = "Rahadi"
|
|
1116
|
+
self.user.save()
|
|
1117
|
+
response = self.get()
|
|
1118
|
+
soup = self.get_soup(response.content)
|
|
1119
|
+
panel = soup.select_one('[data-side-panel="status"]')
|
|
1120
|
+
self.assertIsNotNone(panel)
|
|
1121
|
+
|
|
1122
|
+
def assert_panel_section(label_id, label_text, description):
|
|
1123
|
+
section = panel.select_one(f'[aria-labelledby="{label_id}"]')
|
|
1124
|
+
self.assertIsNotNone(section)
|
|
1125
|
+
label = section.select_one(f"#{label_id}")
|
|
1126
|
+
self.assertIsNotNone(label)
|
|
1127
|
+
self.assertEqual(label.get_text(separator="\n", strip=True), label_text)
|
|
1128
|
+
self.assertEqual(
|
|
1129
|
+
section.get_text(separator="\n", strip=True),
|
|
1130
|
+
f"{label_text}\n{description}",
|
|
1131
|
+
)
|
|
1132
|
+
|
|
1133
|
+
assert_panel_section(
|
|
1134
|
+
"status-sidebar-draft",
|
|
1135
|
+
"Draft",
|
|
1136
|
+
"To be created by Chrismansyah Rahadi",
|
|
1137
|
+
)
|
|
1138
|
+
|
|
1139
|
+
usage_section = panel.select("section")[-1]
|
|
1140
|
+
self.assertIsNotNone(usage_section)
|
|
1141
|
+
self.assertEqual(
|
|
1142
|
+
usage_section.get_text(separator="\n", strip=True),
|
|
1143
|
+
"Usage\nUsed 0 times",
|
|
1144
|
+
)
|
|
1145
|
+
|
|
1146
|
+
|
|
1147
|
+
class TestInlinePanelMedia(WagtailTestUtils, TestCase):
|
|
1148
|
+
"""
|
|
1149
|
+
Test that form media required by InlinePanels is correctly pulled in to the edit page
|
|
1150
|
+
"""
|
|
1151
|
+
|
|
1152
|
+
def test_inline_panel_media(self):
|
|
1153
|
+
self.login()
|
|
1154
|
+
|
|
1155
|
+
response = self.client.get(
|
|
1156
|
+
reverse("wagtailsnippets_snippetstests_multisectionrichtextsnippet:add")
|
|
1157
|
+
)
|
|
1158
|
+
self.assertEqual(response.status_code, 200)
|
|
1159
|
+
self.assertContains(response, "wagtailadmin/js/draftail.js")
|