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, '
test_advert ', 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 away This 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 away This 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 away This 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 away This 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, '
test_advert ', 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 away This 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 away This 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 away This 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 away This 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)