diff --git a/wagtail/admin/tests/pages/__init__.py b/wagtail/admin/tests/pages/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wagtail/admin/tests/pages/test_content_type_use_view.py b/wagtail/admin/tests/pages/test_content_type_use_view.py new file mode 100644 index 000000000..3aff42ca6 --- /dev/null +++ b/wagtail/admin/tests/pages/test_content_type_use_view.py @@ -0,0 +1,20 @@ +from django.test import TestCase +from django.urls import reverse + +from wagtail.tests.utils import WagtailTestUtils + + +class TestContentTypeUse(TestCase, WagtailTestUtils): + fixtures = ['test.json'] + + def setUp(self): + self.user = self.login() + + def test_content_type_use(self): + # Get use of event page + response = self.client.get(reverse('wagtailadmin_pages:type_use', args=('tests', 'eventpage'))) + + # Check response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/content_type_use.html') + self.assertContains(response, "Christmas") diff --git a/wagtail/admin/tests/pages/test_copy_page.py b/wagtail/admin/tests/pages/test_copy_page.py new file mode 100644 index 000000000..19dee3f84 --- /dev/null +++ b/wagtail/admin/tests/pages/test_copy_page.py @@ -0,0 +1,447 @@ +from django import VERSION as DJANGO_VERSION +from django.contrib.auth.models import Group, Permission +from django.http import HttpRequest, HttpResponse +from django.test import TestCase +from django.urls import reverse + +from wagtail.core.models import GroupPagePermission, Page +from wagtail.tests.testapp.models import SimplePage +from wagtail.tests.utils import WagtailTestUtils + + +class TestPageCopy(TestCase, WagtailTestUtils): + + def setUp(self): + # Find root page + self.root_page = Page.objects.get(id=2) + + # Create a page + self.test_page = self.root_page.add_child(instance=SimplePage( + title="Hello world!", + slug='hello-world', + content="hello", + live=True, + has_unpublished_changes=False, + )) + + # Create a couple of child pages + self.test_child_page = self.test_page.add_child(instance=SimplePage( + title="Child page", + slug='child-page', + content="hello", + live=True, + has_unpublished_changes=True, + )) + + self.test_unpublished_child_page = self.test_page.add_child(instance=SimplePage( + title="Unpublished Child page", + slug='unpublished-child-page', + content="hello", + live=False, + has_unpublished_changes=True, + )) + + # Login + self.user = self.login() + + def test_page_copy(self): + response = self.client.get(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, ))) + + # Check response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/copy.html') + + # Make sure all fields are in the form + self.assertContains(response, "New title") + self.assertContains(response, "New slug") + self.assertContains(response, "New parent page") + self.assertContains(response, "Copy subpages") + self.assertContains(response, "Publish copies") + + def test_page_copy_bad_permissions(self): + # Remove privileges from user + self.user.is_superuser = False + self.user.user_permissions.add( + Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') + ) + self.user.save() + + # Get copy page + post_data = { + 'new_title': "Hello world 2", + 'new_slug': 'hello-world', + 'new_parent_page': str(self.test_page.id), + 'copy_subpages': False, + } + response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) + + # A user with no page permissions at all should be redirected to the admin home + self.assertRedirects(response, reverse('wagtailadmin_home')) + + # A user with page permissions, but not add permission at the destination, + # should receive a form validation error + publishers = Group.objects.create(name='Publishers') + GroupPagePermission.objects.create( + group=publishers, page=self.root_page, permission_type='publish' + ) + self.user.groups.add(publishers) + self.user.save() + + # Get copy page + post_data = { + 'new_title': "Hello world 2", + 'new_slug': 'hello-world', + 'new_parent_page': str(self.test_page.id), + 'copy_subpages': False, + } + response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) + form = response.context['form'] + self.assertFalse(form.is_valid()) + self.assertTrue('new_parent_page' in form.errors) + + def test_page_copy_post(self): + post_data = { + 'new_title': "Hello world 2", + 'new_slug': 'hello-world-2', + 'new_parent_page': str(self.root_page.id), + 'copy_subpages': False, + 'publish_copies': False, + } + response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) + + # Check that the user was redirected to the parents explore page + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + + # Get copy + page_copy = self.root_page.get_children().filter(slug='hello-world-2').first() + + # Check that the copy exists + self.assertNotEqual(page_copy, None) + + # Check that the copy is not live + self.assertFalse(page_copy.live) + self.assertTrue(page_copy.has_unpublished_changes) + + # Check that the owner of the page is set correctly + self.assertEqual(page_copy.owner, self.user) + + # Check that the children were not copied + self.assertEqual(page_copy.get_children().count(), 0) + + # treebeard should report no consistency problems with the tree + self.assertFalse(any(Page.find_problems()), 'treebeard found consistency problems') + + def test_page_copy_post_copy_subpages(self): + post_data = { + 'new_title': "Hello world 2", + 'new_slug': 'hello-world-2', + 'new_parent_page': str(self.root_page.id), + 'copy_subpages': True, + 'publish_copies': False, + } + response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) + + # Check that the user was redirected to the parents explore page + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + + # Get copy + page_copy = self.root_page.get_children().filter(slug='hello-world-2').first() + + # Check that the copy exists + self.assertNotEqual(page_copy, None) + + # Check that the copy is not live + self.assertFalse(page_copy.live) + self.assertTrue(page_copy.has_unpublished_changes) + + # Check that the owner of the page is set correctly + self.assertEqual(page_copy.owner, self.user) + + # Check that the children were copied + self.assertEqual(page_copy.get_children().count(), 2) + + # Check the the child pages + # Neither of them should be live + child_copy = page_copy.get_children().filter(slug='child-page').first() + self.assertNotEqual(child_copy, None) + self.assertFalse(child_copy.live) + self.assertTrue(child_copy.has_unpublished_changes) + + unpublished_child_copy = page_copy.get_children().filter(slug='unpublished-child-page').first() + self.assertNotEqual(unpublished_child_copy, None) + self.assertFalse(unpublished_child_copy.live) + self.assertTrue(unpublished_child_copy.has_unpublished_changes) + + # treebeard should report no consistency problems with the tree + self.assertFalse(any(Page.find_problems()), 'treebeard found consistency problems') + + def test_page_copy_post_copy_subpages_publish_copies(self): + post_data = { + 'new_title': "Hello world 2", + 'new_slug': 'hello-world-2', + 'new_parent_page': str(self.root_page.id), + 'copy_subpages': True, + 'publish_copies': True, + } + response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) + + # Check that the user was redirected to the parents explore page + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + + # Get copy + page_copy = self.root_page.get_children().filter(slug='hello-world-2').first() + + # Check that the copy exists + self.assertNotEqual(page_copy, None) + + # Check that the copy is live + self.assertTrue(page_copy.live) + self.assertFalse(page_copy.has_unpublished_changes) + + # Check that the owner of the page is set correctly + self.assertEqual(page_copy.owner, self.user) + + # Check that the children were copied + self.assertEqual(page_copy.get_children().count(), 2) + + # Check the the child pages + # The child_copy should be live but the unpublished_child_copy shouldn't + child_copy = page_copy.get_children().filter(slug='child-page').first() + self.assertNotEqual(child_copy, None) + self.assertTrue(child_copy.live) + self.assertTrue(child_copy.has_unpublished_changes) + + unpublished_child_copy = page_copy.get_children().filter(slug='unpublished-child-page').first() + self.assertNotEqual(unpublished_child_copy, None) + self.assertFalse(unpublished_child_copy.live) + self.assertTrue(unpublished_child_copy.has_unpublished_changes) + + # treebeard should report no consistency problems with the tree + self.assertFalse(any(Page.find_problems()), 'treebeard found consistency problems') + + def test_page_copy_post_new_parent(self): + post_data = { + 'new_title': "Hello world 2", + 'new_slug': 'hello-world-2', + 'new_parent_page': str(self.test_child_page.id), + 'copy_subpages': False, + 'publish_copies': False, + } + response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) + + # Check that the user was redirected to the new parents explore page + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.test_child_page.id, ))) + + # Check that the page was copied to the correct place + self.assertTrue(Page.objects.filter(slug='hello-world-2').first().get_parent(), self.test_child_page) + + # treebeard should report no consistency problems with the tree + self.assertFalse(any(Page.find_problems()), 'treebeard found consistency problems') + + def test_page_copy_post_existing_slug_within_same_parent_page(self): + # This tests the existing slug checking on page copy when not changing the parent page + + # Attempt to copy the page but forget to change the slug + post_data = { + 'new_title': "Hello world 2", + 'new_slug': 'hello-world', + 'new_parent_page': str(self.root_page.id), + 'copy_subpages': False, + } + response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) + + # Should not be redirected (as the save should fail) + self.assertEqual(response.status_code, 200) + + # Check that a form error was raised + self.assertFormError( + response, + 'form', + 'new_slug', + "This slug is already in use within the context of its parent page \"Welcome to your new Wagtail site!\"" + ) + + def test_page_copy_post_and_subpages_to_same_tree_branch(self): + # This tests that a page cannot be copied into itself when copying subpages + post_data = { + 'new_title': "Hello world 2", + 'new_slug': 'hello-world', + 'new_parent_page': str(self.test_child_page.id), + 'copy_subpages': True, + } + response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id,)), post_data) + + # Should not be redirected (as the save should fail) + self.assertEqual(response.status_code, 200) + + # Check that a form error was raised + self.assertFormError( + response, 'form', 'new_parent_page', "You cannot copy a page into itself when copying subpages" + ) + + def test_page_copy_post_existing_slug_to_another_parent_page(self): + # This tests the existing slug checking on page copy when changing the parent page + + # Attempt to copy the page and changed the parent page + post_data = { + 'new_title': "Hello world 2", + 'new_slug': 'hello-world', + 'new_parent_page': str(self.test_child_page.id), + 'copy_subpages': False, + } + response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) + + # Check that the user was redirected to the parents explore page + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.test_child_page.id, ))) + + def test_page_copy_post_invalid_slug(self): + # Attempt to copy the page but set an invalid slug string + post_data = { + 'new_title': "Hello world 2", + 'new_slug': 'hello world!', + 'new_parent_page': str(self.root_page.id), + 'copy_subpages': False, + } + response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) + + # Should not be redirected (as the save should fail) + self.assertEqual(response.status_code, 200) + + # Check that a form error was raised + if DJANGO_VERSION >= (3, 0): + self.assertFormError( + response, 'form', 'new_slug', "Enter a valid “slug” consisting of letters, numbers, underscores or hyphens." + ) + else: + self.assertFormError( + response, 'form', 'new_slug', "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." + ) + + def test_page_copy_no_publish_permission(self): + # Turn user into an editor who can add pages but not publish them + self.user.is_superuser = False + self.user.groups.add( + Group.objects.get(name="Editors"), + ) + self.user.save() + + # Get copy page + response = self.client.get(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, ))) + + # The user should have access to the copy page + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/copy.html') + + # Make sure the "publish copies" field is hidden + self.assertNotContains(response, "Publish copies") + + def test_page_copy_no_publish_permission_post_copy_subpages_publish_copies(self): + # This tests that unprivileged users cannot publish copied pages even if they hack their browser + + # Turn user into an editor who can add pages but not publish them + self.user.is_superuser = False + self.user.groups.add( + Group.objects.get(name="Editors"), + ) + self.user.save() + + # Post + post_data = { + 'new_title': "Hello world 2", + 'new_slug': 'hello-world-2', + 'new_parent_page': str(self.root_page.id), + 'copy_subpages': True, + 'publish_copies': True, + } + response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) + + # Check that the user was redirected to the parents explore page + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + + # Get copy + page_copy = self.root_page.get_children().filter(slug='hello-world-2').first() + + # Check that the copy exists + self.assertNotEqual(page_copy, None) + + # Check that the copy is not live + self.assertFalse(page_copy.live) + + # Check that the owner of the page is set correctly + self.assertEqual(page_copy.owner, self.user) + + # Check that the children were copied + self.assertEqual(page_copy.get_children().count(), 2) + + # Check the the child pages + # Neither of them should be live + child_copy = page_copy.get_children().filter(slug='child-page').first() + self.assertNotEqual(child_copy, None) + self.assertFalse(child_copy.live) + + unpublished_child_copy = page_copy.get_children().filter(slug='unpublished-child-page').first() + self.assertNotEqual(unpublished_child_copy, None) + self.assertFalse(unpublished_child_copy.live) + + # treebeard should report no consistency problems with the tree + self.assertFalse(any(Page.find_problems()), 'treebeard found consistency problems') + + def test_before_copy_page_hook(self): + def hook_func(request, page): + self.assertIsInstance(request, HttpRequest) + self.assertIsInstance(page.specific, SimplePage) + + return HttpResponse("Overridden!") + + with self.register_hook('before_copy_page', hook_func): + response = self.client.get(reverse('wagtailadmin_pages:copy', args=(self.test_page.id,))) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b"Overridden!") + + def test_before_copy_page_hook_post(self): + def hook_func(request, page): + self.assertIsInstance(request, HttpRequest) + self.assertIsInstance(page.specific, SimplePage) + + return HttpResponse("Overridden!") + + with self.register_hook('before_copy_page', hook_func): + post_data = { + 'new_title': "Hello world 2", + 'new_slug': 'hello-world-2', + 'new_parent_page': str(self.root_page.id), + 'copy_subpages': False, + 'publish_copies': False, + } + response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id,)), post_data) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b"Overridden!") + + # page should not be copied + self.assertFalse(Page.objects.filter(title="Hello world 2").exists()) + + def test_after_copy_page_hook(self): + def hook_func(request, page, new_page): + self.assertIsInstance(request, HttpRequest) + self.assertIsInstance(page.specific, SimplePage) + self.assertIsInstance(new_page.specific, SimplePage) + + return HttpResponse("Overridden!") + + with self.register_hook('after_copy_page', hook_func): + post_data = { + 'new_title': "Hello world 2", + 'new_slug': 'hello-world-2', + 'new_parent_page': str(self.root_page.id), + 'copy_subpages': False, + 'publish_copies': False, + } + response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id,)), post_data) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b"Overridden!") + + # page should be copied + self.assertTrue(Page.objects.filter(title="Hello world 2").exists()) diff --git a/wagtail/admin/tests/pages/test_create_page.py b/wagtail/admin/tests/pages/test_create_page.py new file mode 100644 index 000000000..710493cb2 --- /dev/null +++ b/wagtail/admin/tests/pages/test_create_page.py @@ -0,0 +1,881 @@ +import datetime +from unittest import mock + +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group, Permission +from django.core import mail +from django.http import HttpRequest, HttpResponse +from django.test import TestCase +from django.urls import reverse +from django.utils import timezone + +from wagtail.admin.tests.pages.timestamps import submittable_timestamp +from wagtail.core.models import GroupPagePermission, Page, PageRevision +from wagtail.core.signals import page_published +from wagtail.tests.testapp.models import ( + BusinessChild, BusinessIndex, BusinessSubIndex, DefaultStreamPage, SimplePage, + SingletonPage, SingletonPageViaMaxCount, StandardChild, StandardIndex) +from wagtail.tests.utils import WagtailTestUtils + + +class TestPageCreation(TestCase, WagtailTestUtils): + def setUp(self): + # Find root page + self.root_page = Page.objects.get(id=2) + + # Login + self.user = self.login() + + def test_add_subpage(self): + response = self.client.get(reverse('wagtailadmin_pages:add_subpage', args=(self.root_page.id, ))) + self.assertEqual(response.status_code, 200) + + self.assertContains(response, "Simple page") + target_url = reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)) + self.assertContains(response, 'href="%s"' % target_url) + # List of available page types should not contain pages with is_creatable = False + self.assertNotContains(response, "MTI base page") + # List of available page types should not contain abstract pages + self.assertNotContains(response, "Abstract page") + # List of available page types should not contain pages whose parent_page_types forbid it + self.assertNotContains(response, "Business child") + + def test_add_subpage_with_subpage_types(self): + # Add a BusinessIndex to test business rules in + business_index = BusinessIndex( + title="Hello world!", + slug="hello-world", + ) + self.root_page.add_child(instance=business_index) + + response = self.client.get(reverse('wagtailadmin_pages:add_subpage', args=(business_index.id, ))) + self.assertEqual(response.status_code, 200) + + self.assertContains(response, "Business child") + # List should not contain page types not in the subpage_types list + self.assertNotContains(response, "Simple page") + + def test_add_subpage_with_one_valid_subpage_type(self): + # Add a BusinessSubIndex to test business rules in + business_index = BusinessIndex( + title="Hello world!", + slug="hello-world", + ) + self.root_page.add_child(instance=business_index) + business_subindex = BusinessSubIndex( + title="Hello world!", + slug="hello-world", + ) + business_index.add_child(instance=business_subindex) + + response = self.client.get(reverse('wagtailadmin_pages:add_subpage', args=(business_subindex.id, ))) + # Should be redirected to the 'add' page for BusinessChild, the only valid subpage type + self.assertRedirects( + response, + reverse('wagtailadmin_pages:add', args=('tests', 'businesschild', business_subindex.id)) + ) + + def test_add_subpage_bad_permissions(self): + # Remove privileges from user + self.user.is_superuser = False + self.user.user_permissions.add( + Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') + ) + self.user.save() + + # Get add subpage page + response = self.client.get(reverse('wagtailadmin_pages:add_subpage', args=(self.root_page.id, ))) + + # Check that the user received a 403 response + self.assertEqual(response.status_code, 403) + + def test_add_subpage_nonexistantparent(self): + response = self.client.get(reverse('wagtailadmin_pages:add_subpage', args=(100000, ))) + self.assertEqual(response.status_code, 404) + + def test_add_subpage_with_next_param(self): + response = self.client.get( + reverse('wagtailadmin_pages:add_subpage', args=(self.root_page.id, )), + {'next': '/admin/users/'} + ) + self.assertEqual(response.status_code, 200) + + self.assertContains(response, "Simple page") + target_url = reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)) + self.assertContains(response, 'href="%s?next=/admin/users/"' % target_url) + + def test_create_simplepage(self): + response = self.client.get(reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id))) + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'Content') + self.assertContains(response, 'Promote') + # test register_page_action_menu_item hook + self.assertContains(response, '') + self.assertContains(response, 'testapp/js/siren.js') + # test construct_page_action_menu hook + self.assertContains(response, '') + + def test_create_multipart(self): + """ + Test checks if 'enctype="multipart/form-data"' is added and only to forms that require multipart encoding. + """ + # check for SimplePage where is no file field + response = self.client.get(reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id))) + self.assertEqual(response.status_code, 200) + self.assertNotContains(response, 'enctype="multipart/form-data"') + self.assertTemplateUsed(response, 'wagtailadmin/pages/create.html') + + # check for FilePage which has file field + response = self.client.get(reverse('wagtailadmin_pages:add', args=('tests', 'filepage', self.root_page.id))) + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'enctype="multipart/form-data"') + + def test_create_page_without_promote_tab(self): + """ + Test that the Promote tab is not rendered for page classes that define it as empty + """ + response = self.client.get( + reverse('wagtailadmin_pages:add', args=('tests', 'standardindex', self.root_page.id)) + ) + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'Content') + self.assertNotContains(response, 'Promote') + + def test_create_page_with_custom_tabs(self): + """ + Test that custom edit handlers are rendered + """ + response = self.client.get( + reverse('wagtailadmin_pages:add', args=('tests', 'standardchild', self.root_page.id)) + ) + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'Content') + self.assertContains(response, 'Promote') + self.assertContains(response, 'Dinosaurs') + + def test_create_page_with_non_model_field(self): + """ + Test that additional fields defined on the form rather than the model are accepted and rendered + """ + response = self.client.get(reverse('wagtailadmin_pages:add', args=('tests', 'formclassadditionalfieldpage', self.root_page.id))) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/create.html') + self.assertContains(response, "Enter SMS authentication code") + + def test_create_simplepage_bad_permissions(self): + # Remove privileges from user + self.user.is_superuser = False + self.user.user_permissions.add( + Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') + ) + self.user.save() + + # Get page + response = self.client.get(reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id, ))) + + # Check that the user received a 403 response + self.assertEqual(response.status_code, 403) + + def test_cannot_create_page_with_is_creatable_false(self): + # tests.MTIBasePage has is_creatable=False, so attempting to add a new one + # should fail with permission denied + response = self.client.get( + reverse('wagtailadmin_pages:add', args=('tests', 'mtibasepage', self.root_page.id)) + ) + self.assertEqual(response.status_code, 403) + + def test_cannot_create_page_when_can_create_at_returns_false(self): + # issue #2892 + + # Check that creating a second SingletonPage results in a permission + # denied error. + + # SingletonPage overrides the can_create_at method to make it return + # False if another SingletonPage already exists. + + # The Page model now has a max_count attribute (issue 4841), + # but we are leaving this test in place to cover existing behaviour and + # ensure it does not break any code doing this in the wild. + add_url = reverse('wagtailadmin_pages:add', args=[ + SingletonPage._meta.app_label, SingletonPage._meta.model_name, self.root_page.pk]) + + # A single singleton page should be creatable + self.assertTrue(SingletonPage.can_create_at(self.root_page)) + response = self.client.get(add_url) + self.assertEqual(response.status_code, 200) + + # Create a singleton page + self.root_page.add_child(instance=SingletonPage( + title='singleton', slug='singleton')) + + # A second singleton page should not be creatable + self.assertFalse(SingletonPage.can_create_at(self.root_page)) + response = self.client.get(add_url) + self.assertEqual(response.status_code, 403) + + def test_cannot_create_singleton_page_with_max_count(self): + # Check that creating a second SingletonPageViaMaxCount results in a permission + # denied error. + + # SingletonPageViaMaxCount uses the max_count attribute to limit the number of + # instance it can have. + + add_url = reverse('wagtailadmin_pages:add', args=[ + SingletonPageViaMaxCount._meta.app_label, SingletonPageViaMaxCount._meta.model_name, self.root_page.pk]) + + # A single singleton page should be creatable + self.assertTrue(SingletonPageViaMaxCount.can_create_at(self.root_page)) + response = self.client.get(add_url) + self.assertEqual(response.status_code, 200) + + # Create a singleton page + self.root_page.add_child(instance=SingletonPageViaMaxCount( + title='singleton', slug='singleton')) + + # A second singleton page should not be creatable + self.assertFalse(SingletonPageViaMaxCount.can_create_at(self.root_page)) + response = self.client.get(add_url) + self.assertEqual(response.status_code, 403) + + def test_cannot_create_page_with_wrong_parent_page_types(self): + # tests.BusinessChild has limited parent_page_types, so attempting to add + # a new one at the root level should fail with permission denied + response = self.client.get( + reverse('wagtailadmin_pages:add', args=('tests', 'businesschild', self.root_page.id)) + ) + self.assertEqual(response.status_code, 403) + + def test_cannot_create_page_with_wrong_subpage_types(self): + # Add a BusinessIndex to test business rules in + business_index = BusinessIndex( + title="Hello world!", + slug="hello-world", + ) + self.root_page.add_child(instance=business_index) + + # BusinessIndex has limited subpage_types, so attempting to add a SimplePage + # underneath it should fail with permission denied + response = self.client.get( + reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', business_index.id)) + ) + self.assertEqual(response.status_code, 403) + + def test_create_simplepage_post(self): + post_data = { + 'title': "New page!", + 'content': "Some content", + 'slug': 'hello-world', + } + response = self.client.post( + reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), + post_data + ) + + # Find the page and check it + page = Page.objects.get(path__startswith=self.root_page.path, slug='hello-world').specific + + # Should be redirected to edit page + self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(page.id, ))) + + self.assertEqual(page.title, post_data['title']) + self.assertEqual(page.draft_title, post_data['title']) + self.assertIsInstance(page, SimplePage) + self.assertFalse(page.live) + self.assertFalse(page.first_published_at) + + # treebeard should report no consistency problems with the tree + self.assertFalse(any(Page.find_problems()), 'treebeard found consistency problems') + + def test_create_simplepage_scheduled(self): + go_live_at = timezone.now() + datetime.timedelta(days=1) + expire_at = timezone.now() + datetime.timedelta(days=2) + post_data = { + 'title': "New page!", + 'content': "Some content", + 'slug': 'hello-world', + 'go_live_at': submittable_timestamp(go_live_at), + 'expire_at': submittable_timestamp(expire_at), + } + response = self.client.post( + reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), post_data + ) + + # Should be redirected to explorer page + self.assertEqual(response.status_code, 302) + + # Find the page and check the scheduled times + page = Page.objects.get(path__startswith=self.root_page.path, slug='hello-world').specific + self.assertEqual(page.go_live_at.date(), go_live_at.date()) + self.assertEqual(page.expire_at.date(), expire_at.date()) + self.assertEqual(page.expired, False) + self.assertTrue(page.status_string, "draft") + + # No revisions with approved_go_live_at + self.assertFalse(PageRevision.objects.filter(page=page).exclude(approved_go_live_at__isnull=True).exists()) + + def test_create_simplepage_scheduled_go_live_before_expiry(self): + post_data = { + 'title': "New page!", + 'content': "Some content", + 'slug': 'hello-world', + 'go_live_at': submittable_timestamp(timezone.now() + datetime.timedelta(days=2)), + 'expire_at': submittable_timestamp(timezone.now() + datetime.timedelta(days=1)), + } + response = self.client.post( + reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), post_data + ) + + self.assertEqual(response.status_code, 200) + + # Check that a form error was raised + self.assertFormError(response, 'form', 'go_live_at', "Go live date/time must be before expiry date/time") + self.assertFormError(response, 'form', 'expire_at', "Go live date/time must be before expiry date/time") + + # form should be marked as having unsaved changes for the purposes of the dirty-forms warning + self.assertContains(response, "alwaysDirty: true") + + def test_create_simplepage_scheduled_expire_in_the_past(self): + post_data = { + 'title': "New page!", + 'content': "Some content", + 'slug': 'hello-world', + 'expire_at': submittable_timestamp(timezone.now() + datetime.timedelta(days=-1)), + } + response = self.client.post( + reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), post_data + ) + + self.assertEqual(response.status_code, 200) + + # Check that a form error was raised + self.assertFormError(response, 'form', 'expire_at', "Expiry date/time must be in the future") + + # form should be marked as having unsaved changes for the purposes of the dirty-forms warning + self.assertContains(response, "alwaysDirty: true") + + def test_create_simplepage_post_publish(self): + # Connect a mock signal handler to page_published signal + mock_handler = mock.MagicMock() + page_published.connect(mock_handler) + + # Post + post_data = { + 'title': "New page!", + 'content': "Some content", + 'slug': 'hello-world', + 'action-publish': "Publish", + } + response = self.client.post( + reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), post_data + ) + + # Find the page and check it + page = Page.objects.get(path__startswith=self.root_page.path, slug='hello-world').specific + + # Should be redirected to explorer + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + + self.assertEqual(page.title, post_data['title']) + self.assertEqual(page.draft_title, post_data['title']) + self.assertIsInstance(page, SimplePage) + self.assertTrue(page.live) + self.assertTrue(page.first_published_at) + + # Check that the page_published signal was fired + self.assertEqual(mock_handler.call_count, 1) + mock_call = mock_handler.mock_calls[0][2] + + self.assertEqual(mock_call['sender'], page.specific_class) + self.assertEqual(mock_call['instance'], page) + self.assertIsInstance(mock_call['instance'], page.specific_class) + + # treebeard should report no consistency problems with the tree + self.assertFalse(any(Page.find_problems()), 'treebeard found consistency problems') + + def test_create_simplepage_post_publish_scheduled(self): + go_live_at = timezone.now() + datetime.timedelta(days=1) + expire_at = timezone.now() + datetime.timedelta(days=2) + post_data = { + 'title': "New page!", + 'content': "Some content", + 'slug': 'hello-world', + 'action-publish': "Publish", + 'go_live_at': submittable_timestamp(go_live_at), + 'expire_at': submittable_timestamp(expire_at), + } + response = self.client.post( + reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), post_data + ) + + # Should be redirected to explorer page + self.assertEqual(response.status_code, 302) + + # Find the page and check it + page = Page.objects.get(path__startswith=self.root_page.path, slug='hello-world').specific + self.assertEqual(page.go_live_at.date(), go_live_at.date()) + self.assertEqual(page.expire_at.date(), expire_at.date()) + self.assertEqual(page.expired, False) + + # A revision with approved_go_live_at should exist now + self.assertTrue(PageRevision.objects.filter(page=page).exclude(approved_go_live_at__isnull=True).exists()) + # But Page won't be live + self.assertFalse(page.live) + self.assertFalse(page.first_published_at) + self.assertTrue(page.status_string, "scheduled") + + def test_create_simplepage_post_submit(self): + # Create a moderator user for testing email + get_user_model().objects.create_superuser('moderator', 'moderator@email.com', 'password') + + # Submit + post_data = { + 'title': "New page!", + 'content': "Some content", + 'slug': 'hello-world', + 'action-submit': "Submit", + } + response = self.client.post( + reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), post_data + ) + + # Find the page and check it + page = Page.objects.get(path__startswith=self.root_page.path, slug='hello-world').specific + + # Should be redirected to explorer + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + + self.assertEqual(page.title, post_data['title']) + self.assertIsInstance(page, SimplePage) + self.assertFalse(page.live) + self.assertFalse(page.first_published_at) + + # The latest revision for the page should now be in moderation + self.assertTrue(page.get_latest_revision().submitted_for_moderation) + + # Check that the moderator got an email + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].to, ['moderator@email.com']) + self.assertEqual(mail.outbox[0].subject, 'The page "New page!" has been submitted for moderation') + + def test_create_simplepage_post_existing_slug(self): + # This tests the existing slug checking on page save + + # Create a page + self.child_page = SimplePage(title="Hello world!", slug="hello-world", content="hello") + self.root_page.add_child(instance=self.child_page) + + # Attempt to create a new one with the same slug + post_data = { + 'title': "New page!", + 'content': "Some content", + 'slug': 'hello-world', + 'action-publish': "Publish", + } + response = self.client.post( + reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), post_data + ) + + # Should not be redirected (as the save should fail) + self.assertEqual(response.status_code, 200) + + # Check that a form error was raised + self.assertFormError(response, 'form', 'slug', "This slug is already in use") + + # form should be marked as having unsaved changes for the purposes of the dirty-forms warning + self.assertContains(response, "alwaysDirty: true") + + def test_create_nonexistantparent(self): + response = self.client.get(reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', 100000))) + self.assertEqual(response.status_code, 404) + + def test_create_nonpagetype(self): + response = self.client.get( + reverse('wagtailadmin_pages:add', args=('wagtailimages', 'image', self.root_page.id)) + ) + self.assertEqual(response.status_code, 404) + + def test_preview_on_create(self): + post_data = { + 'title': "New page!", + 'content': "Some content", + 'slug': 'hello-world', + 'action-submit': "Submit", + } + preview_url = reverse('wagtailadmin_pages:preview_on_add', + args=('tests', 'simplepage', self.root_page.id)) + response = self.client.post(preview_url, post_data) + + # Check the JSON response + self.assertEqual(response.status_code, 200) + self.assertJSONEqual(response.content.decode(), {'is_valid': True}) + + response = self.client.get(preview_url) + + # Check the HTML response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'tests/simple_page.html') + self.assertContains(response, "New page!") + + # Check that the treebeard attributes were set correctly on the page object + self.assertEqual(response.context['self'].depth, self.root_page.depth + 1) + self.assertTrue(response.context['self'].path.startswith(self.root_page.path)) + self.assertEqual(response.context['self'].get_parent(), self.root_page) + + def test_whitespace_titles(self): + post_data = { + 'title': " ", # Single space on purpose + 'content': "Some content", + 'slug': 'hello-world', + 'action-submit': "Submit", + } + response = self.client.post( + reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), post_data + ) + + # Check that a form error was raised + self.assertFormError(response, 'form', 'title', "This field is required.") + + def test_whitespace_titles_with_tab(self): + post_data = { + 'title': "\t", # Single space on purpose + 'content': "Some content", + 'slug': 'hello-world', + 'action-submit': "Submit", + } + response = self.client.post(reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), post_data) + + # Check that a form error was raised + self.assertFormError(response, 'form', 'title', "This field is required.") + + def test_whitespace_titles_with_tab_in_seo_title(self): + post_data = { + 'title': "Hello", + 'content': "Some content", + 'slug': 'hello-world', + 'action-submit': "Submit", + 'seo_title': '\t' + } + response = self.client.post(reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), post_data) + + # Should be successful, as seo_title is not required + self.assertEqual(response.status_code, 302) + + # The tab should be automatically stripped from the seo_title + page = Page.objects.order_by('-id').first() + self.assertEqual(page.seo_title, '') + + def test_whitespace_is_stripped_from_titles(self): + post_data = { + 'title': " Hello ", + 'content': "Some content", + 'slug': 'hello-world', + 'action-submit': "Submit", + 'seo_title': ' hello SEO ' + } + response = self.client.post(reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), post_data) + + # Should be successful, as both title and seo_title are non-empty after stripping + self.assertEqual(response.status_code, 302) + + # Whitespace should be automatically stripped from title and seo_title + page = Page.objects.order_by('-id').first() + self.assertEqual(page.title, 'Hello') + self.assertEqual(page.draft_title, 'Hello') + self.assertEqual(page.seo_title, 'hello SEO') + + def test_long_slug(self): + post_data = { + 'title': "Hello world", + 'content': "Some content", + 'slug': 'hello-world-hello-world-hello-world-hello-world-hello-world-hello-world-' + 'hello-world-hello-world-hello-world-hello-world-hello-world-hello-world-' + 'hello-world-hello-world-hello-world-hello-world-hello-world-hello-world-' + 'hello-world-hello-world-hello-world-hello-world-hello-world-hello-world', + 'action-submit': "Submit", + } + response = self.client.post( + reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), post_data + ) + + # Check that a form error was raised + self.assertEqual(response.status_code, 200) + self.assertFormError(response, 'form', 'slug', "Ensure this value has at most 255 characters (it has 287).") + + def test_before_create_page_hook(self): + def hook_func(request, parent_page, page_class): + self.assertIsInstance(request, HttpRequest) + self.assertEqual(parent_page.id, self.root_page.id) + self.assertEqual(page_class, SimplePage) + + return HttpResponse("Overridden!") + + with self.register_hook('before_create_page', hook_func): + response = self.client.get( + reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)) + ) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b"Overridden!") + + def test_before_create_page_hook_post(self): + def hook_func(request, parent_page, page_class): + self.assertIsInstance(request, HttpRequest) + self.assertEqual(parent_page.id, self.root_page.id) + self.assertEqual(page_class, SimplePage) + + return HttpResponse("Overridden!") + + with self.register_hook('before_create_page', hook_func): + post_data = { + 'title': "New page!", + 'content': "Some content", + 'slug': 'hello-world', + } + response = self.client.post( + reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), + post_data + ) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b"Overridden!") + + # page should not be created + self.assertFalse(Page.objects.filter(title="New page!").exists()) + + def test_after_create_page_hook(self): + def hook_func(request, page): + self.assertIsInstance(request, HttpRequest) + self.assertIsInstance(page, SimplePage) + + return HttpResponse("Overridden!") + + with self.register_hook('after_create_page', hook_func): + post_data = { + 'title': "New page!", + 'content': "Some content", + 'slug': 'hello-world', + } + response = self.client.post( + reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), + post_data + ) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b"Overridden!") + + # page should be created + self.assertTrue(Page.objects.filter(title="New page!").exists()) + + +class TestPerRequestEditHandler(TestCase, WagtailTestUtils): + fixtures = ['test.json'] + + def setUp(self): + # Find root page + self.root_page = Page.objects.get(id=2) + GroupPagePermission.objects.create( + group=Group.objects.get(name="Site-wide editors"), + page=self.root_page, permission_type='add' + ) + + def test_create_page_with_per_request_custom_edit_handlers(self): + """ + Test that per-request custom behaviour in edit handlers is honoured + """ + # non-superusers should not see secret_data + logged_in = self.client.login(username='siteeditor', password='password') + self.assertTrue(logged_in) + response = self.client.get( + reverse('wagtailadmin_pages:add', args=('tests', 'secretpage', self.root_page.id)) + ) + self.assertEqual(response.status_code, 200) + self.assertContains(response, '"boring_data"') + self.assertNotContains(response, '"secret_data"') + + # superusers should see secret_data + logged_in = self.client.login(username='superuser', password='password') + self.assertTrue(logged_in) + response = self.client.get( + reverse('wagtailadmin_pages:add', args=('tests', 'secretpage', self.root_page.id)) + ) + self.assertEqual(response.status_code, 200) + self.assertContains(response, '"boring_data"') + self.assertContains(response, '"secret_data"') + + +class TestSubpageBusinessRules(TestCase, WagtailTestUtils): + def setUp(self): + # Find root page + self.root_page = Page.objects.get(id=2) + + # Add standard page (allows subpages of any type) + self.standard_index = StandardIndex() + self.standard_index.title = "Standard Index" + self.standard_index.slug = "standard-index" + self.root_page.add_child(instance=self.standard_index) + + # Add business page (allows BusinessChild and BusinessSubIndex as subpages) + self.business_index = BusinessIndex() + self.business_index.title = "Business Index" + self.business_index.slug = "business-index" + self.root_page.add_child(instance=self.business_index) + + # Add business child (allows no subpages) + self.business_child = BusinessChild() + self.business_child.title = "Business Child" + self.business_child.slug = "business-child" + self.business_index.add_child(instance=self.business_child) + + # Add business subindex (allows only BusinessChild as subpages) + self.business_subindex = BusinessSubIndex() + self.business_subindex.title = "Business Subindex" + self.business_subindex.slug = "business-subindex" + self.business_index.add_child(instance=self.business_subindex) + + # Login + self.login() + + def test_standard_subpage(self): + add_subpage_url = reverse('wagtailadmin_pages:add_subpage', args=(self.standard_index.id, )) + + # explorer should contain a link to 'add child page' + response = self.client.get(reverse('wagtailadmin_explore', args=(self.standard_index.id, ))) + self.assertEqual(response.status_code, 200) + self.assertContains(response, add_subpage_url) + + # add_subpage should give us choices of StandardChild, and BusinessIndex. + # BusinessSubIndex and BusinessChild are not allowed + response = self.client.get(add_subpage_url) + self.assertEqual(response.status_code, 200) + self.assertContains(response, StandardChild.get_verbose_name()) + self.assertContains(response, BusinessIndex.get_verbose_name()) + self.assertNotContains(response, BusinessSubIndex.get_verbose_name()) + self.assertNotContains(response, BusinessChild.get_verbose_name()) + + def test_business_subpage(self): + add_subpage_url = reverse('wagtailadmin_pages:add_subpage', args=(self.business_index.id, )) + + # explorer should contain a link to 'add child page' + response = self.client.get(reverse('wagtailadmin_explore', args=(self.business_index.id, ))) + self.assertEqual(response.status_code, 200) + self.assertContains(response, add_subpage_url) + + # add_subpage should give us a cut-down set of page types to choose + response = self.client.get(add_subpage_url) + self.assertEqual(response.status_code, 200) + self.assertNotContains(response, StandardIndex.get_verbose_name()) + self.assertNotContains(response, StandardChild.get_verbose_name()) + self.assertContains(response, BusinessSubIndex.get_verbose_name()) + self.assertContains(response, BusinessChild.get_verbose_name()) + + def test_business_child_subpage(self): + add_subpage_url = reverse('wagtailadmin_pages:add_subpage', args=(self.business_child.id, )) + + # explorer should not contain a link to 'add child page', as this page doesn't accept subpages + response = self.client.get(reverse('wagtailadmin_explore', args=(self.business_child.id, ))) + self.assertEqual(response.status_code, 200) + self.assertNotContains(response, add_subpage_url) + + # this also means that fetching add_subpage is blocked at the permission-check level + response = self.client.get(reverse('wagtailadmin_pages:add_subpage', args=(self.business_child.id, ))) + self.assertEqual(response.status_code, 403) + + def test_cannot_add_invalid_subpage_type(self): + # cannot add StandardChild as a child of BusinessIndex, as StandardChild is not present in subpage_types + response = self.client.get( + reverse('wagtailadmin_pages:add', args=('tests', 'standardchild', self.business_index.id)) + ) + self.assertEqual(response.status_code, 403) + + # likewise for BusinessChild which has an empty subpage_types list + response = self.client.get( + reverse('wagtailadmin_pages:add', args=('tests', 'standardchild', self.business_child.id)) + ) + self.assertEqual(response.status_code, 403) + + # cannot add BusinessChild to StandardIndex, as BusinessChild restricts is parent page types + response = self.client.get( + reverse('wagtailadmin_pages:add', args=('tests', 'businesschild', self.standard_index.id)) + ) + self.assertEqual(response.status_code, 403) + + # but we can add a BusinessChild to BusinessIndex + response = self.client.get( + reverse('wagtailadmin_pages:add', args=('tests', 'businesschild', self.business_index.id)) + ) + self.assertEqual(response.status_code, 200) + + def test_not_prompted_for_page_type_when_only_one_choice(self): + response = self.client.get(reverse('wagtailadmin_pages:add_subpage', args=(self.business_subindex.id, ))) + # BusinessChild is the only valid subpage type of BusinessSubIndex, so redirect straight there + self.assertRedirects( + response, reverse('wagtailadmin_pages:add', args=('tests', 'businesschild', self.business_subindex.id)) + ) + + +class TestInlinePanelMedia(TestCase, WagtailTestUtils): + """ + Test that form media required by InlinePanels is correctly pulled in to the edit page + """ + + def test_inline_panel_media(self): + homepage = Page.objects.get(id=2) + self.login() + + # simplepage does not need draftail... + response = self.client.get(reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', homepage.id))) + self.assertEqual(response.status_code, 200) + self.assertNotContains(response, 'wagtailadmin/js/draftail.js') + + # but sectionedrichtextpage does + response = self.client.get(reverse('wagtailadmin_pages:add', args=('tests', 'sectionedrichtextpage', homepage.id))) + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'wagtailadmin/js/draftail.js') + + +class TestInlineStreamField(TestCase, WagtailTestUtils): + """ + Test that streamfields inside an inline child work + """ + + def test_inline_streamfield(self): + homepage = Page.objects.get(id=2) + self.login() + + response = self.client.get(reverse('wagtailadmin_pages:add', args=('tests', 'inlinestreampage', homepage.id))) + self.assertEqual(response.status_code, 200) + + # response should include HTML declarations for streamfield child blocks + self.assertContains(response, '
') + + +class TestIssue2994(TestCase, WagtailTestUtils): + """ + In contrast to most "standard" form fields, StreamField form widgets generally won't + provide a postdata field with a name exactly matching the field name. To prevent Django + from wrongly interpreting this as the field being omitted from the form, + we need to provide a custom value_omitted_from_data method. + """ + + def setUp(self): + self.root_page = Page.objects.get(id=2) + self.user = self.login() + + def test_page_edit_post_publish_url(self): + # Post + post_data = { + 'title': "Issue 2994 test", + 'slug': 'issue-2994-test', + 'body-count': '1', + 'body-0-deleted': '', + 'body-0-order': '0', + 'body-0-type': 'text', + 'body-0-value': 'hello world', + 'action-publish': "Publish", + } + self.client.post( + reverse('wagtailadmin_pages:add', args=('tests', 'defaultstreampage', self.root_page.id)), post_data + ) + new_page = DefaultStreamPage.objects.get(slug='issue-2994-test') + self.assertEqual(1, len(new_page.body)) + self.assertEqual('hello world', new_page.body[0].value) diff --git a/wagtail/admin/tests/pages/test_dashboard.py b/wagtail/admin/tests/pages/test_dashboard.py new file mode 100644 index 000000000..2ae6b683c --- /dev/null +++ b/wagtail/admin/tests/pages/test_dashboard.py @@ -0,0 +1,92 @@ +from django.contrib.auth import get_user_model +from django.test import TestCase +from django.urls import reverse + +from wagtail.admin.views.home import RecentEditsPanel +from wagtail.core.models import Page +from wagtail.tests.testapp.models import SimplePage +from wagtail.tests.utils import WagtailTestUtils + + +class TestRecentEditsPanel(TestCase, WagtailTestUtils): + def setUp(self): + # Find root page + self.root_page = Page.objects.get(id=2) + + # Add child page + child_page = SimplePage( + title="Hello world!", + slug="hello-world", + content="Some content here", + ) + self.root_page.add_child(instance=child_page) + child_page.save_revision().publish() + self.child_page = SimplePage.objects.get(id=child_page.id) + + get_user_model().objects.create_superuser(username='alice', email='alice@email.com', password='password') + get_user_model().objects.create_superuser(username='bob', email='bob@email.com', password='password') + + def change_something(self, title): + post_data = {'title': title, 'content': "Some content", 'slug': 'hello-world'} + response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) + + # Should be redirected to edit page + self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) + + # The page should have "has_unpublished_changes" flag set + child_page_new = SimplePage.objects.get(id=self.child_page.id) + self.assertTrue(child_page_new.has_unpublished_changes) + + def go_to_dashboard_response(self): + response = self.client.get(reverse('wagtailadmin_home')) + self.assertEqual(response.status_code, 200) + return response + + def test_your_recent_edits(self): + # Login as Bob + self.client.login(username='bob', password='password') + + # Bob hasn't edited anything yet + response = self.client.get(reverse('wagtailadmin_home')) + self.assertNotIn('Your most recent edits', response.content.decode('utf-8')) + + # Login as Alice + self.client.logout() + self.client.login(username='alice', password='password') + + # Alice changes something + self.change_something("Alice's edit") + + # Edit should show up on dashboard + response = self.go_to_dashboard_response() + self.assertIn('Your most recent edits', response.content.decode('utf-8')) + + # Bob changes something + self.client.login(username='bob', password='password') + self.change_something("Bob's edit") + + # Edit shows up on Bobs dashboard + response = self.go_to_dashboard_response() + self.assertIn('Your most recent edits', response.content.decode('utf-8')) + + # Login as Alice again + self.client.logout() + self.client.login(username='alice', password='password') + + # Alice's dashboard should still list that first edit + response = self.go_to_dashboard_response() + self.assertIn('Your most recent edits', response.content.decode('utf-8')) + + def test_panel(self): + """Test if the panel actually returns expected pages """ + self.client.login(username='bob', password='password') + # change a page + self.change_something("Bob's edit") + # set a user to 'mock' a request + self.client.user = get_user_model().objects.get(email='bob@email.com') + # get the panel to get the last edits + panel = RecentEditsPanel(self.client) + # check if the revision is the revision of edited Page + self.assertEqual(panel.last_edits[0][0].page, Page.objects.get(pk=self.child_page.id)) + # check if the page in this list is the specific page of this revision + self.assertEqual(panel.last_edits[0][1], Page.objects.get(pk=self.child_page.id).specific) diff --git a/wagtail/admin/tests/pages/test_delete_page.py b/wagtail/admin/tests/pages/test_delete_page.py new file mode 100644 index 000000000..660c6b832 --- /dev/null +++ b/wagtail/admin/tests/pages/test_delete_page.py @@ -0,0 +1,204 @@ +from unittest import mock + +from django.contrib.auth.models import Permission +from django.db.models.signals import post_delete, pre_delete +from django.http import HttpRequest, HttpResponse +from django.test import TestCase +from django.urls import reverse + +from wagtail.core.models import Page +from wagtail.core.signals import page_unpublished +from wagtail.tests.testapp.models import SimplePage, StandardChild, StandardIndex +from wagtail.tests.utils import WagtailTestUtils + + +class TestPageDelete(TestCase, WagtailTestUtils): + def setUp(self): + # Find root page + self.root_page = Page.objects.get(id=2) + + # Add child page + self.child_page = SimplePage(title="Hello world!", slug="hello-world", content="hello") + self.root_page.add_child(instance=self.child_page) + + # Add a page with child pages of its own + self.child_index = StandardIndex(title="Hello index", slug='hello-index') + self.root_page.add_child(instance=self.child_index) + self.grandchild_page = StandardChild(title="Hello Kitty", slug='hello-kitty') + self.child_index.add_child(instance=self.grandchild_page) + + # Login + self.user = self.login() + + def test_page_delete(self): + response = self.client.get(reverse('wagtailadmin_pages:delete', args=(self.child_page.id, ))) + self.assertEqual(response.status_code, 200) + # deletion should not actually happen on GET + self.assertTrue(SimplePage.objects.filter(id=self.child_page.id).exists()) + + def test_page_delete_specific_admin_title(self): + response = self.client.get(reverse('wagtailadmin_pages:delete', args=(self.child_page.id, ))) + self.assertEqual(response.status_code, 200) + + # The admin_display_title specific to ChildPage is shown on the delete confirmation page. + self.assertContains(response, self.child_page.get_admin_display_title()) + + def test_page_delete_bad_permissions(self): + # Remove privileges from user + self.user.is_superuser = False + self.user.user_permissions.add( + Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') + ) + self.user.save() + + # Get delete page + response = self.client.get(reverse('wagtailadmin_pages:delete', args=(self.child_page.id, ))) + + # Check that the user received a 403 response + self.assertEqual(response.status_code, 403) + + # Check that the deletion has not happened + self.assertTrue(SimplePage.objects.filter(id=self.child_page.id).exists()) + + def test_page_delete_post(self): + # Connect a mock signal handler to page_unpublished signal + mock_handler = mock.MagicMock() + page_unpublished.connect(mock_handler) + + # Post + response = self.client.post(reverse('wagtailadmin_pages:delete', args=(self.child_page.id, ))) + + # Should be redirected to explorer page + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + + # treebeard should report no consistency problems with the tree + self.assertFalse(any(Page.find_problems()), 'treebeard found consistency problems') + + # Check that the page is gone + self.assertEqual(Page.objects.filter(path__startswith=self.root_page.path, slug='hello-world').count(), 0) + + # Check that the page_unpublished signal was fired + self.assertEqual(mock_handler.call_count, 1) + mock_call = mock_handler.mock_calls[0][2] + + self.assertEqual(mock_call['sender'], self.child_page.specific_class) + self.assertEqual(mock_call['instance'], self.child_page) + self.assertIsInstance(mock_call['instance'], self.child_page.specific_class) + + def test_page_delete_notlive_post(self): + # Same as above, but this makes sure the page_unpublished signal is not fired + # when if the page is not live when it is deleted + + # Unpublish the page + self.child_page.live = False + self.child_page.save() + + # Connect a mock signal handler to page_unpublished signal + mock_handler = mock.MagicMock() + page_unpublished.connect(mock_handler) + + # Post + response = self.client.post(reverse('wagtailadmin_pages:delete', args=(self.child_page.id, ))) + + # Should be redirected to explorer page + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + + # treebeard should report no consistency problems with the tree + self.assertFalse(any(Page.find_problems()), 'treebeard found consistency problems') + + # Check that the page is gone + self.assertEqual(Page.objects.filter(path__startswith=self.root_page.path, slug='hello-world').count(), 0) + + # Check that the page_unpublished signal was not fired + self.assertEqual(mock_handler.call_count, 0) + + def test_subpage_deletion(self): + # Connect mock signal handlers to page_unpublished, pre_delete and post_delete signals + unpublish_signals_received = [] + pre_delete_signals_received = [] + post_delete_signals_received = [] + + def page_unpublished_handler(sender, instance, **kwargs): + unpublish_signals_received.append((sender, instance.id)) + + def pre_delete_handler(sender, instance, **kwargs): + pre_delete_signals_received.append((sender, instance.id)) + + def post_delete_handler(sender, instance, **kwargs): + post_delete_signals_received.append((sender, instance.id)) + + page_unpublished.connect(page_unpublished_handler) + pre_delete.connect(pre_delete_handler) + post_delete.connect(post_delete_handler) + + # Post + response = self.client.post(reverse('wagtailadmin_pages:delete', args=(self.child_index.id, ))) + + # Should be redirected to explorer page + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + + # treebeard should report no consistency problems with the tree + self.assertFalse(any(Page.find_problems()), 'treebeard found consistency problems') + + # Check that the page is gone + self.assertFalse(StandardIndex.objects.filter(id=self.child_index.id).exists()) + self.assertFalse(Page.objects.filter(id=self.child_index.id).exists()) + + # Check that the subpage is also gone + self.assertFalse(StandardChild.objects.filter(id=self.grandchild_page.id).exists()) + self.assertFalse(Page.objects.filter(id=self.grandchild_page.id).exists()) + + # Check that the signals were fired for both pages + self.assertIn((StandardIndex, self.child_index.id), unpublish_signals_received) + self.assertIn((StandardChild, self.grandchild_page.id), unpublish_signals_received) + + self.assertIn((StandardIndex, self.child_index.id), pre_delete_signals_received) + self.assertIn((StandardChild, self.grandchild_page.id), pre_delete_signals_received) + + self.assertIn((StandardIndex, self.child_index.id), post_delete_signals_received) + self.assertIn((StandardChild, self.grandchild_page.id), post_delete_signals_received) + + def test_before_delete_page_hook(self): + def hook_func(request, page): + self.assertIsInstance(request, HttpRequest) + self.assertEqual(page.id, self.child_page.id) + + return HttpResponse("Overridden!") + + with self.register_hook('before_delete_page', hook_func): + response = self.client.get(reverse('wagtailadmin_pages:delete', args=(self.child_page.id, ))) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b"Overridden!") + + def test_before_delete_page_hook_post(self): + def hook_func(request, page): + self.assertIsInstance(request, HttpRequest) + self.assertEqual(page.id, self.child_page.id) + + return HttpResponse("Overridden!") + + with self.register_hook('before_delete_page', hook_func): + response = self.client.post(reverse('wagtailadmin_pages:delete', args=(self.child_page.id, ))) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b"Overridden!") + + # page should not be deleted + self.assertTrue(Page.objects.filter(id=self.child_page.id).exists()) + + def test_after_delete_page_hook(self): + def hook_func(request, page): + self.assertIsInstance(request, HttpRequest) + self.assertEqual(page.id, self.child_page.id) + + return HttpResponse("Overridden!") + + with self.register_hook('after_delete_page', hook_func): + response = self.client.post(reverse('wagtailadmin_pages:delete', args=(self.child_page.id, ))) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b"Overridden!") + + # page should be deleted + self.assertFalse(Page.objects.filter(id=self.child_page.id).exists()) diff --git a/wagtail/admin/tests/pages/test_edit_page.py b/wagtail/admin/tests/pages/test_edit_page.py new file mode 100644 index 000000000..51036315c --- /dev/null +++ b/wagtail/admin/tests/pages/test_edit_page.py @@ -0,0 +1,1604 @@ +import datetime +import os +from unittest import mock + +from django.conf import settings +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Permission +from django.core import mail +from django.core.files.base import ContentFile +from django.http import HttpRequest, HttpResponse +from django.test import TestCase, modify_settings +from django.urls import reverse +from django.utils import timezone + +from wagtail.admin.tests.pages.timestamps import submittable_timestamp +from wagtail.core.models import Page, PageRevision, Site +from wagtail.core.signals import page_published +from wagtail.tests.testapp.models import ( + EVENT_AUDIENCE_CHOICES, Advert, AdvertPlacement, EventCategory, + EventPage, EventPageCarouselItem, FilePage, ManyToManyBlogPage, SimplePage, SingleEventPage, StandardIndex, TaggedPage) +from wagtail.tests.utils import WagtailTestUtils + + +class TestPageEdit(TestCase, WagtailTestUtils): + def setUp(self): + # Find root page + self.root_page = Page.objects.get(id=2) + + # Add child page + child_page = SimplePage( + title="Hello world!", + slug="hello-world", + content="hello", + ) + self.root_page.add_child(instance=child_page) + child_page.save_revision().publish() + self.child_page = SimplePage.objects.get(id=child_page.id) + + # Add file page + fake_file = ContentFile("File for testing multipart") + fake_file.name = 'test.txt' + file_page = FilePage( + title="File Page", + slug="file-page", + file_field=fake_file, + ) + self.root_page.add_child(instance=file_page) + file_page.save_revision().publish() + self.file_page = FilePage.objects.get(id=file_page.id) + + # Add event page (to test edit handlers) + self.event_page = EventPage( + title="Event page", slug="event-page", + location='the moon', audience='public', + cost='free', date_from='2001-01-01', + ) + self.root_page.add_child(instance=self.event_page) + + # Add single event page (to test custom URL routes) + self.single_event_page = SingleEventPage( + title="Mars landing", slug="mars-landing", + location='mars', audience='public', + cost='free', date_from='2001-01-01', + ) + self.root_page.add_child(instance=self.single_event_page) + + self.unpublished_page = SimplePage( + title="Hello unpublished world!", + slug="hello-unpublished-world", + content="hello", + live=False, + has_unpublished_changes=True, + ) + self.root_page.add_child(instance=self.unpublished_page) + + # Login + self.user = self.login() + + def test_page_edit(self): + # Tests that the edit page loads + response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.event_page.id, ))) + self.assertEqual(response.status_code, 200) + + # Test InlinePanel labels/headings + self.assertContains(response, 'Speaker lineup') + self.assertContains(response, 'Add speakers') + + # test register_page_action_menu_item hook + self.assertContains(response, '') + self.assertContains(response, 'testapp/js/siren.js') + + # test construct_page_action_menu hook + self.assertContains(response, '') + + def test_edit_draft_page_with_no_revisions(self): + # Tests that the edit page loads + response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.unpublished_page.id, ))) + self.assertEqual(response.status_code, 200) + + def test_edit_multipart(self): + """ + Test checks if 'enctype="multipart/form-data"' is added and only to forms that require multipart encoding. + """ + # check for SimplePage where is no file field + response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.event_page.id, ))) + self.assertEqual(response.status_code, 200) + self.assertNotContains(response, 'enctype="multipart/form-data"') + self.assertTemplateUsed(response, 'wagtailadmin/pages/edit.html') + + # check for FilePage which has file field + response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.file_page.id, ))) + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'enctype="multipart/form-data"') + + def test_upload_file_publish(self): + """ + Check that file uploads work when directly publishing + """ + file_upload = ContentFile(b"A new file", name='published-file.txt') + post_data = { + 'title': 'New file', + 'slug': 'new-file', + 'file_field': file_upload, + 'action-publish': "Publish", + } + response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.file_page.id]), post_data) + + # Should be redirected to explorer + self.assertRedirects(response, reverse('wagtailadmin_explore', args=[self.root_page.id])) + + # Check the new file exists + file_page = FilePage.objects.get() + + self.assertEqual(file_page.file_field.name, file_upload.name) + self.assertTrue(os.path.exists(file_page.file_field.path)) + self.assertEqual(file_page.file_field.read(), b"A new file") + + def test_upload_file_draft(self): + """ + Check that file uploads work when saving a draft + """ + file_upload = ContentFile(b"A new file", name='draft-file.txt') + post_data = { + 'title': 'New file', + 'slug': 'new-file', + 'file_field': file_upload, + } + response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.file_page.id]), post_data) + + # Should be redirected to edit page + self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=[self.file_page.id])) + + # Check the file was uploaded + file_path = os.path.join(settings.MEDIA_ROOT, file_upload.name) + self.assertTrue(os.path.exists(file_path)) + with open(file_path, 'rb') as saved_file: + self.assertEqual(saved_file.read(), b"A new file") + + # Publish the draft just created + FilePage.objects.get().get_latest_revision().publish() + + # Get the file page, check the file is set + file_page = FilePage.objects.get() + self.assertEqual(file_page.file_field.name, file_upload.name) + self.assertTrue(os.path.exists(file_page.file_field.path)) + self.assertEqual(file_page.file_field.read(), b"A new file") + + def test_page_edit_bad_permissions(self): + # Remove privileges from user + self.user.is_superuser = False + self.user.user_permissions.add( + Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') + ) + self.user.save() + + # Get edit page + response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) + + # Check that the user received a 403 response + self.assertEqual(response.status_code, 403) + + def test_page_edit_post(self): + # Tests simple editing + post_data = { + 'title': "I've been edited!", + 'content': "Some content", + 'slug': 'hello-world', + } + response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) + + # Should be redirected to edit page + self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) + + # The page should have "has_unpublished_changes" flag set + child_page_new = SimplePage.objects.get(id=self.child_page.id) + self.assertTrue(child_page_new.has_unpublished_changes) + + # Page fields should not be changed (because we just created a new draft) + self.assertEqual(child_page_new.title, self.child_page.title) + self.assertEqual(child_page_new.content, self.child_page.content) + self.assertEqual(child_page_new.slug, self.child_page.slug) + + # The draft_title should have a new title + self.assertEqual(child_page_new.draft_title, post_data['title']) + + def test_page_edit_post_when_locked(self): + # Tests that trying to edit a locked page results in an error + + # Lock the page + self.child_page.locked = True + self.child_page.save() + + # Post + post_data = { + 'title': "I've been edited!", + 'content': "Some content", + 'slug': 'hello-world', + } + response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) + + # Shouldn't be redirected + self.assertContains(response, "The page could not be saved as it is locked") + + # The page shouldn't have "has_unpublished_changes" flag set + child_page_new = SimplePage.objects.get(id=self.child_page.id) + self.assertFalse(child_page_new.has_unpublished_changes) + + def test_edit_post_scheduled(self): + # put go_live_at and expire_at several days away from the current date, to avoid + # false matches in content_json__contains tests + go_live_at = timezone.now() + datetime.timedelta(days=10) + expire_at = timezone.now() + datetime.timedelta(days=20) + post_data = { + 'title': "I've been edited!", + 'content': "Some content", + 'slug': 'hello-world', + 'go_live_at': submittable_timestamp(go_live_at), + 'expire_at': submittable_timestamp(expire_at), + } + response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) + + # Should be redirected to explorer page + self.assertEqual(response.status_code, 302) + + child_page_new = SimplePage.objects.get(id=self.child_page.id) + + # The page will still be live + self.assertTrue(child_page_new.live) + + # A revision with approved_go_live_at should not exist + self.assertFalse(PageRevision.objects.filter( + page=child_page_new).exclude(approved_go_live_at__isnull=True).exists() + ) + + # But a revision with go_live_at and expire_at in their content json *should* exist + self.assertTrue(PageRevision.objects.filter( + page=child_page_new, content_json__contains=str(go_live_at.date())).exists() + ) + self.assertTrue( + PageRevision.objects.filter(page=child_page_new, content_json__contains=str(expire_at.date())).exists() + ) + + def test_edit_scheduled_go_live_before_expiry(self): + post_data = { + 'title': "I've been edited!", + 'content': "Some content", + 'slug': 'hello-world', + 'go_live_at': submittable_timestamp(timezone.now() + datetime.timedelta(days=2)), + 'expire_at': submittable_timestamp(timezone.now() + datetime.timedelta(days=1)), + } + response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) + + self.assertEqual(response.status_code, 200) + + # Check that a form error was raised + self.assertFormError(response, 'form', 'go_live_at', "Go live date/time must be before expiry date/time") + self.assertFormError(response, 'form', 'expire_at', "Go live date/time must be before expiry date/time") + + # form should be marked as having unsaved changes for the purposes of the dirty-forms warning + self.assertContains(response, "alwaysDirty: true") + + def test_edit_scheduled_expire_in_the_past(self): + post_data = { + 'title': "I've been edited!", + 'content': "Some content", + 'slug': 'hello-world', + 'expire_at': submittable_timestamp(timezone.now() + datetime.timedelta(days=-1)), + } + response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) + + self.assertEqual(response.status_code, 200) + + # Check that a form error was raised + self.assertFormError(response, 'form', 'expire_at', "Expiry date/time must be in the future") + + # form should be marked as having unsaved changes for the purposes of the dirty-forms warning + self.assertContains(response, "alwaysDirty: true") + + def test_page_edit_post_publish(self): + # Connect a mock signal handler to page_published signal + mock_handler = mock.MagicMock() + page_published.connect(mock_handler) + + # Set has_unpublished_changes=True on the existing record to confirm that the publish action + # is resetting it (and not just leaving it alone) + self.child_page.has_unpublished_changes = True + self.child_page.save() + + # Save current value of first_published_at so we can check that it doesn't change + first_published_at = SimplePage.objects.get(id=self.child_page.id).first_published_at + + # Tests publish from edit page + post_data = { + 'title': "I've been edited!", + 'content': "Some content", + 'slug': 'hello-world-new', + 'action-publish': "Publish", + } + response = self.client.post( + reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data, follow=True + ) + + # Should be redirected to explorer + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + + # Check that the page was edited + child_page_new = SimplePage.objects.get(id=self.child_page.id) + self.assertEqual(child_page_new.title, post_data['title']) + self.assertEqual(child_page_new.draft_title, post_data['title']) + + # Check that the page_published signal was fired + self.assertEqual(mock_handler.call_count, 1) + mock_call = mock_handler.mock_calls[0][2] + + self.assertEqual(mock_call['sender'], child_page_new.specific_class) + self.assertEqual(mock_call['instance'], child_page_new) + self.assertIsInstance(mock_call['instance'], child_page_new.specific_class) + + # The page shouldn't have "has_unpublished_changes" flag set + self.assertFalse(child_page_new.has_unpublished_changes) + + # first_published_at should not change as it was already set + self.assertEqual(first_published_at, child_page_new.first_published_at) + + # The "View Live" button should have the updated slug. + for message in response.context['messages']: + self.assertIn('hello-world-new', message.message) + break + + def test_first_published_at_editable(self): + """Test that we can update the first_published_at via the Page edit form, + for page models that expose it.""" + + # Add child page, of a type which has first_published_at in its form + child_page = ManyToManyBlogPage( + title="Hello world!", + slug="hello-again-world", + body="hello", + ) + self.root_page.add_child(instance=child_page) + child_page.save_revision().publish() + self.child_page = ManyToManyBlogPage.objects.get(id=child_page.id) + + initial_delta = self.child_page.first_published_at - timezone.now() + + first_published_at = timezone.now() - datetime.timedelta(days=2) + + post_data = { + 'title': "I've been edited!", + 'body': "Some content", + 'slug': 'hello-again-world', + 'action-publish': "Publish", + 'first_published_at': submittable_timestamp(first_published_at), + } + self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) + + # Get the edited page. + child_page_new = ManyToManyBlogPage.objects.get(id=self.child_page.id) + + # first_published_at should have changed. + new_delta = child_page_new.first_published_at - timezone.now() + self.assertNotEqual(new_delta.days, initial_delta.days) + # first_published_at should be 3 days ago. + self.assertEqual(new_delta.days, -3) + + def test_edit_post_publish_scheduled_unpublished_page(self): + # Unpublish the page + self.child_page.live = False + self.child_page.save() + + go_live_at = timezone.now() + datetime.timedelta(days=1) + expire_at = timezone.now() + datetime.timedelta(days=2) + post_data = { + 'title': "I've been edited!", + 'content': "Some content", + 'slug': 'hello-world', + 'action-publish': "Publish", + 'go_live_at': submittable_timestamp(go_live_at), + 'expire_at': submittable_timestamp(expire_at), + } + response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) + + # Should be redirected to explorer page + self.assertEqual(response.status_code, 302) + + child_page_new = SimplePage.objects.get(id=self.child_page.id) + + # The page should not be live anymore + self.assertFalse(child_page_new.live) + + # Instead a revision with approved_go_live_at should now exist + self.assertTrue( + PageRevision.objects.filter(page=child_page_new).exclude(approved_go_live_at__isnull=True).exists() + ) + + # The page SHOULD have the "has_unpublished_changes" flag set, + # because the changes are not visible as a live page yet + self.assertTrue( + child_page_new.has_unpublished_changes, + "A page scheduled for future publishing should have has_unpublished_changes=True" + ) + + self.assertEqual(child_page_new.status_string, "scheduled") + + def test_edit_post_publish_now_an_already_scheduled_unpublished_page(self): + # Unpublish the page + self.child_page.live = False + self.child_page.save() + + # First let's publish a page with a go_live_at in the future + go_live_at = timezone.now() + datetime.timedelta(days=1) + expire_at = timezone.now() + datetime.timedelta(days=2) + post_data = { + 'title': "I've been edited!", + 'content': "Some content", + 'slug': 'hello-world', + 'action-publish': "Publish", + 'go_live_at': submittable_timestamp(go_live_at), + 'expire_at': submittable_timestamp(expire_at), + } + response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) + + # Should be redirected to edit page + self.assertEqual(response.status_code, 302) + + child_page_new = SimplePage.objects.get(id=self.child_page.id) + + # The page should not be live + self.assertFalse(child_page_new.live) + + self.assertEqual(child_page_new.status_string, "scheduled") + + # Instead a revision with approved_go_live_at should now exist + self.assertTrue( + PageRevision.objects.filter(page=child_page_new).exclude(approved_go_live_at__isnull=True).exists() + ) + + # Now, let's edit it and publish it right now + go_live_at = timezone.now() + post_data = { + 'title': "I've been edited!", + 'content': "Some content", + 'slug': 'hello-world', + 'action-publish': "Publish", + 'go_live_at': "", + } + response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) + + # Should be redirected to edit page + self.assertEqual(response.status_code, 302) + + child_page_new = SimplePage.objects.get(id=self.child_page.id) + + # The page should be live now + self.assertTrue(child_page_new.live) + + # And a revision with approved_go_live_at should not exist + self.assertFalse( + PageRevision.objects.filter(page=child_page_new).exclude(approved_go_live_at__isnull=True).exists() + ) + + def test_edit_post_publish_scheduled_published_page(self): + # Page is live + self.child_page.live = True + self.child_page.save() + + live_revision = self.child_page.live_revision + original_title = self.child_page.title + + go_live_at = timezone.now() + datetime.timedelta(days=1) + expire_at = timezone.now() + datetime.timedelta(days=2) + post_data = { + 'title': "I've been edited!", + 'content': "Some content", + 'slug': 'hello-world', + 'action-publish': "Publish", + 'go_live_at': submittable_timestamp(go_live_at), + 'expire_at': submittable_timestamp(expire_at), + } + response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) + + # Should be redirected to explorer page + self.assertEqual(response.status_code, 302) + + child_page_new = SimplePage.objects.get(id=self.child_page.id) + + # The page should still be live + self.assertTrue(child_page_new.live) + + self.assertEqual(child_page_new.status_string, "live + scheduled") + + # Instead a revision with approved_go_live_at should now exist + self.assertTrue( + PageRevision.objects.filter(page=child_page_new).exclude(approved_go_live_at__isnull=True).exists() + ) + + # The page SHOULD have the "has_unpublished_changes" flag set, + # because the changes are not visible as a live page yet + self.assertTrue( + child_page_new.has_unpublished_changes, + "A page scheduled for future publishing should have has_unpublished_changes=True" + ) + + self.assertNotEqual( + child_page_new.get_latest_revision(), live_revision, + "A page scheduled for future publishing should have a new revision, that is not the live revision" + ) + + self.assertEqual( + child_page_new.title, original_title, + "A live page with scheduled revisions should still have original content" + ) + + def test_edit_post_publish_now_an_already_scheduled_published_page(self): + # Unpublish the page + self.child_page.live = True + self.child_page.save() + + original_title = self.child_page.title + # First let's publish a page with a go_live_at in the future + go_live_at = timezone.now() + datetime.timedelta(days=1) + expire_at = timezone.now() + datetime.timedelta(days=2) + post_data = { + 'title': "I've been edited!", + 'content': "Some content", + 'slug': 'hello-world', + 'action-publish': "Publish", + 'go_live_at': submittable_timestamp(go_live_at), + 'expire_at': submittable_timestamp(expire_at), + } + response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) + + # Should be redirected to edit page + self.assertEqual(response.status_code, 302) + + child_page_new = SimplePage.objects.get(id=self.child_page.id) + + # The page should still be live + self.assertTrue(child_page_new.live) + + # Instead a revision with approved_go_live_at should now exist + self.assertTrue( + PageRevision.objects.filter(page=child_page_new).exclude(approved_go_live_at__isnull=True).exists() + ) + + self.assertEqual( + child_page_new.title, original_title, + "A live page with scheduled revisions should still have original content" + ) + + # Now, let's edit it and publish it right now + go_live_at = timezone.now() + post_data = { + 'title': "I've been edited!", + 'content': "Some content", + 'slug': 'hello-world', + 'action-publish': "Publish", + 'go_live_at': "", + } + response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) + + # Should be redirected to edit page + self.assertEqual(response.status_code, 302) + + child_page_new = SimplePage.objects.get(id=self.child_page.id) + + # The page should be live now + self.assertTrue(child_page_new.live) + + # And a revision with approved_go_live_at should not exist + self.assertFalse( + PageRevision.objects.filter(page=child_page_new).exclude(approved_go_live_at__isnull=True).exists() + ) + + self.assertEqual( + child_page_new.title, post_data['title'], + "A published page should have the new title" + ) + + def test_page_edit_post_submit(self): + # Create a moderator user for testing email + get_user_model().objects.create_superuser('moderator', 'moderator@email.com', 'password') + + # Tests submitting from edit page + post_data = { + 'title': "I've been edited!", + 'content': "Some content", + 'slug': 'hello-world', + 'action-submit': "Submit", + } + response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) + + # Should be redirected to explorer + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + + # The page should have "has_unpublished_changes" flag set + child_page_new = SimplePage.objects.get(id=self.child_page.id) + self.assertTrue(child_page_new.has_unpublished_changes) + + # The latest revision for the page should now be in moderation + self.assertTrue(child_page_new.get_latest_revision().submitted_for_moderation) + + # Check that the moderator got an email + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].to, ['moderator@email.com']) + self.assertEqual( + mail.outbox[0].subject, 'The page "Hello world!" has been submitted for moderation' + ) # Note: should this be "I've been edited!"? + + def test_page_edit_post_existing_slug(self): + # This tests the existing slug checking on page edit + + # Create a page + self.child_page = SimplePage(title="Hello world 2", slug="hello-world2", content="hello") + self.root_page.add_child(instance=self.child_page) + + # Attempt to change the slug to one thats already in use + post_data = { + 'title': "Hello world 2", + 'slug': 'hello-world', + 'action-submit': "Submit", + } + response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) + + # Should not be redirected (as the save should fail) + self.assertEqual(response.status_code, 200) + + # Check that a form error was raised + self.assertFormError(response, 'form', 'slug', "This slug is already in use") + + def test_preview_on_edit(self): + post_data = { + 'title': "I've been edited!", + 'content': "Some content", + 'slug': 'hello-world', + 'action-submit': "Submit", + } + preview_url = reverse('wagtailadmin_pages:preview_on_edit', + args=(self.child_page.id,)) + response = self.client.post(preview_url, post_data) + + # Check the JSON response + self.assertEqual(response.status_code, 200) + self.assertJSONEqual(response.content.decode(), {'is_valid': True}) + + response = self.client.get(preview_url) + + # Check the HTML response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'tests/simple_page.html') + self.assertContains(response, "I've been edited!", html=True) + + def test_preview_on_edit_no_session_key(self): + preview_url = reverse('wagtailadmin_pages:preview_on_edit', + args=(self.child_page.id,)) + + # get() without corresponding post(), key not set. + response = self.client.get(preview_url) + + # Check the HTML response + self.assertEqual(response.status_code, 200) + + # We should have an error page because we are unable to + # preview; the page key was not in the session. + self.assertContains( + response, + "Wagtail - Preview error", + html=True + ) + self.assertContains( + response, + "

Preview error

", + html=True + ) + + @modify_settings(ALLOWED_HOSTS={'append': 'childpage.example.com'}) + def test_preview_uses_correct_site(self): + # create a Site record for the child page + Site.objects.create(hostname='childpage.example.com', root_page=self.child_page) + + post_data = { + 'title': "I've been edited!", + 'content': "Some content", + 'slug': 'hello-world', + 'action-submit': "Submit", + } + preview_url = reverse('wagtailadmin_pages:preview_on_edit', + args=(self.child_page.id,)) + response = self.client.post(preview_url, post_data) + + # Check the JSON response + self.assertEqual(response.status_code, 200) + self.assertJSONEqual(response.content.decode(), {'is_valid': True}) + + response = self.client.get(preview_url) + + # Check that the correct site object has been selected by the site middleware + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'tests/simple_page.html') + self.assertEqual(response.context['request'].site.hostname, 'childpage.example.com') + + def test_editor_picks_up_direct_model_edits(self): + # If a page has no draft edits, the editor should show the version from the live database + # record rather than the latest revision record. This ensures that the edit interface + # reflects any changes made directly on the model. + self.child_page.title = "This title only exists on the live database record" + self.child_page.save() + + response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "This title only exists on the live database record") + + def test_editor_does_not_pick_up_direct_model_edits_when_draft_edits_exist(self): + # If a page has draft edits, we should always show those in the editor, not the live + # database record + self.child_page.content = "Some content with a draft edit" + self.child_page.save_revision() + + # make an independent change to the live database record + self.child_page = SimplePage.objects.get(id=self.child_page.id) + self.child_page.title = "This title only exists on the live database record" + self.child_page.save() + + response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) + self.assertEqual(response.status_code, 200) + self.assertNotContains(response, "This title only exists on the live database record") + self.assertContains(response, "Some content with a draft edit") + + def test_editor_page_shows_live_url_in_status_when_draft_edits_exist(self): + # If a page has draft edits (ie. page has unpublished changes) + # that affect the URL (eg. slug) we should still ensure the + # status button at the top of the page links to the live URL + + self.child_page.content = "Some content with a draft edit" + self.child_page.slug = "revised-slug-in-draft-only" # live version contains 'hello-world' + self.child_page.save_revision() + + response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) + + link_to_draft = 'Current page status: live + draft' + link_to_live = 'Current page status: live + draft' + input_field_for_draft_slug = '' + input_field_for_live_slug = '' + + # Status Link should be the live page (not revision) + self.assertContains(response, link_to_live, html=True) + self.assertNotContains(response, link_to_draft, html=True) + + # Editing input for slug should be the draft revision + self.assertContains(response, input_field_for_draft_slug, html=True) + self.assertNotContains(response, input_field_for_live_slug, html=True) + + def test_editor_page_shows_custom_live_url_in_status_when_draft_edits_exist(self): + # When showing a live URL in the status button that differs from the draft one, + # ensure that we pick up any custom URL logic defined on the specific page model + + self.single_event_page.location = "The other side of Mars" + self.single_event_page.slug = "revised-slug-in-draft-only" # live version contains 'hello-world' + self.single_event_page.save_revision() + + response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.single_event_page.id, ))) + + link_to_draft = 'Current page status: live + draft' + link_to_live = 'Current page status: live + draft' + input_field_for_draft_slug = '' + input_field_for_live_slug = '' + + # Status Link should be the live page (not revision) + self.assertContains(response, link_to_live, html=True) + self.assertNotContains(response, link_to_draft, html=True) + + # Editing input for slug should be the draft revision + self.assertContains(response, input_field_for_draft_slug, html=True) + self.assertNotContains(response, input_field_for_live_slug, html=True) + + def test_before_edit_page_hook(self): + def hook_func(request, page): + self.assertIsInstance(request, HttpRequest) + self.assertEqual(page.id, self.child_page.id) + + return HttpResponse("Overridden!") + + with self.register_hook('before_edit_page', hook_func): + response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b"Overridden!") + + def test_before_edit_page_hook_post(self): + def hook_func(request, page): + self.assertIsInstance(request, HttpRequest) + self.assertEqual(page.id, self.child_page.id) + + return HttpResponse("Overridden!") + + with self.register_hook('before_edit_page', hook_func): + post_data = { + 'title': "I've been edited!", + 'content': "Some content", + 'slug': 'hello-world-new', + 'action-publish': "Publish", + } + response = self.client.post( + reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data + ) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b"Overridden!") + + # page should not be edited + self.assertEqual(Page.objects.get(id=self.child_page.id).title, "Hello world!") + + def test_after_edit_page_hook(self): + def hook_func(request, page): + self.assertIsInstance(request, HttpRequest) + self.assertEqual(page.id, self.child_page.id) + + return HttpResponse("Overridden!") + + with self.register_hook('after_edit_page', hook_func): + post_data = { + 'title': "I've been edited!", + 'content': "Some content", + 'slug': 'hello-world-new', + 'action-publish': "Publish", + } + response = self.client.post( + reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data + ) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b"Overridden!") + + # page should be edited + self.assertEqual(Page.objects.get(id=self.child_page.id).title, "I've been edited!") + + +class TestPageEditReordering(TestCase, WagtailTestUtils): + def setUp(self): + # Find root page + self.root_page = Page.objects.get(id=2) + + # Add event page + self.event_page = EventPage( + title="Event page", slug="event-page", + location='the moon', audience='public', + cost='free', date_from='2001-01-01', + ) + self.event_page.carousel_items = [ + EventPageCarouselItem(caption='1234567', sort_order=1), + EventPageCarouselItem(caption='7654321', sort_order=2), + EventPageCarouselItem(caption='abcdefg', sort_order=3), + ] + self.root_page.add_child(instance=self.event_page) + + # Login + self.user = self.login() + + def check_order(self, response, expected_order): + inline_panel = response.context['edit_handler'].children[0].children[9] + order = [child.form.instance.caption for child in inline_panel.children] + self.assertEqual(order, expected_order) + + def test_order(self): + response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.event_page.id, ))) + + self.assertEqual(response.status_code, 200) + self.check_order(response, ['1234567', '7654321', 'abcdefg']) + + def test_reorder(self): + post_data = { + 'title': "Event page", + 'slug': 'event-page', + + 'date_from': '01/01/2014', + 'cost': '$10', + 'audience': 'public', + 'location': 'somewhere', + + 'related_links-INITIAL_FORMS': 0, + 'related_links-MAX_NUM_FORMS': 1000, + 'related_links-TOTAL_FORMS': 0, + + 'speakers-INITIAL_FORMS': 0, + 'speakers-MAX_NUM_FORMS': 1000, + 'speakers-TOTAL_FORMS': 0, + + 'head_counts-INITIAL_FORMS': 0, + 'head_counts-MAX_NUM_FORMS': 1000, + 'head_counts-TOTAL_FORMS': 0, + + 'carousel_items-INITIAL_FORMS': 3, + 'carousel_items-MAX_NUM_FORMS': 1000, + 'carousel_items-TOTAL_FORMS': 3, + 'carousel_items-0-id': self.event_page.carousel_items.all()[0].id, + 'carousel_items-0-caption': self.event_page.carousel_items.all()[0].caption, + 'carousel_items-0-ORDER': 2, + 'carousel_items-1-id': self.event_page.carousel_items.all()[1].id, + 'carousel_items-1-caption': self.event_page.carousel_items.all()[1].caption, + 'carousel_items-1-ORDER': 3, + 'carousel_items-2-id': self.event_page.carousel_items.all()[2].id, + 'carousel_items-2-caption': self.event_page.carousel_items.all()[2].caption, + 'carousel_items-2-ORDER': 1, + } + response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.event_page.id, )), post_data) + + # Should be redirected back to same page + self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(self.event_page.id, ))) + + # Check order + response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.event_page.id, ))) + + self.assertEqual(response.status_code, 200) + self.check_order(response, ['abcdefg', '1234567', '7654321']) + + def test_reorder_with_validation_error(self): + post_data = { + 'title': "", # Validation error + 'slug': 'event-page', + + 'date_from': '01/01/2014', + 'cost': '$10', + 'audience': 'public', + 'location': 'somewhere', + + 'related_links-INITIAL_FORMS': 0, + 'related_links-MAX_NUM_FORMS': 1000, + 'related_links-TOTAL_FORMS': 0, + + 'speakers-INITIAL_FORMS': 0, + 'speakers-MAX_NUM_FORMS': 1000, + 'speakers-TOTAL_FORMS': 0, + + 'head_counts-INITIAL_FORMS': 0, + 'head_counts-MAX_NUM_FORMS': 1000, + 'head_counts-TOTAL_FORMS': 0, + + 'carousel_items-INITIAL_FORMS': 3, + 'carousel_items-MAX_NUM_FORMS': 1000, + 'carousel_items-TOTAL_FORMS': 3, + 'carousel_items-0-id': self.event_page.carousel_items.all()[0].id, + 'carousel_items-0-caption': self.event_page.carousel_items.all()[0].caption, + 'carousel_items-0-ORDER': 2, + 'carousel_items-1-id': self.event_page.carousel_items.all()[1].id, + 'carousel_items-1-caption': self.event_page.carousel_items.all()[1].caption, + 'carousel_items-1-ORDER': 3, + 'carousel_items-2-id': self.event_page.carousel_items.all()[2].id, + 'carousel_items-2-caption': self.event_page.carousel_items.all()[2].caption, + 'carousel_items-2-ORDER': 1, + } + response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.event_page.id, )), post_data) + + self.assertEqual(response.status_code, 200) + self.check_order(response, ['abcdefg', '1234567', '7654321']) + + +class TestIssue197(TestCase, WagtailTestUtils): + def test_issue_197(self): + # Find root page + self.root_page = Page.objects.get(id=2) + + # Create a tagged page with no tags + self.tagged_page = self.root_page.add_child(instance=TaggedPage( + title="Tagged page", + slug='tagged-page', + live=False, + )) + + # Login + self.user = self.login() + + # Add some tags and publish using edit view + post_data = { + 'title': "Tagged page", + 'slug': 'tagged-page', + 'tags': "hello, world", + 'action-publish': "Publish", + } + response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.tagged_page.id, )), post_data) + + # Should be redirected to explorer + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + + # Check that both tags are in the pages tag set + page = TaggedPage.objects.get(id=self.tagged_page.id) + self.assertIn('hello', page.tags.slugs()) + self.assertIn('world', page.tags.slugs()) + + +class TestChildRelationsOnSuperclass(TestCase, WagtailTestUtils): + # In our test models we define AdvertPlacement as a child relation on the Page model. + # Here we check that this behaves correctly when exposed on the edit form of a Page + # subclass (StandardIndex here). + fixtures = ['test.json'] + + def setUp(self): + # Find root page + self.root_page = Page.objects.get(id=2) + self.test_advert = Advert.objects.get(id=1) + + # Add child page + self.index_page = StandardIndex( + title="My lovely index", + slug="my-lovely-index", + advert_placements=[AdvertPlacement(advert=self.test_advert)] + ) + self.root_page.add_child(instance=self.index_page) + + # Login + self.login() + + def test_get_create_form(self): + response = self.client.get( + reverse('wagtailadmin_pages:add', args=('tests', 'standardindex', self.root_page.id)) + ) + self.assertEqual(response.status_code, 200) + # Response should include an advert_placements formset labelled Adverts + self.assertContains(response, "Adverts") + self.assertContains(response, "id_advert_placements-TOTAL_FORMS") + + def test_post_create_form(self): + post_data = { + 'title': "New index!", + 'slug': 'new-index', + 'advert_placements-TOTAL_FORMS': '1', + 'advert_placements-INITIAL_FORMS': '0', + 'advert_placements-MAX_NUM_FORMS': '1000', + 'advert_placements-0-advert': '1', + 'advert_placements-0-colour': 'yellow', + 'advert_placements-0-id': '', + } + response = self.client.post( + reverse('wagtailadmin_pages:add', args=('tests', 'standardindex', self.root_page.id)), post_data + ) + + # Find the page and check it + page = Page.objects.get(path__startswith=self.root_page.path, slug='new-index').specific + + # Should be redirected to edit page + self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(page.id, ))) + + self.assertEqual(page.advert_placements.count(), 1) + self.assertEqual(page.advert_placements.first().advert.text, 'test_advert') + + def test_post_create_form_with_validation_error_in_formset(self): + post_data = { + 'title': "New index!", + 'slug': 'new-index', + 'advert_placements-TOTAL_FORMS': '1', + 'advert_placements-INITIAL_FORMS': '0', + 'advert_placements-MAX_NUM_FORMS': '1000', + 'advert_placements-0-advert': '1', + 'advert_placements-0-colour': '', # should fail as colour is a required field + 'advert_placements-0-id': '', + } + response = self.client.post( + reverse('wagtailadmin_pages:add', args=('tests', 'standardindex', self.root_page.id)), post_data + ) + + # Should remain on the edit page with a validation error + self.assertEqual(response.status_code, 200) + self.assertContains(response, "This field is required.") + # form should be marked as having unsaved changes + self.assertContains(response, "alwaysDirty: true") + + def test_get_edit_form(self): + response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.index_page.id, ))) + self.assertEqual(response.status_code, 200) + + # Response should include an advert_placements formset labelled Adverts + self.assertContains(response, "Adverts") + self.assertContains(response, "id_advert_placements-TOTAL_FORMS") + # the formset should be populated with an existing form + self.assertContains(response, "id_advert_placements-0-advert") + self.assertContains( + response, '', html=True + ) + + def test_post_edit_form(self): + post_data = { + 'title': "My lovely index", + 'slug': 'my-lovely-index', + 'advert_placements-TOTAL_FORMS': '2', + 'advert_placements-INITIAL_FORMS': '1', + 'advert_placements-MAX_NUM_FORMS': '1000', + 'advert_placements-0-advert': '1', + 'advert_placements-0-colour': 'yellow', + 'advert_placements-0-id': self.index_page.advert_placements.first().id, + 'advert_placements-1-advert': '1', + 'advert_placements-1-colour': 'purple', + 'advert_placements-1-id': '', + 'action-publish': "Publish", + } + response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.index_page.id, )), post_data) + + # Should be redirected to explorer + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + + # Find the page and check it + page = Page.objects.get(id=self.index_page.id).specific + self.assertEqual(page.advert_placements.count(), 2) + self.assertEqual(page.advert_placements.all()[0].advert.text, 'test_advert') + self.assertEqual(page.advert_placements.all()[1].advert.text, 'test_advert') + + def test_post_edit_form_with_validation_error_in_formset(self): + post_data = { + 'title': "My lovely index", + 'slug': 'my-lovely-index', + 'advert_placements-TOTAL_FORMS': '1', + 'advert_placements-INITIAL_FORMS': '1', + 'advert_placements-MAX_NUM_FORMS': '1000', + 'advert_placements-0-advert': '1', + 'advert_placements-0-colour': '', + 'advert_placements-0-id': self.index_page.advert_placements.first().id, + 'action-publish': "Publish", + } + response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.index_page.id, )), post_data) + + # Should remain on the edit page with a validation error + self.assertEqual(response.status_code, 200) + self.assertContains(response, "This field is required.") + # form should be marked as having unsaved changes + self.assertContains(response, "alwaysDirty: true") + + +class TestIssue2492(TestCase, WagtailTestUtils): + """ + The publication submission message generation was performed using + the Page class, as opposed to the specific_class for that Page. + This test ensures that the specific_class url method is called + when the 'view live' message button is created. + """ + + def setUp(self): + self.root_page = Page.objects.get(id=2) + child_page = SingleEventPage( + title="Test Event", slug="test-event", location="test location", + cost="10", date_from=datetime.datetime.now(), + audience=EVENT_AUDIENCE_CHOICES[0][0]) + self.root_page.add_child(instance=child_page) + child_page.save_revision().publish() + self.child_page = SingleEventPage.objects.get(id=child_page.id) + self.user = self.login() + + def test_page_edit_post_publish_url(self): + post_data = { + 'action-publish': "Publish", + 'title': self.child_page.title, + 'date_from': self.child_page.date_from, + 'slug': self.child_page.slug, + 'audience': self.child_page.audience, + 'location': self.child_page.location, + 'cost': self.child_page.cost, + 'carousel_items-TOTAL_FORMS': 0, + 'carousel_items-INITIAL_FORMS': 0, + 'carousel_items-MIN_NUM_FORMS': 0, + 'carousel_items-MAX_NUM_FORMS': 0, + 'speakers-TOTAL_FORMS': 0, + 'speakers-INITIAL_FORMS': 0, + 'speakers-MIN_NUM_FORMS': 0, + 'speakers-MAX_NUM_FORMS': 0, + 'related_links-TOTAL_FORMS': 0, + 'related_links-INITIAL_FORMS': 0, + 'related_links-MIN_NUM_FORMS': 0, + 'related_links-MAX_NUM_FORMS': 0, + 'head_counts-TOTAL_FORMS': 0, + 'head_counts-INITIAL_FORMS': 0, + 'head_counts-MIN_NUM_FORMS': 0, + 'head_counts-MAX_NUM_FORMS': 0, + } + response = self.client.post( + reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), + post_data, follow=True) + + # Grab a fresh copy's URL + new_url = SingleEventPage.objects.get(id=self.child_page.id).url + + # The "View Live" button should have the custom URL. + for message in response.context['messages']: + self.assertIn('"{}"'.format(new_url), message.message) + break + + +class TestIssue3982(TestCase, WagtailTestUtils): + """ + Pages that are not associated with a site, and thus do not have a live URL, + should not display a "View live" link in the flash message after being + edited. + """ + + def setUp(self): + super().setUp() + self.login() + + def _create_page(self, parent): + response = self.client.post( + reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', parent.pk)), + {'title': "Hello, world!", 'content': "Some content", 'slug': 'hello-world', 'action-publish': "publish"}, + follow=True) + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(parent.pk,))) + page = SimplePage.objects.get() + self.assertTrue(page.live) + return response, page + + def test_create_accessible(self): + """ + Create a page under the site root, check the flash message has a valid + "View live" button. + """ + response, page = self._create_page(Page.objects.get(pk=2)) + self.assertIsNotNone(page.url) + self.assertTrue(any( + 'View live' in message.message and page.url in message.message + for message in response.context['messages'])) + + def test_create_inaccessible(self): + """ + Create a page outside of the site root, check the flash message does + not have a "View live" button. + """ + response, page = self._create_page(Page.objects.get(pk=1)) + self.assertIsNone(page.url) + self.assertFalse(any( + 'View live' in message.message + for message in response.context['messages'])) + + def _edit_page(self, parent): + page = parent.add_child(instance=SimplePage(title='Hello, world!', content='Some content')) + response = self.client.post( + reverse('wagtailadmin_pages:edit', args=(page.pk,)), + {'title': "Hello, world!", 'content': "Some content", 'slug': 'hello-world', 'action-publish': "publish"}, + follow=True) + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(parent.pk,))) + page = SimplePage.objects.get(pk=page.pk) + self.assertTrue(page.live) + return response, page + + def test_edit_accessible(self): + """ + Edit a page under the site root, check the flash message has a valid + "View live" button. + """ + response, page = self._edit_page(Page.objects.get(pk=2)) + self.assertIsNotNone(page.url) + self.assertTrue(any( + 'View live' in message.message and page.url in message.message + for message in response.context['messages'])) + + def test_edit_inaccessible(self): + """ + Edit a page outside of the site root, check the flash message does + not have a "View live" button. + """ + response, page = self._edit_page(Page.objects.get(pk=1)) + self.assertIsNone(page.url) + self.assertFalse(any( + 'View live' in message.message + for message in response.context['messages'])) + + def _approve_page(self, parent): + response = self.client.post( + reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', parent.pk)), + {'title': "Hello, world!", 'content': "Some content", 'slug': 'hello-world', 'action-submit': "submit"}, + follow=True) + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(parent.pk,))) + page = SimplePage.objects.get() + self.assertFalse(page.live) + revision = PageRevision.objects.get(page=page) + response = self.client.post(reverse('wagtailadmin_pages:approve_moderation', args=(revision.pk,)), follow=True) + page = SimplePage.objects.get() + self.assertTrue(page.live) + self.assertRedirects(response, reverse('wagtailadmin_home')) + return response, page + + def test_approve_accessible(self): + """ + Edit a page under the site root, check the flash message has a valid + "View live" button. + """ + response, page = self._approve_page(Page.objects.get(pk=2)) + self.assertIsNotNone(page.url) + self.assertTrue(any( + 'View live' in message.message and page.url in message.message + for message in response.context['messages'])) + + def test_approve_inaccessible(self): + """ + Edit a page outside of the site root, check the flash message does + not have a "View live" button. + """ + response, page = self._approve_page(Page.objects.get(pk=1)) + self.assertIsNone(page.url) + self.assertFalse(any( + 'View live' in message.message + for message in response.context['messages'])) + + +class TestParentalM2M(TestCase, WagtailTestUtils): + fixtures = ['test.json'] + + def setUp(self): + self.events_index = Page.objects.get(url_path='/home/events/') + self.christmas_page = Page.objects.get(url_path='/home/events/christmas/') + self.user = self.login() + self.holiday_category = EventCategory.objects.create(name='Holiday') + self.men_with_beards_category = EventCategory.objects.create(name='Men with beards') + + def test_create_and_save(self): + post_data = { + 'title': "Presidents' Day", + 'date_from': "2017-02-20", + 'slug': "presidents-day", + 'audience': "public", + 'location': "America", + 'cost': "$1", + 'carousel_items-TOTAL_FORMS': 0, + 'carousel_items-INITIAL_FORMS': 0, + 'carousel_items-MIN_NUM_FORMS': 0, + 'carousel_items-MAX_NUM_FORMS': 0, + 'speakers-TOTAL_FORMS': 0, + 'speakers-INITIAL_FORMS': 0, + 'speakers-MIN_NUM_FORMS': 0, + 'speakers-MAX_NUM_FORMS': 0, + 'related_links-TOTAL_FORMS': 0, + 'related_links-INITIAL_FORMS': 0, + 'related_links-MIN_NUM_FORMS': 0, + 'related_links-MAX_NUM_FORMS': 0, + 'head_counts-TOTAL_FORMS': 0, + 'head_counts-INITIAL_FORMS': 0, + 'head_counts-MIN_NUM_FORMS': 0, + 'head_counts-MAX_NUM_FORMS': 0, + 'categories': [self.holiday_category.id, self.men_with_beards_category.id] + } + response = self.client.post( + reverse('wagtailadmin_pages:add', args=('tests', 'eventpage', self.events_index.id)), + post_data + ) + created_page = EventPage.objects.get(url_path='/home/events/presidents-day/') + self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(created_page.id, ))) + created_revision = created_page.get_latest_revision_as_page() + + self.assertIn(self.holiday_category, created_revision.categories.all()) + self.assertIn(self.men_with_beards_category, created_revision.categories.all()) + + def test_create_and_publish(self): + post_data = { + 'action-publish': "Publish", + 'title': "Presidents' Day", + 'date_from': "2017-02-20", + 'slug': "presidents-day", + 'audience': "public", + 'location': "America", + 'cost': "$1", + 'carousel_items-TOTAL_FORMS': 0, + 'carousel_items-INITIAL_FORMS': 0, + 'carousel_items-MIN_NUM_FORMS': 0, + 'carousel_items-MAX_NUM_FORMS': 0, + 'speakers-TOTAL_FORMS': 0, + 'speakers-INITIAL_FORMS': 0, + 'speakers-MIN_NUM_FORMS': 0, + 'speakers-MAX_NUM_FORMS': 0, + 'related_links-TOTAL_FORMS': 0, + 'related_links-INITIAL_FORMS': 0, + 'related_links-MIN_NUM_FORMS': 0, + 'related_links-MAX_NUM_FORMS': 0, + 'head_counts-TOTAL_FORMS': 0, + 'head_counts-INITIAL_FORMS': 0, + 'head_counts-MIN_NUM_FORMS': 0, + 'head_counts-MAX_NUM_FORMS': 0, + 'categories': [self.holiday_category.id, self.men_with_beards_category.id] + } + response = self.client.post( + reverse('wagtailadmin_pages:add', args=('tests', 'eventpage', self.events_index.id)), + post_data + ) + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.events_index.id, ))) + + created_page = EventPage.objects.get(url_path='/home/events/presidents-day/') + self.assertIn(self.holiday_category, created_page.categories.all()) + self.assertIn(self.men_with_beards_category, created_page.categories.all()) + + def test_edit_and_save(self): + post_data = { + 'title': "Christmas", + 'date_from': "2017-12-25", + 'slug': "christmas", + 'audience': "public", + 'location': "The North Pole", + 'cost': "Free", + 'carousel_items-TOTAL_FORMS': 0, + 'carousel_items-INITIAL_FORMS': 0, + 'carousel_items-MIN_NUM_FORMS': 0, + 'carousel_items-MAX_NUM_FORMS': 0, + 'speakers-TOTAL_FORMS': 0, + 'speakers-INITIAL_FORMS': 0, + 'speakers-MIN_NUM_FORMS': 0, + 'speakers-MAX_NUM_FORMS': 0, + 'related_links-TOTAL_FORMS': 0, + 'related_links-INITIAL_FORMS': 0, + 'related_links-MIN_NUM_FORMS': 0, + 'related_links-MAX_NUM_FORMS': 0, + 'head_counts-TOTAL_FORMS': 0, + 'head_counts-INITIAL_FORMS': 0, + 'head_counts-MIN_NUM_FORMS': 0, + 'head_counts-MAX_NUM_FORMS': 0, + 'categories': [self.holiday_category.id, self.men_with_beards_category.id] + } + response = self.client.post( + reverse('wagtailadmin_pages:edit', args=(self.christmas_page.id, )), + post_data + ) + self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(self.christmas_page.id, ))) + updated_page = EventPage.objects.get(id=self.christmas_page.id) + created_revision = updated_page.get_latest_revision_as_page() + + self.assertIn(self.holiday_category, created_revision.categories.all()) + self.assertIn(self.men_with_beards_category, created_revision.categories.all()) + + # no change to live page record yet + self.assertEqual(0, updated_page.categories.count()) + + def test_edit_and_publish(self): + post_data = { + 'action-publish': "Publish", + 'title': "Christmas", + 'date_from': "2017-12-25", + 'slug': "christmas", + 'audience': "public", + 'location': "The North Pole", + 'cost': "Free", + 'carousel_items-TOTAL_FORMS': 0, + 'carousel_items-INITIAL_FORMS': 0, + 'carousel_items-MIN_NUM_FORMS': 0, + 'carousel_items-MAX_NUM_FORMS': 0, + 'speakers-TOTAL_FORMS': 0, + 'speakers-INITIAL_FORMS': 0, + 'speakers-MIN_NUM_FORMS': 0, + 'speakers-MAX_NUM_FORMS': 0, + 'related_links-TOTAL_FORMS': 0, + 'related_links-INITIAL_FORMS': 0, + 'related_links-MIN_NUM_FORMS': 0, + 'related_links-MAX_NUM_FORMS': 0, + 'head_counts-TOTAL_FORMS': 0, + 'head_counts-INITIAL_FORMS': 0, + 'head_counts-MIN_NUM_FORMS': 0, + 'head_counts-MAX_NUM_FORMS': 0, + 'categories': [self.holiday_category.id, self.men_with_beards_category.id] + } + response = self.client.post( + reverse('wagtailadmin_pages:edit', args=(self.christmas_page.id, )), + post_data + ) + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.events_index.id, ))) + updated_page = EventPage.objects.get(id=self.christmas_page.id) + self.assertEqual(2, updated_page.categories.count()) + self.assertIn(self.holiday_category, updated_page.categories.all()) + self.assertIn(self.men_with_beards_category, updated_page.categories.all()) + + +class TestValidationErrorMessages(TestCase, WagtailTestUtils): + fixtures = ['test.json'] + + def setUp(self): + self.events_index = Page.objects.get(url_path='/home/events/') + self.christmas_page = Page.objects.get(url_path='/home/events/christmas/') + self.user = self.login() + + def test_field_error(self): + """Field errors should be shown against the relevant fields, not in the header message""" + post_data = { + 'title': "", + 'date_from': "2017-12-25", + 'slug': "christmas", + 'audience': "public", + 'location': "The North Pole", + 'cost': "Free", + 'carousel_items-TOTAL_FORMS': 0, + 'carousel_items-INITIAL_FORMS': 0, + 'carousel_items-MIN_NUM_FORMS': 0, + 'carousel_items-MAX_NUM_FORMS': 0, + 'speakers-TOTAL_FORMS': 0, + 'speakers-INITIAL_FORMS': 0, + 'speakers-MIN_NUM_FORMS': 0, + 'speakers-MAX_NUM_FORMS': 0, + 'related_links-TOTAL_FORMS': 0, + 'related_links-INITIAL_FORMS': 0, + 'related_links-MIN_NUM_FORMS': 0, + 'related_links-MAX_NUM_FORMS': 0, + 'head_counts-TOTAL_FORMS': 0, + 'head_counts-INITIAL_FORMS': 0, + 'head_counts-MIN_NUM_FORMS': 0, + 'head_counts-MAX_NUM_FORMS': 0, + } + response = self.client.post( + reverse('wagtailadmin_pages:edit', args=(self.christmas_page.id, )), + post_data + ) + self.assertEqual(response.status_code, 200) + + self.assertContains(response, "The page could not be saved due to validation errors") + # the error should only appear once: against the field, not in the header message + self.assertContains(response, """

This field is required.

""", count=1, html=True) + self.assertContains(response, "This field is required", count=1) + + def test_non_field_error(self): + """Non-field errors should be shown in the header message""" + post_data = { + 'title': "Christmas", + 'date_from': "2017-12-25", + 'date_to': "2017-12-24", + 'slug': "christmas", + 'audience': "public", + 'location': "The North Pole", + 'cost': "Free", + 'carousel_items-TOTAL_FORMS': 0, + 'carousel_items-INITIAL_FORMS': 0, + 'carousel_items-MIN_NUM_FORMS': 0, + 'carousel_items-MAX_NUM_FORMS': 0, + 'speakers-TOTAL_FORMS': 0, + 'speakers-INITIAL_FORMS': 0, + 'speakers-MIN_NUM_FORMS': 0, + 'speakers-MAX_NUM_FORMS': 0, + 'related_links-TOTAL_FORMS': 0, + 'related_links-INITIAL_FORMS': 0, + 'related_links-MIN_NUM_FORMS': 0, + 'related_links-MAX_NUM_FORMS': 0, + 'head_counts-TOTAL_FORMS': 0, + 'head_counts-INITIAL_FORMS': 0, + 'head_counts-MIN_NUM_FORMS': 0, + 'head_counts-MAX_NUM_FORMS': 0, + } + response = self.client.post( + reverse('wagtailadmin_pages:edit', args=(self.christmas_page.id, )), + post_data + ) + self.assertEqual(response.status_code, 200) + + self.assertContains(response, "The page could not be saved due to validation errors") + self.assertContains(response, "
  • The end date must be after the start date
  • ", count=1) + + def test_field_and_non_field_error(self): + """ + If both field and non-field errors exist, all errors should be shown in the header message + with appropriate context to identify the field; and field errors should also be shown + against the relevant fields. + """ + post_data = { + 'title': "", + 'date_from': "2017-12-25", + 'date_to': "2017-12-24", + 'slug': "christmas", + 'audience': "public", + 'location': "The North Pole", + 'cost': "Free", + 'carousel_items-TOTAL_FORMS': 0, + 'carousel_items-INITIAL_FORMS': 0, + 'carousel_items-MIN_NUM_FORMS': 0, + 'carousel_items-MAX_NUM_FORMS': 0, + 'speakers-TOTAL_FORMS': 0, + 'speakers-INITIAL_FORMS': 0, + 'speakers-MIN_NUM_FORMS': 0, + 'speakers-MAX_NUM_FORMS': 0, + 'related_links-TOTAL_FORMS': 0, + 'related_links-INITIAL_FORMS': 0, + 'related_links-MIN_NUM_FORMS': 0, + 'related_links-MAX_NUM_FORMS': 0, + 'head_counts-TOTAL_FORMS': 0, + 'head_counts-INITIAL_FORMS': 0, + 'head_counts-MIN_NUM_FORMS': 0, + 'head_counts-MAX_NUM_FORMS': 0, + } + response = self.client.post( + reverse('wagtailadmin_pages:edit', args=(self.christmas_page.id, )), + post_data + ) + self.assertEqual(response.status_code, 200) + + self.assertContains(response, "The page could not be saved due to validation errors") + self.assertContains(response, "
  • The end date must be after the start date
  • ", count=1) + + # Error on title shown against the title field + self.assertContains(response, """

    This field is required.

    """, count=1, html=True) + # Error on title shown in the header message + self.assertContains(response, "
  • Title: This field is required.
  • ", count=1) diff --git a/wagtail/admin/tests/pages/test_explorer_view.py b/wagtail/admin/tests/pages/test_explorer_view.py new file mode 100644 index 000000000..61d812c42 --- /dev/null +++ b/wagtail/admin/tests/pages/test_explorer_view.py @@ -0,0 +1,544 @@ +from django.contrib.auth.models import Group, Permission +from django.contrib.contenttypes.models import ContentType +from django.core import paginator +from django.test import TestCase +from django.urls import reverse + +from wagtail.admin.tests.pages.timestamps import local_datetime +from wagtail.core.models import GroupPagePermission, Page +from wagtail.tests.testapp.models import SimplePage, SingleEventPage, StandardIndex +from wagtail.tests.utils import WagtailTestUtils + + +class TestPageExplorer(TestCase, WagtailTestUtils): + def setUp(self): + # Find root page + self.root_page = Page.objects.get(id=2) + + # Add child page + self.child_page = SimplePage( + title="Hello world!", + slug="hello-world", + content="hello", + ) + self.root_page.add_child(instance=self.child_page) + + # more child pages to test ordering + self.old_page = StandardIndex( + title="Old page", + slug="old-page", + latest_revision_created_at=local_datetime(2010, 1, 1) + ) + self.root_page.add_child(instance=self.old_page) + + self.new_page = SimplePage( + title="New page", + slug="new-page", + content="hello", + latest_revision_created_at=local_datetime(2016, 1, 1) + ) + self.root_page.add_child(instance=self.new_page) + + # Login + self.user = self.login() + + def test_explore(self): + response = self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') + self.assertEqual(self.root_page, response.context['parent_page']) + + # child pages should be most recent first + # (with null latest_revision_created_at at the end) + page_ids = [page.id for page in response.context['pages']] + self.assertEqual(page_ids, [self.new_page.id, self.old_page.id, self.child_page.id]) + + def test_explore_root(self): + response = self.client.get(reverse('wagtailadmin_explore_root')) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') + self.assertEqual(Page.objects.get(id=1), response.context['parent_page']) + self.assertTrue(response.context['pages'].paginator.object_list.filter(id=self.root_page.id).exists()) + + def test_explore_root_shows_icon(self): + response = self.client.get(reverse('wagtailadmin_explore_root')) + self.assertEqual(response.status_code, 200) + + # Administrator (or user with add_site permission) should see the + # sites link with the icon-site icon + self.assertContains( + response, + ("""""") + ) + + def test_ordering(self): + response = self.client.get( + reverse('wagtailadmin_explore', args=(self.root_page.id, )), + {'ordering': 'title'} + ) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') + self.assertEqual(response.context['ordering'], 'title') + + # child pages should be ordered by title + page_ids = [page.id for page in response.context['pages']] + self.assertEqual(page_ids, [self.child_page.id, self.new_page.id, self.old_page.id]) + + def test_reverse_ordering(self): + response = self.client.get( + reverse('wagtailadmin_explore', args=(self.root_page.id, )), + {'ordering': '-title'} + ) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') + self.assertEqual(response.context['ordering'], '-title') + + # child pages should be ordered by title + page_ids = [page.id for page in response.context['pages']] + self.assertEqual(page_ids, [self.old_page.id, self.new_page.id, self.child_page.id]) + + def test_ordering_by_last_revision_forward(self): + response = self.client.get( + reverse('wagtailadmin_explore', args=(self.root_page.id, )), + {'ordering': 'latest_revision_created_at'} + ) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') + self.assertEqual(response.context['ordering'], 'latest_revision_created_at') + + # child pages should be oldest revision first + # (with null latest_revision_created_at at the start) + page_ids = [page.id for page in response.context['pages']] + self.assertEqual(page_ids, [self.child_page.id, self.old_page.id, self.new_page.id]) + + def test_invalid_ordering(self): + response = self.client.get( + reverse('wagtailadmin_explore', args=(self.root_page.id, )), + {'ordering': 'invalid_order'} + ) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') + self.assertEqual(response.context['ordering'], '-latest_revision_created_at') + + def test_reordering(self): + response = self.client.get( + reverse('wagtailadmin_explore', args=(self.root_page.id, )), + {'ordering': 'ord'} + ) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') + self.assertEqual(response.context['ordering'], 'ord') + + # child pages should be ordered by native tree order (i.e. by creation time) + page_ids = [page.id for page in response.context['pages']] + self.assertEqual(page_ids, [self.child_page.id, self.old_page.id, self.new_page.id]) + + # Pages must not be paginated + self.assertNotIsInstance(response.context['pages'], paginator.Page) + + def test_construct_explorer_page_queryset_hook(self): + # testapp implements a construct_explorer_page_queryset hook + # that only returns pages with a slug starting with 'hello' + # when the 'polite_pages_only' URL parameter is set + response = self.client.get( + reverse('wagtailadmin_explore', args=(self.root_page.id, )), + {'polite_pages_only': 'yes_please'} + ) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') + page_ids = [page.id for page in response.context['pages']] + self.assertEqual(page_ids, [self.child_page.id]) + + def test_construct_construct_page_listing_buttons_hook(self): + # testapp implements a construct_page_listing_buttons hook + # that add's an dummy button with the label 'Dummy Button' which points + # to '/dummy-button' + response = self.client.get( + reverse('wagtailadmin_explore', args=(self.root_page.id, )), + ) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') + self.assertContains(response, 'Dummy Button') + self.assertContains(response, '/dummy-button') + + def make_pages(self): + for i in range(150): + self.root_page.add_child(instance=SimplePage( + title="Page " + str(i), + slug="page-" + str(i), + content="hello", + )) + + def test_pagination(self): + self.make_pages() + + response = self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, )), {'p': 2}) + + # Check response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') + + # Check that we got the correct page + self.assertEqual(response.context['pages'].number, 2) + + def test_pagination_invalid(self): + self.make_pages() + + response = self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, )), {'p': 'Hello World!'}) + + # Check response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') + + # Check that we got page one + self.assertEqual(response.context['pages'].number, 1) + + def test_pagination_out_of_range(self): + self.make_pages() + + response = self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, )), {'p': 99999}) + + # Check response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') + + # Check that we got the last page + self.assertEqual(response.context['pages'].number, response.context['pages'].paginator.num_pages) + + def test_listing_uses_specific_models(self): + # SingleEventPage has custom URL routing; the 'live' link in the listing + # should show the custom URL, which requires us to use the specific version + # of the class + self.new_event = SingleEventPage( + title="New event", + location='the moon', audience='public', + cost='free', date_from='2001-01-01', + latest_revision_created_at=local_datetime(2016, 1, 1) + ) + self.root_page.add_child(instance=self.new_event) + + response = self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + self.assertEqual(response.status_code, 200) + + self.assertContains(response, '/new-event/pointless-suffix/') + + def make_event_pages(self, count): + for i in range(count): + self.root_page.add_child(instance=SingleEventPage( + title="New event " + str(i), + location='the moon', audience='public', + cost='free', date_from='2001-01-01', + latest_revision_created_at=local_datetime(2016, 1, 1) + )) + + def test_exploring_uses_specific_page_with_custom_display_title(self): + # SingleEventPage has a custom get_admin_display_title method; explorer should + # show the custom title rather than the basic database one + self.make_event_pages(count=1) + response = self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + self.assertContains(response, 'New event 0 (single event)') + + new_event = SingleEventPage.objects.latest('pk') + response = self.client.get(reverse('wagtailadmin_explore', args=(new_event.id, ))) + self.assertContains(response, 'New event 0 (single event)') + + def test_ordering_less_than_100_pages_uses_specific_page_with_custom_display_title(self): + # Reorder view should also use specific pages + # (provided there are <100 pages in the listing, as this may be a significant + # performance hit on larger listings) + # There are 3 pages created in setUp, so 96 more add to a total of 99. + self.make_event_pages(count=96) + response = self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, )) + '?ordering=ord') + self.assertContains(response, 'New event 0 (single event)') + + def test_ordering_100_or_more_pages_uses_generic_page_without_custom_display_title(self): + # There are 3 pages created in setUp, so 97 more add to a total of 100. + self.make_event_pages(count=97) + response = self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, )) + '?ordering=ord') + self.assertNotContains(response, 'New event 0 (single event)') + + def test_parent_page_is_specific(self): + response = self.client.get(reverse('wagtailadmin_explore', args=(self.child_page.id, ))) + self.assertEqual(response.status_code, 200) + + self.assertIsInstance(response.context['parent_page'], SimplePage) + + def test_explorer_no_perms(self): + self.user.is_superuser = False + self.user.user_permissions.add( + Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') + ) + self.user.save() + + admin = reverse('wagtailadmin_home') + self.assertRedirects( + self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, ))), + admin) + self.assertRedirects( + self.client.get(reverse('wagtailadmin_explore_root')), admin) + + def test_explore_with_missing_page_model(self): + # Create a ContentType that doesn't correspond to a real model + missing_page_content_type = ContentType.objects.create(app_label='tests', model='missingpage') + # Turn /home/old-page/ into this content type + Page.objects.filter(id=self.old_page.id).update(content_type=missing_page_content_type) + + # try to browse the the listing that contains the missing model + response = self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') + + # try to browse into the page itself + response = self.client.get(reverse('wagtailadmin_explore', args=(self.old_page.id, ))) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') + + +class TestBreadcrumb(TestCase, WagtailTestUtils): + fixtures = ['test.json'] + + def test_breadcrumb_uses_specific_titles(self): + self.user = self.login() + + # get the explorer view for a subpage of a SimplePage + page = Page.objects.get(url_path='/home/secret-plans/steal-underpants/') + response = self.client.get(reverse('wagtailadmin_explore', args=(page.id, ))) + + # The breadcrumb should pick up SimplePage's overridden get_admin_display_title method + expected_url = reverse('wagtailadmin_explore', args=(Page.objects.get(url_path='/home/secret-plans/').id, )) + self.assertContains(response, """
  • Secret plans (simple page)
  • """ % expected_url) + + +class TestPageExplorerSignposting(TestCase, WagtailTestUtils): + fixtures = ['test.json'] + + def setUp(self): + # Find root page + self.root_page = Page.objects.get(id=1) + + # Find page with an associated site + self.site_page = Page.objects.get(id=2) + + # Add another top-level page (which will have no corresponding site record) + self.no_site_page = SimplePage( + title="Hello world!", + slug="hello-world", + content="hello", + ) + self.root_page.add_child(instance=self.no_site_page) + + # Tests for users that have both add-site permission, and explore permission at the given view; + # warning messages should include advice re configuring sites + + def test_admin_at_root(self): + self.assertTrue(self.client.login(username='superuser', password='password')) + response = self.client.get(reverse('wagtailadmin_explore_root')) + self.assertEqual(response.status_code, 200) + # Administrator (or user with add_site permission) should get the full message + # about configuring sites + self.assertContains( + response, + ( + "The root level is where you can add new sites to your Wagtail installation. " + "Pages created here will not be accessible at any URL until they are associated with a site." + ) + ) + self.assertContains(response, """Configure a site now.""") + + def test_admin_at_non_site_page(self): + self.assertTrue(self.client.login(username='superuser', password='password')) + response = self.client.get(reverse('wagtailadmin_explore', args=(self.no_site_page.id, ))) + self.assertEqual(response.status_code, 200) + # Administrator (or user with add_site permission) should get a warning about + # unroutable pages, and be directed to the site config area + self.assertContains( + response, + ( + "There is no site set up for this location. " + "Pages created here will not be accessible at any URL until a site is associated with this location." + ) + ) + self.assertContains(response, """Configure a site now.""") + + def test_admin_at_site_page(self): + self.assertTrue(self.client.login(username='superuser', password='password')) + response = self.client.get(reverse('wagtailadmin_explore', args=(self.site_page.id, ))) + self.assertEqual(response.status_code, 200) + # There should be no warning message here + self.assertNotContains(response, "Pages created here will not be accessible") + + # Tests for standard users that have explore permission at the given view; + # warning messages should omit advice re configuring sites + + def test_nonadmin_at_root(self): + # Assign siteeditor permission over no_site_page, so that the deepest-common-ancestor + # logic allows them to explore root + GroupPagePermission.objects.create( + group=Group.objects.get(name="Site-wide editors"), + page=self.no_site_page, permission_type='add' + ) + self.assertTrue(self.client.login(username='siteeditor', password='password')) + response = self.client.get(reverse('wagtailadmin_explore_root')) + + self.assertEqual(response.status_code, 200) + # Non-admin should get a simple "create pages as children of the homepage" prompt + self.assertContains( + response, + "Pages created here will not be accessible at any URL. " + "To add pages to an existing site, create them as children of the homepage." + ) + + def test_nonadmin_at_non_site_page(self): + # Assign siteeditor permission over no_site_page + GroupPagePermission.objects.create( + group=Group.objects.get(name="Site-wide editors"), + page=self.no_site_page, permission_type='add' + ) + self.assertTrue(self.client.login(username='siteeditor', password='password')) + response = self.client.get(reverse('wagtailadmin_explore', args=(self.no_site_page.id, ))) + + self.assertEqual(response.status_code, 200) + # Non-admin should get a warning about unroutable pages + self.assertContains( + response, + ( + "There is no site record for this location. " + "Pages created here will not be accessible at any URL." + ) + ) + + def test_nonadmin_at_site_page(self): + self.assertTrue(self.client.login(username='siteeditor', password='password')) + response = self.client.get(reverse('wagtailadmin_explore', args=(self.site_page.id, ))) + self.assertEqual(response.status_code, 200) + # There should be no warning message here + self.assertNotContains(response, "Pages created here will not be accessible") + + # Tests for users that have explore permission *somewhere*, but not at the view being tested; + # in all cases, they should be redirected to their explorable root + + def test_bad_permissions_at_root(self): + # 'siteeditor' does not have permission to explore the root + self.assertTrue(self.client.login(username='siteeditor', password='password')) + response = self.client.get(reverse('wagtailadmin_explore_root')) + + # Users without permission to explore here should be redirected to their explorable root. + self.assertEqual( + (response.status_code, response['Location']), + (302, reverse('wagtailadmin_explore', args=(self.site_page.pk, ))) + ) + + def test_bad_permissions_at_non_site_page(self): + # 'siteeditor' does not have permission to explore no_site_page + self.assertTrue(self.client.login(username='siteeditor', password='password')) + response = self.client.get(reverse('wagtailadmin_explore', args=(self.no_site_page.id, ))) + + # Users without permission to explore here should be redirected to their explorable root. + self.assertEqual( + (response.status_code, response['Location']), + (302, reverse('wagtailadmin_explore', args=(self.site_page.pk, ))) + ) + + def test_bad_permissions_at_site_page(self): + # Adjust siteeditor's permission so that they have permission over no_site_page + # instead of site_page + Group.objects.get(name="Site-wide editors").page_permissions.update(page_id=self.no_site_page.id) + self.assertTrue(self.client.login(username='siteeditor', password='password')) + response = self.client.get(reverse('wagtailadmin_explore', args=(self.site_page.id, ))) + # Users without permission to explore here should be redirected to their explorable root. + self.assertEqual( + (response.status_code, response['Location']), + (302, reverse('wagtailadmin_explore', args=(self.no_site_page.pk, ))) + ) + + +class TestExplorablePageVisibility(TestCase, WagtailTestUtils): + """ + Test the way that the Explorable Pages functionality manifests within the Explorer. + This is isolated in its own test case because it requires a custom page tree and custom set of + users and groups. + The fixture sets up this page tree: + ======================================================== + ID Site Path + ======================================================== + 1 / + 2 testserver /home/ + 3 testserver /home/about-us/ + 4 example.com /example-home/ + 5 example.com /example-home/content/ + 6 example.com /example-home/content/page-1/ + 7 example.com /example-home/content/page-2/ + 9 example.com /example-home/content/page-2/child-1 + 8 example.com /example-home/other-content/ + 10 example2.com /home-2/ + ======================================================== + Group 1 has explore and choose permissions rooted at testserver's homepage. + Group 2 has explore and choose permissions rooted at example.com's page-1. + Group 3 has explore and choose permissions rooted at example.com's other-content. + User "jane" is in Group 1. + User "bob" is in Group 2. + User "sam" is in Groups 1 and 2. + User "josh" is in Groups 2 and 3. + User "mary" is is no Groups, but she has the "access wagtail admin" permission. + User "superman" is an admin. + """ + + fixtures = ['test_explorable_pages.json'] + + # Integration tests adapted from @coredumperror + + def test_admin_can_explore_every_page(self): + self.assertTrue(self.client.login(username='superman', password='password')) + for page in Page.objects.all(): + response = self.client.get(reverse('wagtailadmin_explore', args=[page.pk])) + self.assertEqual(response.status_code, 200) + + def test_admin_sees_root_page_as_explorer_root(self): + self.assertTrue(self.client.login(username='superman', password='password')) + response = self.client.get(reverse('wagtailadmin_explore_root')) + self.assertEqual(response.status_code, 200) + # Administrator should see the full list of children of the Root page. + self.assertContains(response, "Welcome to testserver!") + self.assertContains(response, "Welcome to example.com!") + + def test_admin_sees_breadcrumbs_up_to_root_page(self): + self.assertTrue(self.client.login(username='superman', password='password')) + response = self.client.get(reverse('wagtailadmin_explore', args=[6])) + self.assertEqual(response.status_code, 200) + + self.assertInHTML( + """
  • Root
  • """, + str(response.content) + ) + self.assertInHTML("""
  • Welcome to example.com!
  • """, str(response.content)) + self.assertInHTML("""
  • Content
  • """, str(response.content)) + + def test_nonadmin_sees_breadcrumbs_up_to_cca(self): + self.assertTrue(self.client.login(username='josh', password='password')) + response = self.client.get(reverse('wagtailadmin_explore', args=[6])) + self.assertEqual(response.status_code, 200) + # While at "Page 1", Josh should see the breadcrumbs leading only as far back as the example.com homepage, + # since it's his Closest Common Ancestor. + self.assertInHTML( + """
  • Home
  • """, + str(response.content) + ) + self.assertInHTML("""
  • Content
  • """, str(response.content)) + # The page title shouldn't appear because it's the "home" breadcrumb. + self.assertNotContains(response, "Welcome to example.com!") + + def test_admin_home_page_changes_with_permissions(self): + self.assertTrue(self.client.login(username='bob', password='password')) + response = self.client.get(reverse('wagtailadmin_home')) + self.assertEqual(response.status_code, 200) + # Bob should only see the welcome for example.com, not testserver + self.assertContains(response, "Welcome to the example.com Wagtail CMS") + self.assertNotContains(response, "testserver") + + def test_breadcrumb_with_no_user_permissions(self): + self.assertTrue(self.client.login(username='mary', password='password')) + response = self.client.get(reverse('wagtailadmin_home')) + self.assertEqual(response.status_code, 200) + # Since Mary has no page permissions, she should not see the breadcrumb + self.assertNotContains(response, """
  • Home
  • """) diff --git a/wagtail/admin/tests/pages/test_moderation.py b/wagtail/admin/tests/pages/test_moderation.py new file mode 100644 index 000000000..3d542c37f --- /dev/null +++ b/wagtail/admin/tests/pages/test_moderation.py @@ -0,0 +1,383 @@ +import logging +from itertools import chain +from unittest import mock + +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group, Permission +from django.contrib.messages import constants as message_constants +from django.core import mail +from django.core.mail import EmailMultiAlternatives +from django.test import TestCase, override_settings +from django.urls import reverse + +from wagtail.core.models import GroupPagePermission, Page, PageRevision +from wagtail.core.signals import page_published +from wagtail.tests.testapp.models import SimplePage +from wagtail.tests.utils import WagtailTestUtils +from wagtail.users.models import UserProfile + + +class TestApproveRejectModeration(TestCase, WagtailTestUtils): + def setUp(self): + self.submitter = get_user_model().objects.create_superuser( + username='submitter', + email='submitter@email.com', + password='password', + ) + + self.user = self.login() + + # Create a page and submit it for moderation + root_page = Page.objects.get(id=2) + self.page = SimplePage( + title="Hello world!", + slug='hello-world', + content="hello", + live=False, + has_unpublished_changes=True, + ) + root_page.add_child(instance=self.page) + + self.page.save_revision(user=self.submitter, submitted_for_moderation=True) + self.revision = self.page.get_latest_revision() + + def test_approve_moderation_view(self): + """ + This posts to the approve moderation view and checks that the page was approved + """ + # Connect a mock signal handler to page_published signal + mock_handler = mock.MagicMock() + page_published.connect(mock_handler) + + # Post + response = self.client.post(reverse('wagtailadmin_pages:approve_moderation', args=(self.revision.id, ))) + + # Check that the user was redirected to the dashboard + self.assertRedirects(response, reverse('wagtailadmin_home')) + + page = Page.objects.get(id=self.page.id) + # Page must be live + self.assertTrue(page.live, "Approving moderation failed to set live=True") + # Page should now have no unpublished changes + self.assertFalse( + page.has_unpublished_changes, + "Approving moderation failed to set has_unpublished_changes=False" + ) + + # Check that the page_published signal was fired + self.assertEqual(mock_handler.call_count, 1) + mock_call = mock_handler.mock_calls[0][2] + + self.assertEqual(mock_call['sender'], self.page.specific_class) + self.assertEqual(mock_call['instance'], self.page) + self.assertIsInstance(mock_call['instance'], self.page.specific_class) + + def test_approve_moderation_when_later_revision_exists(self): + self.page.title = "Goodbye world!" + self.page.save_revision(user=self.submitter, submitted_for_moderation=False) + + response = self.client.post(reverse('wagtailadmin_pages:approve_moderation', args=(self.revision.id, ))) + + # Check that the user was redirected to the dashboard + self.assertRedirects(response, reverse('wagtailadmin_home')) + + page = Page.objects.get(id=self.page.id) + # Page must be live + self.assertTrue(page.live, "Approving moderation failed to set live=True") + # Page content should be the submitted version, not the published one + self.assertEqual(page.title, "Hello world!") + # Page should still have unpublished changes + self.assertTrue( + page.has_unpublished_changes, + "has_unpublished_changes incorrectly cleared on approve_moderation when a later revision exists" + ) + + def test_approve_moderation_view_bad_revision_id(self): + """ + This tests that the approve moderation view handles invalid revision ids correctly + """ + # Post + response = self.client.post(reverse('wagtailadmin_pages:approve_moderation', args=(12345, ))) + + # Check that the user received a 404 response + self.assertEqual(response.status_code, 404) + + def test_approve_moderation_view_bad_permissions(self): + """ + This tests that the approve moderation view doesn't allow users without moderation permissions + """ + # Remove privileges from user + self.user.is_superuser = False + self.user.user_permissions.add( + Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') + ) + self.user.save() + + # Post + response = self.client.post(reverse('wagtailadmin_pages:approve_moderation', args=(self.revision.id, ))) + + # Check that the user received a 403 response + self.assertEqual(response.status_code, 403) + + def test_reject_moderation_view(self): + """ + This posts to the reject moderation view and checks that the page was rejected + """ + # Post + response = self.client.post(reverse('wagtailadmin_pages:reject_moderation', args=(self.revision.id, ))) + + # Check that the user was redirected to the dashboard + self.assertRedirects(response, reverse('wagtailadmin_home')) + + # Page must not be live + self.assertFalse(Page.objects.get(id=self.page.id).live) + + # Revision must no longer be submitted for moderation + self.assertFalse(PageRevision.objects.get(id=self.revision.id).submitted_for_moderation) + + def test_reject_moderation_view_bad_revision_id(self): + """ + This tests that the reject moderation view handles invalid revision ids correctly + """ + # Post + response = self.client.post(reverse('wagtailadmin_pages:reject_moderation', args=(12345, ))) + + # Check that the user received a 404 response + self.assertEqual(response.status_code, 404) + + def test_reject_moderation_view_bad_permissions(self): + """ + This tests that the reject moderation view doesn't allow users without moderation permissions + """ + # Remove privileges from user + self.user.is_superuser = False + self.user.user_permissions.add( + Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') + ) + self.user.save() + + # Post + response = self.client.post(reverse('wagtailadmin_pages:reject_moderation', args=(self.revision.id, ))) + + # Check that the user received a 403 response + self.assertEqual(response.status_code, 403) + + def test_preview_for_moderation(self): + response = self.client.get(reverse('wagtailadmin_pages:preview_for_moderation', args=(self.revision.id, ))) + + # Check response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'tests/simple_page.html') + self.assertContains(response, "Hello world!") + + +class TestNotificationPreferences(TestCase, WagtailTestUtils): + def setUp(self): + # Find root page + self.root_page = Page.objects.get(id=2) + + # Login + self.user = self.login() + + # Create two moderator users for testing 'submitted' email + User = get_user_model() + self.moderator = User.objects.create_superuser('moderator', 'moderator@email.com', 'password') + self.moderator2 = User.objects.create_superuser('moderator2', 'moderator2@email.com', 'password') + + # Create a submitter for testing 'rejected' and 'approved' emails + self.submitter = User.objects.create_user('submitter', 'submitter@email.com', 'password') + + # User profiles for moderator2 and the submitter + self.moderator2_profile = UserProfile.get_for_user(self.moderator2) + self.submitter_profile = UserProfile.get_for_user(self.submitter) + + # Create a page and submit it for moderation + self.child_page = SimplePage( + title="Hello world!", + slug='hello-world', + content="hello", + live=False, + ) + self.root_page.add_child(instance=self.child_page) + + # POST data to edit the page + self.post_data = { + 'title': "I've been edited!", + 'content': "Some content", + 'slug': 'hello-world', + 'action-submit': "Submit", + } + + def submit(self): + return self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), self.post_data) + + def silent_submit(self): + """ + Sets up the child_page as needing moderation, without making a request + """ + self.child_page.save_revision(user=self.submitter, submitted_for_moderation=True) + self.revision = self.child_page.get_latest_revision() + + def approve(self): + return self.client.post(reverse('wagtailadmin_pages:approve_moderation', args=(self.revision.id, ))) + + def reject(self): + return self.client.post(reverse('wagtailadmin_pages:reject_moderation', args=(self.revision.id, ))) + + def test_vanilla_profile(self): + # Check that the vanilla profile has rejected notifications on + self.assertEqual(self.submitter_profile.rejected_notifications, True) + + # Check that the vanilla profile has approved notifications on + self.assertEqual(self.submitter_profile.approved_notifications, True) + + def test_submit_notifications_sent(self): + # Submit + self.submit() + + # Check that both the moderators got an email, and no others + self.assertEqual(len(mail.outbox), 2) + email_to = mail.outbox[0].to + mail.outbox[1].to + self.assertIn(self.moderator.email, email_to) + self.assertIn(self.moderator2.email, email_to) + self.assertEqual(len(mail.outbox[0].to), 1) + self.assertEqual(len(mail.outbox[1].to), 1) + + def test_submit_notification_preferences_respected(self): + # moderator2 doesn't want emails + self.moderator2_profile.submitted_notifications = False + self.moderator2_profile.save() + + # Submit + self.submit() + + # Check that only one moderator got an email + self.assertEqual(len(mail.outbox), 1) + self.assertEqual([self.moderator.email], mail.outbox[0].to) + + def test_approved_notifications(self): + # Set up the page version + self.silent_submit() + # Approve + self.approve() + + # Submitter must receive an approved email + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].to, ['submitter@email.com']) + self.assertEqual(mail.outbox[0].subject, 'The page "Hello world!" has been approved') + + def test_approved_notifications_preferences_respected(self): + # Submitter doesn't want 'approved' emails + self.submitter_profile.approved_notifications = False + self.submitter_profile.save() + + # Set up the page version + self.silent_submit() + # Approve + self.approve() + + # No email to send + self.assertEqual(len(mail.outbox), 0) + + def test_rejected_notifications(self): + # Set up the page version + self.silent_submit() + # Reject + self.reject() + + # Submitter must receive a rejected email + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].to, ['submitter@email.com']) + self.assertEqual(mail.outbox[0].subject, 'The page "Hello world!" has been rejected') + + def test_rejected_notification_preferences_respected(self): + # Submitter doesn't want 'rejected' emails + self.submitter_profile.rejected_notifications = False + self.submitter_profile.save() + + # Set up the page version + self.silent_submit() + # Reject + self.reject() + + # No email to send + self.assertEqual(len(mail.outbox), 0) + + def test_moderator_group_notifications(self): + # Create a (non-superuser) moderator + User = get_user_model() + user1 = User.objects.create_user('moduser1', 'moduser1@email.com') + user1.groups.add(Group.objects.get(name='Moderators')) + user1.save() + + # Create another group and user with permission to moderate + modgroup2 = Group.objects.create(name='More moderators') + GroupPagePermission.objects.create( + group=modgroup2, page=self.root_page, permission_type='publish' + ) + user2 = User.objects.create_user('moduser2', 'moduser2@email.com') + user2.groups.add(Group.objects.get(name='More moderators')) + user2.save() + + # Submit + # This used to break in Wagtail 1.3 (Postgres exception, SQLite 3/4 notifications) + response = self.submit() + + # Should be redirected to explorer page + self.assertEqual(response.status_code, 302) + + # Check that the superusers and the moderation group members all got an email + expected_emails = 4 + self.assertEqual(len(mail.outbox), expected_emails) + email_to = [] + for i in range(expected_emails): + self.assertEqual(len(mail.outbox[i].to), 1) + email_to += mail.outbox[i].to + self.assertIn(self.moderator.email, email_to) + self.assertIn(self.moderator2.email, email_to) + self.assertIn(user1.email, email_to) + self.assertIn(user2.email, email_to) + + @override_settings(WAGTAILADMIN_NOTIFICATION_INCLUDE_SUPERUSERS=False) + def test_disable_superuser_notification(self): + # Add one of the superusers to the moderator group + self.moderator.groups.add(Group.objects.get(name='Moderators')) + + response = self.submit() + + # Should be redirected to explorer page + self.assertEqual(response.status_code, 302) + + # Check that the non-moderator superuser is not being notified + expected_emails = 1 + self.assertEqual(len(mail.outbox), expected_emails) + # Use chain as the 'to' field is a list of recipients + email_to = list(chain.from_iterable([m.to for m in mail.outbox])) + self.assertIn(self.moderator.email, email_to) + self.assertNotIn(self.moderator2.email, email_to) + + @mock.patch.object(EmailMultiAlternatives, 'send', side_effect=IOError('Server down')) + def test_email_send_error(self, mock_fn): + logging.disable(logging.CRITICAL) + # Approve + self.silent_submit() + response = self.approve() + logging.disable(logging.NOTSET) + + # An email that fails to send should return a message rather than crash the page + self.assertEqual(response.status_code, 302) + response = self.client.get(reverse('wagtailadmin_home')) + + # There should be one "approved" message and one "failed to send notifications" + messages = list(response.context['messages']) + self.assertEqual(len(messages), 2) + self.assertEqual(messages[0].level, message_constants.SUCCESS) + self.assertEqual(messages[1].level, message_constants.ERROR) + + def test_email_headers(self): + # Submit + self.submit() + + msg_headers = set(mail.outbox[0].message().items()) + headers = {('Auto-Submitted', 'auto-generated')} + self.assertTrue(headers.issubset(msg_headers), msg='Message is missing the Auto-Submitted header.',) diff --git a/wagtail/admin/tests/pages/test_move_page.py b/wagtail/admin/tests/pages/test_move_page.py new file mode 100644 index 000000000..e468dfead --- /dev/null +++ b/wagtail/admin/tests/pages/test_move_page.py @@ -0,0 +1,130 @@ +from django.contrib.auth.models import Permission +from django.contrib.messages import constants as message_constants +from django.http import HttpRequest, HttpResponse +from django.test import TestCase +from django.urls import reverse + +from wagtail.core.models import Page +from wagtail.tests.testapp.models import SimplePage +from wagtail.tests.utils import WagtailTestUtils + + +class TestPageMove(TestCase, WagtailTestUtils): + def setUp(self): + # Find root page + self.root_page = Page.objects.get(id=2) + + # Create three sections + self.section_a = SimplePage(title="Section A", slug="section-a", content="hello") + self.root_page.add_child(instance=self.section_a) + + self.section_b = SimplePage(title="Section B", slug="section-b", content="hello") + self.root_page.add_child(instance=self.section_b) + + self.section_c = SimplePage(title="Section C", slug="section-c", content="hello") + self.root_page.add_child(instance=self.section_c) + + # Add test page A into section A + self.test_page_a = SimplePage(title="Hello world!", slug="hello-world", content="hello") + self.section_a.add_child(instance=self.test_page_a) + + # Add test page B into section C + self.test_page_b = SimplePage(title="Hello world!", slug="hello-world", content="hello") + self.section_c.add_child(instance=self.test_page_b) + + # Login + self.user = self.login() + + def test_page_move(self): + response = self.client.get(reverse('wagtailadmin_pages:move', args=(self.test_page_a.id, ))) + self.assertEqual(response.status_code, 200) + + def test_page_move_bad_permissions(self): + # Remove privileges from user + self.user.is_superuser = False + self.user.user_permissions.add( + Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') + ) + self.user.save() + + # Get move page + response = self.client.get(reverse('wagtailadmin_pages:move', args=(self.test_page_a.id, ))) + + # Check that the user received a 403 response + self.assertEqual(response.status_code, 403) + + def test_page_move_confirm(self): + response = self.client.get( + reverse('wagtailadmin_pages:move_confirm', args=(self.test_page_a.id, self.section_b.id)) + ) + self.assertEqual(response.status_code, 200) + + response = self.client.get( + reverse('wagtailadmin_pages:move_confirm', args=(self.test_page_b.id, self.section_a.id)) + ) + # Duplicate slugs triggers a redirect with an error message. + self.assertEqual(response.status_code, 302) + + response = self.client.get(reverse('wagtailadmin_home')) + messages = list(response.context['messages']) + self.assertEqual(len(messages), 1) + self.assertEqual(messages[0].level, message_constants.ERROR) + # Slug should be in error message. + self.assertIn("{}".format(self.test_page_b.slug), messages[0].message) + + def test_page_set_page_position(self): + response = self.client.get(reverse('wagtailadmin_pages:set_page_position', args=(self.test_page_a.id, ))) + self.assertEqual(response.status_code, 200) + + def test_before_move_page_hook(self): + def hook_func(request, page, destination): + self.assertIsInstance(request, HttpRequest) + self.assertIsInstance(page.specific, SimplePage) + self.assertIsInstance(destination.specific, SimplePage) + + return HttpResponse("Overridden!") + + with self.register_hook('before_move_page', hook_func): + response = self.client.get(reverse('wagtailadmin_pages:move_confirm', args=(self.test_page_a.id, self.section_b.id))) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b"Overridden!") + + def test_before_move_page_hook_post(self): + def hook_func(request, page, destination): + self.assertIsInstance(request, HttpRequest) + self.assertIsInstance(page.specific, SimplePage) + self.assertIsInstance(destination.specific, SimplePage) + + return HttpResponse("Overridden!") + + with self.register_hook('before_move_page', hook_func): + response = self.client.post(reverse('wagtailadmin_pages:move_confirm', args=(self.test_page_a.id, self.section_b.id))) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b"Overridden!") + + # page should not be moved + self.assertEqual( + Page.objects.get(id=self.test_page_a.id).get_parent().id, + self.section_a.id + ) + + def test_after_move_page_hook(self): + def hook_func(request, page): + self.assertIsInstance(request, HttpRequest) + self.assertIsInstance(page.specific, SimplePage) + + return HttpResponse("Overridden!") + + with self.register_hook('after_move_page', hook_func): + response = self.client.post(reverse('wagtailadmin_pages:move_confirm', args=(self.test_page_a.id, self.section_b.id))) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, b"Overridden!") + + # page should be moved + self.assertEqual( + Page.objects.get(id=self.test_page_a.id).get_parent().id, + self.section_b.id + ) diff --git a/wagtail/admin/tests/pages/test_page_locking.py b/wagtail/admin/tests/pages/test_page_locking.py new file mode 100644 index 000000000..107c8076c --- /dev/null +++ b/wagtail/admin/tests/pages/test_page_locking.py @@ -0,0 +1,201 @@ +from django.contrib.auth.models import Permission +from django.test import TestCase +from django.urls import reverse + +from wagtail.core.models import Page +from wagtail.tests.testapp.models import SimplePage +from wagtail.tests.utils import WagtailTestUtils + + +class TestLocking(TestCase, WagtailTestUtils): + def setUp(self): + # Find root page + self.root_page = Page.objects.get(id=2) + + # Login + self.user = self.login() + + # Create a page and submit it for moderation + self.child_page = SimplePage( + title="Hello world!", + slug='hello-world', + content="hello", + live=False, + ) + self.root_page.add_child(instance=self.child_page) + + def test_lock_post(self): + response = self.client.post(reverse('wagtailadmin_pages:lock', args=(self.child_page.id, ))) + + # Check response + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + + # Check that the page is locked + self.assertTrue(Page.objects.get(id=self.child_page.id).locked) + + def test_lock_get(self): + response = self.client.get(reverse('wagtailadmin_pages:lock', args=(self.child_page.id, ))) + + # Check response + self.assertEqual(response.status_code, 405) + + # Check that the page is still unlocked + self.assertFalse(Page.objects.get(id=self.child_page.id).locked) + + def test_lock_post_already_locked(self): + # Lock the page + self.child_page.locked = True + self.child_page.save() + + response = self.client.post(reverse('wagtailadmin_pages:lock', args=(self.child_page.id, ))) + + # Check response + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + + # Check that the page is still locked + self.assertTrue(Page.objects.get(id=self.child_page.id).locked) + + def test_lock_post_with_good_redirect(self): + response = self.client.post(reverse('wagtailadmin_pages:lock', args=(self.child_page.id, )), { + 'next': reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )) + }) + + # Check response + self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) + + # Check that the page is locked + self.assertTrue(Page.objects.get(id=self.child_page.id).locked) + + def test_lock_post_with_bad_redirect(self): + response = self.client.post(reverse('wagtailadmin_pages:lock', args=(self.child_page.id, )), { + 'next': 'http://www.google.co.uk' + }) + + # Check response + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + + # Check that the page is locked + self.assertTrue(Page.objects.get(id=self.child_page.id).locked) + + def test_lock_post_bad_page(self): + response = self.client.post(reverse('wagtailadmin_pages:lock', args=(9999, ))) + + # Check response + self.assertEqual(response.status_code, 404) + + # Check that the page is still unlocked + self.assertFalse(Page.objects.get(id=self.child_page.id).locked) + + def test_lock_post_bad_permissions(self): + # Remove privileges from user + self.user.is_superuser = False + self.user.user_permissions.add( + Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') + ) + self.user.save() + + response = self.client.post(reverse('wagtailadmin_pages:lock', args=(self.child_page.id, ))) + + # Check response + self.assertEqual(response.status_code, 403) + + # Check that the page is still unlocked + self.assertFalse(Page.objects.get(id=self.child_page.id).locked) + + def test_unlock_post(self): + # Lock the page + self.child_page.locked = True + self.child_page.save() + + response = self.client.post(reverse('wagtailadmin_pages:unlock', args=(self.child_page.id, ))) + + # Check response + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + + # Check that the page is unlocked + self.assertFalse(Page.objects.get(id=self.child_page.id).locked) + + def test_unlock_get(self): + # Lock the page + self.child_page.locked = True + self.child_page.save() + + response = self.client.get(reverse('wagtailadmin_pages:unlock', args=(self.child_page.id, ))) + + # Check response + self.assertEqual(response.status_code, 405) + + # Check that the page is still locked + self.assertTrue(Page.objects.get(id=self.child_page.id).locked) + + def test_unlock_post_already_unlocked(self): + response = self.client.post(reverse('wagtailadmin_pages:unlock', args=(self.child_page.id, ))) + + # Check response + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + + # Check that the page is still unlocked + self.assertFalse(Page.objects.get(id=self.child_page.id).locked) + + def test_unlock_post_with_good_redirect(self): + # Lock the page + self.child_page.locked = True + self.child_page.save() + + response = self.client.post(reverse('wagtailadmin_pages:unlock', args=(self.child_page.id, )), { + 'next': reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )) + }) + + # Check response + self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) + + # Check that the page is unlocked + self.assertFalse(Page.objects.get(id=self.child_page.id).locked) + + def test_unlock_post_with_bad_redirect(self): + # Lock the page + self.child_page.locked = True + self.child_page.save() + + response = self.client.post(reverse('wagtailadmin_pages:unlock', args=(self.child_page.id, )), { + 'next': 'http://www.google.co.uk' + }) + + # Check response + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + + # Check that the page is unlocked + self.assertFalse(Page.objects.get(id=self.child_page.id).locked) + + def test_unlock_post_bad_page(self): + # Lock the page + self.child_page.locked = True + self.child_page.save() + + response = self.client.post(reverse('wagtailadmin_pages:unlock', args=(9999, ))) + + # Check response + self.assertEqual(response.status_code, 404) + + # Check that the page is still locked + self.assertTrue(Page.objects.get(id=self.child_page.id).locked) + + def test_unlock_post_bad_permissions(self): + # Remove privileges from user + self.user.is_superuser = False + self.user.user_permissions.add( + Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') + ) + self.user.save() + + # Lock the page + self.child_page.locked = True + self.child_page.save() + + response = self.client.post(reverse('wagtailadmin_pages:unlock', args=(self.child_page.id, ))) + + # Check response + self.assertEqual(response.status_code, 403) + + # Check that the page is still locked + self.assertTrue(Page.objects.get(id=self.child_page.id).locked) diff --git a/wagtail/admin/tests/pages/test_page_search.py b/wagtail/admin/tests/pages/test_page_search.py new file mode 100644 index 000000000..872ead9a7 --- /dev/null +++ b/wagtail/admin/tests/pages/test_page_search.py @@ -0,0 +1,177 @@ +from django.contrib.auth.models import Permission +from django.test import TestCase +from django.urls import reverse + +from wagtail.admin.tests.pages.timestamps import local_datetime +from wagtail.core.models import Page +from wagtail.search.index import SearchField +from wagtail.tests.testapp.models import SimplePage, SingleEventPage +from wagtail.tests.utils import WagtailTestUtils + + +class TestPageSearch(TestCase, WagtailTestUtils): + def setUp(self): + self.user = self.login() + + def get(self, params=None, **extra): + return self.client.get(reverse('wagtailadmin_pages:search'), params or {}, **extra) + + def test_view(self): + response = self.get() + self.assertTemplateUsed(response, 'wagtailadmin/pages/search.html') + self.assertEqual(response.status_code, 200) + + def test_search(self): + response = self.get({'q': "Hello"}) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/search.html') + self.assertEqual(response.context['query_string'], "Hello") + + def test_search_searchable_fields(self): + # Find root page + root_page = Page.objects.get(id=2) + + # Create a page + root_page.add_child(instance=SimplePage( + title="Hi there!", slug='hello-world', content="good morning", + live=True, + has_unpublished_changes=False, + )) + + # Confirm the slug is not being searched + response = self.get({'q': "hello"}) + self.assertNotContains(response, "There is one matching page") + search_fields = Page.search_fields + + # Add slug to the search_fields + Page.search_fields = Page.search_fields + [SearchField('slug', partial_match=True)] + + # Confirm the slug is being searched + response = self.get({'q': "hello"}) + self.assertContains(response, "There is one matching page") + + # Reset the search fields + Page.search_fields = search_fields + + def test_ajax(self): + response = self.get({'q': "Hello"}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') + self.assertEqual(response.status_code, 200) + self.assertTemplateNotUsed(response, 'wagtailadmin/pages/search.html') + self.assertTemplateUsed(response, 'wagtailadmin/pages/search_results.html') + self.assertEqual(response.context['query_string'], "Hello") + + def test_pagination(self): + pages = ['0', '1', '-1', '9999', 'Not a page'] + for page in pages: + response = self.get({'q': "Hello", 'p': page}) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/search.html') + + def test_root_can_appear_in_search_results(self): + response = self.get({'q': "roo"}) + self.assertEqual(response.status_code, 200) + # 'pages' list in the response should contain root + results = response.context['pages'] + self.assertTrue(any([r.slug == 'root' for r in results])) + + def test_search_uses_admin_display_title_from_specific_class(self): + # SingleEventPage has a custom get_admin_display_title method; explorer should + # show the custom title rather than the basic database one + root_page = Page.objects.get(id=2) + new_event = SingleEventPage( + title="Lunar event", + location='the moon', audience='public', + cost='free', date_from='2001-01-01', + latest_revision_created_at=local_datetime(2016, 1, 1) + ) + root_page.add_child(instance=new_event) + response = self.get({'q': "lunar"}) + self.assertContains(response, "Lunar event (single event)") + + def test_search_no_perms(self): + self.user.is_superuser = False + self.user.user_permissions.add( + Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') + ) + self.user.save() + self.assertRedirects(self.get(), '/admin/') + + def test_search_order_by_title(self): + root_page = Page.objects.get(id=2) + new_event = SingleEventPage( + title="Lunar event", + location='the moon', audience='public', + cost='free', date_from='2001-01-01', + latest_revision_created_at=local_datetime(2016, 1, 1) + ) + root_page.add_child(instance=new_event) + + new_event_2 = SingleEventPage( + title="A Lunar event", + location='the moon', audience='public', + cost='free', date_from='2001-01-01', + latest_revision_created_at=local_datetime(2016, 1, 1) + ) + root_page.add_child(instance=new_event_2) + + response = self.get({'q': 'Lunar', 'ordering': 'title'}) + page_ids = [page.id for page in response.context['pages']] + self.assertEqual(page_ids, [new_event_2.id, new_event.id]) + + response = self.get({'q': 'Lunar', 'ordering': '-title'}) + page_ids = [page.id for page in response.context['pages']] + self.assertEqual(page_ids, [new_event.id, new_event_2.id]) + + def test_search_order_by_updated(self): + root_page = Page.objects.get(id=2) + new_event = SingleEventPage( + title="Lunar event", + location='the moon', audience='public', + cost='free', date_from='2001-01-01', + latest_revision_created_at=local_datetime(2016, 1, 1) + ) + root_page.add_child(instance=new_event) + + new_event_2 = SingleEventPage( + title="Lunar event 2", + location='the moon', audience='public', + cost='free', date_from='2001-01-01', + latest_revision_created_at=local_datetime(2015, 1, 1) + ) + root_page.add_child(instance=new_event_2) + + response = self.get({'q': 'Lunar', 'ordering': 'latest_revision_created_at'}) + page_ids = [page.id for page in response.context['pages']] + self.assertEqual(page_ids, [new_event_2.id, new_event.id]) + + response = self.get({'q': 'Lunar', 'ordering': '-latest_revision_created_at'}) + page_ids = [page.id for page in response.context['pages']] + self.assertEqual(page_ids, [new_event.id, new_event_2.id]) + + def test_search_order_by_status(self): + root_page = Page.objects.get(id=2) + live_event = SingleEventPage( + title="Lunar event", + location='the moon', audience='public', + cost='free', date_from='2001-01-01', + latest_revision_created_at=local_datetime(2016, 1, 1), + live=True + ) + root_page.add_child(instance=live_event) + + draft_event = SingleEventPage( + title="Lunar event", + location='the moon', audience='public', + cost='free', date_from='2001-01-01', + latest_revision_created_at=local_datetime(2016, 1, 1), + live=False + ) + root_page.add_child(instance=draft_event) + + response = self.get({'q': 'Lunar', 'ordering': 'live'}) + page_ids = [page.id for page in response.context['pages']] + self.assertEqual(page_ids, [draft_event.id, live_event.id]) + + response = self.get({'q': 'Lunar', 'ordering': '-live'}) + page_ids = [page.id for page in response.context['pages']] + self.assertEqual(page_ids, [live_event.id, draft_event.id]) diff --git a/wagtail/admin/tests/pages/test_preview.py b/wagtail/admin/tests/pages/test_preview.py new file mode 100644 index 000000000..db463dae2 --- /dev/null +++ b/wagtail/admin/tests/pages/test_preview.py @@ -0,0 +1,163 @@ +import datetime + +from django.test import TestCase +from django.urls import reverse +from django.utils import timezone +from freezegun import freeze_time + +from wagtail.admin.views.pages import PreviewOnEdit +from wagtail.core.models import Page +from wagtail.tests.testapp.models import EventCategory +from wagtail.tests.utils import WagtailTestUtils + + +class TestIssue2599(TestCase, WagtailTestUtils): + """ + When previewing a page on creation, we need to assign it a path value consistent with its + (future) position in the tree. The naive way of doing this is to give it an index number + one more than numchild - however, index numbers are not reassigned on page deletion, so + this can result in a path that collides with an existing page (which is invalid). + """ + + def test_issue_2599(self): + homepage = Page.objects.get(id=2) + + child1 = Page(title='child1') + homepage.add_child(instance=child1) + child2 = Page(title='child2') + homepage.add_child(instance=child2) + + child1.delete() + + self.login() + post_data = { + 'title': "New page!", + 'content': "Some content", + 'slug': 'hello-world', + 'action-submit': "Submit", + } + preview_url = reverse('wagtailadmin_pages:preview_on_add', + args=('tests', 'simplepage', homepage.id)) + response = self.client.post(preview_url, post_data) + + # Check the JSON response + self.assertEqual(response.status_code, 200) + self.assertJSONEqual(response.content.decode(), {'is_valid': True}) + + response = self.client.get(preview_url) + + # Check the HTML response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'tests/simple_page.html') + self.assertContains(response, "New page!") + + # Check that the treebeard attributes were set correctly on the page object + self.assertEqual(response.context['self'].depth, homepage.depth + 1) + self.assertTrue(response.context['self'].path.startswith(homepage.path)) + self.assertEqual(response.context['self'].get_parent(), homepage) + + +class TestPreview(TestCase, WagtailTestUtils): + fixtures = ['test.json'] + + def setUp(self): + self.meetings_category = EventCategory.objects.create(name='Meetings') + self.parties_category = EventCategory.objects.create(name='Parties') + self.holidays_category = EventCategory.objects.create(name='Holidays') + + self.home_page = Page.objects.get(url_path='/home/') + self.event_page = Page.objects.get(url_path='/home/events/christmas/') + + self.user = self.login() + + self.post_data = { + 'title': "Beach party", + 'slug': 'beach-party', + 'body': '''{"entityMap": {},"blocks": [ + {"inlineStyleRanges": [], "text": "party on wayne", "depth": 0, "type": "unstyled", "key": "00000", "entityRanges": []} + ]}''', + 'date_from': '2017-08-01', + 'audience': 'public', + 'location': 'the beach', + 'cost': 'six squid', + 'carousel_items-TOTAL_FORMS': 0, + 'carousel_items-INITIAL_FORMS': 0, + 'carousel_items-MIN_NUM_FORMS': 0, + 'carousel_items-MAX_NUM_FORMS': 0, + 'speakers-TOTAL_FORMS': 0, + 'speakers-INITIAL_FORMS': 0, + 'speakers-MIN_NUM_FORMS': 0, + 'speakers-MAX_NUM_FORMS': 0, + 'related_links-TOTAL_FORMS': 0, + 'related_links-INITIAL_FORMS': 0, + 'related_links-MIN_NUM_FORMS': 0, + 'related_links-MAX_NUM_FORMS': 0, + 'head_counts-TOTAL_FORMS': 0, + 'head_counts-INITIAL_FORMS': 0, + 'head_counts-MIN_NUM_FORMS': 0, + 'head_counts-MAX_NUM_FORMS': 0, + 'categories': [self.parties_category.id, self.holidays_category.id], + } + + def test_preview_on_create_with_m2m_field(self): + preview_url = reverse('wagtailadmin_pages:preview_on_add', + args=('tests', 'eventpage', self.home_page.id)) + response = self.client.post(preview_url, self.post_data) + + # Check the JSON response + self.assertEqual(response.status_code, 200) + self.assertJSONEqual(response.content.decode(), {'is_valid': True}) + + response = self.client.get(preview_url) + + # Check the HTML response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'tests/event_page.html') + self.assertContains(response, "Beach party") + self.assertContains(response, "
  • Parties
  • ") + self.assertContains(response, "
  • Holidays
  • ") + + def test_preview_on_edit_with_m2m_field(self): + preview_url = reverse('wagtailadmin_pages:preview_on_edit', + args=(self.event_page.id,)) + response = self.client.post(preview_url, self.post_data) + + # Check the JSON response + self.assertEqual(response.status_code, 200) + self.assertJSONEqual(response.content.decode(), {'is_valid': True}) + + response = self.client.get(preview_url) + + # Check the HTML response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'tests/event_page.html') + self.assertContains(response, "Beach party") + self.assertContains(response, "
  • Parties
  • ") + self.assertContains(response, "
  • Holidays
  • ") + + def test_preview_on_edit_expiry(self): + initial_datetime = timezone.now() + expiry_datetime = initial_datetime + datetime.timedelta( + seconds=PreviewOnEdit.preview_expiration_timeout + 1) + + with freeze_time(initial_datetime) as frozen_datetime: + preview_url = reverse('wagtailadmin_pages:preview_on_edit', + args=(self.event_page.id,)) + response = self.client.post(preview_url, self.post_data) + + # Check the JSON response + self.assertEqual(response.status_code, 200) + + response = self.client.get(preview_url) + + # Check the HTML response + self.assertEqual(response.status_code, 200) + + frozen_datetime.move_to(expiry_datetime) + + preview_url = reverse('wagtailadmin_pages:preview_on_edit', + args=(self.home_page.id,)) + response = self.client.post(preview_url, self.post_data) + self.assertEqual(response.status_code, 200) + response = self.client.get(preview_url) + self.assertEqual(response.status_code, 200) diff --git a/wagtail/admin/tests/pages/test_revisions.py b/wagtail/admin/tests/pages/test_revisions.py new file mode 100644 index 000000000..59215cef5 --- /dev/null +++ b/wagtail/admin/tests/pages/test_revisions.py @@ -0,0 +1,460 @@ +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group, Permission +from django.test import TestCase +from django.urls import reverse +from django.utils import formats +from django.utils.dateparse import parse_date + +from wagtail.admin.tests.pages.timestamps import local_datetime +from wagtail.core.models import Page +from wagtail.tests.testapp.models import EventPage, FormClassAdditionalFieldPage +from wagtail.tests.utils import WagtailTestUtils + + +class TestRevisions(TestCase, WagtailTestUtils): + fixtures = ['test.json'] + + def setUp(self): + self.christmas_event = EventPage.objects.get(url_path='/home/events/christmas/') + self.christmas_event.title = "Last Christmas" + self.christmas_event.date_from = '2013-12-25' + self.christmas_event.body = ( + "

    Last Christmas I gave you my heart, " + "but the very next day you gave it away

    " + ) + self.last_christmas_revision = self.christmas_event.save_revision() + self.last_christmas_revision.created_at = local_datetime(2013, 12, 25) + self.last_christmas_revision.save() + + self.christmas_event.title = "This Christmas" + self.christmas_event.date_from = '2014-12-25' + self.christmas_event.body = ( + "

    This year, to save me from tears, " + "I'll give it to someone special

    " + ) + self.this_christmas_revision = self.christmas_event.save_revision() + self.this_christmas_revision.created_at = local_datetime(2014, 12, 25) + self.this_christmas_revision.save() + + self.login() + + def test_edit_form_has_revisions_link(self): + response = self.client.get( + reverse('wagtailadmin_pages:edit', args=(self.christmas_event.id, )) + ) + self.assertEqual(response.status_code, 200) + revisions_index_url = reverse( + 'wagtailadmin_pages:revisions_index', args=(self.christmas_event.id, ) + ) + self.assertContains(response, revisions_index_url) + + def test_get_revisions_index(self): + response = self.client.get( + reverse('wagtailadmin_pages:revisions_index', args=(self.christmas_event.id, )) + ) + self.assertEqual(response.status_code, 200) + + self.assertContains(response, formats.localize(parse_date('2013-12-25'))) + last_christmas_preview_url = reverse( + 'wagtailadmin_pages:revisions_view', + args=(self.christmas_event.id, self.last_christmas_revision.id) + ) + last_christmas_revert_url = reverse( + 'wagtailadmin_pages:revisions_revert', + args=(self.christmas_event.id, self.last_christmas_revision.id) + ) + self.assertContains(response, last_christmas_preview_url) + self.assertContains(response, last_christmas_revert_url) + + self.assertContains(response, formats.localize(local_datetime(2014, 12, 25))) + this_christmas_preview_url = reverse( + 'wagtailadmin_pages:revisions_view', + args=(self.christmas_event.id, self.this_christmas_revision.id) + ) + this_christmas_revert_url = reverse( + 'wagtailadmin_pages:revisions_revert', + args=(self.christmas_event.id, self.this_christmas_revision.id) + ) + self.assertContains(response, this_christmas_preview_url) + self.assertContains(response, this_christmas_revert_url) + + def request_preview_revision(self): + last_christmas_preview_url = reverse( + 'wagtailadmin_pages:revisions_view', + args=(self.christmas_event.id, self.last_christmas_revision.id) + ) + return self.client.get(last_christmas_preview_url) + + def test_preview_revision(self): + response = self.request_preview_revision() + + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Last Christmas I gave you my heart") + + def test_preview_revision_with_no_page_permissions_redirects_to_admin(self): + admin_only_user = get_user_model().objects.create_user( + username='admin_only', + email='admin_only@email.com', + password='password' + ) + admin_only_user.user_permissions.add( + Permission.objects.get_by_natural_key( + codename='access_admin', + app_label='wagtailadmin', + model='admin' + ) + ) + + self.login(user=admin_only_user) + response = self.request_preview_revision() + + self.assertEqual(response.status_code, 302) + self.assertEqual(response['Location'], reverse('wagtailadmin_home')) + + def test_preview_revision_forbidden_without_permission(self): + # Alter the editors group so it has no permissions for Christmas page. + st_patricks = Page.objects.get(slug='saint-patrick') + editors_group = Group.objects.get(name='Site-wide editors') + editors_group.page_permissions.update(page_id=st_patricks.id) + + editor = get_user_model().objects.get(username='siteeditor') + + self.login(editor) + response = self.request_preview_revision() + + self.assertEqual(response.status_code, 403) + + def test_revert_revision(self): + last_christmas_preview_url = reverse( + 'wagtailadmin_pages:revisions_revert', + args=(self.christmas_event.id, self.last_christmas_revision.id) + ) + response = self.client.get(last_christmas_preview_url) + self.assertEqual(response.status_code, 200) + + self.assertContains(response, "Editing Event page") + self.assertContains(response, "You are viewing a previous revision of this page") + + # Form should show the content of the revision, not the current draft + self.assertContains(response, "Last Christmas I gave you my heart") + + # Form should include a hidden 'revision' field + revision_field = ( + """""" % + self.last_christmas_revision.id + ) + self.assertContains(response, revision_field) + + # Buttons should be relabelled + self.assertContains(response, "Replace current draft") + self.assertContains(response, "Publish this revision") + + def test_scheduled_revision(self): + self.last_christmas_revision.publish() + self.this_christmas_revision.approved_go_live_at = local_datetime(2014, 12, 26) + self.this_christmas_revision.save() + this_christmas_unschedule_url = reverse( + 'wagtailadmin_pages:revisions_unschedule', + args=(self.christmas_event.id, self.this_christmas_revision.id) + ) + response = self.client.get( + reverse('wagtailadmin_pages:revisions_index', args=(self.christmas_event.id, )) + ) + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'Scheduled for') + self.assertContains(response, formats.localize(parse_date('2014-12-26'))) + self.assertContains(response, this_christmas_unschedule_url) + + +class TestCompareRevisions(TestCase, WagtailTestUtils): + # Actual tests for the comparison classes can be found in test_compare.py + + fixtures = ['test.json'] + + def setUp(self): + self.christmas_event = EventPage.objects.get(url_path='/home/events/christmas/') + self.christmas_event.title = "Last Christmas" + self.christmas_event.date_from = '2013-12-25' + self.christmas_event.body = ( + "

    Last Christmas I gave you my heart, " + "but the very next day you gave it away

    " + ) + self.last_christmas_revision = self.christmas_event.save_revision() + self.last_christmas_revision.created_at = local_datetime(2013, 12, 25) + self.last_christmas_revision.save() + + self.christmas_event.title = "This Christmas" + self.christmas_event.date_from = '2014-12-25' + self.christmas_event.body = ( + "

    This year, to save me from tears, " + "I'll give it to someone special

    " + ) + self.this_christmas_revision = self.christmas_event.save_revision() + self.this_christmas_revision.created_at = local_datetime(2014, 12, 25) + self.this_christmas_revision.save() + + self.login() + + def test_compare_revisions(self): + compare_url = reverse( + 'wagtailadmin_pages:revisions_compare', + args=(self.christmas_event.id, self.last_christmas_revision.id, self.this_christmas_revision.id) + ) + response = self.client.get(compare_url) + self.assertEqual(response.status_code, 200) + + self.assertContains( + response, + 'Last Christmas I gave you my heart, but the very next day you gave it awayThis year, to save me from tears, I'll give it to someone special', + html=True + ) + + def test_compare_revisions_earliest(self): + compare_url = reverse( + 'wagtailadmin_pages:revisions_compare', + args=(self.christmas_event.id, 'earliest', self.this_christmas_revision.id) + ) + response = self.client.get(compare_url) + self.assertEqual(response.status_code, 200) + + self.assertContains( + response, + 'Last Christmas I gave you my heart, but the very next day you gave it awayThis year, to save me from tears, I'll give it to someone special', + html=True + ) + + def test_compare_revisions_latest(self): + compare_url = reverse( + 'wagtailadmin_pages:revisions_compare', + args=(self.christmas_event.id, self.last_christmas_revision.id, 'latest') + ) + response = self.client.get(compare_url) + self.assertEqual(response.status_code, 200) + + self.assertContains( + response, + 'Last Christmas I gave you my heart, but the very next day you gave it awayThis year, to save me from tears, I'll give it to someone special', + html=True + ) + + def test_compare_revisions_live(self): + # Mess with the live version, bypassing revisions + self.christmas_event.body = ( + "

    This year, to save me from tears, " + "I'll just feed it to the dog

    " + ) + self.christmas_event.save(update_fields=['body']) + + compare_url = reverse( + 'wagtailadmin_pages:revisions_compare', + args=(self.christmas_event.id, self.last_christmas_revision.id, 'live') + ) + response = self.client.get(compare_url) + self.assertEqual(response.status_code, 200) + + self.assertContains( + response, + 'Last Christmas I gave you my heart, but the very next day you gave it awayThis year, to save me from tears, I'll just feed it to the dog', + html=True + ) + + +class TestCompareRevisionsWithNonModelField(TestCase, WagtailTestUtils): + """ + Tests if form fields defined in the base_form_class will not be included. + in revisions view as they are not actually on the model. + Flagged in issue #3737 + Note: Actual tests for comparison classes can be found in test_compare.py + """ + + fixtures = ['test.json'] + # FormClassAdditionalFieldPage + + def setUp(self): + # Find root page + self.root_page = Page.objects.get(id=2) + + # Add child page of class with base_form_class override + # non model field is 'code' + self.test_page = FormClassAdditionalFieldPage( + title='A Statement', + slug='a-statement', + location='Early Morning Cafe, Mainland, NZ', + body="

    hello

    " + ) + self.root_page.add_child(instance=self.test_page) + + # add new revision + self.test_page.title = 'Statement' + self.test_page.location = 'Victory Monument, Bangkok' + self.test_page.body = ( + "

    I would like very much to go into the forrest.

    " + ) + self.test_page_revision = self.test_page.save_revision() + self.test_page_revision.created_at = local_datetime(2017, 10, 15) + self.test_page_revision.save() + + # add another new revision + self.test_page.title = 'True Statement' + self.test_page.location = 'Victory Monument, Bangkok' + self.test_page.body = ( + "

    I would like very much to go into the forest.

    " + ) + self.test_page_revision_new = self.test_page.save_revision() + self.test_page_revision_new.created_at = local_datetime(2017, 10, 16) + self.test_page_revision_new.save() + + self.login() + + def test_base_form_class_used(self): + """First ensure that the non-model field is appearing in edit.""" + edit_url = reverse('wagtailadmin_pages:add', args=('tests', 'formclassadditionalfieldpage', self.test_page.id)) + response = self.client.get(edit_url) + self.assertContains(response, '', html=True) + + def test_compare_revisions(self): + """Confirm that the non-model field is not shown in revision.""" + compare_url = reverse( + 'wagtailadmin_pages:revisions_compare', + args=(self.test_page.id, self.test_page_revision.id, self.test_page_revision_new.id) + ) + response = self.client.get(compare_url) + self.assertContains(response, 'forrest.forest.') + # should not contain the field defined in the formclass used + self.assertNotContains(response, '

    Code:

    ') + + +class TestRevisionsUnschedule(TestCase, WagtailTestUtils): + fixtures = ['test.json'] + + def setUp(self): + self.christmas_event = EventPage.objects.get(url_path='/home/events/christmas/') + self.christmas_event.title = "Last Christmas" + self.christmas_event.date_from = '2013-12-25' + self.christmas_event.body = ( + "

    Last Christmas I gave you my heart, " + "but the very next day you gave it away

    " + ) + self.last_christmas_revision = self.christmas_event.save_revision() + self.last_christmas_revision.created_at = local_datetime(2013, 12, 25) + self.last_christmas_revision.save() + self.last_christmas_revision.publish() + + self.christmas_event.title = "This Christmas" + self.christmas_event.date_from = '2014-12-25' + self.christmas_event.body = ( + "

    This year, to save me from tears, " + "I'll give it to someone special

    " + ) + self.this_christmas_revision = self.christmas_event.save_revision() + self.this_christmas_revision.created_at = local_datetime(2014, 12, 24) + self.this_christmas_revision.save() + + self.this_christmas_revision.approved_go_live_at = local_datetime(2014, 12, 25) + self.this_christmas_revision.save() + + self.user = self.login() + + def test_unschedule_view(self): + """ + This tests that the unschedule view responds with a confirm page + """ + response = self.client.get(reverse('wagtailadmin_pages:revisions_unschedule', args=(self.christmas_event.id, self.this_christmas_revision.id))) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/revisions/confirm_unschedule.html') + + def test_unschedule_view_invalid_page_id(self): + """ + This tests that the unschedule view returns an error if the page id is invalid + """ + # Get unschedule page + response = self.client.get(reverse('wagtailadmin_pages:revisions_unschedule', args=(12345, 67894))) + + # Check that the user received a 404 response + self.assertEqual(response.status_code, 404) + + def test_unschedule_view_invalid_revision_id(self): + """ + This tests that the unschedule view returns an error if the page id is invalid + """ + # Get unschedule page + response = self.client.get(reverse('wagtailadmin_pages:revisions_unschedule', args=(self.christmas_event.id, 67894))) + + # Check that the user received a 404 response + self.assertEqual(response.status_code, 404) + + def test_unschedule_view_bad_permissions(self): + """ + This tests that the unschedule view doesn't allow users without publish permissions + """ + # Remove privileges from user + self.user.is_superuser = False + self.user.user_permissions.add( + Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') + ) + self.user.save() + + # Get unschedule page + response = self.client.get(reverse('wagtailadmin_pages:revisions_unschedule', args=(self.christmas_event.id, self.this_christmas_revision.id))) + + # Check that the user received a 403 response + self.assertEqual(response.status_code, 403) + + def test_unschedule_view_post(self): + """ + This posts to the unschedule view and checks that the revision was unscheduled + """ + + # Post to the unschedule page + response = self.client.post(reverse('wagtailadmin_pages:revisions_unschedule', args=(self.christmas_event.id, self.this_christmas_revision.id))) + + # Should be redirected to revisions index page + self.assertRedirects(response, reverse('wagtailadmin_pages:revisions_index', args=(self.christmas_event.id, ))) + + # Check that the page has no approved_schedule + self.assertFalse(EventPage.objects.get(id=self.christmas_event.id).approved_schedule) + + # Check that the approved_go_live_at has been cleared from the revision + self.assertIsNone(self.christmas_event.revisions.get(id=self.this_christmas_revision.id).approved_go_live_at) + + +class TestRevisionsUnscheduleForUnpublishedPages(TestCase, WagtailTestUtils): + fixtures = ['test.json'] + + def setUp(self): + self.unpublished_event = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/') + self.unpublished_event.title = "Unpublished Page" + self.unpublished_event.date_from = '2014-12-25' + self.unpublished_event.body = ( + "

    Some Content

    " + ) + self.unpublished_revision = self.unpublished_event.save_revision() + self.unpublished_revision.created_at = local_datetime(2014, 12, 25) + self.unpublished_revision.save() + + self.user = self.login() + + def test_unschedule_view(self): + """ + This tests that the unschedule view responds with a confirm page + """ + response = self.client.get(reverse('wagtailadmin_pages:revisions_unschedule', args=(self.unpublished_event.id, self.unpublished_revision.id))) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/revisions/confirm_unschedule.html') + + def test_unschedule_view_post(self): + """ + This posts to the unschedule view and checks that the revision was unscheduled + """ + + # Post to the unschedule page + response = self.client.post(reverse('wagtailadmin_pages:revisions_unschedule', args=(self.unpublished_event.id, self.unpublished_revision.id))) + + # Should be redirected to revisions index page + self.assertRedirects(response, reverse('wagtailadmin_pages:revisions_index', args=(self.unpublished_event.id, ))) + + # Check that the page has no approved_schedule + self.assertFalse(EventPage.objects.get(id=self.unpublished_event.id).approved_schedule) + + # Check that the approved_go_live_at has been cleared from the revision + self.assertIsNone(self.unpublished_event.revisions.get(id=self.unpublished_revision.id).approved_go_live_at) diff --git a/wagtail/admin/tests/pages/test_unpublish_page.py b/wagtail/admin/tests/pages/test_unpublish_page.py new file mode 100644 index 000000000..820b357f8 --- /dev/null +++ b/wagtail/admin/tests/pages/test_unpublish_page.py @@ -0,0 +1,181 @@ +from unittest import mock + +from django.contrib.auth.models import Permission +from django.test import TestCase +from django.urls import reverse + +from wagtail.core.models import Page +from wagtail.core.signals import page_unpublished +from wagtail.tests.testapp.models import SimplePage +from wagtail.tests.utils import WagtailTestUtils + + +class TestPageUnpublish(TestCase, WagtailTestUtils): + def setUp(self): + self.user = self.login() + + # Create a page to unpublish + self.root_page = Page.objects.get(id=2) + self.page = SimplePage( + title="Hello world!", + slug='hello-world', + content="hello", + live=True, + ) + self.root_page.add_child(instance=self.page) + + def test_unpublish_view(self): + """ + This tests that the unpublish view responds with an unpublish confirm page + """ + # Get unpublish page + response = self.client.get(reverse('wagtailadmin_pages:unpublish', args=(self.page.id, ))) + + # Check that the user received an unpublish confirm page + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/confirm_unpublish.html') + + def test_unpublish_view_invalid_page_id(self): + """ + This tests that the unpublish view returns an error if the page id is invalid + """ + # Get unpublish page + response = self.client.get(reverse('wagtailadmin_pages:unpublish', args=(12345, ))) + + # Check that the user received a 404 response + self.assertEqual(response.status_code, 404) + + def test_unpublish_view_bad_permissions(self): + """ + This tests that the unpublish view doesn't allow users without unpublish permissions + """ + # Remove privileges from user + self.user.is_superuser = False + self.user.user_permissions.add( + Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') + ) + self.user.save() + + # Get unpublish page + response = self.client.get(reverse('wagtailadmin_pages:unpublish', args=(self.page.id, ))) + + # Check that the user received a 403 response + self.assertEqual(response.status_code, 403) + + def test_unpublish_view_post(self): + """ + This posts to the unpublish view and checks that the page was unpublished + """ + # Connect a mock signal handler to page_unpublished signal + mock_handler = mock.MagicMock() + page_unpublished.connect(mock_handler) + + # Post to the unpublish page + response = self.client.post(reverse('wagtailadmin_pages:unpublish', args=(self.page.id, ))) + + # Should be redirected to explorer page + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + + # Check that the page was unpublished + self.assertFalse(SimplePage.objects.get(id=self.page.id).live) + + # Check that the page_unpublished signal was fired + self.assertEqual(mock_handler.call_count, 1) + mock_call = mock_handler.mock_calls[0][2] + + self.assertEqual(mock_call['sender'], self.page.specific_class) + self.assertEqual(mock_call['instance'], self.page) + self.assertIsInstance(mock_call['instance'], self.page.specific_class) + + def test_unpublish_descendants_view(self): + """ + This tests that the unpublish view responds with an unpublish confirm page that does not contain the form field 'include_descendants' + """ + # Get unpublish page + response = self.client.get(reverse('wagtailadmin_pages:unpublish', args=(self.page.id, ))) + + # Check that the user received an unpublish confirm page + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/confirm_unpublish.html') + # Check the form does not contain the checkbox field include_descendants + self.assertNotContains(response, '') + + +class TestPageUnpublishIncludingDescendants(TestCase, WagtailTestUtils): + def setUp(self): + self.user = self.login() + # Find root page + self.root_page = Page.objects.get(id=2) + + # Create a page to unpublish + self.test_page = self.root_page.add_child(instance=SimplePage( + title="Hello world!", + slug='hello-world', + content="hello", + live=True, + has_unpublished_changes=False, + )) + + # Create a couple of child pages + self.test_child_page = self.test_page.add_child(instance=SimplePage( + title="Child page", + slug='child-page', + content="hello", + live=True, + has_unpublished_changes=True, + )) + + self.test_another_child_page = self.test_page.add_child(instance=SimplePage( + title="Another Child page", + slug='another-child-page', + content="hello", + live=True, + has_unpublished_changes=True, + )) + + def test_unpublish_descendants_view(self): + """ + This tests that the unpublish view responds with an unpublish confirm page that contains the form field 'include_descendants' + """ + # Get unpublish page + response = self.client.get(reverse('wagtailadmin_pages:unpublish', args=(self.test_page.id, ))) + + # Check that the user received an unpublish confirm page + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailadmin/pages/confirm_unpublish.html') + # Check the form contains the checkbox field include_descendants + self.assertContains(response, '') + + def test_unpublish_include_children_view_post(self): + """ + This posts to the unpublish view and checks that the page and its descendants were unpublished + """ + # Post to the unpublish page + response = self.client.post(reverse('wagtailadmin_pages:unpublish', args=(self.test_page.id, )), {'include_descendants': 'on'}) + + # Should be redirected to explorer page + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + + # Check that the page was unpublished + self.assertFalse(SimplePage.objects.get(id=self.test_page.id).live) + + # Check that the descendant pages were unpiblished as well + self.assertFalse(SimplePage.objects.get(id=self.test_child_page.id).live) + self.assertFalse(SimplePage.objects.get(id=self.test_another_child_page.id).live) + + def test_unpublish_not_include_children_view_post(self): + """ + This posts to the unpublish view and checks that the page was unpublished but its descendants were not + """ + # Post to the unpublish page + response = self.client.post(reverse('wagtailadmin_pages:unpublish', args=(self.test_page.id, )), {}) + + # Should be redirected to explorer page + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + + # Check that the page was unpublished + self.assertFalse(SimplePage.objects.get(id=self.test_page.id).live) + + # Check that the descendant pages were not unpublished + self.assertTrue(SimplePage.objects.get(id=self.test_child_page.id).live) + self.assertTrue(SimplePage.objects.get(id=self.test_another_child_page.id).live) diff --git a/wagtail/admin/tests/pages/test_view_draft.py b/wagtail/admin/tests/pages/test_view_draft.py new file mode 100644 index 000000000..8f1bd959b --- /dev/null +++ b/wagtail/admin/tests/pages/test_view_draft.py @@ -0,0 +1,78 @@ +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group, Permission +from django.test import TestCase +from django.urls import reverse + +from wagtail.core.models import Page +from wagtail.tests.testapp.models import SimplePage +from wagtail.tests.utils import WagtailTestUtils + + +class TestDraftAccess(TestCase, WagtailTestUtils): + """Tests for the draft view access restrictions.""" + + def setUp(self): + # Find root page + self.root_page = Page.objects.get(id=2) + + # Add child page + self.child_page = SimplePage( + title="Hello world!", + slug="hello-world", + content="hello", + ) + self.root_page.add_child(instance=self.child_page) + + # create user with admin access (but not draft_view access) + user = get_user_model().objects.create_user(username='bob', email='bob@email.com', password='password') + user.user_permissions.add( + Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') + ) + + def test_draft_access_admin(self): + """Test that admin can view draft.""" + # Login as admin + self.user = self.login() + + # Try getting page draft + response = self.client.get(reverse('wagtailadmin_pages:view_draft', args=(self.child_page.id, ))) + + # User can view + self.assertEqual(response.status_code, 200) + + def test_draft_access_unauthorized(self): + """Test that user without edit/publish permission can't view draft.""" + self.assertTrue(self.client.login(username='bob', password='password')) + + # Try getting page draft + response = self.client.get(reverse('wagtailadmin_pages:view_draft', args=(self.child_page.id, ))) + + # User gets Unauthorized response + self.assertEqual(response.status_code, 403) + + def test_draft_access_authorized(self): + """Test that user with edit permission can view draft.""" + # give user the permission to edit page + user = get_user_model().objects.get(username='bob') + user.groups.add(Group.objects.get(name='Moderators')) + user.save() + + self.assertTrue(self.client.login(username='bob', password='password')) + + # Get add subpage page + response = self.client.get(reverse('wagtailadmin_pages:view_draft', args=(self.child_page.id, ))) + + # User can view + self.assertEqual(response.status_code, 200) + + def test_middleware_response_is_returned(self): + """ + If middleware returns a response while serving a page preview, that response should be + returned back to the user + """ + self.login() + response = self.client.get( + reverse('wagtailadmin_pages:view_draft', args=(self.child_page.id, )), + HTTP_USER_AGENT='EvilHacker' + ) + self.assertEqual(response.status_code, 403) diff --git a/wagtail/admin/tests/pages/timestamps.py b/wagtail/admin/tests/pages/timestamps.py new file mode 100644 index 000000000..472059460 --- /dev/null +++ b/wagtail/admin/tests/pages/timestamps.py @@ -0,0 +1,19 @@ +import datetime + +from django.utils import timezone + + +def submittable_timestamp(timestamp): + """ + Helper function to translate a possibly-timezone-aware datetime into the format used in the + go_live_at / expire_at form fields - "YYYY-MM-DD hh:mm", with no timezone indicator. + This will be interpreted as being in the server's timezone (settings.TIME_ZONE), so we + need to pass it through timezone.localtime to ensure that the client and server are in + agreement about what the timestamp means. + """ + return timezone.localtime(timestamp).strftime("%Y-%m-%d %H:%M") + + +def local_datetime(*args): + dt = datetime.datetime(*args) + return timezone.make_aware(dt) diff --git a/wagtail/admin/tests/test_pages_views.py b/wagtail/admin/tests/test_pages_views.py deleted file mode 100644 index 4945e8610..000000000 --- a/wagtail/admin/tests/test_pages_views.py +++ /dev/null @@ -1,5452 +0,0 @@ -import datetime -import logging -import os -from itertools import chain -from unittest import mock - -from django import VERSION as DJANGO_VERSION -from django.conf import settings -from django.contrib.auth import get_user_model -from django.contrib.auth.models import Group, Permission -from django.contrib.contenttypes.models import ContentType -from django.contrib.messages import constants as message_constants -from django.core import mail, paginator -from django.core.files.base import ContentFile -from django.core.mail import EmailMultiAlternatives -from django.db.models.signals import post_delete, pre_delete -from django.http import HttpRequest, HttpResponse -from django.test import TestCase, modify_settings, override_settings -from django.urls import reverse -from django.utils import formats, timezone -from django.utils.dateparse import parse_date -from freezegun import freeze_time - -from wagtail.admin.views.home import RecentEditsPanel -from wagtail.admin.views.pages import PreviewOnEdit -from wagtail.core.models import GroupPagePermission, Page, PageRevision, Site -from wagtail.core.signals import page_published, page_unpublished -from wagtail.search.index import SearchField -from wagtail.tests.testapp.models import ( - EVENT_AUDIENCE_CHOICES, Advert, AdvertPlacement, BusinessChild, BusinessIndex, BusinessSubIndex, - DefaultStreamPage, EventCategory, EventPage, EventPageCarouselItem, FilePage, - FormClassAdditionalFieldPage, ManyToManyBlogPage, SimplePage, SingleEventPage, SingletonPage, - SingletonPageViaMaxCount, StandardChild, StandardIndex, TaggedPage) -from wagtail.tests.utils import WagtailTestUtils -from wagtail.users.models import UserProfile - - -def submittable_timestamp(timestamp): - """ - Helper function to translate a possibly-timezone-aware datetime into the format used in the - go_live_at / expire_at form fields - "YYYY-MM-DD hh:mm", with no timezone indicator. - This will be interpreted as being in the server's timezone (settings.TIME_ZONE), so we - need to pass it through timezone.localtime to ensure that the client and server are in - agreement about what the timestamp means. - """ - return timezone.localtime(timestamp).strftime("%Y-%m-%d %H:%M") - - -def local_datetime(*args): - dt = datetime.datetime(*args) - return timezone.make_aware(dt) - - -class TestPageExplorer(TestCase, WagtailTestUtils): - def setUp(self): - # Find root page - self.root_page = Page.objects.get(id=2) - - # Add child page - self.child_page = SimplePage( - title="Hello world!", - slug="hello-world", - content="hello", - ) - self.root_page.add_child(instance=self.child_page) - - # more child pages to test ordering - self.old_page = StandardIndex( - title="Old page", - slug="old-page", - latest_revision_created_at=local_datetime(2010, 1, 1) - ) - self.root_page.add_child(instance=self.old_page) - - self.new_page = SimplePage( - title="New page", - slug="new-page", - content="hello", - latest_revision_created_at=local_datetime(2016, 1, 1) - ) - self.root_page.add_child(instance=self.new_page) - - # Login - self.user = self.login() - - def test_explore(self): - response = self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') - self.assertEqual(self.root_page, response.context['parent_page']) - - # child pages should be most recent first - # (with null latest_revision_created_at at the end) - page_ids = [page.id for page in response.context['pages']] - self.assertEqual(page_ids, [self.new_page.id, self.old_page.id, self.child_page.id]) - - def test_explore_root(self): - response = self.client.get(reverse('wagtailadmin_explore_root')) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') - self.assertEqual(Page.objects.get(id=1), response.context['parent_page']) - self.assertTrue(response.context['pages'].paginator.object_list.filter(id=self.root_page.id).exists()) - - def test_explore_root_shows_icon(self): - response = self.client.get(reverse('wagtailadmin_explore_root')) - self.assertEqual(response.status_code, 200) - - # Administrator (or user with add_site permission) should see the - # sites link with the icon-site icon - self.assertContains( - response, - ("""""") - ) - - def test_ordering(self): - response = self.client.get( - reverse('wagtailadmin_explore', args=(self.root_page.id, )), - {'ordering': 'title'} - ) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') - self.assertEqual(response.context['ordering'], 'title') - - # child pages should be ordered by title - page_ids = [page.id for page in response.context['pages']] - self.assertEqual(page_ids, [self.child_page.id, self.new_page.id, self.old_page.id]) - - def test_reverse_ordering(self): - response = self.client.get( - reverse('wagtailadmin_explore', args=(self.root_page.id, )), - {'ordering': '-title'} - ) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') - self.assertEqual(response.context['ordering'], '-title') - - # child pages should be ordered by title - page_ids = [page.id for page in response.context['pages']] - self.assertEqual(page_ids, [self.old_page.id, self.new_page.id, self.child_page.id]) - - def test_ordering_by_last_revision_forward(self): - response = self.client.get( - reverse('wagtailadmin_explore', args=(self.root_page.id, )), - {'ordering': 'latest_revision_created_at'} - ) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') - self.assertEqual(response.context['ordering'], 'latest_revision_created_at') - - # child pages should be oldest revision first - # (with null latest_revision_created_at at the start) - page_ids = [page.id for page in response.context['pages']] - self.assertEqual(page_ids, [self.child_page.id, self.old_page.id, self.new_page.id]) - - def test_invalid_ordering(self): - response = self.client.get( - reverse('wagtailadmin_explore', args=(self.root_page.id, )), - {'ordering': 'invalid_order'} - ) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') - self.assertEqual(response.context['ordering'], '-latest_revision_created_at') - - def test_reordering(self): - response = self.client.get( - reverse('wagtailadmin_explore', args=(self.root_page.id, )), - {'ordering': 'ord'} - ) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') - self.assertEqual(response.context['ordering'], 'ord') - - # child pages should be ordered by native tree order (i.e. by creation time) - page_ids = [page.id for page in response.context['pages']] - self.assertEqual(page_ids, [self.child_page.id, self.old_page.id, self.new_page.id]) - - # Pages must not be paginated - self.assertNotIsInstance(response.context['pages'], paginator.Page) - - def test_construct_explorer_page_queryset_hook(self): - # testapp implements a construct_explorer_page_queryset hook - # that only returns pages with a slug starting with 'hello' - # when the 'polite_pages_only' URL parameter is set - response = self.client.get( - reverse('wagtailadmin_explore', args=(self.root_page.id, )), - {'polite_pages_only': 'yes_please'} - ) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') - page_ids = [page.id for page in response.context['pages']] - self.assertEqual(page_ids, [self.child_page.id]) - - def test_construct_construct_page_listing_buttons_hook(self): - # testapp implements a construct_page_listing_buttons hook - # that add's an dummy button with the label 'Dummy Button' which points - # to '/dummy-button' - response = self.client.get( - reverse('wagtailadmin_explore', args=(self.root_page.id, )), - ) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') - self.assertContains(response, 'Dummy Button') - self.assertContains(response, '/dummy-button') - - def make_pages(self): - for i in range(150): - self.root_page.add_child(instance=SimplePage( - title="Page " + str(i), - slug="page-" + str(i), - content="hello", - )) - - def test_pagination(self): - self.make_pages() - - response = self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, )), {'p': 2}) - - # Check response - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') - - # Check that we got the correct page - self.assertEqual(response.context['pages'].number, 2) - - def test_pagination_invalid(self): - self.make_pages() - - response = self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, )), {'p': 'Hello World!'}) - - # Check response - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') - - # Check that we got page one - self.assertEqual(response.context['pages'].number, 1) - - def test_pagination_out_of_range(self): - self.make_pages() - - response = self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, )), {'p': 99999}) - - # Check response - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') - - # Check that we got the last page - self.assertEqual(response.context['pages'].number, response.context['pages'].paginator.num_pages) - - def test_listing_uses_specific_models(self): - # SingleEventPage has custom URL routing; the 'live' link in the listing - # should show the custom URL, which requires us to use the specific version - # of the class - self.new_event = SingleEventPage( - title="New event", - location='the moon', audience='public', - cost='free', date_from='2001-01-01', - latest_revision_created_at=local_datetime(2016, 1, 1) - ) - self.root_page.add_child(instance=self.new_event) - - response = self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - self.assertEqual(response.status_code, 200) - - self.assertContains(response, '/new-event/pointless-suffix/') - - def make_event_pages(self, count): - for i in range(count): - self.root_page.add_child(instance=SingleEventPage( - title="New event " + str(i), - location='the moon', audience='public', - cost='free', date_from='2001-01-01', - latest_revision_created_at=local_datetime(2016, 1, 1) - )) - - def test_exploring_uses_specific_page_with_custom_display_title(self): - # SingleEventPage has a custom get_admin_display_title method; explorer should - # show the custom title rather than the basic database one - self.make_event_pages(count=1) - response = self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - self.assertContains(response, 'New event 0 (single event)') - - new_event = SingleEventPage.objects.latest('pk') - response = self.client.get(reverse('wagtailadmin_explore', args=(new_event.id, ))) - self.assertContains(response, 'New event 0 (single event)') - - def test_ordering_less_than_100_pages_uses_specific_page_with_custom_display_title(self): - # Reorder view should also use specific pages - # (provided there are <100 pages in the listing, as this may be a significant - # performance hit on larger listings) - # There are 3 pages created in setUp, so 96 more add to a total of 99. - self.make_event_pages(count=96) - response = self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, )) + '?ordering=ord') - self.assertContains(response, 'New event 0 (single event)') - - def test_ordering_100_or_more_pages_uses_generic_page_without_custom_display_title(self): - # There are 3 pages created in setUp, so 97 more add to a total of 100. - self.make_event_pages(count=97) - response = self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, )) + '?ordering=ord') - self.assertNotContains(response, 'New event 0 (single event)') - - def test_parent_page_is_specific(self): - response = self.client.get(reverse('wagtailadmin_explore', args=(self.child_page.id, ))) - self.assertEqual(response.status_code, 200) - - self.assertIsInstance(response.context['parent_page'], SimplePage) - - def test_explorer_no_perms(self): - self.user.is_superuser = False - self.user.user_permissions.add( - Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') - ) - self.user.save() - - admin = reverse('wagtailadmin_home') - self.assertRedirects( - self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, ))), - admin) - self.assertRedirects( - self.client.get(reverse('wagtailadmin_explore_root')), admin) - - def test_explore_with_missing_page_model(self): - # Create a ContentType that doesn't correspond to a real model - missing_page_content_type = ContentType.objects.create(app_label='tests', model='missingpage') - # Turn /home/old-page/ into this content type - Page.objects.filter(id=self.old_page.id).update(content_type=missing_page_content_type) - - # try to browse the the listing that contains the missing model - response = self.client.get(reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') - - # try to browse into the page itself - response = self.client.get(reverse('wagtailadmin_explore', args=(self.old_page.id, ))) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailadmin/pages/index.html') - - -class TestBreadcrumb(TestCase, WagtailTestUtils): - fixtures = ['test.json'] - - def test_breadcrumb_uses_specific_titles(self): - self.user = self.login() - - # get the explorer view for a subpage of a SimplePage - page = Page.objects.get(url_path='/home/secret-plans/steal-underpants/') - response = self.client.get(reverse('wagtailadmin_explore', args=(page.id, ))) - - # The breadcrumb should pick up SimplePage's overridden get_admin_display_title method - expected_url = reverse('wagtailadmin_explore', args=(Page.objects.get(url_path='/home/secret-plans/').id, )) - self.assertContains(response, """
  • Secret plans (simple page)
  • """ % expected_url) - - -class TestPageExplorerSignposting(TestCase, WagtailTestUtils): - fixtures = ['test.json'] - - def setUp(self): - # Find root page - self.root_page = Page.objects.get(id=1) - - # Find page with an associated site - self.site_page = Page.objects.get(id=2) - - # Add another top-level page (which will have no corresponding site record) - self.no_site_page = SimplePage( - title="Hello world!", - slug="hello-world", - content="hello", - ) - self.root_page.add_child(instance=self.no_site_page) - - # Tests for users that have both add-site permission, and explore permission at the given view; - # warning messages should include advice re configuring sites - - def test_admin_at_root(self): - self.assertTrue(self.client.login(username='superuser', password='password')) - response = self.client.get(reverse('wagtailadmin_explore_root')) - self.assertEqual(response.status_code, 200) - # Administrator (or user with add_site permission) should get the full message - # about configuring sites - self.assertContains( - response, - ( - "The root level is where you can add new sites to your Wagtail installation. " - "Pages created here will not be accessible at any URL until they are associated with a site." - ) - ) - self.assertContains(response, """Configure a site now.""") - - def test_admin_at_non_site_page(self): - self.assertTrue(self.client.login(username='superuser', password='password')) - response = self.client.get(reverse('wagtailadmin_explore', args=(self.no_site_page.id, ))) - self.assertEqual(response.status_code, 200) - # Administrator (or user with add_site permission) should get a warning about - # unroutable pages, and be directed to the site config area - self.assertContains( - response, - ( - "There is no site set up for this location. " - "Pages created here will not be accessible at any URL until a site is associated with this location." - ) - ) - self.assertContains(response, """Configure a site now.""") - - def test_admin_at_site_page(self): - self.assertTrue(self.client.login(username='superuser', password='password')) - response = self.client.get(reverse('wagtailadmin_explore', args=(self.site_page.id, ))) - self.assertEqual(response.status_code, 200) - # There should be no warning message here - self.assertNotContains(response, "Pages created here will not be accessible") - - # Tests for standard users that have explore permission at the given view; - # warning messages should omit advice re configuring sites - - def test_nonadmin_at_root(self): - # Assign siteeditor permission over no_site_page, so that the deepest-common-ancestor - # logic allows them to explore root - GroupPagePermission.objects.create( - group=Group.objects.get(name="Site-wide editors"), - page=self.no_site_page, permission_type='add' - ) - self.assertTrue(self.client.login(username='siteeditor', password='password')) - response = self.client.get(reverse('wagtailadmin_explore_root')) - - self.assertEqual(response.status_code, 200) - # Non-admin should get a simple "create pages as children of the homepage" prompt - self.assertContains( - response, - "Pages created here will not be accessible at any URL. " - "To add pages to an existing site, create them as children of the homepage." - ) - - def test_nonadmin_at_non_site_page(self): - # Assign siteeditor permission over no_site_page - GroupPagePermission.objects.create( - group=Group.objects.get(name="Site-wide editors"), - page=self.no_site_page, permission_type='add' - ) - self.assertTrue(self.client.login(username='siteeditor', password='password')) - response = self.client.get(reverse('wagtailadmin_explore', args=(self.no_site_page.id, ))) - - self.assertEqual(response.status_code, 200) - # Non-admin should get a warning about unroutable pages - self.assertContains( - response, - ( - "There is no site record for this location. " - "Pages created here will not be accessible at any URL." - ) - ) - - def test_nonadmin_at_site_page(self): - self.assertTrue(self.client.login(username='siteeditor', password='password')) - response = self.client.get(reverse('wagtailadmin_explore', args=(self.site_page.id, ))) - self.assertEqual(response.status_code, 200) - # There should be no warning message here - self.assertNotContains(response, "Pages created here will not be accessible") - - # Tests for users that have explore permission *somewhere*, but not at the view being tested; - # in all cases, they should be redirected to their explorable root - - def test_bad_permissions_at_root(self): - # 'siteeditor' does not have permission to explore the root - self.assertTrue(self.client.login(username='siteeditor', password='password')) - response = self.client.get(reverse('wagtailadmin_explore_root')) - - # Users without permission to explore here should be redirected to their explorable root. - self.assertEqual( - (response.status_code, response['Location']), - (302, reverse('wagtailadmin_explore', args=(self.site_page.pk, ))) - ) - - def test_bad_permissions_at_non_site_page(self): - # 'siteeditor' does not have permission to explore no_site_page - self.assertTrue(self.client.login(username='siteeditor', password='password')) - response = self.client.get(reverse('wagtailadmin_explore', args=(self.no_site_page.id, ))) - - # Users without permission to explore here should be redirected to their explorable root. - self.assertEqual( - (response.status_code, response['Location']), - (302, reverse('wagtailadmin_explore', args=(self.site_page.pk, ))) - ) - - def test_bad_permissions_at_site_page(self): - # Adjust siteeditor's permission so that they have permission over no_site_page - # instead of site_page - Group.objects.get(name="Site-wide editors").page_permissions.update(page_id=self.no_site_page.id) - self.assertTrue(self.client.login(username='siteeditor', password='password')) - response = self.client.get(reverse('wagtailadmin_explore', args=(self.site_page.id, ))) - # Users without permission to explore here should be redirected to their explorable root. - self.assertEqual( - (response.status_code, response['Location']), - (302, reverse('wagtailadmin_explore', args=(self.no_site_page.pk, ))) - ) - - -class TestExplorablePageVisibility(TestCase, WagtailTestUtils): - """ - Test the way that the Explorable Pages functionality manifests within the Explorer. - This is isolated in its own test case because it requires a custom page tree and custom set of - users and groups. - The fixture sets up this page tree: - ======================================================== - ID Site Path - ======================================================== - 1 / - 2 testserver /home/ - 3 testserver /home/about-us/ - 4 example.com /example-home/ - 5 example.com /example-home/content/ - 6 example.com /example-home/content/page-1/ - 7 example.com /example-home/content/page-2/ - 9 example.com /example-home/content/page-2/child-1 - 8 example.com /example-home/other-content/ - 10 example2.com /home-2/ - ======================================================== - Group 1 has explore and choose permissions rooted at testserver's homepage. - Group 2 has explore and choose permissions rooted at example.com's page-1. - Group 3 has explore and choose permissions rooted at example.com's other-content. - User "jane" is in Group 1. - User "bob" is in Group 2. - User "sam" is in Groups 1 and 2. - User "josh" is in Groups 2 and 3. - User "mary" is is no Groups, but she has the "access wagtail admin" permission. - User "superman" is an admin. - """ - - fixtures = ['test_explorable_pages.json'] - - # Integration tests adapted from @coredumperror - - def test_admin_can_explore_every_page(self): - self.assertTrue(self.client.login(username='superman', password='password')) - for page in Page.objects.all(): - response = self.client.get(reverse('wagtailadmin_explore', args=[page.pk])) - self.assertEqual(response.status_code, 200) - - def test_admin_sees_root_page_as_explorer_root(self): - self.assertTrue(self.client.login(username='superman', password='password')) - response = self.client.get(reverse('wagtailadmin_explore_root')) - self.assertEqual(response.status_code, 200) - # Administrator should see the full list of children of the Root page. - self.assertContains(response, "Welcome to testserver!") - self.assertContains(response, "Welcome to example.com!") - - def test_admin_sees_breadcrumbs_up_to_root_page(self): - self.assertTrue(self.client.login(username='superman', password='password')) - response = self.client.get(reverse('wagtailadmin_explore', args=[6])) - self.assertEqual(response.status_code, 200) - - self.assertInHTML( - """
  • Root
  • """, - str(response.content) - ) - self.assertInHTML("""
  • Welcome to example.com!
  • """, str(response.content)) - self.assertInHTML("""
  • Content
  • """, str(response.content)) - - def test_nonadmin_sees_breadcrumbs_up_to_cca(self): - self.assertTrue(self.client.login(username='josh', password='password')) - response = self.client.get(reverse('wagtailadmin_explore', args=[6])) - self.assertEqual(response.status_code, 200) - # While at "Page 1", Josh should see the breadcrumbs leading only as far back as the example.com homepage, - # since it's his Closest Common Ancestor. - self.assertInHTML( - """
  • Home
  • """, - str(response.content) - ) - self.assertInHTML("""
  • Content
  • """, str(response.content)) - # The page title shouldn't appear because it's the "home" breadcrumb. - self.assertNotContains(response, "Welcome to example.com!") - - def test_admin_home_page_changes_with_permissions(self): - self.assertTrue(self.client.login(username='bob', password='password')) - response = self.client.get(reverse('wagtailadmin_home')) - self.assertEqual(response.status_code, 200) - # Bob should only see the welcome for example.com, not testserver - self.assertContains(response, "Welcome to the example.com Wagtail CMS") - self.assertNotContains(response, "testserver") - - def test_breadcrumb_with_no_user_permissions(self): - self.assertTrue(self.client.login(username='mary', password='password')) - response = self.client.get(reverse('wagtailadmin_home')) - self.assertEqual(response.status_code, 200) - # Since Mary has no page permissions, she should not see the breadcrumb - self.assertNotContains(response, """
  • Home
  • """) - - -class TestPageCreation(TestCase, WagtailTestUtils): - def setUp(self): - # Find root page - self.root_page = Page.objects.get(id=2) - - # Login - self.user = self.login() - - def test_add_subpage(self): - response = self.client.get(reverse('wagtailadmin_pages:add_subpage', args=(self.root_page.id, ))) - self.assertEqual(response.status_code, 200) - - self.assertContains(response, "Simple page") - target_url = reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)) - self.assertContains(response, 'href="%s"' % target_url) - # List of available page types should not contain pages with is_creatable = False - self.assertNotContains(response, "MTI base page") - # List of available page types should not contain abstract pages - self.assertNotContains(response, "Abstract page") - # List of available page types should not contain pages whose parent_page_types forbid it - self.assertNotContains(response, "Business child") - - def test_add_subpage_with_subpage_types(self): - # Add a BusinessIndex to test business rules in - business_index = BusinessIndex( - title="Hello world!", - slug="hello-world", - ) - self.root_page.add_child(instance=business_index) - - response = self.client.get(reverse('wagtailadmin_pages:add_subpage', args=(business_index.id, ))) - self.assertEqual(response.status_code, 200) - - self.assertContains(response, "Business child") - # List should not contain page types not in the subpage_types list - self.assertNotContains(response, "Simple page") - - def test_add_subpage_with_one_valid_subpage_type(self): - # Add a BusinessSubIndex to test business rules in - business_index = BusinessIndex( - title="Hello world!", - slug="hello-world", - ) - self.root_page.add_child(instance=business_index) - business_subindex = BusinessSubIndex( - title="Hello world!", - slug="hello-world", - ) - business_index.add_child(instance=business_subindex) - - response = self.client.get(reverse('wagtailadmin_pages:add_subpage', args=(business_subindex.id, ))) - # Should be redirected to the 'add' page for BusinessChild, the only valid subpage type - self.assertRedirects( - response, - reverse('wagtailadmin_pages:add', args=('tests', 'businesschild', business_subindex.id)) - ) - - def test_add_subpage_bad_permissions(self): - # Remove privileges from user - self.user.is_superuser = False - self.user.user_permissions.add( - Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') - ) - self.user.save() - - # Get add subpage page - response = self.client.get(reverse('wagtailadmin_pages:add_subpage', args=(self.root_page.id, ))) - - # Check that the user received a 403 response - self.assertEqual(response.status_code, 403) - - def test_add_subpage_nonexistantparent(self): - response = self.client.get(reverse('wagtailadmin_pages:add_subpage', args=(100000, ))) - self.assertEqual(response.status_code, 404) - - def test_add_subpage_with_next_param(self): - response = self.client.get( - reverse('wagtailadmin_pages:add_subpage', args=(self.root_page.id, )), - {'next': '/admin/users/'} - ) - self.assertEqual(response.status_code, 200) - - self.assertContains(response, "Simple page") - target_url = reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)) - self.assertContains(response, 'href="%s?next=/admin/users/"' % target_url) - - def test_create_simplepage(self): - response = self.client.get(reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id))) - self.assertEqual(response.status_code, 200) - self.assertContains(response, 'Content') - self.assertContains(response, 'Promote') - # test register_page_action_menu_item hook - self.assertContains(response, '') - self.assertContains(response, 'testapp/js/siren.js') - # test construct_page_action_menu hook - self.assertContains(response, '') - - def test_create_multipart(self): - """ - Test checks if 'enctype="multipart/form-data"' is added and only to forms that require multipart encoding. - """ - # check for SimplePage where is no file field - response = self.client.get(reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id))) - self.assertEqual(response.status_code, 200) - self.assertNotContains(response, 'enctype="multipart/form-data"') - self.assertTemplateUsed(response, 'wagtailadmin/pages/create.html') - - # check for FilePage which has file field - response = self.client.get(reverse('wagtailadmin_pages:add', args=('tests', 'filepage', self.root_page.id))) - self.assertEqual(response.status_code, 200) - self.assertContains(response, 'enctype="multipart/form-data"') - - def test_create_page_without_promote_tab(self): - """ - Test that the Promote tab is not rendered for page classes that define it as empty - """ - response = self.client.get( - reverse('wagtailadmin_pages:add', args=('tests', 'standardindex', self.root_page.id)) - ) - self.assertEqual(response.status_code, 200) - self.assertContains(response, 'Content') - self.assertNotContains(response, 'Promote') - - def test_create_page_with_custom_tabs(self): - """ - Test that custom edit handlers are rendered - """ - response = self.client.get( - reverse('wagtailadmin_pages:add', args=('tests', 'standardchild', self.root_page.id)) - ) - self.assertEqual(response.status_code, 200) - self.assertContains(response, 'Content') - self.assertContains(response, 'Promote') - self.assertContains(response, 'Dinosaurs') - - def test_create_page_with_non_model_field(self): - """ - Test that additional fields defined on the form rather than the model are accepted and rendered - """ - response = self.client.get(reverse('wagtailadmin_pages:add', args=('tests', 'formclassadditionalfieldpage', self.root_page.id))) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailadmin/pages/create.html') - self.assertContains(response, "Enter SMS authentication code") - - def test_create_simplepage_bad_permissions(self): - # Remove privileges from user - self.user.is_superuser = False - self.user.user_permissions.add( - Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') - ) - self.user.save() - - # Get page - response = self.client.get(reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id, ))) - - # Check that the user received a 403 response - self.assertEqual(response.status_code, 403) - - def test_cannot_create_page_with_is_creatable_false(self): - # tests.MTIBasePage has is_creatable=False, so attempting to add a new one - # should fail with permission denied - response = self.client.get( - reverse('wagtailadmin_pages:add', args=('tests', 'mtibasepage', self.root_page.id)) - ) - self.assertEqual(response.status_code, 403) - - def test_cannot_create_page_when_can_create_at_returns_false(self): - # issue #2892 - - # Check that creating a second SingletonPage results in a permission - # denied error. - - # SingletonPage overrides the can_create_at method to make it return - # False if another SingletonPage already exists. - - # The Page model now has a max_count attribute (issue 4841), - # but we are leaving this test in place to cover existing behaviour and - # ensure it does not break any code doing this in the wild. - add_url = reverse('wagtailadmin_pages:add', args=[ - SingletonPage._meta.app_label, SingletonPage._meta.model_name, self.root_page.pk]) - - # A single singleton page should be creatable - self.assertTrue(SingletonPage.can_create_at(self.root_page)) - response = self.client.get(add_url) - self.assertEqual(response.status_code, 200) - - # Create a singleton page - self.root_page.add_child(instance=SingletonPage( - title='singleton', slug='singleton')) - - # A second singleton page should not be creatable - self.assertFalse(SingletonPage.can_create_at(self.root_page)) - response = self.client.get(add_url) - self.assertEqual(response.status_code, 403) - - def test_cannot_create_singleton_page_with_max_count(self): - # Check that creating a second SingletonPageViaMaxCount results in a permission - # denied error. - - # SingletonPageViaMaxCount uses the max_count attribute to limit the number of - # instance it can have. - - add_url = reverse('wagtailadmin_pages:add', args=[ - SingletonPageViaMaxCount._meta.app_label, SingletonPageViaMaxCount._meta.model_name, self.root_page.pk]) - - # A single singleton page should be creatable - self.assertTrue(SingletonPageViaMaxCount.can_create_at(self.root_page)) - response = self.client.get(add_url) - self.assertEqual(response.status_code, 200) - - # Create a singleton page - self.root_page.add_child(instance=SingletonPageViaMaxCount( - title='singleton', slug='singleton')) - - # A second singleton page should not be creatable - self.assertFalse(SingletonPageViaMaxCount.can_create_at(self.root_page)) - response = self.client.get(add_url) - self.assertEqual(response.status_code, 403) - - def test_cannot_create_page_with_wrong_parent_page_types(self): - # tests.BusinessChild has limited parent_page_types, so attempting to add - # a new one at the root level should fail with permission denied - response = self.client.get( - reverse('wagtailadmin_pages:add', args=('tests', 'businesschild', self.root_page.id)) - ) - self.assertEqual(response.status_code, 403) - - def test_cannot_create_page_with_wrong_subpage_types(self): - # Add a BusinessIndex to test business rules in - business_index = BusinessIndex( - title="Hello world!", - slug="hello-world", - ) - self.root_page.add_child(instance=business_index) - - # BusinessIndex has limited subpage_types, so attempting to add a SimplePage - # underneath it should fail with permission denied - response = self.client.get( - reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', business_index.id)) - ) - self.assertEqual(response.status_code, 403) - - def test_create_simplepage_post(self): - post_data = { - 'title': "New page!", - 'content': "Some content", - 'slug': 'hello-world', - } - response = self.client.post( - reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), - post_data - ) - - # Find the page and check it - page = Page.objects.get(path__startswith=self.root_page.path, slug='hello-world').specific - - # Should be redirected to edit page - self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(page.id, ))) - - self.assertEqual(page.title, post_data['title']) - self.assertEqual(page.draft_title, post_data['title']) - self.assertIsInstance(page, SimplePage) - self.assertFalse(page.live) - self.assertFalse(page.first_published_at) - - # treebeard should report no consistency problems with the tree - self.assertFalse(any(Page.find_problems()), 'treebeard found consistency problems') - - def test_create_simplepage_scheduled(self): - go_live_at = timezone.now() + datetime.timedelta(days=1) - expire_at = timezone.now() + datetime.timedelta(days=2) - post_data = { - 'title': "New page!", - 'content': "Some content", - 'slug': 'hello-world', - 'go_live_at': submittable_timestamp(go_live_at), - 'expire_at': submittable_timestamp(expire_at), - } - response = self.client.post( - reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), post_data - ) - - # Should be redirected to explorer page - self.assertEqual(response.status_code, 302) - - # Find the page and check the scheduled times - page = Page.objects.get(path__startswith=self.root_page.path, slug='hello-world').specific - self.assertEqual(page.go_live_at.date(), go_live_at.date()) - self.assertEqual(page.expire_at.date(), expire_at.date()) - self.assertEqual(page.expired, False) - self.assertTrue(page.status_string, "draft") - - # No revisions with approved_go_live_at - self.assertFalse(PageRevision.objects.filter(page=page).exclude(approved_go_live_at__isnull=True).exists()) - - def test_create_simplepage_scheduled_go_live_before_expiry(self): - post_data = { - 'title': "New page!", - 'content': "Some content", - 'slug': 'hello-world', - 'go_live_at': submittable_timestamp(timezone.now() + datetime.timedelta(days=2)), - 'expire_at': submittable_timestamp(timezone.now() + datetime.timedelta(days=1)), - } - response = self.client.post( - reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), post_data - ) - - self.assertEqual(response.status_code, 200) - - # Check that a form error was raised - self.assertFormError(response, 'form', 'go_live_at', "Go live date/time must be before expiry date/time") - self.assertFormError(response, 'form', 'expire_at', "Go live date/time must be before expiry date/time") - - # form should be marked as having unsaved changes for the purposes of the dirty-forms warning - self.assertContains(response, "alwaysDirty: true") - - def test_create_simplepage_scheduled_expire_in_the_past(self): - post_data = { - 'title': "New page!", - 'content': "Some content", - 'slug': 'hello-world', - 'expire_at': submittable_timestamp(timezone.now() + datetime.timedelta(days=-1)), - } - response = self.client.post( - reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), post_data - ) - - self.assertEqual(response.status_code, 200) - - # Check that a form error was raised - self.assertFormError(response, 'form', 'expire_at', "Expiry date/time must be in the future") - - # form should be marked as having unsaved changes for the purposes of the dirty-forms warning - self.assertContains(response, "alwaysDirty: true") - - def test_create_simplepage_post_publish(self): - # Connect a mock signal handler to page_published signal - mock_handler = mock.MagicMock() - page_published.connect(mock_handler) - - # Post - post_data = { - 'title': "New page!", - 'content': "Some content", - 'slug': 'hello-world', - 'action-publish': "Publish", - } - response = self.client.post( - reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), post_data - ) - - # Find the page and check it - page = Page.objects.get(path__startswith=self.root_page.path, slug='hello-world').specific - - # Should be redirected to explorer - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - - self.assertEqual(page.title, post_data['title']) - self.assertEqual(page.draft_title, post_data['title']) - self.assertIsInstance(page, SimplePage) - self.assertTrue(page.live) - self.assertTrue(page.first_published_at) - - # Check that the page_published signal was fired - self.assertEqual(mock_handler.call_count, 1) - mock_call = mock_handler.mock_calls[0][2] - - self.assertEqual(mock_call['sender'], page.specific_class) - self.assertEqual(mock_call['instance'], page) - self.assertIsInstance(mock_call['instance'], page.specific_class) - - # treebeard should report no consistency problems with the tree - self.assertFalse(any(Page.find_problems()), 'treebeard found consistency problems') - - def test_create_simplepage_post_publish_scheduled(self): - go_live_at = timezone.now() + datetime.timedelta(days=1) - expire_at = timezone.now() + datetime.timedelta(days=2) - post_data = { - 'title': "New page!", - 'content': "Some content", - 'slug': 'hello-world', - 'action-publish': "Publish", - 'go_live_at': submittable_timestamp(go_live_at), - 'expire_at': submittable_timestamp(expire_at), - } - response = self.client.post( - reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), post_data - ) - - # Should be redirected to explorer page - self.assertEqual(response.status_code, 302) - - # Find the page and check it - page = Page.objects.get(path__startswith=self.root_page.path, slug='hello-world').specific - self.assertEqual(page.go_live_at.date(), go_live_at.date()) - self.assertEqual(page.expire_at.date(), expire_at.date()) - self.assertEqual(page.expired, False) - - # A revision with approved_go_live_at should exist now - self.assertTrue(PageRevision.objects.filter(page=page).exclude(approved_go_live_at__isnull=True).exists()) - # But Page won't be live - self.assertFalse(page.live) - self.assertFalse(page.first_published_at) - self.assertTrue(page.status_string, "scheduled") - - def test_create_simplepage_post_submit(self): - # Create a moderator user for testing email - get_user_model().objects.create_superuser('moderator', 'moderator@email.com', 'password') - - # Submit - post_data = { - 'title': "New page!", - 'content': "Some content", - 'slug': 'hello-world', - 'action-submit': "Submit", - } - response = self.client.post( - reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), post_data - ) - - # Find the page and check it - page = Page.objects.get(path__startswith=self.root_page.path, slug='hello-world').specific - - # Should be redirected to explorer - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - - self.assertEqual(page.title, post_data['title']) - self.assertIsInstance(page, SimplePage) - self.assertFalse(page.live) - self.assertFalse(page.first_published_at) - - # The latest revision for the page should now be in moderation - self.assertTrue(page.get_latest_revision().submitted_for_moderation) - - # Check that the moderator got an email - self.assertEqual(len(mail.outbox), 1) - self.assertEqual(mail.outbox[0].to, ['moderator@email.com']) - self.assertEqual(mail.outbox[0].subject, 'The page "New page!" has been submitted for moderation') - - def test_create_simplepage_post_existing_slug(self): - # This tests the existing slug checking on page save - - # Create a page - self.child_page = SimplePage(title="Hello world!", slug="hello-world", content="hello") - self.root_page.add_child(instance=self.child_page) - - # Attempt to create a new one with the same slug - post_data = { - 'title': "New page!", - 'content': "Some content", - 'slug': 'hello-world', - 'action-publish': "Publish", - } - response = self.client.post( - reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), post_data - ) - - # Should not be redirected (as the save should fail) - self.assertEqual(response.status_code, 200) - - # Check that a form error was raised - self.assertFormError(response, 'form', 'slug', "This slug is already in use") - - # form should be marked as having unsaved changes for the purposes of the dirty-forms warning - self.assertContains(response, "alwaysDirty: true") - - def test_create_nonexistantparent(self): - response = self.client.get(reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', 100000))) - self.assertEqual(response.status_code, 404) - - def test_create_nonpagetype(self): - response = self.client.get( - reverse('wagtailadmin_pages:add', args=('wagtailimages', 'image', self.root_page.id)) - ) - self.assertEqual(response.status_code, 404) - - def test_preview_on_create(self): - post_data = { - 'title': "New page!", - 'content': "Some content", - 'slug': 'hello-world', - 'action-submit': "Submit", - } - preview_url = reverse('wagtailadmin_pages:preview_on_add', - args=('tests', 'simplepage', self.root_page.id)) - response = self.client.post(preview_url, post_data) - - # Check the JSON response - self.assertEqual(response.status_code, 200) - self.assertJSONEqual(response.content.decode(), {'is_valid': True}) - - response = self.client.get(preview_url) - - # Check the HTML response - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'tests/simple_page.html') - self.assertContains(response, "New page!") - - # Check that the treebeard attributes were set correctly on the page object - self.assertEqual(response.context['self'].depth, self.root_page.depth + 1) - self.assertTrue(response.context['self'].path.startswith(self.root_page.path)) - self.assertEqual(response.context['self'].get_parent(), self.root_page) - - def test_whitespace_titles(self): - post_data = { - 'title': " ", # Single space on purpose - 'content': "Some content", - 'slug': 'hello-world', - 'action-submit': "Submit", - } - response = self.client.post( - reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), post_data - ) - - # Check that a form error was raised - self.assertFormError(response, 'form', 'title', "This field is required.") - - def test_whitespace_titles_with_tab(self): - post_data = { - 'title': "\t", # Single space on purpose - 'content': "Some content", - 'slug': 'hello-world', - 'action-submit': "Submit", - } - response = self.client.post(reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), post_data) - - # Check that a form error was raised - self.assertFormError(response, 'form', 'title', "This field is required.") - - def test_whitespace_titles_with_tab_in_seo_title(self): - post_data = { - 'title': "Hello", - 'content': "Some content", - 'slug': 'hello-world', - 'action-submit': "Submit", - 'seo_title': '\t' - } - response = self.client.post(reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), post_data) - - # Should be successful, as seo_title is not required - self.assertEqual(response.status_code, 302) - - # The tab should be automatically stripped from the seo_title - page = Page.objects.order_by('-id').first() - self.assertEqual(page.seo_title, '') - - def test_whitespace_is_stripped_from_titles(self): - post_data = { - 'title': " Hello ", - 'content': "Some content", - 'slug': 'hello-world', - 'action-submit': "Submit", - 'seo_title': ' hello SEO ' - } - response = self.client.post(reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), post_data) - - # Should be successful, as both title and seo_title are non-empty after stripping - self.assertEqual(response.status_code, 302) - - # Whitespace should be automatically stripped from title and seo_title - page = Page.objects.order_by('-id').first() - self.assertEqual(page.title, 'Hello') - self.assertEqual(page.draft_title, 'Hello') - self.assertEqual(page.seo_title, 'hello SEO') - - def test_long_slug(self): - post_data = { - 'title': "Hello world", - 'content': "Some content", - 'slug': 'hello-world-hello-world-hello-world-hello-world-hello-world-hello-world-' - 'hello-world-hello-world-hello-world-hello-world-hello-world-hello-world-' - 'hello-world-hello-world-hello-world-hello-world-hello-world-hello-world-' - 'hello-world-hello-world-hello-world-hello-world-hello-world-hello-world', - 'action-submit': "Submit", - } - response = self.client.post( - reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), post_data - ) - - # Check that a form error was raised - self.assertEqual(response.status_code, 200) - self.assertFormError(response, 'form', 'slug', "Ensure this value has at most 255 characters (it has 287).") - - def test_before_create_page_hook(self): - def hook_func(request, parent_page, page_class): - self.assertIsInstance(request, HttpRequest) - self.assertEqual(parent_page.id, self.root_page.id) - self.assertEqual(page_class, SimplePage) - - return HttpResponse("Overridden!") - - with self.register_hook('before_create_page', hook_func): - response = self.client.get( - reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)) - ) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b"Overridden!") - - def test_before_create_page_hook_post(self): - def hook_func(request, parent_page, page_class): - self.assertIsInstance(request, HttpRequest) - self.assertEqual(parent_page.id, self.root_page.id) - self.assertEqual(page_class, SimplePage) - - return HttpResponse("Overridden!") - - with self.register_hook('before_create_page', hook_func): - post_data = { - 'title': "New page!", - 'content': "Some content", - 'slug': 'hello-world', - } - response = self.client.post( - reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), - post_data - ) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b"Overridden!") - - # page should not be created - self.assertFalse(Page.objects.filter(title="New page!").exists()) - - def test_after_create_page_hook(self): - def hook_func(request, page): - self.assertIsInstance(request, HttpRequest) - self.assertIsInstance(page, SimplePage) - - return HttpResponse("Overridden!") - - with self.register_hook('after_create_page', hook_func): - post_data = { - 'title': "New page!", - 'content': "Some content", - 'slug': 'hello-world', - } - response = self.client.post( - reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', self.root_page.id)), - post_data - ) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b"Overridden!") - - # page should be created - self.assertTrue(Page.objects.filter(title="New page!").exists()) - - -class TestPerRequestEditHandler(TestCase, WagtailTestUtils): - fixtures = ['test.json'] - - def setUp(self): - # Find root page - self.root_page = Page.objects.get(id=2) - GroupPagePermission.objects.create( - group=Group.objects.get(name="Site-wide editors"), - page=self.root_page, permission_type='add' - ) - - def test_create_page_with_per_request_custom_edit_handlers(self): - """ - Test that per-request custom behaviour in edit handlers is honoured - """ - # non-superusers should not see secret_data - logged_in = self.client.login(username='siteeditor', password='password') - self.assertTrue(logged_in) - response = self.client.get( - reverse('wagtailadmin_pages:add', args=('tests', 'secretpage', self.root_page.id)) - ) - self.assertEqual(response.status_code, 200) - self.assertContains(response, '"boring_data"') - self.assertNotContains(response, '"secret_data"') - - # superusers should see secret_data - logged_in = self.client.login(username='superuser', password='password') - self.assertTrue(logged_in) - response = self.client.get( - reverse('wagtailadmin_pages:add', args=('tests', 'secretpage', self.root_page.id)) - ) - self.assertEqual(response.status_code, 200) - self.assertContains(response, '"boring_data"') - self.assertContains(response, '"secret_data"') - - -class TestPageEdit(TestCase, WagtailTestUtils): - def setUp(self): - # Find root page - self.root_page = Page.objects.get(id=2) - - # Add child page - child_page = SimplePage( - title="Hello world!", - slug="hello-world", - content="hello", - ) - self.root_page.add_child(instance=child_page) - child_page.save_revision().publish() - self.child_page = SimplePage.objects.get(id=child_page.id) - - # Add file page - fake_file = ContentFile("File for testing multipart") - fake_file.name = 'test.txt' - file_page = FilePage( - title="File Page", - slug="file-page", - file_field=fake_file, - ) - self.root_page.add_child(instance=file_page) - file_page.save_revision().publish() - self.file_page = FilePage.objects.get(id=file_page.id) - - # Add event page (to test edit handlers) - self.event_page = EventPage( - title="Event page", slug="event-page", - location='the moon', audience='public', - cost='free', date_from='2001-01-01', - ) - self.root_page.add_child(instance=self.event_page) - - # Add single event page (to test custom URL routes) - self.single_event_page = SingleEventPage( - title="Mars landing", slug="mars-landing", - location='mars', audience='public', - cost='free', date_from='2001-01-01', - ) - self.root_page.add_child(instance=self.single_event_page) - - self.unpublished_page = SimplePage( - title="Hello unpublished world!", - slug="hello-unpublished-world", - content="hello", - live=False, - has_unpublished_changes=True, - ) - self.root_page.add_child(instance=self.unpublished_page) - - # Login - self.user = self.login() - - def test_page_edit(self): - # Tests that the edit page loads - response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.event_page.id, ))) - self.assertEqual(response.status_code, 200) - - # Test InlinePanel labels/headings - self.assertContains(response, 'Speaker lineup') - self.assertContains(response, 'Add speakers') - - # test register_page_action_menu_item hook - self.assertContains(response, '') - self.assertContains(response, 'testapp/js/siren.js') - - # test construct_page_action_menu hook - self.assertContains(response, '') - - def test_edit_draft_page_with_no_revisions(self): - # Tests that the edit page loads - response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.unpublished_page.id, ))) - self.assertEqual(response.status_code, 200) - - def test_edit_multipart(self): - """ - Test checks if 'enctype="multipart/form-data"' is added and only to forms that require multipart encoding. - """ - # check for SimplePage where is no file field - response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.event_page.id, ))) - self.assertEqual(response.status_code, 200) - self.assertNotContains(response, 'enctype="multipart/form-data"') - self.assertTemplateUsed(response, 'wagtailadmin/pages/edit.html') - - # check for FilePage which has file field - response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.file_page.id, ))) - self.assertEqual(response.status_code, 200) - self.assertContains(response, 'enctype="multipart/form-data"') - - def test_upload_file_publish(self): - """ - Check that file uploads work when directly publishing - """ - file_upload = ContentFile(b"A new file", name='published-file.txt') - post_data = { - 'title': 'New file', - 'slug': 'new-file', - 'file_field': file_upload, - 'action-publish': "Publish", - } - response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.file_page.id]), post_data) - - # Should be redirected to explorer - self.assertRedirects(response, reverse('wagtailadmin_explore', args=[self.root_page.id])) - - # Check the new file exists - file_page = FilePage.objects.get() - - self.assertEqual(file_page.file_field.name, file_upload.name) - self.assertTrue(os.path.exists(file_page.file_field.path)) - self.assertEqual(file_page.file_field.read(), b"A new file") - - def test_upload_file_draft(self): - """ - Check that file uploads work when saving a draft - """ - file_upload = ContentFile(b"A new file", name='draft-file.txt') - post_data = { - 'title': 'New file', - 'slug': 'new-file', - 'file_field': file_upload, - } - response = self.client.post(reverse('wagtailadmin_pages:edit', args=[self.file_page.id]), post_data) - - # Should be redirected to edit page - self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=[self.file_page.id])) - - # Check the file was uploaded - file_path = os.path.join(settings.MEDIA_ROOT, file_upload.name) - self.assertTrue(os.path.exists(file_path)) - with open(file_path, 'rb') as saved_file: - self.assertEqual(saved_file.read(), b"A new file") - - # Publish the draft just created - FilePage.objects.get().get_latest_revision().publish() - - # Get the file page, check the file is set - file_page = FilePage.objects.get() - self.assertEqual(file_page.file_field.name, file_upload.name) - self.assertTrue(os.path.exists(file_page.file_field.path)) - self.assertEqual(file_page.file_field.read(), b"A new file") - - def test_page_edit_bad_permissions(self): - # Remove privileges from user - self.user.is_superuser = False - self.user.user_permissions.add( - Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') - ) - self.user.save() - - # Get edit page - response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) - - # Check that the user received a 403 response - self.assertEqual(response.status_code, 403) - - def test_page_edit_post(self): - # Tests simple editing - post_data = { - 'title': "I've been edited!", - 'content': "Some content", - 'slug': 'hello-world', - } - response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) - - # Should be redirected to edit page - self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) - - # The page should have "has_unpublished_changes" flag set - child_page_new = SimplePage.objects.get(id=self.child_page.id) - self.assertTrue(child_page_new.has_unpublished_changes) - - # Page fields should not be changed (because we just created a new draft) - self.assertEqual(child_page_new.title, self.child_page.title) - self.assertEqual(child_page_new.content, self.child_page.content) - self.assertEqual(child_page_new.slug, self.child_page.slug) - - # The draft_title should have a new title - self.assertEqual(child_page_new.draft_title, post_data['title']) - - def test_page_edit_post_when_locked(self): - # Tests that trying to edit a locked page results in an error - - # Lock the page - self.child_page.locked = True - self.child_page.save() - - # Post - post_data = { - 'title': "I've been edited!", - 'content': "Some content", - 'slug': 'hello-world', - } - response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) - - # Shouldn't be redirected - self.assertContains(response, "The page could not be saved as it is locked") - - # The page shouldn't have "has_unpublished_changes" flag set - child_page_new = SimplePage.objects.get(id=self.child_page.id) - self.assertFalse(child_page_new.has_unpublished_changes) - - def test_edit_post_scheduled(self): - # put go_live_at and expire_at several days away from the current date, to avoid - # false matches in content_json__contains tests - go_live_at = timezone.now() + datetime.timedelta(days=10) - expire_at = timezone.now() + datetime.timedelta(days=20) - post_data = { - 'title': "I've been edited!", - 'content': "Some content", - 'slug': 'hello-world', - 'go_live_at': submittable_timestamp(go_live_at), - 'expire_at': submittable_timestamp(expire_at), - } - response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) - - # Should be redirected to explorer page - self.assertEqual(response.status_code, 302) - - child_page_new = SimplePage.objects.get(id=self.child_page.id) - - # The page will still be live - self.assertTrue(child_page_new.live) - - # A revision with approved_go_live_at should not exist - self.assertFalse(PageRevision.objects.filter( - page=child_page_new).exclude(approved_go_live_at__isnull=True).exists() - ) - - # But a revision with go_live_at and expire_at in their content json *should* exist - self.assertTrue(PageRevision.objects.filter( - page=child_page_new, content_json__contains=str(go_live_at.date())).exists() - ) - self.assertTrue( - PageRevision.objects.filter(page=child_page_new, content_json__contains=str(expire_at.date())).exists() - ) - - def test_edit_scheduled_go_live_before_expiry(self): - post_data = { - 'title': "I've been edited!", - 'content': "Some content", - 'slug': 'hello-world', - 'go_live_at': submittable_timestamp(timezone.now() + datetime.timedelta(days=2)), - 'expire_at': submittable_timestamp(timezone.now() + datetime.timedelta(days=1)), - } - response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) - - self.assertEqual(response.status_code, 200) - - # Check that a form error was raised - self.assertFormError(response, 'form', 'go_live_at', "Go live date/time must be before expiry date/time") - self.assertFormError(response, 'form', 'expire_at', "Go live date/time must be before expiry date/time") - - # form should be marked as having unsaved changes for the purposes of the dirty-forms warning - self.assertContains(response, "alwaysDirty: true") - - def test_edit_scheduled_expire_in_the_past(self): - post_data = { - 'title': "I've been edited!", - 'content': "Some content", - 'slug': 'hello-world', - 'expire_at': submittable_timestamp(timezone.now() + datetime.timedelta(days=-1)), - } - response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) - - self.assertEqual(response.status_code, 200) - - # Check that a form error was raised - self.assertFormError(response, 'form', 'expire_at', "Expiry date/time must be in the future") - - # form should be marked as having unsaved changes for the purposes of the dirty-forms warning - self.assertContains(response, "alwaysDirty: true") - - def test_page_edit_post_publish(self): - # Connect a mock signal handler to page_published signal - mock_handler = mock.MagicMock() - page_published.connect(mock_handler) - - # Set has_unpublished_changes=True on the existing record to confirm that the publish action - # is resetting it (and not just leaving it alone) - self.child_page.has_unpublished_changes = True - self.child_page.save() - - # Save current value of first_published_at so we can check that it doesn't change - first_published_at = SimplePage.objects.get(id=self.child_page.id).first_published_at - - # Tests publish from edit page - post_data = { - 'title': "I've been edited!", - 'content': "Some content", - 'slug': 'hello-world-new', - 'action-publish': "Publish", - } - response = self.client.post( - reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data, follow=True - ) - - # Should be redirected to explorer - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - - # Check that the page was edited - child_page_new = SimplePage.objects.get(id=self.child_page.id) - self.assertEqual(child_page_new.title, post_data['title']) - self.assertEqual(child_page_new.draft_title, post_data['title']) - - # Check that the page_published signal was fired - self.assertEqual(mock_handler.call_count, 1) - mock_call = mock_handler.mock_calls[0][2] - - self.assertEqual(mock_call['sender'], child_page_new.specific_class) - self.assertEqual(mock_call['instance'], child_page_new) - self.assertIsInstance(mock_call['instance'], child_page_new.specific_class) - - # The page shouldn't have "has_unpublished_changes" flag set - self.assertFalse(child_page_new.has_unpublished_changes) - - # first_published_at should not change as it was already set - self.assertEqual(first_published_at, child_page_new.first_published_at) - - # The "View Live" button should have the updated slug. - for message in response.context['messages']: - self.assertIn('hello-world-new', message.message) - break - - def test_first_published_at_editable(self): - """Test that we can update the first_published_at via the Page edit form, - for page models that expose it.""" - - # Add child page, of a type which has first_published_at in its form - child_page = ManyToManyBlogPage( - title="Hello world!", - slug="hello-again-world", - body="hello", - ) - self.root_page.add_child(instance=child_page) - child_page.save_revision().publish() - self.child_page = ManyToManyBlogPage.objects.get(id=child_page.id) - - initial_delta = self.child_page.first_published_at - timezone.now() - - first_published_at = timezone.now() - datetime.timedelta(days=2) - - post_data = { - 'title': "I've been edited!", - 'body': "Some content", - 'slug': 'hello-again-world', - 'action-publish': "Publish", - 'first_published_at': submittable_timestamp(first_published_at), - } - self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) - - # Get the edited page. - child_page_new = ManyToManyBlogPage.objects.get(id=self.child_page.id) - - # first_published_at should have changed. - new_delta = child_page_new.first_published_at - timezone.now() - self.assertNotEqual(new_delta.days, initial_delta.days) - # first_published_at should be 3 days ago. - self.assertEqual(new_delta.days, -3) - - def test_edit_post_publish_scheduled_unpublished_page(self): - # Unpublish the page - self.child_page.live = False - self.child_page.save() - - go_live_at = timezone.now() + datetime.timedelta(days=1) - expire_at = timezone.now() + datetime.timedelta(days=2) - post_data = { - 'title': "I've been edited!", - 'content': "Some content", - 'slug': 'hello-world', - 'action-publish': "Publish", - 'go_live_at': submittable_timestamp(go_live_at), - 'expire_at': submittable_timestamp(expire_at), - } - response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) - - # Should be redirected to explorer page - self.assertEqual(response.status_code, 302) - - child_page_new = SimplePage.objects.get(id=self.child_page.id) - - # The page should not be live anymore - self.assertFalse(child_page_new.live) - - # Instead a revision with approved_go_live_at should now exist - self.assertTrue( - PageRevision.objects.filter(page=child_page_new).exclude(approved_go_live_at__isnull=True).exists() - ) - - # The page SHOULD have the "has_unpublished_changes" flag set, - # because the changes are not visible as a live page yet - self.assertTrue( - child_page_new.has_unpublished_changes, - "A page scheduled for future publishing should have has_unpublished_changes=True" - ) - - self.assertEqual(child_page_new.status_string, "scheduled") - - def test_edit_post_publish_now_an_already_scheduled_unpublished_page(self): - # Unpublish the page - self.child_page.live = False - self.child_page.save() - - # First let's publish a page with a go_live_at in the future - go_live_at = timezone.now() + datetime.timedelta(days=1) - expire_at = timezone.now() + datetime.timedelta(days=2) - post_data = { - 'title': "I've been edited!", - 'content': "Some content", - 'slug': 'hello-world', - 'action-publish': "Publish", - 'go_live_at': submittable_timestamp(go_live_at), - 'expire_at': submittable_timestamp(expire_at), - } - response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) - - # Should be redirected to edit page - self.assertEqual(response.status_code, 302) - - child_page_new = SimplePage.objects.get(id=self.child_page.id) - - # The page should not be live - self.assertFalse(child_page_new.live) - - self.assertEqual(child_page_new.status_string, "scheduled") - - # Instead a revision with approved_go_live_at should now exist - self.assertTrue( - PageRevision.objects.filter(page=child_page_new).exclude(approved_go_live_at__isnull=True).exists() - ) - - # Now, let's edit it and publish it right now - go_live_at = timezone.now() - post_data = { - 'title': "I've been edited!", - 'content': "Some content", - 'slug': 'hello-world', - 'action-publish': "Publish", - 'go_live_at': "", - } - response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) - - # Should be redirected to edit page - self.assertEqual(response.status_code, 302) - - child_page_new = SimplePage.objects.get(id=self.child_page.id) - - # The page should be live now - self.assertTrue(child_page_new.live) - - # And a revision with approved_go_live_at should not exist - self.assertFalse( - PageRevision.objects.filter(page=child_page_new).exclude(approved_go_live_at__isnull=True).exists() - ) - - def test_edit_post_publish_scheduled_published_page(self): - # Page is live - self.child_page.live = True - self.child_page.save() - - live_revision = self.child_page.live_revision - original_title = self.child_page.title - - go_live_at = timezone.now() + datetime.timedelta(days=1) - expire_at = timezone.now() + datetime.timedelta(days=2) - post_data = { - 'title': "I've been edited!", - 'content': "Some content", - 'slug': 'hello-world', - 'action-publish': "Publish", - 'go_live_at': submittable_timestamp(go_live_at), - 'expire_at': submittable_timestamp(expire_at), - } - response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) - - # Should be redirected to explorer page - self.assertEqual(response.status_code, 302) - - child_page_new = SimplePage.objects.get(id=self.child_page.id) - - # The page should still be live - self.assertTrue(child_page_new.live) - - self.assertEqual(child_page_new.status_string, "live + scheduled") - - # Instead a revision with approved_go_live_at should now exist - self.assertTrue( - PageRevision.objects.filter(page=child_page_new).exclude(approved_go_live_at__isnull=True).exists() - ) - - # The page SHOULD have the "has_unpublished_changes" flag set, - # because the changes are not visible as a live page yet - self.assertTrue( - child_page_new.has_unpublished_changes, - "A page scheduled for future publishing should have has_unpublished_changes=True" - ) - - self.assertNotEqual( - child_page_new.get_latest_revision(), live_revision, - "A page scheduled for future publishing should have a new revision, that is not the live revision" - ) - - self.assertEqual( - child_page_new.title, original_title, - "A live page with scheduled revisions should still have original content" - ) - - def test_edit_post_publish_now_an_already_scheduled_published_page(self): - # Unpublish the page - self.child_page.live = True - self.child_page.save() - - original_title = self.child_page.title - # First let's publish a page with a go_live_at in the future - go_live_at = timezone.now() + datetime.timedelta(days=1) - expire_at = timezone.now() + datetime.timedelta(days=2) - post_data = { - 'title': "I've been edited!", - 'content': "Some content", - 'slug': 'hello-world', - 'action-publish': "Publish", - 'go_live_at': submittable_timestamp(go_live_at), - 'expire_at': submittable_timestamp(expire_at), - } - response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) - - # Should be redirected to edit page - self.assertEqual(response.status_code, 302) - - child_page_new = SimplePage.objects.get(id=self.child_page.id) - - # The page should still be live - self.assertTrue(child_page_new.live) - - # Instead a revision with approved_go_live_at should now exist - self.assertTrue( - PageRevision.objects.filter(page=child_page_new).exclude(approved_go_live_at__isnull=True).exists() - ) - - self.assertEqual( - child_page_new.title, original_title, - "A live page with scheduled revisions should still have original content" - ) - - # Now, let's edit it and publish it right now - go_live_at = timezone.now() - post_data = { - 'title': "I've been edited!", - 'content': "Some content", - 'slug': 'hello-world', - 'action-publish': "Publish", - 'go_live_at': "", - } - response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) - - # Should be redirected to edit page - self.assertEqual(response.status_code, 302) - - child_page_new = SimplePage.objects.get(id=self.child_page.id) - - # The page should be live now - self.assertTrue(child_page_new.live) - - # And a revision with approved_go_live_at should not exist - self.assertFalse( - PageRevision.objects.filter(page=child_page_new).exclude(approved_go_live_at__isnull=True).exists() - ) - - self.assertEqual( - child_page_new.title, post_data['title'], - "A published page should have the new title" - ) - - def test_page_edit_post_submit(self): - # Create a moderator user for testing email - get_user_model().objects.create_superuser('moderator', 'moderator@email.com', 'password') - - # Tests submitting from edit page - post_data = { - 'title': "I've been edited!", - 'content': "Some content", - 'slug': 'hello-world', - 'action-submit': "Submit", - } - response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) - - # Should be redirected to explorer - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - - # The page should have "has_unpublished_changes" flag set - child_page_new = SimplePage.objects.get(id=self.child_page.id) - self.assertTrue(child_page_new.has_unpublished_changes) - - # The latest revision for the page should now be in moderation - self.assertTrue(child_page_new.get_latest_revision().submitted_for_moderation) - - # Check that the moderator got an email - self.assertEqual(len(mail.outbox), 1) - self.assertEqual(mail.outbox[0].to, ['moderator@email.com']) - self.assertEqual( - mail.outbox[0].subject, 'The page "Hello world!" has been submitted for moderation' - ) # Note: should this be "I've been edited!"? - - def test_page_edit_post_existing_slug(self): - # This tests the existing slug checking on page edit - - # Create a page - self.child_page = SimplePage(title="Hello world 2", slug="hello-world2", content="hello") - self.root_page.add_child(instance=self.child_page) - - # Attempt to change the slug to one thats already in use - post_data = { - 'title': "Hello world 2", - 'slug': 'hello-world', - 'action-submit': "Submit", - } - response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) - - # Should not be redirected (as the save should fail) - self.assertEqual(response.status_code, 200) - - # Check that a form error was raised - self.assertFormError(response, 'form', 'slug', "This slug is already in use") - - def test_preview_on_edit(self): - post_data = { - 'title': "I've been edited!", - 'content': "Some content", - 'slug': 'hello-world', - 'action-submit': "Submit", - } - preview_url = reverse('wagtailadmin_pages:preview_on_edit', - args=(self.child_page.id,)) - response = self.client.post(preview_url, post_data) - - # Check the JSON response - self.assertEqual(response.status_code, 200) - self.assertJSONEqual(response.content.decode(), {'is_valid': True}) - - response = self.client.get(preview_url) - - # Check the HTML response - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'tests/simple_page.html') - self.assertContains(response, "I've been edited!", html=True) - - def test_preview_on_edit_no_session_key(self): - preview_url = reverse('wagtailadmin_pages:preview_on_edit', - args=(self.child_page.id,)) - - # get() without corresponding post(), key not set. - response = self.client.get(preview_url) - - # Check the HTML response - self.assertEqual(response.status_code, 200) - - # We should have an error page because we are unable to - # preview; the page key was not in the session. - self.assertContains( - response, - "Wagtail - Preview error", - html=True - ) - self.assertContains( - response, - "

    Preview error

    ", - html=True - ) - - @modify_settings(ALLOWED_HOSTS={'append': 'childpage.example.com'}) - def test_preview_uses_correct_site(self): - # create a Site record for the child page - Site.objects.create(hostname='childpage.example.com', root_page=self.child_page) - - post_data = { - 'title': "I've been edited!", - 'content': "Some content", - 'slug': 'hello-world', - 'action-submit': "Submit", - } - preview_url = reverse('wagtailadmin_pages:preview_on_edit', - args=(self.child_page.id,)) - response = self.client.post(preview_url, post_data) - - # Check the JSON response - self.assertEqual(response.status_code, 200) - self.assertJSONEqual(response.content.decode(), {'is_valid': True}) - - response = self.client.get(preview_url) - - # Check that the correct site object has been selected by the site middleware - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'tests/simple_page.html') - self.assertEqual(response.context['request'].site.hostname, 'childpage.example.com') - - def test_editor_picks_up_direct_model_edits(self): - # If a page has no draft edits, the editor should show the version from the live database - # record rather than the latest revision record. This ensures that the edit interface - # reflects any changes made directly on the model. - self.child_page.title = "This title only exists on the live database record" - self.child_page.save() - - response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) - self.assertEqual(response.status_code, 200) - self.assertContains(response, "This title only exists on the live database record") - - def test_editor_does_not_pick_up_direct_model_edits_when_draft_edits_exist(self): - # If a page has draft edits, we should always show those in the editor, not the live - # database record - self.child_page.content = "Some content with a draft edit" - self.child_page.save_revision() - - # make an independent change to the live database record - self.child_page = SimplePage.objects.get(id=self.child_page.id) - self.child_page.title = "This title only exists on the live database record" - self.child_page.save() - - response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) - self.assertEqual(response.status_code, 200) - self.assertNotContains(response, "This title only exists on the live database record") - self.assertContains(response, "Some content with a draft edit") - - def test_editor_page_shows_live_url_in_status_when_draft_edits_exist(self): - # If a page has draft edits (ie. page has unpublished changes) - # that affect the URL (eg. slug) we should still ensure the - # status button at the top of the page links to the live URL - - self.child_page.content = "Some content with a draft edit" - self.child_page.slug = "revised-slug-in-draft-only" # live version contains 'hello-world' - self.child_page.save_revision() - - response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) - - link_to_draft = 'Current page status: live + draft' - link_to_live = 'Current page status: live + draft' - input_field_for_draft_slug = '' - input_field_for_live_slug = '' - - # Status Link should be the live page (not revision) - self.assertContains(response, link_to_live, html=True) - self.assertNotContains(response, link_to_draft, html=True) - - # Editing input for slug should be the draft revision - self.assertContains(response, input_field_for_draft_slug, html=True) - self.assertNotContains(response, input_field_for_live_slug, html=True) - - def test_editor_page_shows_custom_live_url_in_status_when_draft_edits_exist(self): - # When showing a live URL in the status button that differs from the draft one, - # ensure that we pick up any custom URL logic defined on the specific page model - - self.single_event_page.location = "The other side of Mars" - self.single_event_page.slug = "revised-slug-in-draft-only" # live version contains 'hello-world' - self.single_event_page.save_revision() - - response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.single_event_page.id, ))) - - link_to_draft = 'Current page status: live + draft' - link_to_live = 'Current page status: live + draft' - input_field_for_draft_slug = '' - input_field_for_live_slug = '' - - # Status Link should be the live page (not revision) - self.assertContains(response, link_to_live, html=True) - self.assertNotContains(response, link_to_draft, html=True) - - # Editing input for slug should be the draft revision - self.assertContains(response, input_field_for_draft_slug, html=True) - self.assertNotContains(response, input_field_for_live_slug, html=True) - - def test_before_edit_page_hook(self): - def hook_func(request, page): - self.assertIsInstance(request, HttpRequest) - self.assertEqual(page.id, self.child_page.id) - - return HttpResponse("Overridden!") - - with self.register_hook('before_edit_page', hook_func): - response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b"Overridden!") - - def test_before_edit_page_hook_post(self): - def hook_func(request, page): - self.assertIsInstance(request, HttpRequest) - self.assertEqual(page.id, self.child_page.id) - - return HttpResponse("Overridden!") - - with self.register_hook('before_edit_page', hook_func): - post_data = { - 'title': "I've been edited!", - 'content': "Some content", - 'slug': 'hello-world-new', - 'action-publish': "Publish", - } - response = self.client.post( - reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data - ) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b"Overridden!") - - # page should not be edited - self.assertEqual(Page.objects.get(id=self.child_page.id).title, "Hello world!") - - def test_after_edit_page_hook(self): - def hook_func(request, page): - self.assertIsInstance(request, HttpRequest) - self.assertEqual(page.id, self.child_page.id) - - return HttpResponse("Overridden!") - - with self.register_hook('after_edit_page', hook_func): - post_data = { - 'title': "I've been edited!", - 'content': "Some content", - 'slug': 'hello-world-new', - 'action-publish': "Publish", - } - response = self.client.post( - reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data - ) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b"Overridden!") - - # page should be edited - self.assertEqual(Page.objects.get(id=self.child_page.id).title, "I've been edited!") - - -class TestPageEditReordering(TestCase, WagtailTestUtils): - def setUp(self): - # Find root page - self.root_page = Page.objects.get(id=2) - - # Add event page - self.event_page = EventPage( - title="Event page", slug="event-page", - location='the moon', audience='public', - cost='free', date_from='2001-01-01', - ) - self.event_page.carousel_items = [ - EventPageCarouselItem(caption='1234567', sort_order=1), - EventPageCarouselItem(caption='7654321', sort_order=2), - EventPageCarouselItem(caption='abcdefg', sort_order=3), - ] - self.root_page.add_child(instance=self.event_page) - - # Login - self.user = self.login() - - def check_order(self, response, expected_order): - inline_panel = response.context['edit_handler'].children[0].children[9] - order = [child.form.instance.caption for child in inline_panel.children] - self.assertEqual(order, expected_order) - - def test_order(self): - response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.event_page.id, ))) - - self.assertEqual(response.status_code, 200) - self.check_order(response, ['1234567', '7654321', 'abcdefg']) - - def test_reorder(self): - post_data = { - 'title': "Event page", - 'slug': 'event-page', - - 'date_from': '01/01/2014', - 'cost': '$10', - 'audience': 'public', - 'location': 'somewhere', - - 'related_links-INITIAL_FORMS': 0, - 'related_links-MAX_NUM_FORMS': 1000, - 'related_links-TOTAL_FORMS': 0, - - 'speakers-INITIAL_FORMS': 0, - 'speakers-MAX_NUM_FORMS': 1000, - 'speakers-TOTAL_FORMS': 0, - - 'head_counts-INITIAL_FORMS': 0, - 'head_counts-MAX_NUM_FORMS': 1000, - 'head_counts-TOTAL_FORMS': 0, - - 'carousel_items-INITIAL_FORMS': 3, - 'carousel_items-MAX_NUM_FORMS': 1000, - 'carousel_items-TOTAL_FORMS': 3, - 'carousel_items-0-id': self.event_page.carousel_items.all()[0].id, - 'carousel_items-0-caption': self.event_page.carousel_items.all()[0].caption, - 'carousel_items-0-ORDER': 2, - 'carousel_items-1-id': self.event_page.carousel_items.all()[1].id, - 'carousel_items-1-caption': self.event_page.carousel_items.all()[1].caption, - 'carousel_items-1-ORDER': 3, - 'carousel_items-2-id': self.event_page.carousel_items.all()[2].id, - 'carousel_items-2-caption': self.event_page.carousel_items.all()[2].caption, - 'carousel_items-2-ORDER': 1, - } - response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.event_page.id, )), post_data) - - # Should be redirected back to same page - self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(self.event_page.id, ))) - - # Check order - response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.event_page.id, ))) - - self.assertEqual(response.status_code, 200) - self.check_order(response, ['abcdefg', '1234567', '7654321']) - - def test_reorder_with_validation_error(self): - post_data = { - 'title': "", # Validation error - 'slug': 'event-page', - - 'date_from': '01/01/2014', - 'cost': '$10', - 'audience': 'public', - 'location': 'somewhere', - - 'related_links-INITIAL_FORMS': 0, - 'related_links-MAX_NUM_FORMS': 1000, - 'related_links-TOTAL_FORMS': 0, - - 'speakers-INITIAL_FORMS': 0, - 'speakers-MAX_NUM_FORMS': 1000, - 'speakers-TOTAL_FORMS': 0, - - 'head_counts-INITIAL_FORMS': 0, - 'head_counts-MAX_NUM_FORMS': 1000, - 'head_counts-TOTAL_FORMS': 0, - - 'carousel_items-INITIAL_FORMS': 3, - 'carousel_items-MAX_NUM_FORMS': 1000, - 'carousel_items-TOTAL_FORMS': 3, - 'carousel_items-0-id': self.event_page.carousel_items.all()[0].id, - 'carousel_items-0-caption': self.event_page.carousel_items.all()[0].caption, - 'carousel_items-0-ORDER': 2, - 'carousel_items-1-id': self.event_page.carousel_items.all()[1].id, - 'carousel_items-1-caption': self.event_page.carousel_items.all()[1].caption, - 'carousel_items-1-ORDER': 3, - 'carousel_items-2-id': self.event_page.carousel_items.all()[2].id, - 'carousel_items-2-caption': self.event_page.carousel_items.all()[2].caption, - 'carousel_items-2-ORDER': 1, - } - response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.event_page.id, )), post_data) - - self.assertEqual(response.status_code, 200) - self.check_order(response, ['abcdefg', '1234567', '7654321']) - - -class TestPageDelete(TestCase, WagtailTestUtils): - def setUp(self): - # Find root page - self.root_page = Page.objects.get(id=2) - - # Add child page - self.child_page = SimplePage(title="Hello world!", slug="hello-world", content="hello") - self.root_page.add_child(instance=self.child_page) - - # Add a page with child pages of its own - self.child_index = StandardIndex(title="Hello index", slug='hello-index') - self.root_page.add_child(instance=self.child_index) - self.grandchild_page = StandardChild(title="Hello Kitty", slug='hello-kitty') - self.child_index.add_child(instance=self.grandchild_page) - - # Login - self.user = self.login() - - def test_page_delete(self): - response = self.client.get(reverse('wagtailadmin_pages:delete', args=(self.child_page.id, ))) - self.assertEqual(response.status_code, 200) - # deletion should not actually happen on GET - self.assertTrue(SimplePage.objects.filter(id=self.child_page.id).exists()) - - def test_page_delete_specific_admin_title(self): - response = self.client.get(reverse('wagtailadmin_pages:delete', args=(self.child_page.id, ))) - self.assertEqual(response.status_code, 200) - - # The admin_display_title specific to ChildPage is shown on the delete confirmation page. - self.assertContains(response, self.child_page.get_admin_display_title()) - - def test_page_delete_bad_permissions(self): - # Remove privileges from user - self.user.is_superuser = False - self.user.user_permissions.add( - Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') - ) - self.user.save() - - # Get delete page - response = self.client.get(reverse('wagtailadmin_pages:delete', args=(self.child_page.id, ))) - - # Check that the user received a 403 response - self.assertEqual(response.status_code, 403) - - # Check that the deletion has not happened - self.assertTrue(SimplePage.objects.filter(id=self.child_page.id).exists()) - - def test_page_delete_post(self): - # Connect a mock signal handler to page_unpublished signal - mock_handler = mock.MagicMock() - page_unpublished.connect(mock_handler) - - # Post - response = self.client.post(reverse('wagtailadmin_pages:delete', args=(self.child_page.id, ))) - - # Should be redirected to explorer page - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - - # treebeard should report no consistency problems with the tree - self.assertFalse(any(Page.find_problems()), 'treebeard found consistency problems') - - # Check that the page is gone - self.assertEqual(Page.objects.filter(path__startswith=self.root_page.path, slug='hello-world').count(), 0) - - # Check that the page_unpublished signal was fired - self.assertEqual(mock_handler.call_count, 1) - mock_call = mock_handler.mock_calls[0][2] - - self.assertEqual(mock_call['sender'], self.child_page.specific_class) - self.assertEqual(mock_call['instance'], self.child_page) - self.assertIsInstance(mock_call['instance'], self.child_page.specific_class) - - def test_page_delete_notlive_post(self): - # Same as above, but this makes sure the page_unpublished signal is not fired - # when if the page is not live when it is deleted - - # Unpublish the page - self.child_page.live = False - self.child_page.save() - - # Connect a mock signal handler to page_unpublished signal - mock_handler = mock.MagicMock() - page_unpublished.connect(mock_handler) - - # Post - response = self.client.post(reverse('wagtailadmin_pages:delete', args=(self.child_page.id, ))) - - # Should be redirected to explorer page - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - - # treebeard should report no consistency problems with the tree - self.assertFalse(any(Page.find_problems()), 'treebeard found consistency problems') - - # Check that the page is gone - self.assertEqual(Page.objects.filter(path__startswith=self.root_page.path, slug='hello-world').count(), 0) - - # Check that the page_unpublished signal was not fired - self.assertEqual(mock_handler.call_count, 0) - - def test_subpage_deletion(self): - # Connect mock signal handlers to page_unpublished, pre_delete and post_delete signals - unpublish_signals_received = [] - pre_delete_signals_received = [] - post_delete_signals_received = [] - - def page_unpublished_handler(sender, instance, **kwargs): - unpublish_signals_received.append((sender, instance.id)) - - def pre_delete_handler(sender, instance, **kwargs): - pre_delete_signals_received.append((sender, instance.id)) - - def post_delete_handler(sender, instance, **kwargs): - post_delete_signals_received.append((sender, instance.id)) - - page_unpublished.connect(page_unpublished_handler) - pre_delete.connect(pre_delete_handler) - post_delete.connect(post_delete_handler) - - # Post - response = self.client.post(reverse('wagtailadmin_pages:delete', args=(self.child_index.id, ))) - - # Should be redirected to explorer page - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - - # treebeard should report no consistency problems with the tree - self.assertFalse(any(Page.find_problems()), 'treebeard found consistency problems') - - # Check that the page is gone - self.assertFalse(StandardIndex.objects.filter(id=self.child_index.id).exists()) - self.assertFalse(Page.objects.filter(id=self.child_index.id).exists()) - - # Check that the subpage is also gone - self.assertFalse(StandardChild.objects.filter(id=self.grandchild_page.id).exists()) - self.assertFalse(Page.objects.filter(id=self.grandchild_page.id).exists()) - - # Check that the signals were fired for both pages - self.assertIn((StandardIndex, self.child_index.id), unpublish_signals_received) - self.assertIn((StandardChild, self.grandchild_page.id), unpublish_signals_received) - - self.assertIn((StandardIndex, self.child_index.id), pre_delete_signals_received) - self.assertIn((StandardChild, self.grandchild_page.id), pre_delete_signals_received) - - self.assertIn((StandardIndex, self.child_index.id), post_delete_signals_received) - self.assertIn((StandardChild, self.grandchild_page.id), post_delete_signals_received) - - def test_before_delete_page_hook(self): - def hook_func(request, page): - self.assertIsInstance(request, HttpRequest) - self.assertEqual(page.id, self.child_page.id) - - return HttpResponse("Overridden!") - - with self.register_hook('before_delete_page', hook_func): - response = self.client.get(reverse('wagtailadmin_pages:delete', args=(self.child_page.id, ))) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b"Overridden!") - - def test_before_delete_page_hook_post(self): - def hook_func(request, page): - self.assertIsInstance(request, HttpRequest) - self.assertEqual(page.id, self.child_page.id) - - return HttpResponse("Overridden!") - - with self.register_hook('before_delete_page', hook_func): - response = self.client.post(reverse('wagtailadmin_pages:delete', args=(self.child_page.id, ))) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b"Overridden!") - - # page should not be deleted - self.assertTrue(Page.objects.filter(id=self.child_page.id).exists()) - - def test_after_delete_page_hook(self): - def hook_func(request, page): - self.assertIsInstance(request, HttpRequest) - self.assertEqual(page.id, self.child_page.id) - - return HttpResponse("Overridden!") - - with self.register_hook('after_delete_page', hook_func): - response = self.client.post(reverse('wagtailadmin_pages:delete', args=(self.child_page.id, ))) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b"Overridden!") - - # page should be deleted - self.assertFalse(Page.objects.filter(id=self.child_page.id).exists()) - - -class TestPageSearch(TestCase, WagtailTestUtils): - def setUp(self): - self.user = self.login() - - def get(self, params=None, **extra): - return self.client.get(reverse('wagtailadmin_pages:search'), params or {}, **extra) - - def test_view(self): - response = self.get() - self.assertTemplateUsed(response, 'wagtailadmin/pages/search.html') - self.assertEqual(response.status_code, 200) - - def test_search(self): - response = self.get({'q': "Hello"}) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailadmin/pages/search.html') - self.assertEqual(response.context['query_string'], "Hello") - - def test_search_searchable_fields(self): - # Find root page - root_page = Page.objects.get(id=2) - - # Create a page - root_page.add_child(instance=SimplePage( - title="Hi there!", slug='hello-world', content="good morning", - live=True, - has_unpublished_changes=False, - )) - - # Confirm the slug is not being searched - response = self.get({'q': "hello"}) - self.assertNotContains(response, "There is one matching page") - search_fields = Page.search_fields - - # Add slug to the search_fields - Page.search_fields = Page.search_fields + [SearchField('slug', partial_match=True)] - - # Confirm the slug is being searched - response = self.get({'q': "hello"}) - self.assertContains(response, "There is one matching page") - - # Reset the search fields - Page.search_fields = search_fields - - def test_ajax(self): - response = self.get({'q': "Hello"}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') - self.assertEqual(response.status_code, 200) - self.assertTemplateNotUsed(response, 'wagtailadmin/pages/search.html') - self.assertTemplateUsed(response, 'wagtailadmin/pages/search_results.html') - self.assertEqual(response.context['query_string'], "Hello") - - def test_pagination(self): - pages = ['0', '1', '-1', '9999', 'Not a page'] - for page in pages: - response = self.get({'q': "Hello", 'p': page}) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailadmin/pages/search.html') - - def test_root_can_appear_in_search_results(self): - response = self.get({'q': "roo"}) - self.assertEqual(response.status_code, 200) - # 'pages' list in the response should contain root - results = response.context['pages'] - self.assertTrue(any([r.slug == 'root' for r in results])) - - def test_search_uses_admin_display_title_from_specific_class(self): - # SingleEventPage has a custom get_admin_display_title method; explorer should - # show the custom title rather than the basic database one - root_page = Page.objects.get(id=2) - new_event = SingleEventPage( - title="Lunar event", - location='the moon', audience='public', - cost='free', date_from='2001-01-01', - latest_revision_created_at=local_datetime(2016, 1, 1) - ) - root_page.add_child(instance=new_event) - response = self.get({'q': "lunar"}) - self.assertContains(response, "Lunar event (single event)") - - def test_search_no_perms(self): - self.user.is_superuser = False - self.user.user_permissions.add( - Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') - ) - self.user.save() - self.assertRedirects(self.get(), '/admin/') - - def test_search_order_by_title(self): - root_page = Page.objects.get(id=2) - new_event = SingleEventPage( - title="Lunar event", - location='the moon', audience='public', - cost='free', date_from='2001-01-01', - latest_revision_created_at=local_datetime(2016, 1, 1) - ) - root_page.add_child(instance=new_event) - - new_event_2 = SingleEventPage( - title="A Lunar event", - location='the moon', audience='public', - cost='free', date_from='2001-01-01', - latest_revision_created_at=local_datetime(2016, 1, 1) - ) - root_page.add_child(instance=new_event_2) - - response = self.get({'q': 'Lunar', 'ordering': 'title'}) - page_ids = [page.id for page in response.context['pages']] - self.assertEqual(page_ids, [new_event_2.id, new_event.id]) - - response = self.get({'q': 'Lunar', 'ordering': '-title'}) - page_ids = [page.id for page in response.context['pages']] - self.assertEqual(page_ids, [new_event.id, new_event_2.id]) - - def test_search_order_by_updated(self): - root_page = Page.objects.get(id=2) - new_event = SingleEventPage( - title="Lunar event", - location='the moon', audience='public', - cost='free', date_from='2001-01-01', - latest_revision_created_at=local_datetime(2016, 1, 1) - ) - root_page.add_child(instance=new_event) - - new_event_2 = SingleEventPage( - title="Lunar event 2", - location='the moon', audience='public', - cost='free', date_from='2001-01-01', - latest_revision_created_at=local_datetime(2015, 1, 1) - ) - root_page.add_child(instance=new_event_2) - - response = self.get({'q': 'Lunar', 'ordering': 'latest_revision_created_at'}) - page_ids = [page.id for page in response.context['pages']] - self.assertEqual(page_ids, [new_event_2.id, new_event.id]) - - response = self.get({'q': 'Lunar', 'ordering': '-latest_revision_created_at'}) - page_ids = [page.id for page in response.context['pages']] - self.assertEqual(page_ids, [new_event.id, new_event_2.id]) - - def test_search_order_by_status(self): - root_page = Page.objects.get(id=2) - live_event = SingleEventPage( - title="Lunar event", - location='the moon', audience='public', - cost='free', date_from='2001-01-01', - latest_revision_created_at=local_datetime(2016, 1, 1), - live=True - ) - root_page.add_child(instance=live_event) - - draft_event = SingleEventPage( - title="Lunar event", - location='the moon', audience='public', - cost='free', date_from='2001-01-01', - latest_revision_created_at=local_datetime(2016, 1, 1), - live=False - ) - root_page.add_child(instance=draft_event) - - response = self.get({'q': 'Lunar', 'ordering': 'live'}) - page_ids = [page.id for page in response.context['pages']] - self.assertEqual(page_ids, [draft_event.id, live_event.id]) - - response = self.get({'q': 'Lunar', 'ordering': '-live'}) - page_ids = [page.id for page in response.context['pages']] - self.assertEqual(page_ids, [live_event.id, draft_event.id]) - - -class TestPageMove(TestCase, WagtailTestUtils): - def setUp(self): - # Find root page - self.root_page = Page.objects.get(id=2) - - # Create three sections - self.section_a = SimplePage(title="Section A", slug="section-a", content="hello") - self.root_page.add_child(instance=self.section_a) - - self.section_b = SimplePage(title="Section B", slug="section-b", content="hello") - self.root_page.add_child(instance=self.section_b) - - self.section_c = SimplePage(title="Section C", slug="section-c", content="hello") - self.root_page.add_child(instance=self.section_c) - - # Add test page A into section A - self.test_page_a = SimplePage(title="Hello world!", slug="hello-world", content="hello") - self.section_a.add_child(instance=self.test_page_a) - - # Add test page B into section C - self.test_page_b = SimplePage(title="Hello world!", slug="hello-world", content="hello") - self.section_c.add_child(instance=self.test_page_b) - - # Login - self.user = self.login() - - def test_page_move(self): - response = self.client.get(reverse('wagtailadmin_pages:move', args=(self.test_page_a.id, ))) - self.assertEqual(response.status_code, 200) - - def test_page_move_bad_permissions(self): - # Remove privileges from user - self.user.is_superuser = False - self.user.user_permissions.add( - Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') - ) - self.user.save() - - # Get move page - response = self.client.get(reverse('wagtailadmin_pages:move', args=(self.test_page_a.id, ))) - - # Check that the user received a 403 response - self.assertEqual(response.status_code, 403) - - def test_page_move_confirm(self): - response = self.client.get( - reverse('wagtailadmin_pages:move_confirm', args=(self.test_page_a.id, self.section_b.id)) - ) - self.assertEqual(response.status_code, 200) - - response = self.client.get( - reverse('wagtailadmin_pages:move_confirm', args=(self.test_page_b.id, self.section_a.id)) - ) - # Duplicate slugs triggers a redirect with an error message. - self.assertEqual(response.status_code, 302) - - response = self.client.get(reverse('wagtailadmin_home')) - messages = list(response.context['messages']) - self.assertEqual(len(messages), 1) - self.assertEqual(messages[0].level, message_constants.ERROR) - # Slug should be in error message. - self.assertIn("{}".format(self.test_page_b.slug), messages[0].message) - - def test_page_set_page_position(self): - response = self.client.get(reverse('wagtailadmin_pages:set_page_position', args=(self.test_page_a.id, ))) - self.assertEqual(response.status_code, 200) - - def test_before_move_page_hook(self): - def hook_func(request, page, destination): - self.assertIsInstance(request, HttpRequest) - self.assertIsInstance(page.specific, SimplePage) - self.assertIsInstance(destination.specific, SimplePage) - - return HttpResponse("Overridden!") - - with self.register_hook('before_move_page', hook_func): - response = self.client.get(reverse('wagtailadmin_pages:move_confirm', args=(self.test_page_a.id, self.section_b.id))) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b"Overridden!") - - def test_before_move_page_hook_post(self): - def hook_func(request, page, destination): - self.assertIsInstance(request, HttpRequest) - self.assertIsInstance(page.specific, SimplePage) - self.assertIsInstance(destination.specific, SimplePage) - - return HttpResponse("Overridden!") - - with self.register_hook('before_move_page', hook_func): - response = self.client.post(reverse('wagtailadmin_pages:move_confirm', args=(self.test_page_a.id, self.section_b.id))) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b"Overridden!") - - # page should not be moved - self.assertEqual( - Page.objects.get(id=self.test_page_a.id).get_parent().id, - self.section_a.id - ) - - def test_after_move_page_hook(self): - def hook_func(request, page): - self.assertIsInstance(request, HttpRequest) - self.assertIsInstance(page.specific, SimplePage) - - return HttpResponse("Overridden!") - - with self.register_hook('after_move_page', hook_func): - response = self.client.post(reverse('wagtailadmin_pages:move_confirm', args=(self.test_page_a.id, self.section_b.id))) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b"Overridden!") - - # page should be moved - self.assertEqual( - Page.objects.get(id=self.test_page_a.id).get_parent().id, - self.section_b.id - ) - - -class TestPageCopy(TestCase, WagtailTestUtils): - - def setUp(self): - # Find root page - self.root_page = Page.objects.get(id=2) - - # Create a page - self.test_page = self.root_page.add_child(instance=SimplePage( - title="Hello world!", - slug='hello-world', - content="hello", - live=True, - has_unpublished_changes=False, - )) - - # Create a couple of child pages - self.test_child_page = self.test_page.add_child(instance=SimplePage( - title="Child page", - slug='child-page', - content="hello", - live=True, - has_unpublished_changes=True, - )) - - self.test_unpublished_child_page = self.test_page.add_child(instance=SimplePage( - title="Unpublished Child page", - slug='unpublished-child-page', - content="hello", - live=False, - has_unpublished_changes=True, - )) - - # Login - self.user = self.login() - - def test_page_copy(self): - response = self.client.get(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, ))) - - # Check response - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailadmin/pages/copy.html') - - # Make sure all fields are in the form - self.assertContains(response, "New title") - self.assertContains(response, "New slug") - self.assertContains(response, "New parent page") - self.assertContains(response, "Copy subpages") - self.assertContains(response, "Publish copies") - - def test_page_copy_bad_permissions(self): - # Remove privileges from user - self.user.is_superuser = False - self.user.user_permissions.add( - Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') - ) - self.user.save() - - # Get copy page - post_data = { - 'new_title': "Hello world 2", - 'new_slug': 'hello-world', - 'new_parent_page': str(self.test_page.id), - 'copy_subpages': False, - } - response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) - - # A user with no page permissions at all should be redirected to the admin home - self.assertRedirects(response, reverse('wagtailadmin_home')) - - # A user with page permissions, but not add permission at the destination, - # should receive a form validation error - publishers = Group.objects.create(name='Publishers') - GroupPagePermission.objects.create( - group=publishers, page=self.root_page, permission_type='publish' - ) - self.user.groups.add(publishers) - self.user.save() - - # Get copy page - post_data = { - 'new_title': "Hello world 2", - 'new_slug': 'hello-world', - 'new_parent_page': str(self.test_page.id), - 'copy_subpages': False, - } - response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) - form = response.context['form'] - self.assertFalse(form.is_valid()) - self.assertTrue('new_parent_page' in form.errors) - - def test_page_copy_post(self): - post_data = { - 'new_title': "Hello world 2", - 'new_slug': 'hello-world-2', - 'new_parent_page': str(self.root_page.id), - 'copy_subpages': False, - 'publish_copies': False, - } - response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) - - # Check that the user was redirected to the parents explore page - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - - # Get copy - page_copy = self.root_page.get_children().filter(slug='hello-world-2').first() - - # Check that the copy exists - self.assertNotEqual(page_copy, None) - - # Check that the copy is not live - self.assertFalse(page_copy.live) - self.assertTrue(page_copy.has_unpublished_changes) - - # Check that the owner of the page is set correctly - self.assertEqual(page_copy.owner, self.user) - - # Check that the children were not copied - self.assertEqual(page_copy.get_children().count(), 0) - - # treebeard should report no consistency problems with the tree - self.assertFalse(any(Page.find_problems()), 'treebeard found consistency problems') - - def test_page_copy_post_copy_subpages(self): - post_data = { - 'new_title': "Hello world 2", - 'new_slug': 'hello-world-2', - 'new_parent_page': str(self.root_page.id), - 'copy_subpages': True, - 'publish_copies': False, - } - response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) - - # Check that the user was redirected to the parents explore page - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - - # Get copy - page_copy = self.root_page.get_children().filter(slug='hello-world-2').first() - - # Check that the copy exists - self.assertNotEqual(page_copy, None) - - # Check that the copy is not live - self.assertFalse(page_copy.live) - self.assertTrue(page_copy.has_unpublished_changes) - - # Check that the owner of the page is set correctly - self.assertEqual(page_copy.owner, self.user) - - # Check that the children were copied - self.assertEqual(page_copy.get_children().count(), 2) - - # Check the the child pages - # Neither of them should be live - child_copy = page_copy.get_children().filter(slug='child-page').first() - self.assertNotEqual(child_copy, None) - self.assertFalse(child_copy.live) - self.assertTrue(child_copy.has_unpublished_changes) - - unpublished_child_copy = page_copy.get_children().filter(slug='unpublished-child-page').first() - self.assertNotEqual(unpublished_child_copy, None) - self.assertFalse(unpublished_child_copy.live) - self.assertTrue(unpublished_child_copy.has_unpublished_changes) - - # treebeard should report no consistency problems with the tree - self.assertFalse(any(Page.find_problems()), 'treebeard found consistency problems') - - def test_page_copy_post_copy_subpages_publish_copies(self): - post_data = { - 'new_title': "Hello world 2", - 'new_slug': 'hello-world-2', - 'new_parent_page': str(self.root_page.id), - 'copy_subpages': True, - 'publish_copies': True, - } - response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) - - # Check that the user was redirected to the parents explore page - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - - # Get copy - page_copy = self.root_page.get_children().filter(slug='hello-world-2').first() - - # Check that the copy exists - self.assertNotEqual(page_copy, None) - - # Check that the copy is live - self.assertTrue(page_copy.live) - self.assertFalse(page_copy.has_unpublished_changes) - - # Check that the owner of the page is set correctly - self.assertEqual(page_copy.owner, self.user) - - # Check that the children were copied - self.assertEqual(page_copy.get_children().count(), 2) - - # Check the the child pages - # The child_copy should be live but the unpublished_child_copy shouldn't - child_copy = page_copy.get_children().filter(slug='child-page').first() - self.assertNotEqual(child_copy, None) - self.assertTrue(child_copy.live) - self.assertTrue(child_copy.has_unpublished_changes) - - unpublished_child_copy = page_copy.get_children().filter(slug='unpublished-child-page').first() - self.assertNotEqual(unpublished_child_copy, None) - self.assertFalse(unpublished_child_copy.live) - self.assertTrue(unpublished_child_copy.has_unpublished_changes) - - # treebeard should report no consistency problems with the tree - self.assertFalse(any(Page.find_problems()), 'treebeard found consistency problems') - - def test_page_copy_post_new_parent(self): - post_data = { - 'new_title': "Hello world 2", - 'new_slug': 'hello-world-2', - 'new_parent_page': str(self.test_child_page.id), - 'copy_subpages': False, - 'publish_copies': False, - } - response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) - - # Check that the user was redirected to the new parents explore page - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.test_child_page.id, ))) - - # Check that the page was copied to the correct place - self.assertTrue(Page.objects.filter(slug='hello-world-2').first().get_parent(), self.test_child_page) - - # treebeard should report no consistency problems with the tree - self.assertFalse(any(Page.find_problems()), 'treebeard found consistency problems') - - def test_page_copy_post_existing_slug_within_same_parent_page(self): - # This tests the existing slug checking on page copy when not changing the parent page - - # Attempt to copy the page but forget to change the slug - post_data = { - 'new_title': "Hello world 2", - 'new_slug': 'hello-world', - 'new_parent_page': str(self.root_page.id), - 'copy_subpages': False, - } - response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) - - # Should not be redirected (as the save should fail) - self.assertEqual(response.status_code, 200) - - # Check that a form error was raised - self.assertFormError( - response, - 'form', - 'new_slug', - "This slug is already in use within the context of its parent page \"Welcome to your new Wagtail site!\"" - ) - - def test_page_copy_post_and_subpages_to_same_tree_branch(self): - # This tests that a page cannot be copied into itself when copying subpages - post_data = { - 'new_title': "Hello world 2", - 'new_slug': 'hello-world', - 'new_parent_page': str(self.test_child_page.id), - 'copy_subpages': True, - } - response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id,)), post_data) - - # Should not be redirected (as the save should fail) - self.assertEqual(response.status_code, 200) - - # Check that a form error was raised - self.assertFormError( - response, 'form', 'new_parent_page', "You cannot copy a page into itself when copying subpages" - ) - - def test_page_copy_post_existing_slug_to_another_parent_page(self): - # This tests the existing slug checking on page copy when changing the parent page - - # Attempt to copy the page and changed the parent page - post_data = { - 'new_title': "Hello world 2", - 'new_slug': 'hello-world', - 'new_parent_page': str(self.test_child_page.id), - 'copy_subpages': False, - } - response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) - - # Check that the user was redirected to the parents explore page - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.test_child_page.id, ))) - - def test_page_copy_post_invalid_slug(self): - # Attempt to copy the page but set an invalid slug string - post_data = { - 'new_title': "Hello world 2", - 'new_slug': 'hello world!', - 'new_parent_page': str(self.root_page.id), - 'copy_subpages': False, - } - response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) - - # Should not be redirected (as the save should fail) - self.assertEqual(response.status_code, 200) - - # Check that a form error was raised - if DJANGO_VERSION >= (3, 0): - self.assertFormError( - response, 'form', 'new_slug', "Enter a valid “slug” consisting of letters, numbers, underscores or hyphens." - ) - else: - self.assertFormError( - response, 'form', 'new_slug', "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." - ) - - def test_page_copy_no_publish_permission(self): - # Turn user into an editor who can add pages but not publish them - self.user.is_superuser = False - self.user.groups.add( - Group.objects.get(name="Editors"), - ) - self.user.save() - - # Get copy page - response = self.client.get(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, ))) - - # The user should have access to the copy page - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailadmin/pages/copy.html') - - # Make sure the "publish copies" field is hidden - self.assertNotContains(response, "Publish copies") - - def test_page_copy_no_publish_permission_post_copy_subpages_publish_copies(self): - # This tests that unprivileged users cannot publish copied pages even if they hack their browser - - # Turn user into an editor who can add pages but not publish them - self.user.is_superuser = False - self.user.groups.add( - Group.objects.get(name="Editors"), - ) - self.user.save() - - # Post - post_data = { - 'new_title': "Hello world 2", - 'new_slug': 'hello-world-2', - 'new_parent_page': str(self.root_page.id), - 'copy_subpages': True, - 'publish_copies': True, - } - response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) - - # Check that the user was redirected to the parents explore page - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - - # Get copy - page_copy = self.root_page.get_children().filter(slug='hello-world-2').first() - - # Check that the copy exists - self.assertNotEqual(page_copy, None) - - # Check that the copy is not live - self.assertFalse(page_copy.live) - - # Check that the owner of the page is set correctly - self.assertEqual(page_copy.owner, self.user) - - # Check that the children were copied - self.assertEqual(page_copy.get_children().count(), 2) - - # Check the the child pages - # Neither of them should be live - child_copy = page_copy.get_children().filter(slug='child-page').first() - self.assertNotEqual(child_copy, None) - self.assertFalse(child_copy.live) - - unpublished_child_copy = page_copy.get_children().filter(slug='unpublished-child-page').first() - self.assertNotEqual(unpublished_child_copy, None) - self.assertFalse(unpublished_child_copy.live) - - # treebeard should report no consistency problems with the tree - self.assertFalse(any(Page.find_problems()), 'treebeard found consistency problems') - - def test_before_copy_page_hook(self): - def hook_func(request, page): - self.assertIsInstance(request, HttpRequest) - self.assertIsInstance(page.specific, SimplePage) - - return HttpResponse("Overridden!") - - with self.register_hook('before_copy_page', hook_func): - response = self.client.get(reverse('wagtailadmin_pages:copy', args=(self.test_page.id,))) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b"Overridden!") - - def test_before_copy_page_hook_post(self): - def hook_func(request, page): - self.assertIsInstance(request, HttpRequest) - self.assertIsInstance(page.specific, SimplePage) - - return HttpResponse("Overridden!") - - with self.register_hook('before_copy_page', hook_func): - post_data = { - 'new_title': "Hello world 2", - 'new_slug': 'hello-world-2', - 'new_parent_page': str(self.root_page.id), - 'copy_subpages': False, - 'publish_copies': False, - } - response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id,)), post_data) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b"Overridden!") - - # page should not be copied - self.assertFalse(Page.objects.filter(title="Hello world 2").exists()) - - def test_after_copy_page_hook(self): - def hook_func(request, page, new_page): - self.assertIsInstance(request, HttpRequest) - self.assertIsInstance(page.specific, SimplePage) - self.assertIsInstance(new_page.specific, SimplePage) - - return HttpResponse("Overridden!") - - with self.register_hook('after_copy_page', hook_func): - post_data = { - 'new_title': "Hello world 2", - 'new_slug': 'hello-world-2', - 'new_parent_page': str(self.root_page.id), - 'copy_subpages': False, - 'publish_copies': False, - } - response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id,)), post_data) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b"Overridden!") - - # page should be copied - self.assertTrue(Page.objects.filter(title="Hello world 2").exists()) - - -class TestPageUnpublish(TestCase, WagtailTestUtils): - def setUp(self): - self.user = self.login() - - # Create a page to unpublish - self.root_page = Page.objects.get(id=2) - self.page = SimplePage( - title="Hello world!", - slug='hello-world', - content="hello", - live=True, - ) - self.root_page.add_child(instance=self.page) - - def test_unpublish_view(self): - """ - This tests that the unpublish view responds with an unpublish confirm page - """ - # Get unpublish page - response = self.client.get(reverse('wagtailadmin_pages:unpublish', args=(self.page.id, ))) - - # Check that the user received an unpublish confirm page - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailadmin/pages/confirm_unpublish.html') - - def test_unpublish_view_invalid_page_id(self): - """ - This tests that the unpublish view returns an error if the page id is invalid - """ - # Get unpublish page - response = self.client.get(reverse('wagtailadmin_pages:unpublish', args=(12345, ))) - - # Check that the user received a 404 response - self.assertEqual(response.status_code, 404) - - def test_unpublish_view_bad_permissions(self): - """ - This tests that the unpublish view doesn't allow users without unpublish permissions - """ - # Remove privileges from user - self.user.is_superuser = False - self.user.user_permissions.add( - Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') - ) - self.user.save() - - # Get unpublish page - response = self.client.get(reverse('wagtailadmin_pages:unpublish', args=(self.page.id, ))) - - # Check that the user received a 403 response - self.assertEqual(response.status_code, 403) - - def test_unpublish_view_post(self): - """ - This posts to the unpublish view and checks that the page was unpublished - """ - # Connect a mock signal handler to page_unpublished signal - mock_handler = mock.MagicMock() - page_unpublished.connect(mock_handler) - - # Post to the unpublish page - response = self.client.post(reverse('wagtailadmin_pages:unpublish', args=(self.page.id, ))) - - # Should be redirected to explorer page - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - - # Check that the page was unpublished - self.assertFalse(SimplePage.objects.get(id=self.page.id).live) - - # Check that the page_unpublished signal was fired - self.assertEqual(mock_handler.call_count, 1) - mock_call = mock_handler.mock_calls[0][2] - - self.assertEqual(mock_call['sender'], self.page.specific_class) - self.assertEqual(mock_call['instance'], self.page) - self.assertIsInstance(mock_call['instance'], self.page.specific_class) - - def test_unpublish_descendants_view(self): - """ - This tests that the unpublish view responds with an unpublish confirm page that does not contain the form field 'include_descendants' - """ - # Get unpublish page - response = self.client.get(reverse('wagtailadmin_pages:unpublish', args=(self.page.id, ))) - - # Check that the user received an unpublish confirm page - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailadmin/pages/confirm_unpublish.html') - # Check the form does not contain the checkbox field include_descendants - self.assertNotContains(response, '') - - -class TestPageUnpublishIncludingDescendants(TestCase, WagtailTestUtils): - def setUp(self): - self.user = self.login() - # Find root page - self.root_page = Page.objects.get(id=2) - - # Create a page to unpublish - self.test_page = self.root_page.add_child(instance=SimplePage( - title="Hello world!", - slug='hello-world', - content="hello", - live=True, - has_unpublished_changes=False, - )) - - # Create a couple of child pages - self.test_child_page = self.test_page.add_child(instance=SimplePage( - title="Child page", - slug='child-page', - content="hello", - live=True, - has_unpublished_changes=True, - )) - - self.test_another_child_page = self.test_page.add_child(instance=SimplePage( - title="Another Child page", - slug='another-child-page', - content="hello", - live=True, - has_unpublished_changes=True, - )) - - def test_unpublish_descendants_view(self): - """ - This tests that the unpublish view responds with an unpublish confirm page that contains the form field 'include_descendants' - """ - # Get unpublish page - response = self.client.get(reverse('wagtailadmin_pages:unpublish', args=(self.test_page.id, ))) - - # Check that the user received an unpublish confirm page - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailadmin/pages/confirm_unpublish.html') - # Check the form contains the checkbox field include_descendants - self.assertContains(response, '') - - def test_unpublish_include_children_view_post(self): - """ - This posts to the unpublish view and checks that the page and its descendants were unpublished - """ - # Post to the unpublish page - response = self.client.post(reverse('wagtailadmin_pages:unpublish', args=(self.test_page.id, )), {'include_descendants': 'on'}) - - # Should be redirected to explorer page - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - - # Check that the page was unpublished - self.assertFalse(SimplePage.objects.get(id=self.test_page.id).live) - - # Check that the descendant pages were unpiblished as well - self.assertFalse(SimplePage.objects.get(id=self.test_child_page.id).live) - self.assertFalse(SimplePage.objects.get(id=self.test_another_child_page.id).live) - - def test_unpublish_not_include_children_view_post(self): - """ - This posts to the unpublish view and checks that the page was unpublished but its descendants were not - """ - # Post to the unpublish page - response = self.client.post(reverse('wagtailadmin_pages:unpublish', args=(self.test_page.id, )), {}) - - # Should be redirected to explorer page - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - - # Check that the page was unpublished - self.assertFalse(SimplePage.objects.get(id=self.test_page.id).live) - - # Check that the descendant pages were not unpublished - self.assertTrue(SimplePage.objects.get(id=self.test_child_page.id).live) - self.assertTrue(SimplePage.objects.get(id=self.test_another_child_page.id).live) - - -class TestApproveRejectModeration(TestCase, WagtailTestUtils): - def setUp(self): - self.submitter = get_user_model().objects.create_superuser( - username='submitter', - email='submitter@email.com', - password='password', - ) - - self.user = self.login() - - # Create a page and submit it for moderation - root_page = Page.objects.get(id=2) - self.page = SimplePage( - title="Hello world!", - slug='hello-world', - content="hello", - live=False, - has_unpublished_changes=True, - ) - root_page.add_child(instance=self.page) - - self.page.save_revision(user=self.submitter, submitted_for_moderation=True) - self.revision = self.page.get_latest_revision() - - def test_approve_moderation_view(self): - """ - This posts to the approve moderation view and checks that the page was approved - """ - # Connect a mock signal handler to page_published signal - mock_handler = mock.MagicMock() - page_published.connect(mock_handler) - - # Post - response = self.client.post(reverse('wagtailadmin_pages:approve_moderation', args=(self.revision.id, ))) - - # Check that the user was redirected to the dashboard - self.assertRedirects(response, reverse('wagtailadmin_home')) - - page = Page.objects.get(id=self.page.id) - # Page must be live - self.assertTrue(page.live, "Approving moderation failed to set live=True") - # Page should now have no unpublished changes - self.assertFalse( - page.has_unpublished_changes, - "Approving moderation failed to set has_unpublished_changes=False" - ) - - # Check that the page_published signal was fired - self.assertEqual(mock_handler.call_count, 1) - mock_call = mock_handler.mock_calls[0][2] - - self.assertEqual(mock_call['sender'], self.page.specific_class) - self.assertEqual(mock_call['instance'], self.page) - self.assertIsInstance(mock_call['instance'], self.page.specific_class) - - def test_approve_moderation_when_later_revision_exists(self): - self.page.title = "Goodbye world!" - self.page.save_revision(user=self.submitter, submitted_for_moderation=False) - - response = self.client.post(reverse('wagtailadmin_pages:approve_moderation', args=(self.revision.id, ))) - - # Check that the user was redirected to the dashboard - self.assertRedirects(response, reverse('wagtailadmin_home')) - - page = Page.objects.get(id=self.page.id) - # Page must be live - self.assertTrue(page.live, "Approving moderation failed to set live=True") - # Page content should be the submitted version, not the published one - self.assertEqual(page.title, "Hello world!") - # Page should still have unpublished changes - self.assertTrue( - page.has_unpublished_changes, - "has_unpublished_changes incorrectly cleared on approve_moderation when a later revision exists" - ) - - def test_approve_moderation_view_bad_revision_id(self): - """ - This tests that the approve moderation view handles invalid revision ids correctly - """ - # Post - response = self.client.post(reverse('wagtailadmin_pages:approve_moderation', args=(12345, ))) - - # Check that the user received a 404 response - self.assertEqual(response.status_code, 404) - - def test_approve_moderation_view_bad_permissions(self): - """ - This tests that the approve moderation view doesn't allow users without moderation permissions - """ - # Remove privileges from user - self.user.is_superuser = False - self.user.user_permissions.add( - Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') - ) - self.user.save() - - # Post - response = self.client.post(reverse('wagtailadmin_pages:approve_moderation', args=(self.revision.id, ))) - - # Check that the user received a 403 response - self.assertEqual(response.status_code, 403) - - def test_reject_moderation_view(self): - """ - This posts to the reject moderation view and checks that the page was rejected - """ - # Post - response = self.client.post(reverse('wagtailadmin_pages:reject_moderation', args=(self.revision.id, ))) - - # Check that the user was redirected to the dashboard - self.assertRedirects(response, reverse('wagtailadmin_home')) - - # Page must not be live - self.assertFalse(Page.objects.get(id=self.page.id).live) - - # Revision must no longer be submitted for moderation - self.assertFalse(PageRevision.objects.get(id=self.revision.id).submitted_for_moderation) - - def test_reject_moderation_view_bad_revision_id(self): - """ - This tests that the reject moderation view handles invalid revision ids correctly - """ - # Post - response = self.client.post(reverse('wagtailadmin_pages:reject_moderation', args=(12345, ))) - - # Check that the user received a 404 response - self.assertEqual(response.status_code, 404) - - def test_reject_moderation_view_bad_permissions(self): - """ - This tests that the reject moderation view doesn't allow users without moderation permissions - """ - # Remove privileges from user - self.user.is_superuser = False - self.user.user_permissions.add( - Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') - ) - self.user.save() - - # Post - response = self.client.post(reverse('wagtailadmin_pages:reject_moderation', args=(self.revision.id, ))) - - # Check that the user received a 403 response - self.assertEqual(response.status_code, 403) - - def test_preview_for_moderation(self): - response = self.client.get(reverse('wagtailadmin_pages:preview_for_moderation', args=(self.revision.id, ))) - - # Check response - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'tests/simple_page.html') - self.assertContains(response, "Hello world!") - - -class TestContentTypeUse(TestCase, WagtailTestUtils): - fixtures = ['test.json'] - - def setUp(self): - self.user = self.login() - - def test_content_type_use(self): - # Get use of event page - response = self.client.get(reverse('wagtailadmin_pages:type_use', args=('tests', 'eventpage'))) - - # Check response - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailadmin/pages/content_type_use.html') - self.assertContains(response, "Christmas") - - -class TestSubpageBusinessRules(TestCase, WagtailTestUtils): - def setUp(self): - # Find root page - self.root_page = Page.objects.get(id=2) - - # Add standard page (allows subpages of any type) - self.standard_index = StandardIndex() - self.standard_index.title = "Standard Index" - self.standard_index.slug = "standard-index" - self.root_page.add_child(instance=self.standard_index) - - # Add business page (allows BusinessChild and BusinessSubIndex as subpages) - self.business_index = BusinessIndex() - self.business_index.title = "Business Index" - self.business_index.slug = "business-index" - self.root_page.add_child(instance=self.business_index) - - # Add business child (allows no subpages) - self.business_child = BusinessChild() - self.business_child.title = "Business Child" - self.business_child.slug = "business-child" - self.business_index.add_child(instance=self.business_child) - - # Add business subindex (allows only BusinessChild as subpages) - self.business_subindex = BusinessSubIndex() - self.business_subindex.title = "Business Subindex" - self.business_subindex.slug = "business-subindex" - self.business_index.add_child(instance=self.business_subindex) - - # Login - self.login() - - def test_standard_subpage(self): - add_subpage_url = reverse('wagtailadmin_pages:add_subpage', args=(self.standard_index.id, )) - - # explorer should contain a link to 'add child page' - response = self.client.get(reverse('wagtailadmin_explore', args=(self.standard_index.id, ))) - self.assertEqual(response.status_code, 200) - self.assertContains(response, add_subpage_url) - - # add_subpage should give us choices of StandardChild, and BusinessIndex. - # BusinessSubIndex and BusinessChild are not allowed - response = self.client.get(add_subpage_url) - self.assertEqual(response.status_code, 200) - self.assertContains(response, StandardChild.get_verbose_name()) - self.assertContains(response, BusinessIndex.get_verbose_name()) - self.assertNotContains(response, BusinessSubIndex.get_verbose_name()) - self.assertNotContains(response, BusinessChild.get_verbose_name()) - - def test_business_subpage(self): - add_subpage_url = reverse('wagtailadmin_pages:add_subpage', args=(self.business_index.id, )) - - # explorer should contain a link to 'add child page' - response = self.client.get(reverse('wagtailadmin_explore', args=(self.business_index.id, ))) - self.assertEqual(response.status_code, 200) - self.assertContains(response, add_subpage_url) - - # add_subpage should give us a cut-down set of page types to choose - response = self.client.get(add_subpage_url) - self.assertEqual(response.status_code, 200) - self.assertNotContains(response, StandardIndex.get_verbose_name()) - self.assertNotContains(response, StandardChild.get_verbose_name()) - self.assertContains(response, BusinessSubIndex.get_verbose_name()) - self.assertContains(response, BusinessChild.get_verbose_name()) - - def test_business_child_subpage(self): - add_subpage_url = reverse('wagtailadmin_pages:add_subpage', args=(self.business_child.id, )) - - # explorer should not contain a link to 'add child page', as this page doesn't accept subpages - response = self.client.get(reverse('wagtailadmin_explore', args=(self.business_child.id, ))) - self.assertEqual(response.status_code, 200) - self.assertNotContains(response, add_subpage_url) - - # this also means that fetching add_subpage is blocked at the permission-check level - response = self.client.get(reverse('wagtailadmin_pages:add_subpage', args=(self.business_child.id, ))) - self.assertEqual(response.status_code, 403) - - def test_cannot_add_invalid_subpage_type(self): - # cannot add StandardChild as a child of BusinessIndex, as StandardChild is not present in subpage_types - response = self.client.get( - reverse('wagtailadmin_pages:add', args=('tests', 'standardchild', self.business_index.id)) - ) - self.assertEqual(response.status_code, 403) - - # likewise for BusinessChild which has an empty subpage_types list - response = self.client.get( - reverse('wagtailadmin_pages:add', args=('tests', 'standardchild', self.business_child.id)) - ) - self.assertEqual(response.status_code, 403) - - # cannot add BusinessChild to StandardIndex, as BusinessChild restricts is parent page types - response = self.client.get( - reverse('wagtailadmin_pages:add', args=('tests', 'businesschild', self.standard_index.id)) - ) - self.assertEqual(response.status_code, 403) - - # but we can add a BusinessChild to BusinessIndex - response = self.client.get( - reverse('wagtailadmin_pages:add', args=('tests', 'businesschild', self.business_index.id)) - ) - self.assertEqual(response.status_code, 200) - - def test_not_prompted_for_page_type_when_only_one_choice(self): - response = self.client.get(reverse('wagtailadmin_pages:add_subpage', args=(self.business_subindex.id, ))) - # BusinessChild is the only valid subpage type of BusinessSubIndex, so redirect straight there - self.assertRedirects( - response, reverse('wagtailadmin_pages:add', args=('tests', 'businesschild', self.business_subindex.id)) - ) - - -class TestNotificationPreferences(TestCase, WagtailTestUtils): - def setUp(self): - # Find root page - self.root_page = Page.objects.get(id=2) - - # Login - self.user = self.login() - - # Create two moderator users for testing 'submitted' email - User = get_user_model() - self.moderator = User.objects.create_superuser('moderator', 'moderator@email.com', 'password') - self.moderator2 = User.objects.create_superuser('moderator2', 'moderator2@email.com', 'password') - - # Create a submitter for testing 'rejected' and 'approved' emails - self.submitter = User.objects.create_user('submitter', 'submitter@email.com', 'password') - - # User profiles for moderator2 and the submitter - self.moderator2_profile = UserProfile.get_for_user(self.moderator2) - self.submitter_profile = UserProfile.get_for_user(self.submitter) - - # Create a page and submit it for moderation - self.child_page = SimplePage( - title="Hello world!", - slug='hello-world', - content="hello", - live=False, - ) - self.root_page.add_child(instance=self.child_page) - - # POST data to edit the page - self.post_data = { - 'title': "I've been edited!", - 'content': "Some content", - 'slug': 'hello-world', - 'action-submit': "Submit", - } - - def submit(self): - return self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), self.post_data) - - def silent_submit(self): - """ - Sets up the child_page as needing moderation, without making a request - """ - self.child_page.save_revision(user=self.submitter, submitted_for_moderation=True) - self.revision = self.child_page.get_latest_revision() - - def approve(self): - return self.client.post(reverse('wagtailadmin_pages:approve_moderation', args=(self.revision.id, ))) - - def reject(self): - return self.client.post(reverse('wagtailadmin_pages:reject_moderation', args=(self.revision.id, ))) - - def test_vanilla_profile(self): - # Check that the vanilla profile has rejected notifications on - self.assertEqual(self.submitter_profile.rejected_notifications, True) - - # Check that the vanilla profile has approved notifications on - self.assertEqual(self.submitter_profile.approved_notifications, True) - - def test_submit_notifications_sent(self): - # Submit - self.submit() - - # Check that both the moderators got an email, and no others - self.assertEqual(len(mail.outbox), 2) - email_to = mail.outbox[0].to + mail.outbox[1].to - self.assertIn(self.moderator.email, email_to) - self.assertIn(self.moderator2.email, email_to) - self.assertEqual(len(mail.outbox[0].to), 1) - self.assertEqual(len(mail.outbox[1].to), 1) - - def test_submit_notification_preferences_respected(self): - # moderator2 doesn't want emails - self.moderator2_profile.submitted_notifications = False - self.moderator2_profile.save() - - # Submit - self.submit() - - # Check that only one moderator got an email - self.assertEqual(len(mail.outbox), 1) - self.assertEqual([self.moderator.email], mail.outbox[0].to) - - def test_approved_notifications(self): - # Set up the page version - self.silent_submit() - # Approve - self.approve() - - # Submitter must receive an approved email - self.assertEqual(len(mail.outbox), 1) - self.assertEqual(mail.outbox[0].to, ['submitter@email.com']) - self.assertEqual(mail.outbox[0].subject, 'The page "Hello world!" has been approved') - - def test_approved_notifications_preferences_respected(self): - # Submitter doesn't want 'approved' emails - self.submitter_profile.approved_notifications = False - self.submitter_profile.save() - - # Set up the page version - self.silent_submit() - # Approve - self.approve() - - # No email to send - self.assertEqual(len(mail.outbox), 0) - - def test_rejected_notifications(self): - # Set up the page version - self.silent_submit() - # Reject - self.reject() - - # Submitter must receive a rejected email - self.assertEqual(len(mail.outbox), 1) - self.assertEqual(mail.outbox[0].to, ['submitter@email.com']) - self.assertEqual(mail.outbox[0].subject, 'The page "Hello world!" has been rejected') - - def test_rejected_notification_preferences_respected(self): - # Submitter doesn't want 'rejected' emails - self.submitter_profile.rejected_notifications = False - self.submitter_profile.save() - - # Set up the page version - self.silent_submit() - # Reject - self.reject() - - # No email to send - self.assertEqual(len(mail.outbox), 0) - - def test_moderator_group_notifications(self): - # Create a (non-superuser) moderator - User = get_user_model() - user1 = User.objects.create_user('moduser1', 'moduser1@email.com') - user1.groups.add(Group.objects.get(name='Moderators')) - user1.save() - - # Create another group and user with permission to moderate - modgroup2 = Group.objects.create(name='More moderators') - GroupPagePermission.objects.create( - group=modgroup2, page=self.root_page, permission_type='publish' - ) - user2 = User.objects.create_user('moduser2', 'moduser2@email.com') - user2.groups.add(Group.objects.get(name='More moderators')) - user2.save() - - # Submit - # This used to break in Wagtail 1.3 (Postgres exception, SQLite 3/4 notifications) - response = self.submit() - - # Should be redirected to explorer page - self.assertEqual(response.status_code, 302) - - # Check that the superusers and the moderation group members all got an email - expected_emails = 4 - self.assertEqual(len(mail.outbox), expected_emails) - email_to = [] - for i in range(expected_emails): - self.assertEqual(len(mail.outbox[i].to), 1) - email_to += mail.outbox[i].to - self.assertIn(self.moderator.email, email_to) - self.assertIn(self.moderator2.email, email_to) - self.assertIn(user1.email, email_to) - self.assertIn(user2.email, email_to) - - @override_settings(WAGTAILADMIN_NOTIFICATION_INCLUDE_SUPERUSERS=False) - def test_disable_superuser_notification(self): - # Add one of the superusers to the moderator group - self.moderator.groups.add(Group.objects.get(name='Moderators')) - - response = self.submit() - - # Should be redirected to explorer page - self.assertEqual(response.status_code, 302) - - # Check that the non-moderator superuser is not being notified - expected_emails = 1 - self.assertEqual(len(mail.outbox), expected_emails) - # Use chain as the 'to' field is a list of recipients - email_to = list(chain.from_iterable([m.to for m in mail.outbox])) - self.assertIn(self.moderator.email, email_to) - self.assertNotIn(self.moderator2.email, email_to) - - @mock.patch.object(EmailMultiAlternatives, 'send', side_effect=IOError('Server down')) - def test_email_send_error(self, mock_fn): - logging.disable(logging.CRITICAL) - # Approve - self.silent_submit() - response = self.approve() - logging.disable(logging.NOTSET) - - # An email that fails to send should return a message rather than crash the page - self.assertEqual(response.status_code, 302) - response = self.client.get(reverse('wagtailadmin_home')) - - # There should be one "approved" message and one "failed to send notifications" - messages = list(response.context['messages']) - self.assertEqual(len(messages), 2) - self.assertEqual(messages[0].level, message_constants.SUCCESS) - self.assertEqual(messages[1].level, message_constants.ERROR) - - def test_email_headers(self): - # Submit - self.submit() - - msg_headers = set(mail.outbox[0].message().items()) - headers = {('Auto-Submitted', 'auto-generated')} - self.assertTrue(headers.issubset(msg_headers), msg='Message is missing the Auto-Submitted header.',) - - -class TestLocking(TestCase, WagtailTestUtils): - def setUp(self): - # Find root page - self.root_page = Page.objects.get(id=2) - - # Login - self.user = self.login() - - # Create a page and submit it for moderation - self.child_page = SimplePage( - title="Hello world!", - slug='hello-world', - content="hello", - live=False, - ) - self.root_page.add_child(instance=self.child_page) - - def test_lock_post(self): - response = self.client.post(reverse('wagtailadmin_pages:lock', args=(self.child_page.id, ))) - - # Check response - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - - # Check that the page is locked - self.assertTrue(Page.objects.get(id=self.child_page.id).locked) - - def test_lock_get(self): - response = self.client.get(reverse('wagtailadmin_pages:lock', args=(self.child_page.id, ))) - - # Check response - self.assertEqual(response.status_code, 405) - - # Check that the page is still unlocked - self.assertFalse(Page.objects.get(id=self.child_page.id).locked) - - def test_lock_post_already_locked(self): - # Lock the page - self.child_page.locked = True - self.child_page.save() - - response = self.client.post(reverse('wagtailadmin_pages:lock', args=(self.child_page.id, ))) - - # Check response - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - - # Check that the page is still locked - self.assertTrue(Page.objects.get(id=self.child_page.id).locked) - - def test_lock_post_with_good_redirect(self): - response = self.client.post(reverse('wagtailadmin_pages:lock', args=(self.child_page.id, )), { - 'next': reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )) - }) - - # Check response - self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) - - # Check that the page is locked - self.assertTrue(Page.objects.get(id=self.child_page.id).locked) - - def test_lock_post_with_bad_redirect(self): - response = self.client.post(reverse('wagtailadmin_pages:lock', args=(self.child_page.id, )), { - 'next': 'http://www.google.co.uk' - }) - - # Check response - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - - # Check that the page is locked - self.assertTrue(Page.objects.get(id=self.child_page.id).locked) - - def test_lock_post_bad_page(self): - response = self.client.post(reverse('wagtailadmin_pages:lock', args=(9999, ))) - - # Check response - self.assertEqual(response.status_code, 404) - - # Check that the page is still unlocked - self.assertFalse(Page.objects.get(id=self.child_page.id).locked) - - def test_lock_post_bad_permissions(self): - # Remove privileges from user - self.user.is_superuser = False - self.user.user_permissions.add( - Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') - ) - self.user.save() - - response = self.client.post(reverse('wagtailadmin_pages:lock', args=(self.child_page.id, ))) - - # Check response - self.assertEqual(response.status_code, 403) - - # Check that the page is still unlocked - self.assertFalse(Page.objects.get(id=self.child_page.id).locked) - - def test_unlock_post(self): - # Lock the page - self.child_page.locked = True - self.child_page.save() - - response = self.client.post(reverse('wagtailadmin_pages:unlock', args=(self.child_page.id, ))) - - # Check response - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - - # Check that the page is unlocked - self.assertFalse(Page.objects.get(id=self.child_page.id).locked) - - def test_unlock_get(self): - # Lock the page - self.child_page.locked = True - self.child_page.save() - - response = self.client.get(reverse('wagtailadmin_pages:unlock', args=(self.child_page.id, ))) - - # Check response - self.assertEqual(response.status_code, 405) - - # Check that the page is still locked - self.assertTrue(Page.objects.get(id=self.child_page.id).locked) - - def test_unlock_post_already_unlocked(self): - response = self.client.post(reverse('wagtailadmin_pages:unlock', args=(self.child_page.id, ))) - - # Check response - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - - # Check that the page is still unlocked - self.assertFalse(Page.objects.get(id=self.child_page.id).locked) - - def test_unlock_post_with_good_redirect(self): - # Lock the page - self.child_page.locked = True - self.child_page.save() - - response = self.client.post(reverse('wagtailadmin_pages:unlock', args=(self.child_page.id, )), { - 'next': reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )) - }) - - # Check response - self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) - - # Check that the page is unlocked - self.assertFalse(Page.objects.get(id=self.child_page.id).locked) - - def test_unlock_post_with_bad_redirect(self): - # Lock the page - self.child_page.locked = True - self.child_page.save() - - response = self.client.post(reverse('wagtailadmin_pages:unlock', args=(self.child_page.id, )), { - 'next': 'http://www.google.co.uk' - }) - - # Check response - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - - # Check that the page is unlocked - self.assertFalse(Page.objects.get(id=self.child_page.id).locked) - - def test_unlock_post_bad_page(self): - # Lock the page - self.child_page.locked = True - self.child_page.save() - - response = self.client.post(reverse('wagtailadmin_pages:unlock', args=(9999, ))) - - # Check response - self.assertEqual(response.status_code, 404) - - # Check that the page is still locked - self.assertTrue(Page.objects.get(id=self.child_page.id).locked) - - def test_unlock_post_bad_permissions(self): - # Remove privileges from user - self.user.is_superuser = False - self.user.user_permissions.add( - Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') - ) - self.user.save() - - # Lock the page - self.child_page.locked = True - self.child_page.save() - - response = self.client.post(reverse('wagtailadmin_pages:unlock', args=(self.child_page.id, ))) - - # Check response - self.assertEqual(response.status_code, 403) - - # Check that the page is still locked - self.assertTrue(Page.objects.get(id=self.child_page.id).locked) - - -class TestIssue197(TestCase, WagtailTestUtils): - def test_issue_197(self): - # Find root page - self.root_page = Page.objects.get(id=2) - - # Create a tagged page with no tags - self.tagged_page = self.root_page.add_child(instance=TaggedPage( - title="Tagged page", - slug='tagged-page', - live=False, - )) - - # Login - self.user = self.login() - - # Add some tags and publish using edit view - post_data = { - 'title': "Tagged page", - 'slug': 'tagged-page', - 'tags': "hello, world", - 'action-publish': "Publish", - } - response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.tagged_page.id, )), post_data) - - # Should be redirected to explorer - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - - # Check that both tags are in the pages tag set - page = TaggedPage.objects.get(id=self.tagged_page.id) - self.assertIn('hello', page.tags.slugs()) - self.assertIn('world', page.tags.slugs()) - - -class TestChildRelationsOnSuperclass(TestCase, WagtailTestUtils): - # In our test models we define AdvertPlacement as a child relation on the Page model. - # Here we check that this behaves correctly when exposed on the edit form of a Page - # subclass (StandardIndex here). - fixtures = ['test.json'] - - def setUp(self): - # Find root page - self.root_page = Page.objects.get(id=2) - self.test_advert = Advert.objects.get(id=1) - - # Add child page - self.index_page = StandardIndex( - title="My lovely index", - slug="my-lovely-index", - advert_placements=[AdvertPlacement(advert=self.test_advert)] - ) - self.root_page.add_child(instance=self.index_page) - - # Login - self.login() - - def test_get_create_form(self): - response = self.client.get( - reverse('wagtailadmin_pages:add', args=('tests', 'standardindex', self.root_page.id)) - ) - self.assertEqual(response.status_code, 200) - # Response should include an advert_placements formset labelled Adverts - self.assertContains(response, "Adverts") - self.assertContains(response, "id_advert_placements-TOTAL_FORMS") - - def test_post_create_form(self): - post_data = { - 'title': "New index!", - 'slug': 'new-index', - 'advert_placements-TOTAL_FORMS': '1', - 'advert_placements-INITIAL_FORMS': '0', - 'advert_placements-MAX_NUM_FORMS': '1000', - 'advert_placements-0-advert': '1', - 'advert_placements-0-colour': 'yellow', - 'advert_placements-0-id': '', - } - response = self.client.post( - reverse('wagtailadmin_pages:add', args=('tests', 'standardindex', self.root_page.id)), post_data - ) - - # Find the page and check it - page = Page.objects.get(path__startswith=self.root_page.path, slug='new-index').specific - - # Should be redirected to edit page - self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(page.id, ))) - - self.assertEqual(page.advert_placements.count(), 1) - self.assertEqual(page.advert_placements.first().advert.text, 'test_advert') - - def test_post_create_form_with_validation_error_in_formset(self): - post_data = { - 'title': "New index!", - 'slug': 'new-index', - 'advert_placements-TOTAL_FORMS': '1', - 'advert_placements-INITIAL_FORMS': '0', - 'advert_placements-MAX_NUM_FORMS': '1000', - 'advert_placements-0-advert': '1', - 'advert_placements-0-colour': '', # should fail as colour is a required field - 'advert_placements-0-id': '', - } - response = self.client.post( - reverse('wagtailadmin_pages:add', args=('tests', 'standardindex', self.root_page.id)), post_data - ) - - # Should remain on the edit page with a validation error - self.assertEqual(response.status_code, 200) - self.assertContains(response, "This field is required.") - # form should be marked as having unsaved changes - self.assertContains(response, "alwaysDirty: true") - - def test_get_edit_form(self): - response = self.client.get(reverse('wagtailadmin_pages:edit', args=(self.index_page.id, ))) - self.assertEqual(response.status_code, 200) - - # Response should include an advert_placements formset labelled Adverts - self.assertContains(response, "Adverts") - self.assertContains(response, "id_advert_placements-TOTAL_FORMS") - # the formset should be populated with an existing form - self.assertContains(response, "id_advert_placements-0-advert") - self.assertContains( - response, '', html=True - ) - - def test_post_edit_form(self): - post_data = { - 'title': "My lovely index", - 'slug': 'my-lovely-index', - 'advert_placements-TOTAL_FORMS': '2', - 'advert_placements-INITIAL_FORMS': '1', - 'advert_placements-MAX_NUM_FORMS': '1000', - 'advert_placements-0-advert': '1', - 'advert_placements-0-colour': 'yellow', - 'advert_placements-0-id': self.index_page.advert_placements.first().id, - 'advert_placements-1-advert': '1', - 'advert_placements-1-colour': 'purple', - 'advert_placements-1-id': '', - 'action-publish': "Publish", - } - response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.index_page.id, )), post_data) - - # Should be redirected to explorer - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) - - # Find the page and check it - page = Page.objects.get(id=self.index_page.id).specific - self.assertEqual(page.advert_placements.count(), 2) - self.assertEqual(page.advert_placements.all()[0].advert.text, 'test_advert') - self.assertEqual(page.advert_placements.all()[1].advert.text, 'test_advert') - - def test_post_edit_form_with_validation_error_in_formset(self): - post_data = { - 'title': "My lovely index", - 'slug': 'my-lovely-index', - 'advert_placements-TOTAL_FORMS': '1', - 'advert_placements-INITIAL_FORMS': '1', - 'advert_placements-MAX_NUM_FORMS': '1000', - 'advert_placements-0-advert': '1', - 'advert_placements-0-colour': '', - 'advert_placements-0-id': self.index_page.advert_placements.first().id, - 'action-publish': "Publish", - } - response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.index_page.id, )), post_data) - - # Should remain on the edit page with a validation error - self.assertEqual(response.status_code, 200) - self.assertContains(response, "This field is required.") - # form should be marked as having unsaved changes - self.assertContains(response, "alwaysDirty: true") - - -class TestRevisions(TestCase, WagtailTestUtils): - fixtures = ['test.json'] - - def setUp(self): - self.christmas_event = EventPage.objects.get(url_path='/home/events/christmas/') - self.christmas_event.title = "Last Christmas" - self.christmas_event.date_from = '2013-12-25' - self.christmas_event.body = ( - "

    Last Christmas I gave you my heart, " - "but the very next day you gave it away

    " - ) - self.last_christmas_revision = self.christmas_event.save_revision() - self.last_christmas_revision.created_at = local_datetime(2013, 12, 25) - self.last_christmas_revision.save() - - self.christmas_event.title = "This Christmas" - self.christmas_event.date_from = '2014-12-25' - self.christmas_event.body = ( - "

    This year, to save me from tears, " - "I'll give it to someone special

    " - ) - self.this_christmas_revision = self.christmas_event.save_revision() - self.this_christmas_revision.created_at = local_datetime(2014, 12, 25) - self.this_christmas_revision.save() - - self.login() - - def test_edit_form_has_revisions_link(self): - response = self.client.get( - reverse('wagtailadmin_pages:edit', args=(self.christmas_event.id, )) - ) - self.assertEqual(response.status_code, 200) - revisions_index_url = reverse( - 'wagtailadmin_pages:revisions_index', args=(self.christmas_event.id, ) - ) - self.assertContains(response, revisions_index_url) - - def test_get_revisions_index(self): - response = self.client.get( - reverse('wagtailadmin_pages:revisions_index', args=(self.christmas_event.id, )) - ) - self.assertEqual(response.status_code, 200) - - self.assertContains(response, formats.localize(parse_date('2013-12-25'))) - last_christmas_preview_url = reverse( - 'wagtailadmin_pages:revisions_view', - args=(self.christmas_event.id, self.last_christmas_revision.id) - ) - last_christmas_revert_url = reverse( - 'wagtailadmin_pages:revisions_revert', - args=(self.christmas_event.id, self.last_christmas_revision.id) - ) - self.assertContains(response, last_christmas_preview_url) - self.assertContains(response, last_christmas_revert_url) - - self.assertContains(response, formats.localize(local_datetime(2014, 12, 25))) - this_christmas_preview_url = reverse( - 'wagtailadmin_pages:revisions_view', - args=(self.christmas_event.id, self.this_christmas_revision.id) - ) - this_christmas_revert_url = reverse( - 'wagtailadmin_pages:revisions_revert', - args=(self.christmas_event.id, self.this_christmas_revision.id) - ) - self.assertContains(response, this_christmas_preview_url) - self.assertContains(response, this_christmas_revert_url) - - def request_preview_revision(self): - last_christmas_preview_url = reverse( - 'wagtailadmin_pages:revisions_view', - args=(self.christmas_event.id, self.last_christmas_revision.id) - ) - return self.client.get(last_christmas_preview_url) - - def test_preview_revision(self): - response = self.request_preview_revision() - - self.assertEqual(response.status_code, 200) - self.assertContains(response, "Last Christmas I gave you my heart") - - def test_preview_revision_with_no_page_permissions_redirects_to_admin(self): - admin_only_user = get_user_model().objects.create_user( - username='admin_only', - email='admin_only@email.com', - password='password' - ) - admin_only_user.user_permissions.add( - Permission.objects.get_by_natural_key( - codename='access_admin', - app_label='wagtailadmin', - model='admin' - ) - ) - - self.login(user=admin_only_user) - response = self.request_preview_revision() - - self.assertEqual(response.status_code, 302) - self.assertEqual(response['Location'], reverse('wagtailadmin_home')) - - def test_preview_revision_forbidden_without_permission(self): - # Alter the editors group so it has no permissions for Christmas page. - st_patricks = Page.objects.get(slug='saint-patrick') - editors_group = Group.objects.get(name='Site-wide editors') - editors_group.page_permissions.update(page_id=st_patricks.id) - - editor = get_user_model().objects.get(username='siteeditor') - - self.login(editor) - response = self.request_preview_revision() - - self.assertEqual(response.status_code, 403) - - def test_revert_revision(self): - last_christmas_preview_url = reverse( - 'wagtailadmin_pages:revisions_revert', - args=(self.christmas_event.id, self.last_christmas_revision.id) - ) - response = self.client.get(last_christmas_preview_url) - self.assertEqual(response.status_code, 200) - - self.assertContains(response, "Editing Event page") - self.assertContains(response, "You are viewing a previous revision of this page") - - # Form should show the content of the revision, not the current draft - self.assertContains(response, "Last Christmas I gave you my heart") - - # Form should include a hidden 'revision' field - revision_field = ( - """""" % - self.last_christmas_revision.id - ) - self.assertContains(response, revision_field) - - # Buttons should be relabelled - self.assertContains(response, "Replace current draft") - self.assertContains(response, "Publish this revision") - - def test_scheduled_revision(self): - self.last_christmas_revision.publish() - self.this_christmas_revision.approved_go_live_at = local_datetime(2014, 12, 26) - self.this_christmas_revision.save() - this_christmas_unschedule_url = reverse( - 'wagtailadmin_pages:revisions_unschedule', - args=(self.christmas_event.id, self.this_christmas_revision.id) - ) - response = self.client.get( - reverse('wagtailadmin_pages:revisions_index', args=(self.christmas_event.id, )) - ) - self.assertEqual(response.status_code, 200) - self.assertContains(response, 'Scheduled for') - self.assertContains(response, formats.localize(parse_date('2014-12-26'))) - self.assertContains(response, this_christmas_unschedule_url) - - -class TestCompareRevisions(TestCase, WagtailTestUtils): - # Actual tests for the comparison classes can be found in test_compare.py - - fixtures = ['test.json'] - - def setUp(self): - self.christmas_event = EventPage.objects.get(url_path='/home/events/christmas/') - self.christmas_event.title = "Last Christmas" - self.christmas_event.date_from = '2013-12-25' - self.christmas_event.body = ( - "

    Last Christmas I gave you my heart, " - "but the very next day you gave it away

    " - ) - self.last_christmas_revision = self.christmas_event.save_revision() - self.last_christmas_revision.created_at = local_datetime(2013, 12, 25) - self.last_christmas_revision.save() - - self.christmas_event.title = "This Christmas" - self.christmas_event.date_from = '2014-12-25' - self.christmas_event.body = ( - "

    This year, to save me from tears, " - "I'll give it to someone special

    " - ) - self.this_christmas_revision = self.christmas_event.save_revision() - self.this_christmas_revision.created_at = local_datetime(2014, 12, 25) - self.this_christmas_revision.save() - - self.login() - - def test_compare_revisions(self): - compare_url = reverse( - 'wagtailadmin_pages:revisions_compare', - args=(self.christmas_event.id, self.last_christmas_revision.id, self.this_christmas_revision.id) - ) - response = self.client.get(compare_url) - self.assertEqual(response.status_code, 200) - - self.assertContains( - response, - 'Last Christmas I gave you my heart, but the very next day you gave it awayThis year, to save me from tears, I'll give it to someone special', - html=True - ) - - def test_compare_revisions_earliest(self): - compare_url = reverse( - 'wagtailadmin_pages:revisions_compare', - args=(self.christmas_event.id, 'earliest', self.this_christmas_revision.id) - ) - response = self.client.get(compare_url) - self.assertEqual(response.status_code, 200) - - self.assertContains( - response, - 'Last Christmas I gave you my heart, but the very next day you gave it awayThis year, to save me from tears, I'll give it to someone special', - html=True - ) - - def test_compare_revisions_latest(self): - compare_url = reverse( - 'wagtailadmin_pages:revisions_compare', - args=(self.christmas_event.id, self.last_christmas_revision.id, 'latest') - ) - response = self.client.get(compare_url) - self.assertEqual(response.status_code, 200) - - self.assertContains( - response, - 'Last Christmas I gave you my heart, but the very next day you gave it awayThis year, to save me from tears, I'll give it to someone special', - html=True - ) - - def test_compare_revisions_live(self): - # Mess with the live version, bypassing revisions - self.christmas_event.body = ( - "

    This year, to save me from tears, " - "I'll just feed it to the dog

    " - ) - self.christmas_event.save(update_fields=['body']) - - compare_url = reverse( - 'wagtailadmin_pages:revisions_compare', - args=(self.christmas_event.id, self.last_christmas_revision.id, 'live') - ) - response = self.client.get(compare_url) - self.assertEqual(response.status_code, 200) - - self.assertContains( - response, - 'Last Christmas I gave you my heart, but the very next day you gave it awayThis year, to save me from tears, I'll just feed it to the dog', - html=True - ) - - -class TestCompareRevisionsWithNonModelField(TestCase, WagtailTestUtils): - """ - Tests if form fields defined in the base_form_class will not be included. - in revisions view as they are not actually on the model. - Flagged in issue #3737 - Note: Actual tests for comparison classes can be found in test_compare.py - """ - - fixtures = ['test.json'] - # FormClassAdditionalFieldPage - - def setUp(self): - # Find root page - self.root_page = Page.objects.get(id=2) - - # Add child page of class with base_form_class override - # non model field is 'code' - self.test_page = FormClassAdditionalFieldPage( - title='A Statement', - slug='a-statement', - location='Early Morning Cafe, Mainland, NZ', - body="

    hello

    " - ) - self.root_page.add_child(instance=self.test_page) - - # add new revision - self.test_page.title = 'Statement' - self.test_page.location = 'Victory Monument, Bangkok' - self.test_page.body = ( - "

    I would like very much to go into the forrest.

    " - ) - self.test_page_revision = self.test_page.save_revision() - self.test_page_revision.created_at = local_datetime(2017, 10, 15) - self.test_page_revision.save() - - # add another new revision - self.test_page.title = 'True Statement' - self.test_page.location = 'Victory Monument, Bangkok' - self.test_page.body = ( - "

    I would like very much to go into the forest.

    " - ) - self.test_page_revision_new = self.test_page.save_revision() - self.test_page_revision_new.created_at = local_datetime(2017, 10, 16) - self.test_page_revision_new.save() - - self.login() - - def test_base_form_class_used(self): - """First ensure that the non-model field is appearing in edit.""" - edit_url = reverse('wagtailadmin_pages:add', args=('tests', 'formclassadditionalfieldpage', self.test_page.id)) - response = self.client.get(edit_url) - self.assertContains(response, '', html=True) - - def test_compare_revisions(self): - """Confirm that the non-model field is not shown in revision.""" - compare_url = reverse( - 'wagtailadmin_pages:revisions_compare', - args=(self.test_page.id, self.test_page_revision.id, self.test_page_revision_new.id) - ) - response = self.client.get(compare_url) - self.assertContains(response, 'forrest.forest.') - # should not contain the field defined in the formclass used - self.assertNotContains(response, '

    Code:

    ') - - -class TestRevisionsUnschedule(TestCase, WagtailTestUtils): - fixtures = ['test.json'] - - def setUp(self): - self.christmas_event = EventPage.objects.get(url_path='/home/events/christmas/') - self.christmas_event.title = "Last Christmas" - self.christmas_event.date_from = '2013-12-25' - self.christmas_event.body = ( - "

    Last Christmas I gave you my heart, " - "but the very next day you gave it away

    " - ) - self.last_christmas_revision = self.christmas_event.save_revision() - self.last_christmas_revision.created_at = local_datetime(2013, 12, 25) - self.last_christmas_revision.save() - self.last_christmas_revision.publish() - - self.christmas_event.title = "This Christmas" - self.christmas_event.date_from = '2014-12-25' - self.christmas_event.body = ( - "

    This year, to save me from tears, " - "I'll give it to someone special

    " - ) - self.this_christmas_revision = self.christmas_event.save_revision() - self.this_christmas_revision.created_at = local_datetime(2014, 12, 24) - self.this_christmas_revision.save() - - self.this_christmas_revision.approved_go_live_at = local_datetime(2014, 12, 25) - self.this_christmas_revision.save() - - self.user = self.login() - - def test_unschedule_view(self): - """ - This tests that the unschedule view responds with a confirm page - """ - response = self.client.get(reverse('wagtailadmin_pages:revisions_unschedule', args=(self.christmas_event.id, self.this_christmas_revision.id))) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailadmin/pages/revisions/confirm_unschedule.html') - - def test_unschedule_view_invalid_page_id(self): - """ - This tests that the unschedule view returns an error if the page id is invalid - """ - # Get unschedule page - response = self.client.get(reverse('wagtailadmin_pages:revisions_unschedule', args=(12345, 67894))) - - # Check that the user received a 404 response - self.assertEqual(response.status_code, 404) - - def test_unschedule_view_invalid_revision_id(self): - """ - This tests that the unschedule view returns an error if the page id is invalid - """ - # Get unschedule page - response = self.client.get(reverse('wagtailadmin_pages:revisions_unschedule', args=(self.christmas_event.id, 67894))) - - # Check that the user received a 404 response - self.assertEqual(response.status_code, 404) - - def test_unschedule_view_bad_permissions(self): - """ - This tests that the unschedule view doesn't allow users without publish permissions - """ - # Remove privileges from user - self.user.is_superuser = False - self.user.user_permissions.add( - Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') - ) - self.user.save() - - # Get unschedule page - response = self.client.get(reverse('wagtailadmin_pages:revisions_unschedule', args=(self.christmas_event.id, self.this_christmas_revision.id))) - - # Check that the user received a 403 response - self.assertEqual(response.status_code, 403) - - def test_unschedule_view_post(self): - """ - This posts to the unschedule view and checks that the revision was unscheduled - """ - - # Post to the unschedule page - response = self.client.post(reverse('wagtailadmin_pages:revisions_unschedule', args=(self.christmas_event.id, self.this_christmas_revision.id))) - - # Should be redirected to revisions index page - self.assertRedirects(response, reverse('wagtailadmin_pages:revisions_index', args=(self.christmas_event.id, ))) - - # Check that the page has no approved_schedule - self.assertFalse(EventPage.objects.get(id=self.christmas_event.id).approved_schedule) - - # Check that the approved_go_live_at has been cleared from the revision - self.assertIsNone(self.christmas_event.revisions.get(id=self.this_christmas_revision.id).approved_go_live_at) - - -class TestRevisionsUnscheduleForUnpublishedPages(TestCase, WagtailTestUtils): - fixtures = ['test.json'] - - def setUp(self): - self.unpublished_event = EventPage.objects.get(url_path='/home/events/tentative-unpublished-event/') - self.unpublished_event.title = "Unpublished Page" - self.unpublished_event.date_from = '2014-12-25' - self.unpublished_event.body = ( - "

    Some Content

    " - ) - self.unpublished_revision = self.unpublished_event.save_revision() - self.unpublished_revision.created_at = local_datetime(2014, 12, 25) - self.unpublished_revision.save() - - self.user = self.login() - - def test_unschedule_view(self): - """ - This tests that the unschedule view responds with a confirm page - """ - response = self.client.get(reverse('wagtailadmin_pages:revisions_unschedule', args=(self.unpublished_event.id, self.unpublished_revision.id))) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailadmin/pages/revisions/confirm_unschedule.html') - - def test_unschedule_view_post(self): - """ - This posts to the unschedule view and checks that the revision was unscheduled - """ - - # Post to the unschedule page - response = self.client.post(reverse('wagtailadmin_pages:revisions_unschedule', args=(self.unpublished_event.id, self.unpublished_revision.id))) - - # Should be redirected to revisions index page - self.assertRedirects(response, reverse('wagtailadmin_pages:revisions_index', args=(self.unpublished_event.id, ))) - - # Check that the page has no approved_schedule - self.assertFalse(EventPage.objects.get(id=self.unpublished_event.id).approved_schedule) - - # Check that the approved_go_live_at has been cleared from the revision - self.assertIsNone(self.unpublished_event.revisions.get(id=self.unpublished_revision.id).approved_go_live_at) - - -class TestIssue2599(TestCase, WagtailTestUtils): - """ - When previewing a page on creation, we need to assign it a path value consistent with its - (future) position in the tree. The naive way of doing this is to give it an index number - one more than numchild - however, index numbers are not reassigned on page deletion, so - this can result in a path that collides with an existing page (which is invalid). - """ - - def test_issue_2599(self): - homepage = Page.objects.get(id=2) - - child1 = Page(title='child1') - homepage.add_child(instance=child1) - child2 = Page(title='child2') - homepage.add_child(instance=child2) - - child1.delete() - - self.login() - post_data = { - 'title': "New page!", - 'content': "Some content", - 'slug': 'hello-world', - 'action-submit': "Submit", - } - preview_url = reverse('wagtailadmin_pages:preview_on_add', - args=('tests', 'simplepage', homepage.id)) - response = self.client.post(preview_url, post_data) - - # Check the JSON response - self.assertEqual(response.status_code, 200) - self.assertJSONEqual(response.content.decode(), {'is_valid': True}) - - response = self.client.get(preview_url) - - # Check the HTML response - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'tests/simple_page.html') - self.assertContains(response, "New page!") - - # Check that the treebeard attributes were set correctly on the page object - self.assertEqual(response.context['self'].depth, homepage.depth + 1) - self.assertTrue(response.context['self'].path.startswith(homepage.path)) - self.assertEqual(response.context['self'].get_parent(), homepage) - - -class TestIssue2492(TestCase, WagtailTestUtils): - """ - The publication submission message generation was performed using - the Page class, as opposed to the specific_class for that Page. - This test ensures that the specific_class url method is called - when the 'view live' message button is created. - """ - - def setUp(self): - self.root_page = Page.objects.get(id=2) - child_page = SingleEventPage( - title="Test Event", slug="test-event", location="test location", - cost="10", date_from=datetime.datetime.now(), - audience=EVENT_AUDIENCE_CHOICES[0][0]) - self.root_page.add_child(instance=child_page) - child_page.save_revision().publish() - self.child_page = SingleEventPage.objects.get(id=child_page.id) - self.user = self.login() - - def test_page_edit_post_publish_url(self): - post_data = { - 'action-publish': "Publish", - 'title': self.child_page.title, - 'date_from': self.child_page.date_from, - 'slug': self.child_page.slug, - 'audience': self.child_page.audience, - 'location': self.child_page.location, - 'cost': self.child_page.cost, - 'carousel_items-TOTAL_FORMS': 0, - 'carousel_items-INITIAL_FORMS': 0, - 'carousel_items-MIN_NUM_FORMS': 0, - 'carousel_items-MAX_NUM_FORMS': 0, - 'speakers-TOTAL_FORMS': 0, - 'speakers-INITIAL_FORMS': 0, - 'speakers-MIN_NUM_FORMS': 0, - 'speakers-MAX_NUM_FORMS': 0, - 'related_links-TOTAL_FORMS': 0, - 'related_links-INITIAL_FORMS': 0, - 'related_links-MIN_NUM_FORMS': 0, - 'related_links-MAX_NUM_FORMS': 0, - 'head_counts-TOTAL_FORMS': 0, - 'head_counts-INITIAL_FORMS': 0, - 'head_counts-MIN_NUM_FORMS': 0, - 'head_counts-MAX_NUM_FORMS': 0, - } - response = self.client.post( - reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), - post_data, follow=True) - - # Grab a fresh copy's URL - new_url = SingleEventPage.objects.get(id=self.child_page.id).url - - # The "View Live" button should have the custom URL. - for message in response.context['messages']: - self.assertIn('"{}"'.format(new_url), message.message) - break - - -class TestIssue3982(TestCase, WagtailTestUtils): - """ - Pages that are not associated with a site, and thus do not have a live URL, - should not display a "View live" link in the flash message after being - edited. - """ - - def setUp(self): - super().setUp() - self.login() - - def _create_page(self, parent): - response = self.client.post( - reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', parent.pk)), - {'title': "Hello, world!", 'content': "Some content", 'slug': 'hello-world', 'action-publish': "publish"}, - follow=True) - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(parent.pk,))) - page = SimplePage.objects.get() - self.assertTrue(page.live) - return response, page - - def test_create_accessible(self): - """ - Create a page under the site root, check the flash message has a valid - "View live" button. - """ - response, page = self._create_page(Page.objects.get(pk=2)) - self.assertIsNotNone(page.url) - self.assertTrue(any( - 'View live' in message.message and page.url in message.message - for message in response.context['messages'])) - - def test_create_inaccessible(self): - """ - Create a page outside of the site root, check the flash message does - not have a "View live" button. - """ - response, page = self._create_page(Page.objects.get(pk=1)) - self.assertIsNone(page.url) - self.assertFalse(any( - 'View live' in message.message - for message in response.context['messages'])) - - def _edit_page(self, parent): - page = parent.add_child(instance=SimplePage(title='Hello, world!', content='Some content')) - response = self.client.post( - reverse('wagtailadmin_pages:edit', args=(page.pk,)), - {'title': "Hello, world!", 'content': "Some content", 'slug': 'hello-world', 'action-publish': "publish"}, - follow=True) - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(parent.pk,))) - page = SimplePage.objects.get(pk=page.pk) - self.assertTrue(page.live) - return response, page - - def test_edit_accessible(self): - """ - Edit a page under the site root, check the flash message has a valid - "View live" button. - """ - response, page = self._edit_page(Page.objects.get(pk=2)) - self.assertIsNotNone(page.url) - self.assertTrue(any( - 'View live' in message.message and page.url in message.message - for message in response.context['messages'])) - - def test_edit_inaccessible(self): - """ - Edit a page outside of the site root, check the flash message does - not have a "View live" button. - """ - response, page = self._edit_page(Page.objects.get(pk=1)) - self.assertIsNone(page.url) - self.assertFalse(any( - 'View live' in message.message - for message in response.context['messages'])) - - def _approve_page(self, parent): - response = self.client.post( - reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', parent.pk)), - {'title': "Hello, world!", 'content': "Some content", 'slug': 'hello-world', 'action-submit': "submit"}, - follow=True) - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(parent.pk,))) - page = SimplePage.objects.get() - self.assertFalse(page.live) - revision = PageRevision.objects.get(page=page) - response = self.client.post(reverse('wagtailadmin_pages:approve_moderation', args=(revision.pk,)), follow=True) - page = SimplePage.objects.get() - self.assertTrue(page.live) - self.assertRedirects(response, reverse('wagtailadmin_home')) - return response, page - - def test_approve_accessible(self): - """ - Edit a page under the site root, check the flash message has a valid - "View live" button. - """ - response, page = self._approve_page(Page.objects.get(pk=2)) - self.assertIsNotNone(page.url) - self.assertTrue(any( - 'View live' in message.message and page.url in message.message - for message in response.context['messages'])) - - def test_approve_inaccessible(self): - """ - Edit a page outside of the site root, check the flash message does - not have a "View live" button. - """ - response, page = self._approve_page(Page.objects.get(pk=1)) - self.assertIsNone(page.url) - self.assertFalse(any( - 'View live' in message.message - for message in response.context['messages'])) - - -class TestInlinePanelMedia(TestCase, WagtailTestUtils): - """ - Test that form media required by InlinePanels is correctly pulled in to the edit page - """ - - def test_inline_panel_media(self): - homepage = Page.objects.get(id=2) - self.login() - - # simplepage does not need draftail... - response = self.client.get(reverse('wagtailadmin_pages:add', args=('tests', 'simplepage', homepage.id))) - self.assertEqual(response.status_code, 200) - self.assertNotContains(response, 'wagtailadmin/js/draftail.js') - - # but sectionedrichtextpage does - response = self.client.get(reverse('wagtailadmin_pages:add', args=('tests', 'sectionedrichtextpage', homepage.id))) - self.assertEqual(response.status_code, 200) - self.assertContains(response, 'wagtailadmin/js/draftail.js') - - -class TestInlineStreamField(TestCase, WagtailTestUtils): - """ - Test that streamfields inside an inline child work - """ - - def test_inline_streamfield(self): - homepage = Page.objects.get(id=2) - self.login() - - response = self.client.get(reverse('wagtailadmin_pages:add', args=('tests', 'inlinestreampage', homepage.id))) - self.assertEqual(response.status_code, 200) - - # response should include HTML declarations for streamfield child blocks - self.assertContains(response, '
    ') - - -class TestRecentEditsPanel(TestCase, WagtailTestUtils): - def setUp(self): - # Find root page - self.root_page = Page.objects.get(id=2) - - # Add child page - child_page = SimplePage( - title="Hello world!", - slug="hello-world", - content="Some content here", - ) - self.root_page.add_child(instance=child_page) - child_page.save_revision().publish() - self.child_page = SimplePage.objects.get(id=child_page.id) - - get_user_model().objects.create_superuser(username='alice', email='alice@email.com', password='password') - get_user_model().objects.create_superuser(username='bob', email='bob@email.com', password='password') - - def change_something(self, title): - post_data = {'title': title, 'content': "Some content", 'slug': 'hello-world'} - response = self.client.post(reverse('wagtailadmin_pages:edit', args=(self.child_page.id, )), post_data) - - # Should be redirected to edit page - self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(self.child_page.id, ))) - - # The page should have "has_unpublished_changes" flag set - child_page_new = SimplePage.objects.get(id=self.child_page.id) - self.assertTrue(child_page_new.has_unpublished_changes) - - def go_to_dashboard_response(self): - response = self.client.get(reverse('wagtailadmin_home')) - self.assertEqual(response.status_code, 200) - return response - - def test_your_recent_edits(self): - # Login as Bob - self.client.login(username='bob', password='password') - - # Bob hasn't edited anything yet - response = self.client.get(reverse('wagtailadmin_home')) - self.assertNotIn('Your most recent edits', response.content.decode('utf-8')) - - # Login as Alice - self.client.logout() - self.client.login(username='alice', password='password') - - # Alice changes something - self.change_something("Alice's edit") - - # Edit should show up on dashboard - response = self.go_to_dashboard_response() - self.assertIn('Your most recent edits', response.content.decode('utf-8')) - - # Bob changes something - self.client.login(username='bob', password='password') - self.change_something("Bob's edit") - - # Edit shows up on Bobs dashboard - response = self.go_to_dashboard_response() - self.assertIn('Your most recent edits', response.content.decode('utf-8')) - - # Login as Alice again - self.client.logout() - self.client.login(username='alice', password='password') - - # Alice's dashboard should still list that first edit - response = self.go_to_dashboard_response() - self.assertIn('Your most recent edits', response.content.decode('utf-8')) - - def test_panel(self): - """Test if the panel actually returns expected pages """ - self.client.login(username='bob', password='password') - # change a page - self.change_something("Bob's edit") - # set a user to 'mock' a request - self.client.user = get_user_model().objects.get(email='bob@email.com') - # get the panel to get the last edits - panel = RecentEditsPanel(self.client) - # check if the revision is the revision of edited Page - self.assertEqual(panel.last_edits[0][0].page, Page.objects.get(pk=self.child_page.id)) - # check if the page in this list is the specific page of this revision - self.assertEqual(panel.last_edits[0][1], Page.objects.get(pk=self.child_page.id).specific) - - -class TestIssue2994(TestCase, WagtailTestUtils): - """ - In contrast to most "standard" form fields, StreamField form widgets generally won't - provide a postdata field with a name exactly matching the field name. To prevent Django - from wrongly interpreting this as the field being omitted from the form, - we need to provide a custom value_omitted_from_data method. - """ - - def setUp(self): - self.root_page = Page.objects.get(id=2) - self.user = self.login() - - def test_page_edit_post_publish_url(self): - # Post - post_data = { - 'title': "Issue 2994 test", - 'slug': 'issue-2994-test', - 'body-count': '1', - 'body-0-deleted': '', - 'body-0-order': '0', - 'body-0-type': 'text', - 'body-0-value': 'hello world', - 'action-publish': "Publish", - } - self.client.post( - reverse('wagtailadmin_pages:add', args=('tests', 'defaultstreampage', self.root_page.id)), post_data - ) - new_page = DefaultStreamPage.objects.get(slug='issue-2994-test') - self.assertEqual(1, len(new_page.body)) - self.assertEqual('hello world', new_page.body[0].value) - - -class TestParentalM2M(TestCase, WagtailTestUtils): - fixtures = ['test.json'] - - def setUp(self): - self.events_index = Page.objects.get(url_path='/home/events/') - self.christmas_page = Page.objects.get(url_path='/home/events/christmas/') - self.user = self.login() - self.holiday_category = EventCategory.objects.create(name='Holiday') - self.men_with_beards_category = EventCategory.objects.create(name='Men with beards') - - def test_create_and_save(self): - post_data = { - 'title': "Presidents' Day", - 'date_from': "2017-02-20", - 'slug': "presidents-day", - 'audience': "public", - 'location': "America", - 'cost': "$1", - 'carousel_items-TOTAL_FORMS': 0, - 'carousel_items-INITIAL_FORMS': 0, - 'carousel_items-MIN_NUM_FORMS': 0, - 'carousel_items-MAX_NUM_FORMS': 0, - 'speakers-TOTAL_FORMS': 0, - 'speakers-INITIAL_FORMS': 0, - 'speakers-MIN_NUM_FORMS': 0, - 'speakers-MAX_NUM_FORMS': 0, - 'related_links-TOTAL_FORMS': 0, - 'related_links-INITIAL_FORMS': 0, - 'related_links-MIN_NUM_FORMS': 0, - 'related_links-MAX_NUM_FORMS': 0, - 'head_counts-TOTAL_FORMS': 0, - 'head_counts-INITIAL_FORMS': 0, - 'head_counts-MIN_NUM_FORMS': 0, - 'head_counts-MAX_NUM_FORMS': 0, - 'categories': [self.holiday_category.id, self.men_with_beards_category.id] - } - response = self.client.post( - reverse('wagtailadmin_pages:add', args=('tests', 'eventpage', self.events_index.id)), - post_data - ) - created_page = EventPage.objects.get(url_path='/home/events/presidents-day/') - self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(created_page.id, ))) - created_revision = created_page.get_latest_revision_as_page() - - self.assertIn(self.holiday_category, created_revision.categories.all()) - self.assertIn(self.men_with_beards_category, created_revision.categories.all()) - - def test_create_and_publish(self): - post_data = { - 'action-publish': "Publish", - 'title': "Presidents' Day", - 'date_from': "2017-02-20", - 'slug': "presidents-day", - 'audience': "public", - 'location': "America", - 'cost': "$1", - 'carousel_items-TOTAL_FORMS': 0, - 'carousel_items-INITIAL_FORMS': 0, - 'carousel_items-MIN_NUM_FORMS': 0, - 'carousel_items-MAX_NUM_FORMS': 0, - 'speakers-TOTAL_FORMS': 0, - 'speakers-INITIAL_FORMS': 0, - 'speakers-MIN_NUM_FORMS': 0, - 'speakers-MAX_NUM_FORMS': 0, - 'related_links-TOTAL_FORMS': 0, - 'related_links-INITIAL_FORMS': 0, - 'related_links-MIN_NUM_FORMS': 0, - 'related_links-MAX_NUM_FORMS': 0, - 'head_counts-TOTAL_FORMS': 0, - 'head_counts-INITIAL_FORMS': 0, - 'head_counts-MIN_NUM_FORMS': 0, - 'head_counts-MAX_NUM_FORMS': 0, - 'categories': [self.holiday_category.id, self.men_with_beards_category.id] - } - response = self.client.post( - reverse('wagtailadmin_pages:add', args=('tests', 'eventpage', self.events_index.id)), - post_data - ) - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.events_index.id, ))) - - created_page = EventPage.objects.get(url_path='/home/events/presidents-day/') - self.assertIn(self.holiday_category, created_page.categories.all()) - self.assertIn(self.men_with_beards_category, created_page.categories.all()) - - def test_edit_and_save(self): - post_data = { - 'title': "Christmas", - 'date_from': "2017-12-25", - 'slug': "christmas", - 'audience': "public", - 'location': "The North Pole", - 'cost': "Free", - 'carousel_items-TOTAL_FORMS': 0, - 'carousel_items-INITIAL_FORMS': 0, - 'carousel_items-MIN_NUM_FORMS': 0, - 'carousel_items-MAX_NUM_FORMS': 0, - 'speakers-TOTAL_FORMS': 0, - 'speakers-INITIAL_FORMS': 0, - 'speakers-MIN_NUM_FORMS': 0, - 'speakers-MAX_NUM_FORMS': 0, - 'related_links-TOTAL_FORMS': 0, - 'related_links-INITIAL_FORMS': 0, - 'related_links-MIN_NUM_FORMS': 0, - 'related_links-MAX_NUM_FORMS': 0, - 'head_counts-TOTAL_FORMS': 0, - 'head_counts-INITIAL_FORMS': 0, - 'head_counts-MIN_NUM_FORMS': 0, - 'head_counts-MAX_NUM_FORMS': 0, - 'categories': [self.holiday_category.id, self.men_with_beards_category.id] - } - response = self.client.post( - reverse('wagtailadmin_pages:edit', args=(self.christmas_page.id, )), - post_data - ) - self.assertRedirects(response, reverse('wagtailadmin_pages:edit', args=(self.christmas_page.id, ))) - updated_page = EventPage.objects.get(id=self.christmas_page.id) - created_revision = updated_page.get_latest_revision_as_page() - - self.assertIn(self.holiday_category, created_revision.categories.all()) - self.assertIn(self.men_with_beards_category, created_revision.categories.all()) - - # no change to live page record yet - self.assertEqual(0, updated_page.categories.count()) - - def test_edit_and_publish(self): - post_data = { - 'action-publish': "Publish", - 'title': "Christmas", - 'date_from': "2017-12-25", - 'slug': "christmas", - 'audience': "public", - 'location': "The North Pole", - 'cost': "Free", - 'carousel_items-TOTAL_FORMS': 0, - 'carousel_items-INITIAL_FORMS': 0, - 'carousel_items-MIN_NUM_FORMS': 0, - 'carousel_items-MAX_NUM_FORMS': 0, - 'speakers-TOTAL_FORMS': 0, - 'speakers-INITIAL_FORMS': 0, - 'speakers-MIN_NUM_FORMS': 0, - 'speakers-MAX_NUM_FORMS': 0, - 'related_links-TOTAL_FORMS': 0, - 'related_links-INITIAL_FORMS': 0, - 'related_links-MIN_NUM_FORMS': 0, - 'related_links-MAX_NUM_FORMS': 0, - 'head_counts-TOTAL_FORMS': 0, - 'head_counts-INITIAL_FORMS': 0, - 'head_counts-MIN_NUM_FORMS': 0, - 'head_counts-MAX_NUM_FORMS': 0, - 'categories': [self.holiday_category.id, self.men_with_beards_category.id] - } - response = self.client.post( - reverse('wagtailadmin_pages:edit', args=(self.christmas_page.id, )), - post_data - ) - self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.events_index.id, ))) - updated_page = EventPage.objects.get(id=self.christmas_page.id) - self.assertEqual(2, updated_page.categories.count()) - self.assertIn(self.holiday_category, updated_page.categories.all()) - self.assertIn(self.men_with_beards_category, updated_page.categories.all()) - - -class TestValidationErrorMessages(TestCase, WagtailTestUtils): - fixtures = ['test.json'] - - def setUp(self): - self.events_index = Page.objects.get(url_path='/home/events/') - self.christmas_page = Page.objects.get(url_path='/home/events/christmas/') - self.user = self.login() - - def test_field_error(self): - """Field errors should be shown against the relevant fields, not in the header message""" - post_data = { - 'title': "", - 'date_from': "2017-12-25", - 'slug': "christmas", - 'audience': "public", - 'location': "The North Pole", - 'cost': "Free", - 'carousel_items-TOTAL_FORMS': 0, - 'carousel_items-INITIAL_FORMS': 0, - 'carousel_items-MIN_NUM_FORMS': 0, - 'carousel_items-MAX_NUM_FORMS': 0, - 'speakers-TOTAL_FORMS': 0, - 'speakers-INITIAL_FORMS': 0, - 'speakers-MIN_NUM_FORMS': 0, - 'speakers-MAX_NUM_FORMS': 0, - 'related_links-TOTAL_FORMS': 0, - 'related_links-INITIAL_FORMS': 0, - 'related_links-MIN_NUM_FORMS': 0, - 'related_links-MAX_NUM_FORMS': 0, - 'head_counts-TOTAL_FORMS': 0, - 'head_counts-INITIAL_FORMS': 0, - 'head_counts-MIN_NUM_FORMS': 0, - 'head_counts-MAX_NUM_FORMS': 0, - } - response = self.client.post( - reverse('wagtailadmin_pages:edit', args=(self.christmas_page.id, )), - post_data - ) - self.assertEqual(response.status_code, 200) - - self.assertContains(response, "The page could not be saved due to validation errors") - # the error should only appear once: against the field, not in the header message - self.assertContains(response, """

    This field is required.

    """, count=1, html=True) - self.assertContains(response, "This field is required", count=1) - - def test_non_field_error(self): - """Non-field errors should be shown in the header message""" - post_data = { - 'title': "Christmas", - 'date_from': "2017-12-25", - 'date_to': "2017-12-24", - 'slug': "christmas", - 'audience': "public", - 'location': "The North Pole", - 'cost': "Free", - 'carousel_items-TOTAL_FORMS': 0, - 'carousel_items-INITIAL_FORMS': 0, - 'carousel_items-MIN_NUM_FORMS': 0, - 'carousel_items-MAX_NUM_FORMS': 0, - 'speakers-TOTAL_FORMS': 0, - 'speakers-INITIAL_FORMS': 0, - 'speakers-MIN_NUM_FORMS': 0, - 'speakers-MAX_NUM_FORMS': 0, - 'related_links-TOTAL_FORMS': 0, - 'related_links-INITIAL_FORMS': 0, - 'related_links-MIN_NUM_FORMS': 0, - 'related_links-MAX_NUM_FORMS': 0, - 'head_counts-TOTAL_FORMS': 0, - 'head_counts-INITIAL_FORMS': 0, - 'head_counts-MIN_NUM_FORMS': 0, - 'head_counts-MAX_NUM_FORMS': 0, - } - response = self.client.post( - reverse('wagtailadmin_pages:edit', args=(self.christmas_page.id, )), - post_data - ) - self.assertEqual(response.status_code, 200) - - self.assertContains(response, "The page could not be saved due to validation errors") - self.assertContains(response, "
  • The end date must be after the start date
  • ", count=1) - - def test_field_and_non_field_error(self): - """ - If both field and non-field errors exist, all errors should be shown in the header message - with appropriate context to identify the field; and field errors should also be shown - against the relevant fields. - """ - post_data = { - 'title': "", - 'date_from': "2017-12-25", - 'date_to': "2017-12-24", - 'slug': "christmas", - 'audience': "public", - 'location': "The North Pole", - 'cost': "Free", - 'carousel_items-TOTAL_FORMS': 0, - 'carousel_items-INITIAL_FORMS': 0, - 'carousel_items-MIN_NUM_FORMS': 0, - 'carousel_items-MAX_NUM_FORMS': 0, - 'speakers-TOTAL_FORMS': 0, - 'speakers-INITIAL_FORMS': 0, - 'speakers-MIN_NUM_FORMS': 0, - 'speakers-MAX_NUM_FORMS': 0, - 'related_links-TOTAL_FORMS': 0, - 'related_links-INITIAL_FORMS': 0, - 'related_links-MIN_NUM_FORMS': 0, - 'related_links-MAX_NUM_FORMS': 0, - 'head_counts-TOTAL_FORMS': 0, - 'head_counts-INITIAL_FORMS': 0, - 'head_counts-MIN_NUM_FORMS': 0, - 'head_counts-MAX_NUM_FORMS': 0, - } - response = self.client.post( - reverse('wagtailadmin_pages:edit', args=(self.christmas_page.id, )), - post_data - ) - self.assertEqual(response.status_code, 200) - - self.assertContains(response, "The page could not be saved due to validation errors") - self.assertContains(response, "
  • The end date must be after the start date
  • ", count=1) - - # Error on title shown against the title field - self.assertContains(response, """

    This field is required.

    """, count=1, html=True) - # Error on title shown in the header message - self.assertContains(response, "
  • Title: This field is required.
  • ", count=1) - - -class TestDraftAccess(TestCase, WagtailTestUtils): - """Tests for the draft view access restrictions.""" - - def setUp(self): - # Find root page - self.root_page = Page.objects.get(id=2) - - # Add child page - self.child_page = SimplePage( - title="Hello world!", - slug="hello-world", - content="hello", - ) - self.root_page.add_child(instance=self.child_page) - - # create user with admin access (but not draft_view access) - user = get_user_model().objects.create_user(username='bob', email='bob@email.com', password='password') - user.user_permissions.add( - Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') - ) - - def test_draft_access_admin(self): - """Test that admin can view draft.""" - # Login as admin - self.user = self.login() - - # Try getting page draft - response = self.client.get(reverse('wagtailadmin_pages:view_draft', args=(self.child_page.id, ))) - - # User can view - self.assertEqual(response.status_code, 200) - - def test_draft_access_unauthorized(self): - """Test that user without edit/publish permission can't view draft.""" - self.assertTrue(self.client.login(username='bob', password='password')) - - # Try getting page draft - response = self.client.get(reverse('wagtailadmin_pages:view_draft', args=(self.child_page.id, ))) - - # User gets Unauthorized response - self.assertEqual(response.status_code, 403) - - def test_draft_access_authorized(self): - """Test that user with edit permission can view draft.""" - # give user the permission to edit page - user = get_user_model().objects.get(username='bob') - user.groups.add(Group.objects.get(name='Moderators')) - user.save() - - self.assertTrue(self.client.login(username='bob', password='password')) - - # Get add subpage page - response = self.client.get(reverse('wagtailadmin_pages:view_draft', args=(self.child_page.id, ))) - - # User can view - self.assertEqual(response.status_code, 200) - - def test_middleware_response_is_returned(self): - """ - If middleware returns a response while serving a page preview, that response should be - returned back to the user - """ - self.login() - response = self.client.get( - reverse('wagtailadmin_pages:view_draft', args=(self.child_page.id, )), - HTTP_USER_AGENT='EvilHacker' - ) - self.assertEqual(response.status_code, 403) - - -class TestPreview(TestCase, WagtailTestUtils): - fixtures = ['test.json'] - - def setUp(self): - self.meetings_category = EventCategory.objects.create(name='Meetings') - self.parties_category = EventCategory.objects.create(name='Parties') - self.holidays_category = EventCategory.objects.create(name='Holidays') - - self.home_page = Page.objects.get(url_path='/home/') - self.event_page = Page.objects.get(url_path='/home/events/christmas/') - - self.user = self.login() - - self.post_data = { - 'title': "Beach party", - 'slug': 'beach-party', - 'body': '''{"entityMap": {},"blocks": [ - {"inlineStyleRanges": [], "text": "party on wayne", "depth": 0, "type": "unstyled", "key": "00000", "entityRanges": []} - ]}''', - 'date_from': '2017-08-01', - 'audience': 'public', - 'location': 'the beach', - 'cost': 'six squid', - 'carousel_items-TOTAL_FORMS': 0, - 'carousel_items-INITIAL_FORMS': 0, - 'carousel_items-MIN_NUM_FORMS': 0, - 'carousel_items-MAX_NUM_FORMS': 0, - 'speakers-TOTAL_FORMS': 0, - 'speakers-INITIAL_FORMS': 0, - 'speakers-MIN_NUM_FORMS': 0, - 'speakers-MAX_NUM_FORMS': 0, - 'related_links-TOTAL_FORMS': 0, - 'related_links-INITIAL_FORMS': 0, - 'related_links-MIN_NUM_FORMS': 0, - 'related_links-MAX_NUM_FORMS': 0, - 'head_counts-TOTAL_FORMS': 0, - 'head_counts-INITIAL_FORMS': 0, - 'head_counts-MIN_NUM_FORMS': 0, - 'head_counts-MAX_NUM_FORMS': 0, - 'categories': [self.parties_category.id, self.holidays_category.id], - } - - def test_preview_on_create_with_m2m_field(self): - preview_url = reverse('wagtailadmin_pages:preview_on_add', - args=('tests', 'eventpage', self.home_page.id)) - response = self.client.post(preview_url, self.post_data) - - # Check the JSON response - self.assertEqual(response.status_code, 200) - self.assertJSONEqual(response.content.decode(), {'is_valid': True}) - - response = self.client.get(preview_url) - - # Check the HTML response - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'tests/event_page.html') - self.assertContains(response, "Beach party") - self.assertContains(response, "
  • Parties
  • ") - self.assertContains(response, "
  • Holidays
  • ") - - def test_preview_on_edit_with_m2m_field(self): - preview_url = reverse('wagtailadmin_pages:preview_on_edit', - args=(self.event_page.id,)) - response = self.client.post(preview_url, self.post_data) - - # Check the JSON response - self.assertEqual(response.status_code, 200) - self.assertJSONEqual(response.content.decode(), {'is_valid': True}) - - response = self.client.get(preview_url) - - # Check the HTML response - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'tests/event_page.html') - self.assertContains(response, "Beach party") - self.assertContains(response, "
  • Parties
  • ") - self.assertContains(response, "
  • Holidays
  • ") - - def test_preview_on_edit_expiry(self): - initial_datetime = timezone.now() - expiry_datetime = initial_datetime + datetime.timedelta( - seconds=PreviewOnEdit.preview_expiration_timeout + 1) - - with freeze_time(initial_datetime) as frozen_datetime: - preview_url = reverse('wagtailadmin_pages:preview_on_edit', - args=(self.event_page.id,)) - response = self.client.post(preview_url, self.post_data) - - # Check the JSON response - self.assertEqual(response.status_code, 200) - - response = self.client.get(preview_url) - - # Check the HTML response - self.assertEqual(response.status_code, 200) - - frozen_datetime.move_to(expiry_datetime) - - preview_url = reverse('wagtailadmin_pages:preview_on_edit', - args=(self.home_page.id,)) - response = self.client.post(preview_url, self.post_data) - self.assertEqual(response.status_code, 200) - response = self.client.get(preview_url) - self.assertEqual(response.status_code, 200)