Initial commit

This commit is contained in:
2024-08-27 20:33:44 +02:00
commit 1f1832267d
14794 changed files with 1599592 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
from django.test import TestCase
from django.urls import reverse
from wagtail.test.utils import WagtailTestUtils
class TestBulkActionDispatcher(WagtailTestUtils, TestCase):
def setUp(self):
# Login
self.user = self.login()
def test_bulk_action_invalid_action(self):
url = reverse(
"wagtail_bulk_action",
args=(
"wagtailcore",
"page",
"ships",
),
)
response = self.client.get(url)
self.assertEqual(response.status_code, 404)
def test_bulk_action_invalid_model(self):
url = reverse(
"wagtail_bulk_action",
args=(
"doesnotexist",
"doesnotexist",
"doesnotexist",
),
)
response = self.client.get(url)
self.assertEqual(response.status_code, 404)

View File

@@ -0,0 +1,364 @@
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
from django.http.response import HttpResponse
from django.test import TestCase
from django.urls import reverse
from wagtail.admin.views.pages.bulk_actions.page_bulk_action import PageBulkAction
from wagtail.models import Page
from wagtail.signals import page_unpublished
from wagtail.test.testapp.models import SimplePage
from wagtail.test.utils import WagtailTestUtils
class TestBulkDelete(WagtailTestUtils, TestCase):
def setUp(self):
# Find root page
self.root_page = Page.objects.get(id=2)
# Add child pages
self.child_pages = [
SimplePage(
title=f"Hello world!-{i}", slug=f"hello-world-{i}", content=f"hello-{i}"
)
for i in range(1, 5)
]
# first three child pages will be deleted
self.pages_to_be_deleted = self.child_pages[:3]
self.pages_not_to_be_deleted = self.child_pages[3:]
for child_page in self.child_pages:
self.root_page.add_child(instance=child_page)
# map of the form { page: [child_pages] } to be added
self.grandchildren_pages = {
self.pages_to_be_deleted[0]: [
SimplePage(
title="Hello world!-a", slug="hello-world-a", content="hello-a"
)
],
self.pages_to_be_deleted[1]: [
SimplePage(
title="Hello world!-b", slug="hello-world-b", content="hello-b"
),
SimplePage(
title="Hello world!-c", slug="hello-world-c", content="hello-c"
),
],
}
for child_page, grandchild_pages in self.grandchildren_pages.items():
for grandchild_page in grandchild_pages:
child_page.add_child(instance=grandchild_page)
self.url = (
reverse(
"wagtail_bulk_action",
args=(
"wagtailcore",
"page",
"delete",
),
)
+ "?"
)
for child_page in self.pages_to_be_deleted:
self.url += f"&id={child_page.id}"
# Login
self.user = self.login()
def test_bulk_delete_get(self):
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
# deletion should not actually happen on GET
for child_page in self.child_pages:
self.assertTrue(SimplePage.objects.filter(id=child_page.id).exists())
html = response.content.decode()
for child_page in self.pages_to_be_deleted:
# check if the pages to be deleted and number of descendant pages are displayed
needle = "<li>"
needle += '<a href="{edit_page_url}" target="_blank" rel="noreferrer">{page_title}</a>'.format(
edit_page_url=reverse("wagtailadmin_pages:edit", args=[child_page.id]),
page_title=child_page.title,
)
descendants = len(self.grandchildren_pages.get(child_page, []))
if descendants:
needle += "<p>"
if descendants == 1:
needle += "This will also delete one more subpage."
else:
needle += f"This will also delete {descendants} more subpages."
needle += "</p>"
needle += "</li>"
self.assertInHTML(needle, html)
def test_page_delete_specific_admin_title(self):
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
# The number of pages to be deleted is shown on the delete confirmation page.
self.assertContains(response, f"Delete {len(self.pages_to_be_deleted)} pages")
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(self.url)
# Check that the user received a 200 redirect response
self.assertEqual(response.status_code, 200)
# Check that the deletion has not happened
for child_page in self.child_pages:
self.assertTrue(SimplePage.objects.filter(id=child_page.id).exists())
html = response.content.decode()
self.assertInHTML(
"<p>You don't have permission to delete these pages</p>", html
)
for child_page in self.pages_to_be_deleted:
self.assertInHTML(f"<li>{child_page.title}</li>", html)
self.assertTagInHTML(
f"""<form action="{self.url}" method="POST"></form>""",
html,
count=0,
)
def test_bulk_delete_post(self):
# Connect a mock signal handler to page_unpublished signal
mock_handler = mock.MagicMock()
page_unpublished.connect(mock_handler)
try:
# Post
response = self.client.post(self.url)
# Should be redirected to explorer page
self.assertEqual(response.status_code, 302)
# treebeard should report no consistency problems with the tree
self.assertFalse(
any(Page.find_problems()), msg="treebeard found consistency problems"
)
# Check that the child pages to be deleted are gone
for child_page in self.pages_to_be_deleted:
self.assertFalse(SimplePage.objects.filter(id=child_page.id).exists())
# Check that the child pages not to be deleted remain
for child_page in self.pages_not_to_be_deleted:
self.assertTrue(SimplePage.objects.filter(id=child_page.id).exists())
# Check that the page_unpublished signal was fired for all pages
num_descendants = sum(len(i) for i in self.grandchildren_pages.values())
self.assertEqual(
mock_handler.call_count, len(self.pages_to_be_deleted) + num_descendants
)
i = 0
for child_page in self.pages_to_be_deleted:
mock_call = mock_handler.mock_calls[i][2]
i += 1
self.assertEqual(mock_call["sender"], child_page.specific_class)
self.assertEqual(mock_call["instance"], child_page)
self.assertIsInstance(mock_call["instance"], child_page.specific_class)
for grandchildren_page in self.grandchildren_pages.get(child_page, []):
mock_call = mock_handler.mock_calls[i][2]
i += 1
self.assertEqual(
mock_call["sender"], grandchildren_page.specific_class
)
self.assertEqual(mock_call["instance"], grandchildren_page)
self.assertIsInstance(
mock_call["instance"], grandchildren_page.specific_class
)
finally:
page_unpublished.disconnect(mock_handler)
def test_bulk_delete_notlive_post(self):
# Same as above, but this makes sure the page_unpublished signal is not fired
# for the page that is not live when it is deleted
# Unpublish the first child page
page_to_be_unpublished = self.pages_to_be_deleted[0]
page_to_be_unpublished.unpublish(user=self.user)
# Connect a mock signal handler to page_unpublished signal
mock_handler = mock.MagicMock()
page_unpublished.connect(mock_handler)
try:
# Post
response = self.client.post(self.url)
# Should be redirected to explorer page
self.assertEqual(response.status_code, 302)
# treebeard should report no consistency problems with the tree
self.assertFalse(
any(Page.find_problems()), msg="treebeard found consistency problems"
)
# Check that the child pages to be deleted are gone
for child_page in self.pages_to_be_deleted:
self.assertFalse(SimplePage.objects.filter(id=child_page.id).exists())
# Check that the child pages not to be deleted remain
for child_page in self.pages_not_to_be_deleted:
self.assertTrue(SimplePage.objects.filter(id=child_page.id).exists())
# Check that the page_unpublished signal was not fired
num_descendants = sum(len(v) for v in self.grandchildren_pages.values())
self.assertEqual(
mock_handler.call_count,
len(self.pages_to_be_deleted) + num_descendants - 1,
)
# check that only signals for other pages are fired
i = 0
for child_page in self.pages_to_be_deleted:
if child_page.id != page_to_be_unpublished.id:
mock_call = mock_handler.mock_calls[i][2]
i += 1
self.assertEqual(mock_call["sender"], child_page.specific_class)
self.assertEqual(mock_call["instance"], child_page)
self.assertIsInstance(
mock_call["instance"], child_page.specific_class
)
for grandchildren_page in self.grandchildren_pages.get(child_page, []):
mock_call = mock_handler.mock_calls[i][2]
i += 1
self.assertEqual(
mock_call["sender"], grandchildren_page.specific_class
)
self.assertEqual(mock_call["instance"], grandchildren_page)
self.assertIsInstance(
mock_call["instance"], grandchildren_page.specific_class
)
finally:
page_unpublished.disconnect(mock_handler)
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.pk))
def pre_delete_handler(sender, instance, **kwargs):
pre_delete_signals_received.append((sender, instance.pk))
def post_delete_handler(sender, instance, **kwargs):
post_delete_signals_received.append((sender, instance.pk))
page_unpublished.connect(page_unpublished_handler)
pre_delete.connect(pre_delete_handler)
post_delete.connect(post_delete_handler)
try:
# Post
response = self.client.post(self.url)
# Should be redirected to explorer page
self.assertEqual(response.status_code, 302)
# treebeard should report no consistency problems with the tree
self.assertFalse(
any(Page.find_problems()), msg="treebeard found consistency problems"
)
# Check that the child pages to be deleted are gone
for child_page in self.pages_to_be_deleted:
self.assertFalse(SimplePage.objects.filter(id=child_page.id).exists())
# Check that the child pages not to be deleted remain
for child_page in self.pages_not_to_be_deleted:
self.assertTrue(SimplePage.objects.filter(id=child_page.id).exists())
# Check that the subpages are also gone
for grandchild_pages in self.grandchildren_pages.values():
for grandchild_page in grandchild_pages:
self.assertFalse(
SimplePage.objects.filter(id=grandchild_page.id).exists()
)
# Check that the signals were fired for all child and grandchild pages
for child_page, grandchild_pages in self.grandchildren_pages.items():
self.assertIn((SimplePage, child_page.id), unpublish_signals_received)
self.assertIn((SimplePage, child_page.id), pre_delete_signals_received)
self.assertIn((SimplePage, child_page.id), post_delete_signals_received)
for grandchild_page in grandchild_pages:
self.assertIn(
(SimplePage, grandchild_page.id), unpublish_signals_received
)
self.assertIn(
(SimplePage, grandchild_page.id), pre_delete_signals_received
)
self.assertIn(
(SimplePage, grandchild_page.id), post_delete_signals_received
)
self.assertEqual(response.status_code, 302)
finally:
page_unpublished.disconnect(page_unpublished_handler)
pre_delete.disconnect(pre_delete_handler)
post_delete.disconnect(post_delete_handler)
def test_before_delete_page_hook(self):
def hook_func(request, action_type, pages, action_class_instance):
self.assertEqual(action_type, "delete")
self.assertIsInstance(request, HttpRequest)
self.assertIsInstance(action_class_instance, PageBulkAction)
for i, page in enumerate(pages):
self.assertEqual(page.id, self.pages_to_be_deleted[i].id)
return HttpResponse("Overridden!")
with self.register_hook("before_bulk_action", hook_func):
response = self.client.post(self.url)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b"Overridden!")
# Check that the child pages are not deleted
for child_page in self.child_pages:
self.assertTrue(SimplePage.objects.filter(id=child_page.id).exists())
def test_after_delete_page_hook(self):
def hook_func(request, action_type, pages, action_class_instance):
self.assertEqual(action_type, "delete")
self.assertIsInstance(request, HttpRequest)
self.assertIsInstance(action_class_instance, PageBulkAction)
for i, page in enumerate(pages):
self.assertEqual(page.id, self.pages_to_be_deleted[i].id)
return HttpResponse("Overridden!")
with self.register_hook("after_bulk_action", hook_func):
response = self.client.post(self.url)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b"Overridden!")
# Check that the child pages to be deleted are gone
for child_page in self.pages_to_be_deleted:
self.assertFalse(SimplePage.objects.filter(id=child_page.id).exists())
# Check that the child pages not to be deleted remain
for child_page in self.pages_not_to_be_deleted:
self.assertTrue(SimplePage.objects.filter(id=child_page.id).exists())

View File

@@ -0,0 +1,321 @@
from unittest import mock
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.http import HttpRequest, HttpResponse
from django.test import TestCase
from django.urls import reverse
from wagtail.admin.views.pages.bulk_actions.page_bulk_action import PageBulkAction
from wagtail.models import Page
from wagtail.signals import post_page_move, pre_page_move
from wagtail.test.testapp.models import BusinessChild, SimplePage
from wagtail.test.utils import WagtailTestUtils
class TestBulkMove(WagtailTestUtils, TestCase):
fixtures = ["test.json"]
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-a", 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-b", content="hello"
)
self.section_c.add_child(instance=self.test_page_b)
# Add test page B_1 into section C
self.test_page_b_1 = SimplePage(
title="Hello world!", slug="hello-world-b-1", content="hello"
)
self.section_c.add_child(instance=self.test_page_b_1)
# Add test page A_1 into section C having same slug as test page A
self.test_page_a_1 = SimplePage(
title="Hello world!", slug="hello-world-a", content="hello"
)
self.section_c.add_child(instance=self.test_page_a_1)
# Add unpublished page to the root with a child page
self.unpublished_page = SimplePage(
title="Unpublished", slug="unpublished", content="hello"
)
sub_page = SimplePage(title="Sub Page", slug="sub-page", content="child")
self.root_page.add_child(instance=self.unpublished_page)
self.unpublished_page.add_child(instance=sub_page)
# unpublish pages last (used to validate the edit only permission)
self.unpublished_page.unpublish()
sub_page.unpublish()
self.pages_to_be_moved = [self.test_page_b, self.test_page_b_1]
self.url = (
reverse(
"wagtail_bulk_action",
args=(
"wagtailcore",
"page",
"move",
),
)
+ f"?id={self.test_page_b.id}&id={self.test_page_b_1.id}"
)
# Login
self.user = self.login()
def test_bulk_move(self):
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
html = response.content.decode()
self.assertInHTML("<p>Are you sure you want to move these pages?</p>", html)
self.assertInHTML(
'<li><a href="{edit_page_url}" target="_blank" rel="noreferrer">Hello world! (simple page)</a></li>'.format(
edit_page_url=reverse(
"wagtailadmin_pages:edit", args=[self.test_page_b.id]
),
),
html,
)
def test_bulk_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(self.url)
self.assertEqual(response.status_code, 200)
html = response.content.decode()
self.assertInHTML("<p>You don't have permission to move these pages</p>", html)
for child_page in self.pages_to_be_moved:
self.assertInHTML(f"<li>{child_page.title}</li>", html)
self.assertTagInHTML(
f"""<form action="{self.url}" method="POST"></form>""",
html,
count=0,
)
def test_user_without_bulk_delete_permission_can_move(self):
# to verify that a user without bulk delete permission is able to move a page with a child page
self.client.logout()
user = get_user_model().objects.get(email="siteeditor@example.com")
self.login(user)
# ensure the bulk_delete is not applicable to this user
can_bulk_delete = self.test_page_b.permissions_for_user(user).can_delete()
self.assertFalse(can_bulk_delete)
response = self.client.get(
reverse(
"wagtail_bulk_action",
args=(
"wagtailcore",
"page",
"move",
),
)
+ f"?id={self.unpublished_page.id}"
)
self.assertEqual(response.status_code, 200)
def test_bulk_move_destination_not_allowed(self):
page = BusinessChild(title="Section no child", slug="section-no-child")
self.root_page.add_child(instance=page)
response = self.client.post(self.url, {"chooser": page.id})
html = response.content.decode()
self.assertInHTML(
f"<p>The following pages cannot be moved to {page.title}</p>", html
)
for child_page in self.pages_to_be_moved:
self.assertInHTML(
'<li><a href="{edit_page_url}" target="_blank" rel="noreferrer">{page_title}</a></li>'.format(
edit_page_url=reverse(
"wagtailadmin_pages:edit", args=[child_page.id]
),
page_title=child_page.title,
),
html,
)
def test_bulk_move_slug_already_taken(self):
temp_page_1 = SimplePage(
title="Hello world!", slug="hello-world-b", content="hello"
)
temp_page_2 = SimplePage(
title="Hello world!", slug="hello-world-b-1", content="hello"
)
self.section_b.add_child(instance=temp_page_1)
self.section_b.add_child(instance=temp_page_2)
response = self.client.post(self.url, {"chooser": self.section_b.id})
html = response.content.decode()
self.assertInHTML(
"<p>The following pages cannot be moved due to duplicate slugs</p>", html
)
for child_page in self.pages_to_be_moved:
self.assertInHTML(
'<li><a href="{edit_page_url}" target="_blank" rel="noreferrer">{page_title}</a></li>'.format(
edit_page_url=reverse(
"wagtailadmin_pages:edit", args=[child_page.id]
),
page_title=child_page.title,
),
html,
)
def test_bulk_move_triggers_signals(self):
# Connect a mock signal handler to pre_page_move and post_page_move signals
pre_moved_handler = mock.MagicMock()
post_moved_handler = mock.MagicMock()
pre_page_move.connect(pre_moved_handler)
post_page_move.connect(post_moved_handler)
# Post to view confirm move page
try:
self.client.post(self.url, {"chooser": self.section_b.id})
finally:
# Disconnect mock handler to prevent cross-test pollution
pre_page_move.disconnect(pre_moved_handler)
post_page_move.disconnect(post_moved_handler)
# Check that the pre_page_move signals were fired
self.assertTrue(
pre_moved_handler.mock_calls[0].called_with(
sender=self.test_page_b.specific_class,
instance=self.test_page_b,
parent_page_before=self.section_c,
parent_page_after=self.section_b,
url_path_before="/home/section-c/hello-world-b/",
url_path_after="/home/section-b/hello-world-b/",
)
)
self.assertTrue(
pre_moved_handler.mock_calls[1].called_with(
sender=self.test_page_b_1.specific_class,
instance=self.test_page_b_1,
parent_page_before=self.section_c,
parent_page_after=self.section_b,
url_path_before="/home/section-c/hello-world-b-1/",
url_path_after="/home/section-b/hello-world-b-1/",
)
)
# Check that the post_page_move signals were fired
self.assertTrue(
post_moved_handler.mock_calls[0].called_with(
sender=self.test_page_b.specific_class,
instance=self.test_page_b,
parent_page_before=self.section_c,
parent_page_after=self.section_b,
url_path_before="/home/section-c/hello-world-b/",
url_path_after="/home/section-b/hello-world-b/",
)
)
self.assertTrue(
post_moved_handler.mock_calls[1].called_with(
sender=self.test_page_b_1.specific_class,
instance=self.test_page_b_1,
parent_page_before=self.section_c,
parent_page_after=self.section_b,
url_path_before="/home/section-c/hello-world-b-1/",
url_path_after="/home/section-b/hello-world-b-1/",
)
)
def test_before_bulk_move_hook(self):
def hook_func(request, action_type, pages, action_class_instance):
self.assertEqual(action_type, "move")
self.assertIsInstance(request, HttpRequest)
self.assertIsInstance(action_class_instance, PageBulkAction)
for i, page in enumerate(pages):
self.assertEqual(page.id, self.pages_to_be_moved[i].id)
return HttpResponse("Overridden!")
with self.register_hook("before_bulk_action", hook_func):
response = self.client.post(self.url, {"chooser": self.section_b.id})
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b"Overridden!")
self.assertEqual(
Page.objects.get(id=self.test_page_b.id).get_parent().id, self.section_c.id
)
self.assertEqual(
Page.objects.get(id=self.test_page_b_1.id).get_parent().id,
self.section_c.id,
)
def test_after_bulk_move_hook(self):
def hook_func(request, action_type, pages, action_class_instance):
self.assertEqual(action_type, "move")
self.assertIsInstance(request, HttpRequest)
self.assertIsInstance(action_class_instance, PageBulkAction)
for i, page in enumerate(pages):
self.assertEqual(page.id, self.pages_to_be_moved[i].id)
return HttpResponse("Overridden!")
with self.register_hook("after_bulk_action", hook_func):
response = self.client.post(self.url, {"chooser": self.section_b.id})
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b"Overridden!")
# pages should be moved
self.assertEqual(
Page.objects.get(id=self.test_page_b.id).get_parent().id, self.section_b.id
)
self.assertEqual(
Page.objects.get(id=self.test_page_b_1.id).get_parent().id,
self.section_b.id,
)

View File

@@ -0,0 +1,381 @@
from unittest import mock
from django.contrib.auth.models import Permission
from django.http import HttpRequest, HttpResponse
from django.test import TestCase
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from wagtail.admin.views.pages.bulk_actions.page_bulk_action import PageBulkAction
from wagtail.models import Page
from wagtail.signals import page_published
from wagtail.test.testapp.models import SimplePage
from wagtail.test.utils import WagtailTestUtils
class TestBulkPublish(WagtailTestUtils, TestCase):
def setUp(self):
self.root_page = Page.objects.get(id=2)
# Add child pages which will have already been published before we
# bulk publish them.
self.child_pages = [
SimplePage(
title=f"Hello world!-{i}",
slug=f"hello-world-{i}",
content=f"Hello world {i}!",
live=False,
)
for i in range(1, 5)
]
self.pages_to_be_published = self.child_pages[:3]
self.pages_not_to_be_published = self.child_pages[3:]
for child_page in self.child_pages:
self.root_page.add_child(instance=child_page)
for i, child_page in enumerate(self.child_pages):
child_page.content = f"Hello published world {i}!"
child_page.save_revision()
# Add an additional child page which will be bulk published from a
# draft-only state.
draft_page = SimplePage(
title="Hello world!-5",
slug="hello-world-5",
content="Hello published world 5!",
live=False,
)
self.root_page.add_child(instance=draft_page)
self.child_pages.append(draft_page)
self.pages_to_be_published.append(draft_page)
self.url = (
reverse(
"wagtail_bulk_action",
args=(
"wagtailcore",
"page",
"publish",
),
)
+ "?"
)
for child_page in self.pages_to_be_published:
self.url += f"id={child_page.id}&"
self.redirect_url = reverse("wagtailadmin_explore", args=(self.root_page.id,))
self.user = self.login()
def test_publish_view(self):
"""
This tests that the publish view responds with a publish confirm page
"""
# Request confirm publish page
response = self.client.get(self.url)
# # Check that the user received a publish confirm page
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(
response, "wagtailadmin/pages/bulk_actions/confirm_bulk_publish.html"
)
# Page titles shown on the confirmation page should use SimplePage's custom get_admin_display_title method
self.assertContains(response, "Hello world!-1 (simple page)")
def test_publish_view_invalid_page_id(self):
"""
This tests that the publish view returns an error if the page id is invalid
"""
# Request confirm publish page but with illegal page id
response = self.client.get(
reverse(
"wagtail_bulk_action",
args=(
"wagtailcore",
"page",
"publish",
),
)
)
# Check that the user received a 404 response
self.assertEqual(response.status_code, 404)
def test_publish_view_bad_permissions(self):
"""
This tests that the publish 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()
# Request confirm publish page
response = self.client.get(self.url)
# Check that the user received a 200 redirected response
self.assertEqual(response.status_code, 200)
html = response.content.decode()
self.assertInHTML(
"<p>You don't have permission to publish these pages</p>", html
)
for child_page in self.pages_to_be_published:
self.assertInHTML(f"<li>{child_page.title}</li>", html)
def test_publish_view_post(self):
"""
This posts to the publish view and checks that the page was published
"""
# Connect a mock signal handler to page_published signal
mock_handler = mock.MagicMock()
page_published.connect(mock_handler)
try:
# Post to the publish page
response = self.client.post(self.url)
# Should be redirected to explorer page
self.assertEqual(response.status_code, 302)
# Check that the child pages were published
for child_page in self.pages_to_be_published:
published_page = SimplePage.objects.get(id=child_page.id)
self.assertTrue(published_page.live)
self.assertIn("Hello published", published_page.content)
# Check that the child pages not to be published remain
for child_page in self.pages_not_to_be_published:
self.assertFalse(Page.objects.get(id=child_page.id).live)
# Check that the page_published signal was fired
self.assertEqual(mock_handler.call_count, len(self.pages_to_be_published))
for i, child_page in enumerate(self.pages_to_be_published):
mock_call = mock_handler.mock_calls[i][2]
self.assertEqual(mock_call["sender"], child_page.specific_class)
self.assertEqual(mock_call["instance"], child_page)
self.assertIsInstance(mock_call["instance"], child_page.specific_class)
finally:
page_published.disconnect(mock_handler)
def test_after_publish_page(self):
def hook_func(request, action_type, pages, action_class_instance):
self.assertEqual(action_type, "publish")
self.assertIsInstance(request, HttpRequest)
self.assertIsInstance(action_class_instance, PageBulkAction)
for i, page in enumerate(pages):
self.assertEqual(page.id, self.pages_to_be_published[i].id)
return HttpResponse("Overridden!")
with self.register_hook("after_bulk_action", hook_func):
response = self.client.post(self.url)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b"Overridden!")
for child_page in self.pages_to_be_published:
child_page.refresh_from_db()
self.assertEqual(child_page.status_string, _("live"))
def test_before_publish_page(self):
def hook_func(request, action_type, pages, action_class_instance):
self.assertEqual(action_type, "publish")
self.assertIsInstance(request, HttpRequest)
self.assertIsInstance(action_class_instance, PageBulkAction)
for i, page in enumerate(pages):
self.assertEqual(page.id, self.pages_to_be_published[i].id)
return HttpResponse("Overridden!")
with self.register_hook("before_bulk_action", hook_func):
response = self.client.post(self.url)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b"Overridden!")
def test_publish_descendants_view(self):
"""
This tests that the publish view responds with a publish confirm page that does not contain the form field 'include_descendants'
"""
# Get publish page for page with no descendants
response = self.client.get(self.url)
# Check that the user received a publish confirm page
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(
response, "wagtailadmin/pages/bulk_actions/confirm_bulk_publish.html"
)
# Check the form does not contain the checkbox field include_descendants
self.assertNotContains(
response,
'name="include_descendants"',
)
class TestBulkPublishIncludingDescendants(WagtailTestUtils, TestCase):
def setUp(self):
self.root_page = Page.objects.get(id=2)
# Add child pages
self.child_pages = [
SimplePage(
title=f"Hello world!-{i}",
slug=f"hello-world-{i}",
content=f"Hello world {i}!",
live=False,
)
for i in range(1, 5)
]
self.pages_to_be_published = self.child_pages[:3]
self.pages_not_to_be_published = self.child_pages[3:]
for child_page in self.child_pages:
self.root_page.add_child(instance=child_page)
for i, child_page in enumerate(self.child_pages):
child_page.content = f"Hello updated world {i}!"
child_page.save_revision()
# Add grandchild pages which will have already been published before
# we bulk publish them.
# map of the form { page: [child_pages] } to be added
self.grandchildren_pages = {
self.pages_to_be_published[0]: [
SimplePage(
title="Hello world!-a",
slug="hello-world-a",
content="Hello world a!",
live=False,
)
],
self.pages_to_be_published[1]: [
SimplePage(
title="Hello world!-b",
slug="hello-world-b",
content="Hello world b!",
live=False,
),
SimplePage(
title="Hello world!-c",
slug="hello-world-c",
content="Hello world c!",
live=False,
),
],
}
for child_page, grandchild_pages in self.grandchildren_pages.items():
for grandchild_page in grandchild_pages:
child_page.add_child(instance=grandchild_page)
for child_page, grandchild_pages in self.grandchildren_pages.items():
for grandchild_page in grandchild_pages:
grandchild_page.content = grandchild_page.content.replace(
"Hello world", "Hello grandchild"
)
grandchild_page.save_revision()
# Add an additional grandchild page which will be bulk published from
# a draft-only state.
draft_page = SimplePage(
title="Hello world!-d",
slug="hello-world-d",
content="Hello grandchild d!",
live=False,
)
self.pages_to_be_published[1].add_child(instance=draft_page)
self.grandchildren_pages[self.pages_to_be_published[1]].append(draft_page)
self.url = (
reverse(
"wagtail_bulk_action",
args=(
"wagtailcore",
"page",
"publish",
),
)
+ "?"
)
for child_page in self.pages_to_be_published:
self.url += f"&id={child_page.id}"
self.user = self.login()
def test_publish_descendants_view(self):
"""
This tests that the publish view responds with a publish confirm page that contains the form field 'include_descendants'
"""
# Get publish page
response = self.client.get(self.url)
# Check that the user received a publish confirm page
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(
response, "wagtailadmin/pages/bulk_actions/confirm_bulk_publish.html"
)
# Check the form contains the checkbox field include_descendants
self.assertContains(
response,
'name="include_descendants"',
)
def test_publish_include_children_view_post(self):
"""
This posts to the publish view and checks that the page and its descendants were published
"""
# Post to the publish page
response = self.client.post(self.url, {"include_descendants": "on"})
# Should be redirected to explorer page
self.assertEqual(response.status_code, 302)
# Check that the child pages were published
for child_page in self.pages_to_be_published:
published_child_page = SimplePage.objects.get(id=child_page.id)
self.assertTrue(published_child_page.live)
self.assertIn("Hello updated", published_child_page.content)
# Check that the child pages not to be published remain
for child_page in self.pages_not_to_be_published:
self.assertFalse(Page.objects.get(id=child_page.id).live)
for grandchild_pages in self.grandchildren_pages.values():
for grandchild_page in grandchild_pages:
published_grandchild_page = SimplePage.objects.get(
id=grandchild_page.id
)
self.assertTrue(published_grandchild_page.live)
self.assertIn("Hello grandchild", published_grandchild_page.content)
def test_publish_not_include_children_view_post(self):
"""
This posts to the publish view and checks that the page was published but its descendants were not
"""
# Post to the publish page
response = self.client.post(self.url, {})
# Should be redirected to explorer page
self.assertEqual(response.status_code, 302)
# Check that the child pages were published
for child_page in self.pages_to_be_published:
published_child_page = SimplePage.objects.get(id=child_page.id)
self.assertTrue(published_child_page.live)
self.assertIn("Hello updated", published_child_page.content)
# Check that the descendant pages were not published
for grandchild_pages in self.grandchildren_pages.values():
for grandchild_page in grandchild_pages:
self.assertFalse(Page.objects.get(id=grandchild_page.id).live)

View File

@@ -0,0 +1,312 @@
from unittest import mock
from django.contrib.auth.models import Permission
from django.http import HttpRequest, HttpResponse
from django.test import TestCase
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from wagtail.admin.views.pages.bulk_actions.page_bulk_action import PageBulkAction
from wagtail.models import Page
from wagtail.signals import page_unpublished
from wagtail.test.testapp.models import SimplePage
from wagtail.test.utils import WagtailTestUtils
class TestBulkUnpublish(WagtailTestUtils, TestCase):
def setUp(self):
# Create pages to unpublish
self.root_page = Page.objects.get(id=2)
self.child_pages = [
SimplePage(
title=f"Hello world!-{i}", slug=f"hello-world-{i}", content=f"hello-{i}"
)
for i in range(1, 5)
]
# first three child pages will be unpublished
self.pages_to_be_unpublished = self.child_pages[:3]
self.pages_not_to_be_unpublished = self.child_pages[3:]
for child_page in self.child_pages:
self.root_page.add_child(instance=child_page)
self.url = (
reverse(
"wagtail_bulk_action",
args=(
"wagtailcore",
"page",
"unpublish",
),
)
+ "?"
)
for child_page in self.pages_to_be_unpublished:
self.url += f"&id={child_page.id}"
self.redirect_url = reverse("wagtailadmin_explore", args=(self.root_page.id,))
# Login
self.user = self.login()
def test_unpublish_view(self):
"""
This tests that the unpublish view responds with an unpublish confirm page
"""
# Request confirm unpublish page
response = self.client.get(self.url)
# Check that the user received an unpublish confirm page
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(
response, "wagtailadmin/pages/bulk_actions/confirm_bulk_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
"""
# Request confirm unpublish page but with illegal page id
response = self.client.get(
reverse(
"wagtail_bulk_action",
args=(
"wagtailcore",
"page",
"unpublish",
),
)
)
# 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()
# Request confirm unpublish page
response = self.client.get(self.url)
# Check that the user received a 200 redirected response
self.assertEqual(response.status_code, 200)
html = response.content.decode()
self.assertInHTML(
"<p>You don't have permission to unpublish these pages</p>", html
)
for child_page in self.pages_to_be_unpublished:
self.assertInHTML(f"<li>{child_page.title}</li>", html)
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)
try:
# Post to the unpublish page
response = self.client.post(self.url)
# Should be redirected to explorer page
self.assertEqual(response.status_code, 302)
# Check that the child pages were unpublished
for child_page in self.pages_to_be_unpublished:
self.assertFalse(SimplePage.objects.get(id=child_page.id).live)
# Check that the child pages not to be unpublished remain
for child_page in self.pages_not_to_be_unpublished:
self.assertTrue(SimplePage.objects.get(id=child_page.id).live)
# Check that the page_unpublished signal was fired
self.assertEqual(mock_handler.call_count, len(self.pages_to_be_unpublished))
for i, child_page in enumerate(self.pages_to_be_unpublished):
mock_call = mock_handler.mock_calls[i][2]
self.assertEqual(mock_call["sender"], child_page.specific_class)
self.assertEqual(mock_call["instance"], child_page)
self.assertIsInstance(mock_call["instance"], child_page.specific_class)
finally:
page_unpublished.disconnect(mock_handler)
def test_after_unpublish_page(self):
def hook_func(request, action_type, pages, action_class_instance):
self.assertEqual(action_type, "unpublish")
self.assertIsInstance(request, HttpRequest)
self.assertIsInstance(action_class_instance, PageBulkAction)
for i, page in enumerate(pages):
self.assertEqual(page.id, self.pages_to_be_unpublished[i].id)
return HttpResponse("Overridden!")
with self.register_hook("after_bulk_action", hook_func):
response = self.client.post(self.url)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b"Overridden!")
for child_page in self.pages_to_be_unpublished:
child_page.refresh_from_db()
self.assertEqual(child_page.status_string, _("draft"))
def test_before_unpublish_page(self):
def hook_func(request, action_type, pages, action_class_instance):
self.assertEqual(action_type, "unpublish")
self.assertIsInstance(request, HttpRequest)
self.assertIsInstance(action_class_instance, PageBulkAction)
for i, page in enumerate(pages):
self.assertEqual(page.id, self.pages_to_be_unpublished[i].id)
return HttpResponse("Overridden!")
with self.register_hook("before_bulk_action", hook_func):
response = self.client.post(self.url)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b"Overridden!")
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 for page with no descendants
response = self.client.get(self.url)
# Check that the user received an unpublish confirm page
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(
response, "wagtailadmin/pages/bulk_actions/confirm_bulk_unpublish.html"
)
# Check the form does not contain the checkbox field include_descendants
self.assertContains(
response,
'name="include_descendants"',
count=0,
)
class TestBulkUnpublishIncludingDescendants(WagtailTestUtils, TestCase):
def setUp(self):
# Find root page
self.root_page = Page.objects.get(id=2)
self.child_pages = [
SimplePage(
title=f"Hello world!-{i}", slug=f"hello-world-{i}", content=f"hello-{i}"
)
for i in range(1, 5)
]
# first three child pages will be unpublished
self.pages_to_be_unpublished = self.child_pages[:3]
self.pages_not_to_be_unpublished = self.child_pages[3:]
for child_page in self.child_pages:
self.root_page.add_child(instance=child_page)
# map of the form { page: [child_pages] } to be added
self.grandchildren_pages = {
self.pages_to_be_unpublished[0]: [
SimplePage(
title="Hello world!-a", slug="hello-world-a", content="hello-a"
)
],
self.pages_to_be_unpublished[1]: [
SimplePage(
title="Hello world!-b", slug="hello-world-b", content="hello-b"
),
SimplePage(
title="Hello world!-c", slug="hello-world-c", content="hello-c"
),
],
}
for child_page, grandchild_pages in self.grandchildren_pages.items():
for grandchild_page in grandchild_pages:
child_page.add_child(instance=grandchild_page)
self.url = (
reverse(
"wagtail_bulk_action",
args=(
"wagtailcore",
"page",
"unpublish",
),
)
+ "?"
)
for child_page in self.pages_to_be_unpublished:
self.url += f"&id={child_page.id}"
self.redirect_url = reverse("wagtailadmin_explore", args=(self.root_page.id,))
self.user = self.login()
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(self.url)
# Check that the user received an unpublish confirm page
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(
response, "wagtailadmin/pages/bulk_actions/confirm_bulk_unpublish.html"
)
# Check the form contains the checkbox field include_descendants
self.assertContains(
response,
'name="include_descendants"',
)
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(self.url, {"include_descendants": "on"})
# Should be redirected to explorer page
self.assertEqual(response.status_code, 302)
# Check that the child pages were unpublished
for child_page in self.pages_to_be_unpublished:
self.assertFalse(SimplePage.objects.get(id=child_page.id).live)
# Check that the child pages not to be unpublished remain
for child_page in self.pages_not_to_be_unpublished:
self.assertTrue(SimplePage.objects.get(id=child_page.id).live)
for grandchild_pages in self.grandchildren_pages.values():
for grandchild_page in grandchild_pages:
self.assertFalse(SimplePage.objects.get(id=grandchild_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(self.url, {})
# Should be redirected to explorer page
self.assertEqual(response.status_code, 302)
# Check that the child pages were unpublished
for child_page in self.pages_to_be_unpublished:
self.assertFalse(SimplePage.objects.get(id=child_page.id).live)
# Check that the descendant pages were not unpublished
for grandchild_pages in self.grandchildren_pages.values():
for grandchild_page in grandchild_pages:
self.assertTrue(SimplePage.objects.get(id=grandchild_page.id).live)

View File

@@ -0,0 +1,36 @@
from django.test import TestCase
from django.urls import reverse
from django.utils.http import urlencode
from wagtail.test.testapp.models import EventPage
from wagtail.test.utils import WagtailTestUtils
class TestContentTypeUse(WagtailTestUtils, TestCase):
fixtures = ["test.json"]
def setUp(self):
self.user = self.login()
self.christmas_page = EventPage.objects.get(title="Christmas")
def test_content_type_use(self):
# Get use of event page
request_url = reverse(
"wagtailadmin_pages:type_use", args=("tests", "eventpage")
)
response = self.client.get(request_url)
# Check response
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailadmin/generic/listing.html")
self.assertTemplateUsed(response, "wagtailadmin/pages/usage_results.html")
self.assertContains(response, "Christmas")
# Links to 'delete' etc should include a 'next' URL parameter pointing back here
delete_url = (
reverse("wagtailadmin_pages:delete", args=(self.christmas_page.id,))
+ "?"
+ urlencode({"next": request_url})
)
self.assertContains(response, delete_url)
self.assertNotContains(response, "data-bulk-action-select-all-checkbox")

View File

@@ -0,0 +1,89 @@
from django.contrib.auth.models import Permission
from django.test import TestCase
from django.urls import reverse
from wagtail.models import Page, PageLogEntry
from wagtail.test.testapp.models import SimplePage
from wagtail.test.utils import WagtailTestUtils
class TestConvertAlias(WagtailTestUtils, TestCase):
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 alias page
self.alias_page = self.child_page.create_alias(update_slug="alias-page")
# Login
self.user = self.login()
def test_convert_alias(self):
response = self.client.get(
reverse("wagtailadmin_pages:convert_alias", args=[self.alias_page.id])
)
self.assertEqual(response.status_code, 200)
def test_convert_alias_not_alias(self):
response = self.client.get(
reverse("wagtailadmin_pages:convert_alias", args=[self.child_page.id])
)
self.assertEqual(response.status_code, 404)
def test_convert_alias_bad_permission(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.get(
reverse("wagtailadmin_pages:convert_alias", args=[self.alias_page.id])
)
# Check that the user received a permission denied response
self.assertRedirects(response, "/admin/")
def test_post_convert_alias(self):
response = self.client.post(
reverse("wagtailadmin_pages:convert_alias", args=[self.alias_page.id])
)
# User should be redirected to the edit view of the converted page
self.assertRedirects(
response, reverse("wagtailadmin_pages:edit", args=[self.alias_page.id])
)
# Check the page was converted
self.alias_page.refresh_from_db()
self.assertIsNone(self.alias_page.alias_of)
# Check that a revision was created
revision = self.alias_page.revisions.get()
self.assertEqual(revision.user, self.user)
self.assertEqual(self.alias_page.live_revision, revision)
# Check audit log
log = PageLogEntry.objects.get(action="wagtail.convert_alias")
self.assertFalse(log.content_changed)
self.assertEqual(
log.data,
{
"page": {
"id": self.alias_page.id,
"title": self.alias_page.get_admin_display_title(),
}
},
)
self.assertEqual(log.page, self.alias_page.page_ptr)
self.assertEqual(log.revision, revision)
self.assertEqual(log.user, self.user)

View File

@@ -0,0 +1,800 @@
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.models import GroupPagePermission, Page
from wagtail.test.testapp.models import (
CustomCopyFormPage,
EventPage,
EventPageSpeaker,
PageWithExcludedCopyField,
SimplePage,
)
from wagtail.test.utils import WagtailTestUtils
class TestPageCopy(WagtailTestUtils, TestCase):
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")
self.assertContains(response, "Alias")
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,
"alias": 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,
"alias": 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.assertIn("new_parent_page", 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,
"alias": 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.assertIsNotNone(page_copy)
# 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()), msg="treebeard found consistency problems"
)
def test_page_with_exclude_fields_in_copy(self):
original_page = self.test_page.add_child(
instance=PageWithExcludedCopyField(
title="Page with exclude_fields_in_copy",
slug="page-with-exclude-fields-in-copy",
content="Copy me",
special_field="Don't copy me",
live=True,
has_unpublished_changes=False,
)
)
post_data = {
"new_title": f"{original_page.title} 2",
"new_slug": f"{original_page.slug}-2",
"new_parent_page": str(self.root_page.id),
"copy_subpages": False,
"publish_copies": False,
"alias": False,
}
self.client.post(
reverse("wagtailadmin_pages:copy", args=(original_page.id,)), post_data
)
# Get copy
page_copy = PageWithExcludedCopyField.objects.get(slug=post_data["new_slug"])
self.assertEqual(page_copy.content, original_page.content)
self.assertNotEqual(page_copy.special_field, original_page.special_field)
self.assertEqual(
page_copy.special_field, page_copy._meta.get_field("special_field").default
)
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,
"alias": 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.assertIsNotNone(page_copy)
# 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 child pages
# Neither of them should be live
child_copy = page_copy.get_children().filter(slug="child-page").first()
self.assertIsNotNone(child_copy)
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.assertIsNotNone(unpublished_child_copy)
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()), msg="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,
"alias": 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.assertIsNotNone(page_copy)
# 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 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.assertIsNotNone(child_copy)
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.assertIsNotNone(unpublished_child_copy)
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()), msg="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,
"alias": 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(),
msg=self.test_child_page,
)
# treebeard should report no consistency problems with the tree
self.assertFalse(
any(Page.find_problems()), msg="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,
"alias": 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.context["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,
"alias": 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.context["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,
"alias": 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,
"alias": 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.context["form"],
"new_slug",
"Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or hyphens.",
)
def test_page_copy_post_valid_unicode_slug(self):
post_data = {
"new_title": "Hello wɜːld",
"new_slug": "hello-wɜːld",
"new_parent_page": str(self.test_page.id),
"copy_subpages": False,
"alias": False,
}
response = self.client.post(
reverse("wagtailadmin_pages:copy", args=(self.test_page.id,)), post_data
)
# Check response
self.assertRedirects(
response, reverse("wagtailadmin_explore", args=(self.test_page.id,))
)
# Get copy
page_copy = (
self.test_page.get_children().filter(slug=post_data["new_slug"]).first()
)
# Check that the copy exists with the good slug
self.assertIsNotNone(page_copy)
self.assertEqual(page_copy.slug, post_data["new_slug"])
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,
"alias": 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.assertIsNotNone(page_copy)
# 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 child pages
# Neither of them should be live
child_copy = page_copy.get_children().filter(slug="child-page").first()
self.assertIsNotNone(child_copy)
self.assertFalse(child_copy.live)
unpublished_child_copy = (
page_copy.get_children().filter(slug="unpublished-child-page").first()
)
self.assertIsNotNone(unpublished_child_copy)
self.assertFalse(unpublished_child_copy.live)
# treebeard should report no consistency problems with the tree
self.assertFalse(
any(Page.find_problems()), msg="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,
"alias": 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,
"alias": 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())
def test_page_copy_alias_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,
"alias": 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().get(slug="hello-world-2")
# Check the copy is an alias of the original
self.assertEqual(page_copy.alias_of, self.test_page.page_ptr)
# Check that the copy is live
# Note: publish_copies is ignored. Alias pages always keep the same state as their original
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 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()), msg="treebeard found consistency problems"
)
def test_page_copy_alias_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,
"alias": 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().get(slug="hello-world-2")
# Check the copy is an alias of the original
self.assertEqual(page_copy.alias_of, self.test_page.page_ptr)
# Check that the copy is live
# Note: publish_copies is ignored. Alias pages always keep the same state as their original
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 child pages
# Neither of them should be live
child_copy = page_copy.get_children().filter(slug="child-page").first()
self.assertIsNotNone(child_copy)
self.assertEqual(child_copy.alias_of, self.test_child_page.page_ptr)
self.assertTrue(child_copy.live)
self.assertFalse(child_copy.has_unpublished_changes)
unpublished_child_copy = (
page_copy.get_children().filter(slug="unpublished-child-page").first()
)
self.assertIsNotNone(unpublished_child_copy)
self.assertEqual(
unpublished_child_copy.alias_of, self.test_unpublished_child_page.page_ptr
)
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()), msg="treebeard found consistency problems"
)
def test_page_copy_alias_post_without_source_publish_permission(self):
# Check for issue #7293 - If the user has permission to publish at a destination, but not the source.
# Wagtail would crash on attempt to copy
# Create a new section
self.destination_page = self.root_page.add_child(
instance=SimplePage(
title="Destination page",
slug="destination-page",
content="hello",
live=True,
has_unpublished_changes=False,
)
)
# Make user a moderator and make it so they can only publish at the destination page
self.user.is_superuser = False
self.user.groups.add(Group.objects.get(name="Moderators"))
self.user.save()
GroupPagePermission.objects.filter(permission__codename="publish_page").update(
page=self.destination_page
)
post_data = {
"new_title": self.test_child_page.title,
"new_slug": self.test_child_page.slug,
"new_parent_page": str(self.destination_page.id),
"copy_subpages": False,
"publish_copies": False,
"alias": False,
}
response = self.client.post(
reverse("wagtailadmin_pages:copy", args=[self.test_child_page.id]),
post_data,
)
# We only need to check that it didn't crash
self.assertEqual(response.status_code, 302)
def test_copy_page_with_unique_uuids_in_orderables(self):
"""
Test that a page with orderables can be copied and the translation
keys are updated.
"""
event_page = EventPage(
title="Moon Landing",
location="the moon",
audience="public",
cost="free on TV",
date_from="1969-07-20",
)
self.root_page.add_child(instance=event_page)
event_page.speakers.add(
EventPageSpeaker(
first_name="Neil",
last_name="Armstrong",
)
)
# ensure there's a revision (which should capture the new speaker orderables)
# before copying the page
event_page.save_revision().publish()
post_data = {
"new_title": "New Moon landing",
"new_slug": "moon-landing-redux",
"new_parent_page": str(self.root_page.id),
"copy_subpages": False,
"publish_copies": False,
"alias": False,
}
self.client.post(
reverse("wagtailadmin_pages:copy", args=[event_page.id]), post_data
)
new_page = EventPage.objects.last()
# Hack: get the page instance from the edit form which assembles it from the
# latest revision. While we could do new_page.get_latest_revision().as_object()
# this follow the edit view closer and should it change the test is less
# prone to continue working because we're skipping some step
response = self.client.get(
reverse("wagtailadmin_pages:edit", args=[new_page.id])
)
new_page_on_edit_form = response.context["form"].instance
# publish the page, similar to EditView.publish_action()
new_page_on_edit_form.save_revision().publish()
self.assertNotEqual(
event_page.speakers.first().translation_key,
new_page.speakers.first().translation_key,
)
def test_page_copy_with_custom_copy_form(self):
custom_copy_form_page = self.root_page.add_child(
instance=CustomCopyFormPage(
title="Hello world!",
slug="copy-form",
live=True,
has_unpublished_changes=False,
)
)
response = self.client.get(
reverse("wagtailadmin_pages:copy", args=(custom_copy_form_page.id,))
)
# Check response
self.assertEqual(response.status_code, 200)
# Check if slug is hello-world-2
self.assertContains(response, "copy-form-2")

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
from django.test import TestCase
from wagtail.test.utils import WagtailTestUtils
from wagtail.test.utils.template_tests import AdminTemplateTestUtils
class TestCustomListing(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
fixtures = ["test.json"]
def test_get(self):
self.login()
response = self.client.get("/admin/event_pages/")
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailadmin/pages/index.html")
self.assertContains(response, "Event pages")
self.assertContains(response, "Christmas")
self.assertContains(response, "Saint Patrick")
self.assertNotContains(response, "Welcome to the Wagtail test site!")
self.assertBreadcrumbsItemsRendered(
[{"url": "", "label": "Event pages"}],
response.content,
)
soup = self.get_soup(response.content)
breadcrumbs_icon = soup.select_one(".w-breadcrumbs__icon")
self.assertIsNotNone(breadcrumbs_icon)
use = breadcrumbs_icon.select_one("use")
self.assertIsNotNone(use)
self.assertEqual(use["href"], "#icon-calendar")
def test_filter(self):
self.login()
response = self.client.get("/admin/event_pages/", {"audience": "private"})
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailadmin/pages/index.html")
self.assertContains(response, "Event pages")
self.assertNotContains(response, "Christmas")
self.assertContains(response, "Saint Patrick")

View File

@@ -0,0 +1,366 @@
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, override_settings
from django.urls import reverse
from wagtail.models import Page
from wagtail.signals import page_unpublished
from wagtail.test.testapp.models import SimplePage, StandardChild, StandardIndex
from wagtail.test.utils import WagtailTestUtils
class TestPageDelete(WagtailTestUtils, TestCase):
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())
@override_settings(WAGTAILADMIN_UNSAFE_PAGE_DELETION_LIMIT=10)
def test_confirm_delete_scenario_1(self):
# If the number of pages to be deleted are less than
# WAGTAILADMIN_UNSAFE_PAGE_DELETION_LIMIT then don't need
# for confirmation
child_1 = SimplePage(title="child 1", slug="child-1", content="hello")
self.child_page.add_child(instance=child_1)
child_2 = SimplePage(title="child 2", slug="child-2", content="hello")
self.child_page.add_child(instance=child_2)
response = self.client.get(
reverse("wagtailadmin_pages:delete", args=(self.child_page.id,))
)
self.assertEqual(response.status_code, 200)
self.assertNotContains(response, '<input type="text" name="confirm_site_name"')
# deletion should not actually happen on GET
self.assertTrue(SimplePage.objects.filter(id=self.child_page.id).exists())
# And admin should be able to delete page without any confirmation
self.client.post(
reverse("wagtailadmin_pages:delete", args=(self.child_page.id,))
)
# Check that page is deleted
self.assertFalse(SimplePage.objects.filter(id=self.child_page.id).exists())
@override_settings(WAGTAILADMIN_UNSAFE_PAGE_DELETION_LIMIT=3)
@override_settings(WAGTAIL_SITE_NAME="mysite")
def test_confirm_delete_scenario_2(self):
# If the number of pages to be deleted are greater than or equal to
# WAGTAILADMIN_UNSAFE_PAGE_DELETION_LIMIT then show input box
# to input wagtail_site_name.
child_1 = SimplePage(title="child 1", slug="child-1", content="hello")
self.child_page.add_child(instance=child_1)
child_2 = SimplePage(title="child 2", slug="child-2", content="hello")
self.child_page.add_child(instance=child_2)
response = self.client.get(
reverse("wagtailadmin_pages:delete", args=(self.child_page.id,))
)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "This action will delete total <b>3</b> pages.")
self.assertContains(response, "Please type <b>mysite</b> to confirm.")
self.assertContains(response, '<input type="text" name="confirm_site_name"')
# deletion should not actually happen on GET
self.assertTrue(SimplePage.objects.filter(id=self.child_page.id).exists())
@override_settings(WAGTAILADMIN_UNSAFE_PAGE_DELETION_LIMIT=3)
@override_settings(WAGTAIL_SITE_NAME="mysite")
def test_confirm_delete_scenario_3(self):
# If admin entered the incorrect site name and submit
# the form, then site should not be deleted and same
# form should be displayed again.
child_1 = SimplePage(title="child 1", slug="child-1", content="hello")
self.child_page.add_child(instance=child_1)
child_2 = SimplePage(title="child 2", slug="child-2", content="hello")
self.child_page.add_child(instance=child_2)
response = self.client.post(
reverse("wagtailadmin_pages:delete", args=(self.child_page.id,)),
data={"confirm_site_name": "random"},
)
self.assertEqual(response.status_code, 200)
# One error messages should be returned
messages = [m.message for m in response.context["messages"]]
self.assertEqual(len(messages), 1)
self.assertContains(response, "This action will delete total <b>3</b> pages.")
self.assertContains(response, "Please type <b>mysite</b> to confirm.")
self.assertContains(response, '<input type="text" name="confirm_site_name"')
# Site should not be deleted
self.assertTrue(SimplePage.objects.filter(id=self.child_page.id).exists())
@override_settings(WAGTAILADMIN_UNSAFE_PAGE_DELETION_LIMIT=3)
@override_settings(WAGTAIL_SITE_NAME="mysite")
def test_confirm_delete_scenario_4(self):
# If admin entered the correct site name and submit the form
# then the site should be deleted
child_1 = SimplePage(title="child 1", slug="child-1", content="hello")
self.child_page.add_child(instance=child_1)
child_2 = SimplePage(title="child 2", slug="child-2", content="hello")
self.child_page.add_child(instance=child_2)
response = self.client.post(
reverse("wagtailadmin_pages:delete", args=(self.child_page.id,)),
data={"confirm_site_name": "mysite"},
)
# Should be redirected to explorer page
self.assertRedirects(
response, reverse("wagtailadmin_explore", args=(self.root_page.id,))
)
# Check that the page is deleted
self.assertFalse(SimplePage.objects.filter(id=self.child_page.id).exists())
# Check that the subpage is also deleted
self.assertFalse(SimplePage.objects.filter(id=child_1.id).exists())
self.assertFalse(SimplePage.objects.filter(id=child_2.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 302 redirect response
self.assertEqual(response.status_code, 302)
# 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)
try:
# 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()), msg="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)
finally:
page_unpublished.disconnect(mock_handler)
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)
try:
# 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()), msg="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)
finally:
page_unpublished.disconnect(mock_handler)
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.pk))
def pre_delete_handler(sender, instance, **kwargs):
pre_delete_signals_received.append((sender, instance.pk))
def post_delete_handler(sender, instance, **kwargs):
post_delete_signals_received.append((sender, instance.pk))
page_unpublished.connect(page_unpublished_handler)
pre_delete.connect(pre_delete_handler)
post_delete.connect(post_delete_handler)
try:
# 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()), msg="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
)
finally:
page_unpublished.disconnect(page_unpublished_handler)
pre_delete.disconnect(pre_delete_handler)
post_delete.disconnect(post_delete_handler)
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())

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,264 @@
from unittest import mock
from django.contrib.auth import get_user_model
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.models import Page
from wagtail.signals import post_page_move, pre_page_move
from wagtail.test.testapp.models import BusinessSubIndex, SimplePage
from wagtail.test.utils import WagtailTestUtils
class TestPageMove(WagtailTestUtils, TestCase):
fixtures = ["test.json"]
@classmethod
def setUpTestData(cls):
# Find root page
cls.root_page = Page.objects.get(id=2)
# Create three sections
cls.section_a = SimplePage(title="Section A", slug="section-a", content="hello")
cls.root_page.add_child(instance=cls.section_a)
cls.section_b = SimplePage(title="Section B", slug="section-b", content="hello")
cls.root_page.add_child(instance=cls.section_b)
cls.section_c = SimplePage(title="Section C", slug="section-c", content="hello")
cls.root_page.add_child(instance=cls.section_c)
# Add test page A into section A
cls.test_page_a = SimplePage(
title="Hello world!", slug="hello-world", content="hello"
)
cls.section_a.add_child(instance=cls.test_page_a)
# Add test page B into section C
cls.test_page_b = SimplePage(
title="Hello world!", slug="hello-world", content="hello"
)
cls.section_c.add_child(instance=cls.test_page_b)
# Add unpublished page to the root with a child page
cls.unpublished_page = SimplePage(
title="Unpublished", slug="unpublished", content="hello"
)
sub_page = SimplePage(title="Sub Page", slug="sub-page", content="child")
cls.root_page.add_child(instance=cls.unpublished_page)
cls.unpublished_page.add_child(instance=sub_page)
# unpublish pages last (used to validate the edit only permission)
cls.unpublished_page.unpublish()
sub_page.unpublish()
def setUp(self):
# 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)
self.assertContains(response, self.section_a.title)
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 302 redirected response
self.assertEqual(response.status_code, 302)
def test_user_without_bulk_delete_permission_can_move(self):
# to verify that a user without bulk delete permission is able to move a page with a child page
self.client.logout()
user = get_user_model().objects.get(email="siteeditor@example.com")
self.login(user)
# ensure the bulk_delete is not applicable to this user
can_bulk_delete = self.test_page_b.permissions_for_user(user).can_delete()
self.assertFalse(can_bulk_delete)
response = self.client.get(
reverse("wagtailadmin_pages:move", args=(self.unpublished_page.id,))
)
self.assertEqual(response.status_code, 200)
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(self.test_page_b.slug, messages[0].message)
def test_move_triggers_signals(self):
# Connect a mock signal handler to pre_page_move and post_page_move signals
pre_moved_handler = mock.MagicMock()
post_moved_handler = mock.MagicMock()
pre_page_move.connect(pre_moved_handler)
post_page_move.connect(post_moved_handler)
# Post to view to move page
try:
self.client.post(
reverse(
"wagtailadmin_pages:move_confirm",
args=(self.test_page_a.id, self.section_b.id),
)
)
finally:
# Disconnect mock handler to prevent cross-test pollution
pre_page_move.disconnect(pre_moved_handler)
post_page_move.disconnect(post_moved_handler)
# parent_page_before returns the non-specific page type, and that's OK
nonspecific_section_a = Page.objects.get(pk=self.section_a.pk)
# Check that the pre_page_move signal was fired
self.assertEqual(pre_moved_handler.call_count, 1)
pre_moved_handler.assert_called_with(
signal=mock.ANY,
sender=self.test_page_a.specific_class,
instance=self.test_page_a,
parent_page_before=nonspecific_section_a,
parent_page_after=self.section_b,
url_path_before="/home/section-a/hello-world/",
url_path_after="/home/section-b/hello-world/",
)
# Check that the post_page_move signal was fired
self.assertEqual(post_moved_handler.call_count, 1)
post_moved_handler.assert_called_with(
signal=mock.ANY,
sender=self.test_page_a.specific_class,
# during the move operation, we reloaded the page as a non-specific instance
instance=Page.objects.get(pk=self.test_page_a.pk),
parent_page_before=nonspecific_section_a,
parent_page_after=self.section_b,
url_path_before="/home/section-a/hello-world/",
url_path_after="/home/section-b/hello-world/",
)
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
)
def test_page_move_after_parent_page_types_changes_to_different_parent_model(self):
# Test for issue #10348
# While BusinessSubIndex cannot be created under a SimplePage, we can
# still create it under a SimplePage invoking django-treebeard's add_child
# which works great for our purposes.
self.assertFalse(BusinessSubIndex.can_exist_under(self.section_a))
page = self.section_a.add_child(
instance=BusinessSubIndex(
title="Business Sub Index",
slug="business-sub-index",
live=True,
has_unpublished_changes=False,
)
)
response = self.client.get(reverse("wagtailadmin_pages:move", args=(page.id,)))
self.assertEqual(response.status_code, 200)
form = response.context["move_form"]
self.assertEqual(form.fields["new_parent_page"].initial.pk, self.section_a.pk)
self.assertNotContains(response, self.section_a.title)

View File

@@ -0,0 +1,345 @@
from django.contrib.auth.models import Group, Permission
from django.test import TestCase
from django.urls import reverse
from django.utils import timezone
from django.utils.html import escape
from wagtail.models import Page
from wagtail.test.testapp.models import SimplePage
from wagtail.test.utils import WagtailTestUtils
class TestLocking(WagtailTestUtils, TestCase):
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
page = Page.objects.get(id=self.child_page.id)
self.assertTrue(page.locked)
self.assertEqual(page.locked_by, self.user)
self.assertIsNotNone(page.locked_at)
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
page = Page.objects.get(id=self.child_page.id)
self.assertFalse(page.locked)
self.assertIsNone(page.locked_by)
self.assertIsNone(page.locked_at)
def test_lock_post_already_locked(self):
# Lock the page
self.child_page.locked = True
self.child_page.locked_by = self.user
self.child_page.locked_at = timezone.now()
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
page = Page.objects.get(id=self.child_page.id)
self.assertTrue(page.locked)
self.assertEqual(page.locked_by, self.user)
self.assertIsNotNone(page.locked_at)
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
page = Page.objects.get(id=self.child_page.id)
self.assertTrue(page.locked)
self.assertEqual(page.locked_by, self.user)
self.assertIsNotNone(page.locked_at)
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
page = Page.objects.get(id=self.child_page.id)
self.assertTrue(page.locked)
self.assertEqual(page.locked_by, self.user)
self.assertIsNotNone(page.locked_at)
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
page = Page.objects.get(id=self.child_page.id)
self.assertFalse(page.locked)
self.assertIsNone(page.locked_by)
self.assertIsNone(page.locked_at)
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, 302)
# Check that the page is still unlocked
page = Page.objects.get(id=self.child_page.id)
self.assertFalse(page.locked)
self.assertIsNone(page.locked_by)
self.assertIsNone(page.locked_at)
def test_locked_pages_dashboard_panel(self):
self.child_page.locked = True
self.child_page.locked_by = self.user
self.child_page.locked_at = timezone.now()
self.child_page.save()
response = self.client.get(reverse("wagtailadmin_home"))
self.assertContains(response, "Your locked pages")
# check that Unlock button is present
self.assertContains(response, "Unlock")
def test_unlock_post(self):
# Lock the page
self.child_page.locked = True
self.child_page.locked_by = self.user
self.child_page.locked_at = timezone.now()
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
page = Page.objects.get(id=self.child_page.id)
self.assertFalse(page.locked)
self.assertIsNone(page.locked_by)
self.assertIsNone(page.locked_at)
def test_unlock_get(self):
# Lock the page
self.child_page.locked = True
self.child_page.locked_by = self.user
self.child_page.locked_at = timezone.now()
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
page = Page.objects.get(id=self.child_page.id)
self.assertTrue(page.locked)
self.assertEqual(page.locked_by, self.user)
self.assertIsNotNone(page.locked_at)
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
page = Page.objects.get(id=self.child_page.id)
self.assertFalse(page.locked)
self.assertIsNone(page.locked_by)
self.assertIsNone(page.locked_at)
def test_unlock_post_with_good_redirect(self):
# Lock the page
self.child_page.locked = True
self.child_page.locked_by = self.user
self.child_page.locked_at = timezone.now()
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,))},
follow=True,
)
# Check response
self.assertRedirects(
response, reverse("wagtailadmin_pages:edit", args=(self.child_page.id,))
)
# Should show unlocked message
self.assertContains(
response, escape("Page 'Hello world! (simple page)' is now unlocked.")
)
# Message shouldn't be wrapped in a tuple
self.assertNotContains(
response,
escape(("Page 'Hello world! (simple page)' is now unlocked.",)),
)
# Check that the page is unlocked
page = Page.objects.get(id=self.child_page.id)
self.assertFalse(page.locked)
self.assertIsNone(page.locked_by)
self.assertIsNone(page.locked_at)
def test_unlock_post_with_bad_redirect(self):
# Lock the page
self.child_page.locked = True
self.child_page.locked_by = self.user
self.child_page.locked_at = timezone.now()
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
page = Page.objects.get(id=self.child_page.id)
self.assertFalse(page.locked)
self.assertIsNone(page.locked_by)
self.assertIsNone(page.locked_at)
def test_unlock_post_bad_page(self):
# Lock the page
self.child_page.locked = True
self.child_page.locked_by = self.user
self.child_page.locked_at = timezone.now()
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
page = Page.objects.get(id=self.child_page.id)
self.assertTrue(page.locked)
self.assertEqual(page.locked_by, self.user)
self.assertIsNotNone(page.locked_at)
def test_unlock_post_bad_permissions(self):
# Remove privileges from user
self.user.is_superuser = False
self.user.groups.add(Group.objects.get(name="Editors"))
self.user.save()
# Lock the page
self.child_page.locked = True
self.child_page.locked_at = timezone.now()
self.child_page.save()
response = self.client.post(
reverse("wagtailadmin_pages:unlock", args=(self.child_page.id,))
)
# Check response
self.assertEqual(response.status_code, 302)
# Check that the page is still locked
page = Page.objects.get(id=self.child_page.id)
self.assertTrue(page.locked)
self.assertIsNotNone(page.locked_at)
def test_unlock_post_own_page_with_bad_permissions(self):
# Unlike the previous test, the user can unlock pages that they have locked
# Remove privileges from user
self.user.is_superuser = False
self.user.groups.add(Group.objects.get(name="Editors"))
self.user.save()
# Lock the page
self.child_page.locked = True
self.child_page.locked_by = self.user
self.child_page.locked_at = timezone.now()
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 still locked
page = Page.objects.get(id=self.child_page.id)
self.assertFalse(page.locked)
self.assertIsNone(page.locked_by)
self.assertIsNone(page.locked_at)

View File

@@ -0,0 +1,329 @@
from io import StringIO
from django.contrib.auth.models import Permission
from django.core import management
from django.test import TransactionTestCase
from django.urls import reverse
from django.utils.http import urlencode
from wagtail.models import Page
from wagtail.test.testapp.models import EventIndex, SimplePage, SingleEventPage
from wagtail.test.utils import WagtailTestUtils
from wagtail.test.utils.timestamps import local_datetime
class TestPageSearch(WagtailTestUtils, TransactionTestCase):
fixtures = ["test_empty.json"]
def setUp(self):
super().setUp()
management.call_command(
"update_index",
backend_name="default",
stdout=StringIO(),
chunk_size=50,
)
self.user = self.login()
def get(self, params=None, url_name="wagtailadmin_pages:search", **extra):
return self.client.get(reverse(url_name), 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):
# Find root page
root_page = Page.objects.get(id=2)
# Create a page
new_page = root_page.add_child(
instance=SimplePage(
title="Hello from Cauldron Lake",
slug="bright-falls",
content="It's not a lake, it's an ocean",
live=True,
has_unpublished_changes=False,
)
)
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")
next_url = urlencode({"next": reverse("wagtailadmin_pages:search")})
expected_new_page_copy_url = (
reverse("wagtailadmin_pages:copy", args=(new_page.pk,)) + f"?{next_url}"
)
self.assertContains(response, f'href="{expected_new_page_copy_url}"')
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="Greetings!",
slug="hello",
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")
# Confirm the title is being searched
response = self.get({"q": "greetings"})
self.assertContains(response, "There is one matching page")
def test_ajax(self):
# Find root page
root_page = Page.objects.get(id=2)
# Create a page
new_page = root_page.add_child(
instance=SimplePage(
title="Hello from Cauldron Lake",
slug="bright-falls",
content="It's not a lake, it's an ocean",
live=True,
has_unpublished_changes=False,
)
)
response = self.get(
{"q": "Hello"}, url_name="wagtailadmin_pages:search_results"
)
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")
next_url = urlencode({"next": reverse("wagtailadmin_pages:search")})
expected_new_page_copy_url = (
reverse("wagtailadmin_pages:copy", args=(new_page.pk,)) + f"?{next_url}"
)
self.assertContains(response, f'href="{expected_new_page_copy_url}"')
def test_pagination(self):
# page numbers in range should be accepted
response = self.get({"q": "Hello", "p": 1})
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailadmin/pages/search.html")
# page numbers out of range should return 404
response = self.get({"q": "Hello", "p": 9999})
self.assertEqual(response.status_code, 404)
def test_root_can_appear_in_search_results(self):
response = self.get({"q": "root"})
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])
def test_search_filter_content_type(self):
# Correct content_type
response = self.get({"content_type": "demosite.standardpage"})
self.assertEqual(response.status_code, 200)
# Incorrect content_type
response = self.get({"content_type": "demosite.standardpage.error"})
self.assertEqual(response.status_code, 404)
def test_empty_search_renders_content_type_facets(self):
root_page = Page.objects.get(id=2)
event_index = EventIndex(
title="ALL THE EVENTS",
intro="It's just a nod to the canon",
)
root_page.add_child(instance=event_index)
params = [{"q": ""}, {}]
url = reverse("wagtailadmin_pages:search")
for param in params:
with self.subTest(param=param):
response = self.get(param)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailadmin/pages/search.html")
self.assertEqual(response.context["query_string"], "")
self.assertContains(response, "Page types")
self.assertContains(response, "All (3)")
# The test fixture contains the root page and the welcome page
# with the base page type
self.assertContains(response, "Page (2)")
self.assertContains(response, "ALL THE EVENTS")
self.assertContains(response, "Event index (1)")
self.assertContains(
response,
f"{url}?q=&amp;content_type=tests.eventindex",
)
def test_empty_search_with_content_type_filter(self):
root_page = Page.objects.get(id=2)
event_index = EventIndex(
title="ALL THE EVENTS",
intro="It's just a nod to the canon",
)
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=event_index)
root_page.add_child(instance=new_event)
params = [
{"q": "", "content_type": "tests.singleeventpage"},
{"content_type": "tests.singleeventpage"},
]
url = reverse("wagtailadmin_pages:search")
for param in params:
with self.subTest(param=param):
response = self.get(param)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailadmin/pages/search.html")
self.assertEqual(response.context["query_string"], "")
self.assertContains(response, "Page types")
self.assertContains(response, "All (4)")
# The test fixture contains the root page and the welcome page
# with the base page type
self.assertContains(response, "Page (2)")
self.assertContains(response, "Single event page (1)")
self.assertContains(response, "Lunar event")
self.assertNotContains(response, "ALL THE EVENTS")
self.assertContains(response, "Event index (1)")
self.assertContains(
response,
f"{url}?q=&amp;content_type=tests.eventindex",
)

View File

@@ -0,0 +1,122 @@
from django.test import TestCase
from django.urls import reverse
from wagtail.models import Page
from wagtail.test.testapp.models import (
FormPageWithRedirect,
PageChooserModel,
SimplePage,
)
from wagtail.test.utils import WagtailTestUtils
from wagtail.test.utils.template_tests import AdminTemplateTestUtils
class TestPageUsage(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
# We don't show the "Home" breadcrumb item in page views
base_breadcrumb_items = []
def setUp(self):
self.user = self.login()
self.root_page = Page.objects.get(id=2)
page = SimplePage(
title="Hello world!",
slug="hello-world",
content="hello",
)
self.root_page.add_child(instance=page)
page.save_revision().publish()
self.page = SimplePage.objects.get(id=page.id)
def test_simple(self):
usage_url = reverse("wagtailadmin_pages:usage", args=(self.page.id,))
response = self.client.get(usage_url)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailadmin/generic/listing.html")
self.assertContains(response, "Usage")
self.assertContains(response, "Hello world!")
items = [
{
"url": reverse("wagtailadmin_explore_root"),
"label": "Root",
},
{
"url": reverse("wagtailadmin_explore", args=(self.root_page.id,)),
"label": "Welcome to your new Wagtail site!",
},
{
"url": reverse("wagtailadmin_explore", args=(self.page.id,)),
"label": "Hello world! (simple page)",
},
{
"url": "",
"label": "Usage",
"sublabel": "Hello world! (simple page)",
},
]
self.assertBreadcrumbsItemsRendered(items, response.content)
# There should be exactly one edit link, rendered as a header button
edit_url = reverse("wagtailadmin_pages:edit", args=(self.page.id,))
soup = self.get_soup(response.content)
edit_links = soup.select(f"a[href='{edit_url}']")
self.assertEqual(len(edit_links), 1)
edit_link = edit_links[0]
classes = edit_link.attrs.get("class")
self.assertIn("w-header-button", classes)
self.assertIn("button", classes)
def test_has_private_usage(self):
PageChooserModel.objects.create(page=self.page)
usage_url = reverse("wagtailadmin_pages:usage", args=(self.page.id,))
response = self.client.get(usage_url)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailadmin/generic/listing.html")
self.assertContains(response, "Usage")
self.assertContains(response, "Hello world!")
self.assertContains(response, "(Private page chooser model)")
self.assertContains(response, "<td>Page chooser model</td>", html=True)
def test_has_editable_usage(self):
form_page = FormPageWithRedirect(
title="Contact us",
slug="contact-us",
to_address="to@email.com",
from_address="from@email.com",
subject="The subject",
thank_you_redirect_page=self.page,
)
form_page = self.root_page.add_child(instance=form_page)
usage_url = reverse("wagtailadmin_pages:usage", args=(self.page.id,))
response = self.client.get(usage_url)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailadmin/generic/listing.html")
self.assertContains(response, "Usage")
self.assertContains(response, "Hello world!")
self.assertContains(response, "Contact us")
self.assertContains(
response, reverse("wagtailadmin_pages:edit", args=(form_page.id,))
)
self.assertContains(response, "Thank you redirect page")
self.assertContains(response, "<td>Form page with redirect</td>", html=True)
def test_pagination(self):
for _ in range(50):
PageChooserModel.objects.create(page=self.page)
usage_url = reverse("wagtailadmin_pages:usage", args=(self.page.id,))
response = self.client.get(f"{usage_url}?p=2")
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailadmin/generic/listing.html")
self.assertContains(response, "Page 2 of 3.")
self.assertContains(response, f"{usage_url}?p=1")
self.assertContains(response, f"{usage_url}?p=3")

View File

@@ -0,0 +1,163 @@
from unittest import mock
from django.contrib.auth.models import Group, Permission
from django.test import TestCase
from django.urls import reverse
from wagtail.models import GroupPagePermission, Page
from wagtail.test.testapp.models import BusinessIndex, EventIndex, EventPage, SimplePage
from wagtail.test.utils import WagtailTestUtils
from wagtail.test.utils.template_tests import AdminTemplateTestUtils
class TestParentPageChooserView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
fixtures = ["test.json"]
def setUp(self):
super().setUp()
self.user = self.login()
self.view_url = reverse("event_pages:choose_parent")
def test_get_page_parent_chooser(self):
response = self.client.get(self.view_url)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailadmin/pages/choose_parent.html")
self.assertBreadcrumbsItemsRendered(
[
{"url": reverse("event_pages:index"), "label": "Event pages"},
{"url": "", "label": "Choose parent", "sublabel": "Event page"},
],
response.content,
)
def test_parent_chooser_redirect(self):
parent_page = Page.objects.first()
form_data = {
"parent_page": parent_page.pk,
}
response = self.client.post(self.view_url, form_data)
self.assertRedirects(
response,
reverse(
"wagtailadmin_pages:add", args=("tests", "eventpage", parent_page.pk)
),
)
# Test another parent to make sure everything is working as intended
another_parent = parent_page.get_first_child()
form_data["parent_page"] = another_parent.pk
response = self.client.post(self.view_url, form_data)
self.assertRedirects(
response,
reverse(
"wagtailadmin_pages:add", args=("tests", "eventpage", another_parent.pk)
),
)
def test_no_parent_selected(self):
error_html = """<p class="error-message">This field is required.</p>"""
response = self.client.post(self.view_url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, error_html, html=True)
def test_user_no_add_subpage_permission(self):
parent_page = Page.objects.first()
test_group = Group.objects.create(name="test_group")
test_group.permissions.add(Permission.objects.get(codename="access_admin"))
page_with_add_permission = Page(title="Page not to be selected")
page_with_no_permission = Page(title="Page to be selected")
parent_page.add_child(instance=page_with_add_permission)
parent_page.add_child(instance=page_with_no_permission)
GroupPagePermission.objects.create(
group=test_group,
page=page_with_add_permission,
permission_type="add",
)
form_data = {
"parent_page": page_with_no_permission.pk,
}
self.user.is_superuser = False
self.user.groups.add(test_group)
self.user.save()
response = self.client.post(self.view_url, form_data)
self.assertEqual(response.status_code, 200)
self.assertContains(
response,
" You do not have permission to create a page under &quot;%s&quot;. "
% page_with_no_permission.get_admin_display_title(),
)
form_data["parent_page"] = page_with_add_permission.pk
response = self.client.post(self.view_url, form_data)
self.assertRedirects(
response,
reverse(
"wagtailadmin_pages:add",
args=("tests", "eventpage", page_with_add_permission.pk),
),
)
def test_choosing_parent_with_unsupported_subpage_type(self):
parent_page = Page.objects.first()
page_with_limited_subtypes = BusinessIndex(title="EventPage unsupported")
parent_page.add_child(instance=page_with_limited_subtypes)
form_data = {
"parent_page": page_with_limited_subtypes.pk,
}
response = self.client.post(self.view_url, form_data)
self.assertEqual(response.status_code, 200)
self.assertContains(
response,
"You cannot create a page of type &quot;Event page&quot; under &quot;%s&quot;."
% page_with_limited_subtypes.get_admin_display_title(),
)
def test_skip_if_only_one_valid_parent(self):
self.assertEqual(EventIndex.objects.count(), 1)
with mock.patch.object(EventPage, "allowed_parent_page_models") as mock_method:
mock_method.return_value = [EventIndex]
self.assertEqual(EventPage.allowed_parent_page_models(), [EventIndex])
response = self.client.get(self.view_url)
self.assertRedirects(
response,
reverse(
"wagtailadmin_pages:add",
args=("tests", "eventpage", EventIndex.objects.first().pk),
),
)
def test_skip_if_user_only_has_permission_for_one_parent(self):
test_group = Group.objects.create(name="test_group")
test_group.permissions.add(Permission.objects.get(codename="access_admin"))
self.user.is_superuser = False
self.user.groups.add(test_group)
self.user.save()
# Only grant the user permission to add pages under one leaf page
parent_page = SimplePage.objects.filter(numchild=0).first()
GroupPagePermission.objects.create(
group=test_group,
page=parent_page,
permission_type="add",
)
# Should redirect to the add view with the only valid parent page
response = self.client.get(self.view_url)
self.assertRedirects(
response,
reverse(
"wagtailadmin_pages:add",
args=("tests", "eventpage", parent_page.pk),
),
)

View File

@@ -0,0 +1,653 @@
import datetime
from functools import wraps
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.preview import PreviewOnEdit
from wagtail.models import Page
from wagtail.test.testapp.models import (
EventCategory,
MultiPreviewModesPage,
SimplePage,
StreamPage,
)
from wagtail.test.utils import WagtailTestUtils
class TestIssue2599(WagtailTestUtils, TestCase):
"""
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, "is_available": 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)
def clear_edit_handler(page_cls):
def decorator(fn):
@wraps(fn)
def decorated(*args, **kwargs):
# Clear any old panel definitions generated
page_cls.get_edit_handler.cache_clear()
try:
fn(*args, **kwargs)
finally:
# Clear the bad panel definition generated just now
page_cls.get_edit_handler.cache_clear()
return decorated
return decorator
class TestPreview(WagtailTestUtils, TestCase):
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],
"comments-TOTAL_FORMS": 0,
"comments-INITIAL_FORMS": 0,
"comments-MIN_NUM_FORMS": 0,
"comments-MAX_NUM_FORMS": 1000,
}
def test_preview_on_create_with_no_session_data(self):
preview_url = reverse(
"wagtailadmin_pages:preview_on_add",
args=("tests", "eventpage", self.home_page.id),
)
preview_session_key = "wagtail-preview-tests-eventpage-{}".format(
self.home_page.id
)
self.assertNotIn(preview_session_key, self.client.session)
response = self.client.get(preview_url)
# The preview should be unavailable
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailadmin/generic/preview_error.html")
self.assertContains(
response,
"<title>Preview not available - Wagtail</title>",
html=True,
)
self.assertContains(
response,
'<h1 class="preview-error__title">Preview not available</h1>',
html=True,
)
def test_preview_on_create_with_invalid_data(self):
preview_url = reverse(
"wagtailadmin_pages:preview_on_add",
args=("tests", "eventpage", self.home_page.id),
)
preview_session_key = "wagtail-preview-tests-eventpage-{}".format(
self.home_page.id
)
self.assertNotIn(preview_session_key, self.client.session)
response = self.client.post(preview_url, {**self.post_data, "title": ""})
# Check the JSON response
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
response.content.decode(),
{"is_valid": False, "is_available": False},
)
# The invalid data should not be saved in the session
self.assertNotIn(preview_session_key, self.client.session)
response = self.client.get(preview_url)
# The preview should still be unavailable
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailadmin/generic/preview_error.html")
self.assertContains(
response,
"<title>Preview not available - Wagtail</title>",
html=True,
)
self.assertContains(
response,
'<h1 class="preview-error__title">Preview not available</h1>',
html=True,
)
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, "is_available": True},
)
# Check the user can refresh the preview
preview_session_key = "wagtail-preview-tests-eventpage-{}".format(
self.home_page.id
)
self.assertIn(preview_session_key, self.client.session)
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, "<li>Parties</li>")
self.assertContains(response, "<li>Holidays</li>")
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, "is_available": True},
)
# Check the user can refresh the preview
preview_session_key = f"wagtail-preview-{self.event_page.id}"
self.assertIn(preview_session_key, self.client.session)
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, "<li>Parties</li>")
self.assertContains(response, "<li>Holidays</li>")
def test_preview_on_edit_with_valid_then_invalid_data(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, "is_available": True},
)
# Send an invalid update request
response = self.client.post(preview_url, {**self.post_data, "title": ""})
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
response.content.decode(),
{"is_valid": False, "is_available": True},
)
# Check the user can still see the preview with the last valid data
preview_session_key = f"wagtail-preview-{self.event_page.id}"
self.assertIn(preview_session_key, self.client.session)
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, "<li>Parties</li>")
self.assertContains(response, "<li>Holidays</li>")
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)
def test_preview_on_create_clear_preview_data(self):
preview_session_key = "wagtail-preview-tests-eventpage-{}".format(
self.home_page.id
)
# Set a fake preview session data for the page
self.client.session[preview_session_key] = "test data"
preview_url = reverse(
"wagtailadmin_pages:preview_on_add",
args=("tests", "eventpage", self.home_page.id),
)
response = self.client.delete(preview_url)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
response.content.decode(),
{"success": True},
)
# The data should no longer exist in the session
self.assertNotIn(preview_session_key, self.client.session)
response = self.client.get(preview_url)
# The preview should be unavailable
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailadmin/generic/preview_error.html")
self.assertContains(
response,
"<title>Preview not available - Wagtail</title>",
html=True,
)
self.assertContains(
response,
'<h1 class="preview-error__title">Preview not available</h1>',
html=True,
)
def test_preview_on_edit_clear_preview_data(self):
preview_session_key = f"wagtail-preview-{self.event_page.id}"
# Set a fake preview session data for the page
self.client.session[preview_session_key] = "test data"
preview_url = reverse(
"wagtailadmin_pages:preview_on_edit", args=(self.event_page.id,)
)
response = self.client.delete(preview_url)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
response.content.decode(),
{"success": True},
)
# The data should no longer exist in the session
self.assertNotIn(preview_session_key, self.client.session)
response = self.client.get(preview_url)
# The preview should be unavailable
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "wagtailadmin/generic/preview_error.html")
self.assertContains(
response,
"<title>Preview not available - Wagtail</title>",
html=True,
)
self.assertContains(
response,
'<h1 class="preview-error__title">Preview not available</h1>',
html=True,
)
def test_preview_modes(self):
preview_url = reverse(
"wagtailadmin_pages:preview_on_add",
args=("tests", "multipreviewmodespage", self.home_page.id),
)
response = self.client.post(preview_url, data={"title": "Test", "slug": "test"})
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
response.content.decode(),
{"is_valid": True, "is_available": True},
)
cases = [
("", "tests/simple_page_alt.html"),
("?mode=original", "tests/simple_page.html"),
("?mode=alt%231", "tests/simple_page_alt.html"),
]
for params, template in cases:
with self.subTest(params=params, template=template):
response = self.client.get(preview_url + params)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, template)
class TestEnablePreview(WagtailTestUtils, TestCase):
def setUp(self):
self.root_page = Page.objects.get(id=2)
self.user = self.login()
# SimplePage only has one preview mode
self.single = SimplePage(title="Single preview mode", content="foo")
# MultiPreviewModesPage has two preview modes
self.multiple = MultiPreviewModesPage(title="Multiple preview modes")
self.root_page.add_child(instance=self.single)
self.root_page.add_child(instance=self.multiple)
def get_url_on_add(self, name, page):
model_name = type(page)._meta.model_name
return reverse(
f"wagtailadmin_pages:{name}",
args=("tests", model_name, self.root_page.id),
)
def get_url_on_edit(self, name, page):
return reverse(f"wagtailadmin_pages:{name}", args=(page.id,))
def test_show_preview_panel_on_create_with_single_mode(self):
create_url = self.get_url_on_add("add", self.single)
preview_url = self.get_url_on_add("preview_on_add", self.single)
new_tab_url = preview_url + "?mode="
response = self.client.get(create_url)
self.assertEqual(response.status_code, 200)
# Should show the preview panel
self.assertContains(response, 'data-side-panel="preview"')
self.assertContains(response, 'data-action="%s"' % preview_url)
# Should have the preview side panel toggle button
soup = self.get_soup(response.content)
toggle_button = soup.find("button", {"data-side-panel-toggle": "preview"})
self.assertIsNotNone(toggle_button)
self.assertEqual("w-tooltip w-kbd", toggle_button["data-controller"])
self.assertEqual("mod+p", toggle_button["data-w-kbd-key-value"])
# Should show the iframe
self.assertContains(
response,
'<iframe id="preview-iframe" loading="lazy" title="Preview" class="preview-panel__iframe" data-preview-iframe aria-describedby="preview-panel-error-banner">',
)
# Should show the new tab button with the default mode set
self.assertContains(response, f'href="{new_tab_url}" target="_blank"')
# Should not show the preview mode selection
self.assertNotContains(
response,
'<select id="id_preview_mode" name="preview_mode" class="preview-panel__mode-select" data-preview-mode-select>',
)
def test_show_preview_panel_on_create_with_multiple_modes(self):
create_url = self.get_url_on_add("add", self.multiple)
preview_url = self.get_url_on_add("preview_on_add", self.multiple)
new_tab_url = preview_url + "?mode=alt%231"
response = self.client.get(create_url)
self.assertEqual(response.status_code, 200)
# Should show the preview panel
self.assertContains(response, 'data-side-panel-toggle="preview"')
self.assertContains(response, 'data-side-panel="preview"')
self.assertContains(response, 'data-action="%s"' % preview_url)
# Should show the iframe
self.assertContains(
response,
'<iframe id="preview-iframe" loading="lazy" title="Preview" class="preview-panel__iframe" data-preview-iframe aria-describedby="preview-panel-error-banner">',
)
# Should show the new tab button with the default mode set and correctly quoted
self.assertContains(response, f'href="{new_tab_url}" target="_blank"')
# should show the preview mode selection
self.assertContains(
response,
'<select id="id_preview_mode" name="preview_mode" class="preview-panel__mode-select" data-preview-mode-select>',
)
self.assertContains(response, '<option value="original">Original</option>')
# Should respect the default_preview_mode
self.assertContains(
response, '<option value="alt#1" selected>Alternate</option>'
)
def test_show_preview_panel_on_edit_with_single_mode(self):
edit_url = self.get_url_on_edit("edit", self.single)
preview_url = self.get_url_on_edit("preview_on_edit", self.single)
new_tab_url = preview_url + "?mode="
response = self.client.get(edit_url)
self.assertEqual(response.status_code, 200)
# Should show the preview panel
self.assertContains(response, 'data-side-panel-toggle="preview"')
self.assertContains(response, 'data-side-panel="preview"')
self.assertContains(response, 'data-action="%s"' % preview_url)
# Should show the iframe
self.assertContains(
response,
'<iframe id="preview-iframe" loading="lazy" title="Preview" class="preview-panel__iframe" data-preview-iframe aria-describedby="preview-panel-error-banner">',
)
# Should show the new tab button with the default mode set
self.assertContains(response, f'href="{new_tab_url}" target="_blank"')
# Should not show the preview mode selection
self.assertNotContains(
response,
'<select id="id_preview_mode" name="preview_mode" class="preview-panel__mode-select" data-preview-mode-select>',
)
def test_show_preview_panel_on_edit_with_multiple_modes(self):
edit_url = self.get_url_on_edit("edit", self.multiple)
preview_url = self.get_url_on_edit("preview_on_edit", self.multiple)
new_tab_url = preview_url + "?mode=alt%231"
response = self.client.get(edit_url)
self.assertEqual(response.status_code, 200)
# Should show the preview panel
self.assertContains(response, 'data-side-panel-toggle="preview"')
self.assertContains(response, 'data-side-panel="preview"')
self.assertContains(response, 'data-action="%s"' % preview_url)
# Should show the iframe
self.assertContains(
response,
'<iframe id="preview-iframe" loading="lazy" title="Preview" class="preview-panel__iframe" data-preview-iframe aria-describedby="preview-panel-error-banner">',
)
# Should show the new tab button with the default mode set and correctly quoted
self.assertContains(response, f'href="{new_tab_url}" target="_blank"')
# should show the preview mode selection
self.assertContains(
response,
'<select id="id_preview_mode" name="preview_mode" class="preview-panel__mode-select" data-preview-mode-select>',
)
self.assertContains(response, '<option value="original">Original</option>')
# Should respect the default_preview_mode
self.assertContains(
response, '<option value="alt#1" selected>Alternate</option>'
)
def test_show_preview_on_revisions_list(self):
latest_revision = self.single.save_revision(log_action=True)
history_url = self.get_url_on_edit("history", self.single)
preview_url = reverse(
"wagtailadmin_pages:revisions_view",
args=(self.single.id, latest_revision.id),
)
response = self.client.get(history_url)
soup = self.get_soup(response.content)
preview_link = soup.find("a", {"href": preview_url})
self.assertEqual(len(preview_link), 1)
self.assertEqual("Preview", preview_link.text)
class TestDisablePreviewButton(WagtailTestUtils, TestCase):
"""
Test that preview button can be disabled by setting preview_modes to an empty list
"""
def setUp(self):
# Find root page
self.root_page = Page.objects.get(id=2)
# Login
self.user = self.login()
def test_disable_preview_on_create(self):
# StreamPage has preview_modes = []
response = self.client.get(
reverse(
"wagtailadmin_pages:add",
args=("tests", "streampage", self.root_page.id),
)
)
self.assertEqual(response.status_code, 200)
preview_url = reverse(
"wagtailadmin_pages:preview_on_add",
args=("tests", "streampage", self.root_page.id),
)
self.assertNotContains(response, 'data-side-panel-toggle="preview"')
self.assertNotContains(response, 'data-side-panel="preview"')
self.assertNotContains(response, 'data-action="%s"' % preview_url)
def test_disable_preview_on_edit(self):
stream_page = StreamPage(title="stream page", body=[("text", "hello")])
self.root_page.add_child(instance=stream_page)
# StreamPage has preview_modes = []
response = self.client.get(
reverse("wagtailadmin_pages:edit", args=(stream_page.id,))
)
self.assertEqual(response.status_code, 200)
preview_url = reverse(
"wagtailadmin_pages:preview_on_edit", args=(stream_page.id,)
)
self.assertNotContains(response, 'data-side-panel-toggle="preview"')
self.assertNotContains(response, 'data-side-panel="preview"')
self.assertNotContains(response, 'data-action="%s"' % preview_url)
def test_disable_preview_on_revisions_list(self):
stream_page = StreamPage(title="stream page", body=[("text", "hello")])
self.root_page.add_child(instance=stream_page)
latest_revision = stream_page.save_revision(log_action=True)
# StreamPage has preview_modes = []
response = self.client.get(
reverse("wagtailadmin_pages:history", args=(stream_page.id,))
)
preview_url = reverse(
"wagtailadmin_pages:revisions_view",
args=(stream_page.id, latest_revision.id),
)
self.assertNotContains(response, preview_url)
soup = self.get_soup(response.content)
preview_link = soup.find("a", {"href": preview_url})
self.assertIsNone(preview_link)

View File

@@ -0,0 +1,142 @@
from django.test import TestCase
from django.urls import reverse
from wagtail.models import Page
from wagtail.test.testapp.models import BusinessChild, BusinessIndex, SimplePage
from wagtail.test.utils import WagtailTestUtils
class TestPageReorder(WagtailTestUtils, TestCase):
fixtures = ["test.json"]
def __init__(self, methodName: str = ...) -> None:
super().__init__(methodName)
def setUp(self):
# Find root page
self.root_page = Page.objects.get(id=2)
# root
# |- simple_index (SimplePage)
# | |- simple_child_1 (SimplePage)
# | |- simple_child_2 (SimplePage)
# | |- simple_child_3 (SimplePage)
self.index_page = SimplePage(title="Simple", slug="simple", content="hello")
self.root_page.add_child(instance=self.index_page)
self.child_1 = SimplePage(
title="Child 1 of SimplePage", slug="child-1", content="hello"
)
self.index_page.add_child(instance=self.child_1)
self.child_2 = SimplePage(
title="Child 2 of SimplePage", slug="child-2", content="hello"
)
self.index_page.add_child(instance=self.child_2)
self.child_3 = SimplePage(
title="Child 3 of SimplePage", slug="child-3", content="hello"
)
self.index_page.add_child(instance=self.child_3)
# Login
self.user = self.login()
def test_page_set_page_position_get_request_with_simple_page(self):
"""
Test that GET requests to set_page_position view don't alter the page order.
"""
response = self.client.get(
reverse("wagtailadmin_pages:set_page_position", args=(self.child_1.id,))
)
self.assertEqual(response.status_code, 200)
# Ensure page order does not change:
child_slugs = self.index_page.get_children().values_list("slug", flat=True)
self.assertListEqual(list(child_slugs), ["child-1", "child-2", "child-3"])
def test_page_set_page_position_without_position_argument_moves_it_to_the_end(self):
response = self.client.post(
reverse("wagtailadmin_pages:set_page_position", args=(self.child_1.id,))
)
self.assertEqual(response.status_code, 200)
# check if child_1 is the last child page:
child_slugs = self.index_page.get_children().values_list("slug", flat=True)
self.assertListEqual(list(child_slugs), ["child-2", "child-3", "child-1"])
def test_page_move_page_position_up(self):
"""Moves child 3 to the first position."""
response = self.client.post(
reverse("wagtailadmin_pages:set_page_position", args=(self.child_3.id,))
+ "?position=0"
)
self.assertEqual(response.status_code, 200)
# check if child_3 is the first child page:
child_slugs = self.index_page.get_children().values_list("slug", flat=True)
self.assertListEqual(list(child_slugs), ["child-3", "child-1", "child-2"])
def test_page_move_page_position_down(self):
"""
Moves child 3 to the first position."""
response = self.client.post(
reverse("wagtailadmin_pages:set_page_position", args=(self.child_1.id,))
+ "?position=1"
)
self.assertEqual(response.status_code, 200)
# check if child_1 is the second child page:
child_slugs = self.index_page.get_children().values_list("slug", flat=True)
self.assertListEqual(list(child_slugs), ["child-2", "child-1", "child-3"])
def test_page_move_page_position_to_the_same_position(self):
"""
Moves child 3 to the first position."""
response = self.client.post(
reverse("wagtailadmin_pages:set_page_position", args=(self.child_1.id,))
+ "?position=0"
)
self.assertEqual(response.status_code, 200)
# Ensure page order does not change:
child_slugs = self.index_page.get_children().values_list("slug", flat=True)
self.assertListEqual(list(child_slugs), ["child-1", "child-2", "child-3"])
def test_page_set_page_position_with_invalid_target_position(self):
response = self.client.post(
reverse("wagtailadmin_pages:set_page_position", args=(self.child_3.id,))
+ "?position=99"
)
self.assertEqual(response.status_code, 200)
# Ensure page order does not change:
child_slugs = self.index_page.get_children().values_list("slug", flat=True)
self.assertListEqual(list(child_slugs), ["child-1", "child-2", "child-3"])
class TestPageReorderWithParentPageRestrictions(TestPageReorder):
"""
This testCase is the same as the TestPageReorder class above, but with a different
page type: BusinessChild has a parent_page_types restriction.
This ensures that this restriction doesn't affect the ability to reorder pages.
"""
def setUp(self):
# Find root page
self.root_page = Page.objects.get(id=2)
# root
# |- index_page (BusinessIndex)
# | |- child_1 (BusinessChild)
# | |- child_2 (BusinessChild)
# | |- child_3 (BusinessChild)
self.index_page = BusinessIndex(title="Simple", slug="simple")
self.root_page.add_child(instance=self.index_page)
self.child_1 = BusinessChild(title="Child 1 of BusinessIndex", slug="child-1")
self.index_page.add_child(instance=self.child_1)
self.child_2 = BusinessChild(title="Child 2 of BusinessIndex", slug="child-2")
self.index_page.add_child(instance=self.child_2)
self.child_3 = BusinessChild(title="Child 3 of BusinessIndex", slug="child-3")
self.index_page.add_child(instance=self.child_3)
# Login
self.user = self.login()

View File

@@ -0,0 +1,648 @@
from django.conf import settings
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 freezegun import freeze_time
from wagtail.admin.staticfiles import versioned_static
from wagtail.models import Page
from wagtail.test.testapp.models import (
DefaultStreamPage,
EventPage,
FormClassAdditionalFieldPage,
SecretPage,
)
from wagtail.test.utils import WagtailTestUtils
from wagtail.test.utils.timestamps import local_datetime
class TestRevisions(WagtailTestUtils, TestCase):
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 = (
"<p>Last Christmas I gave you my heart, "
"but the very next day you gave it away</p>"
)
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 = (
"<p>This year, to save me from tears, "
"I'll give it to someone special</p>"
)
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_get_revisions_index(self):
response = self.client.get(
reverse(
"wagtailadmin_pages:revisions_index", args=(self.christmas_event.id,)
)
)
history_url = reverse(
"wagtailadmin_pages:history", args=(self.christmas_event.id,)
)
self.assertRedirects(response, history_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")
# Should show edit link in the userbar
# https://github.com/wagtail/wagtail/issues/10002
self.assertContains(response, "Edit this page")
self.assertContains(
response,
reverse("wagtailadmin_pages:edit", args=(self.christmas_event.id,)),
)
def test_preview_revision_with_no_page_permissions_redirects_to_admin(self):
admin_only_user = self.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(email="siteeditor@example.com")
self.login(editor)
response = self.request_preview_revision()
self.assertEqual(response.status_code, 302)
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 version 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 = (
"""<input type="hidden" name="revision" value="%d" />"""
% 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 version")
@freeze_time("2014-12-20 12:00:00")
def test_scheduled_revision(self):
if settings.USE_TZ:
# 12:00 UTC
self.christmas_event.go_live_at = "2014-12-26T12:00:00.000Z"
else:
# 12:00 in no specific timezone
self.christmas_event.go_live_at = "2014-12-26T12:00:00"
this_christmas_revision = self.christmas_event.save_revision(log_action=True)
this_christmas_revision.publish(log_action=True)
this_christmas_unschedule_url = reverse(
"wagtailadmin_pages:revisions_unschedule",
args=(self.christmas_event.id, this_christmas_revision.id),
)
response = self.client.get(
reverse("wagtailadmin_pages:history", args=(self.christmas_event.id,))
)
self.assertEqual(response.status_code, 200)
if settings.USE_TZ:
# the default timezone is "Asia/Tokyo", so we expect UTC +9
expected_date_string = "Dec. 26, 2014, 9 p.m."
else:
expected_date_string = "Dec. 26, 2014, noon"
self.assertContains(
response, f"Page scheduled for publishing at {expected_date_string}"
)
self.assertContains(response, this_christmas_unschedule_url)
class TestStreamRevisions(WagtailTestUtils, TestCase):
def setUp(self):
self.root_page = Page.objects.get(id=2)
self.test_page = DefaultStreamPage(
title="A DefaultStreamPage",
slug="a-defaultstreampage",
)
self.root_page.add_child(instance=self.test_page)
self.test_page.title = "An Updated DefaultStreamPage"
self.first_revision = self.test_page.save_revision()
self.first_revision.created_at = local_datetime(2022, 5, 10)
self.first_revision.save()
self.login()
def test_revert_revision(self):
test_page_revert_url = reverse(
"wagtailadmin_pages:revisions_revert",
args=(self.test_page.id, self.first_revision.id),
)
response = self.client.get(test_page_revert_url)
self.assertEqual(response.status_code, 200)
html = response.content.decode()
blocks_js = versioned_static("wagtailadmin/js/telepath/blocks.js")
streamfield_css = versioned_static("wagtailadmin/css/panels/streamfield.css")
# The media files for StreamField should be included in the HTML
self.assertTagInHTML(f'<script src="{blocks_js}"></script>', html)
self.assertTagInHTML(
f'<link href="{streamfield_css}" media="all" rel="stylesheet">',
html,
allow_extra_attrs=True, # Django 4.1 removes the type="text/css" attribute
)
class TestCompareRevisions(WagtailTestUtils, TestCase):
# 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 = (
"<p>Last Christmas I gave you my heart, "
"but the very next day you gave it away</p>"
)
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 = (
"<p>This year, to save me from tears, "
"I'll give it to someone special</p>"
)
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,
'<span class="deletion">Last Christmas I gave you my heart, but the very next day you gave it away</span><span class="addition">This year, to save me from tears, I&#39;ll give it to someone special</span>',
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,
'<span class="deletion">Last Christmas I gave you my heart, but the very next day you gave it away</span><span class="addition">This year, to save me from tears, I&#39;ll give it to someone special</span>',
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,
'<span class="deletion">Last Christmas I gave you my heart, but the very next day you gave it away</span><span class="addition">This year, to save me from tears, I&#39;ll give it to someone special</span>',
html=True,
)
def test_compare_revisions_live(self):
# Mess with the live version, bypassing revisions
self.christmas_event.body = (
"<p>This year, to save me from tears, " "I'll just feed it to the dog</p>"
)
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,
'<span class="deletion">Last Christmas I gave you my heart, but the very next day you gave it away</span><span class="addition">This year, to save me from tears, I&#39;ll just feed it to the dog</span>',
html=True,
)
class TestCompareRevisionsWithPerUserEditHandlers(WagtailTestUtils, TestCase):
fixtures = ["test.json"]
def setUp(self):
self.home = Page.objects.get(url_path="/home/")
self.secret_page = SecretPage(
title="Secret page",
boring_data="InnocentCorp is the leading supplier of door hinges",
secret_data="for flying saucers",
)
self.home.add_child(instance=self.secret_page)
self.old_revision = self.secret_page.save_revision()
self.secret_page.boring_data = (
"InnocentCorp is the leading supplier of rubber sprockets"
)
self.secret_page.secret_data = "for fake moon landings"
self.new_revision = self.secret_page.save_revision()
self.compare_url = reverse(
"wagtailadmin_pages:revisions_compare",
args=(self.secret_page.id, self.old_revision.id, self.new_revision.id),
)
def test_comparison_as_superuser(self):
self.login()
response = self.client.get(self.compare_url)
self.assertEqual(response.status_code, 200)
self.assertContains(
response,
'InnocentCorp is the leading supplier of <span class="deletion">door hinges</span><span class="addition">rubber sprockets</span>',
html=True,
)
self.assertContains(
response,
'for <span class="deletion">flying saucers</span><span class="addition">fake moon landings</span>',
html=True,
)
def test_comparison_as_ordinary_user(self):
user = self.create_user(username="editor", password="password")
user.groups.add(Group.objects.get(name="Site-wide editors"))
self.login(username="editor", password="password")
response = self.client.get(self.compare_url)
self.assertEqual(response.status_code, 200)
self.assertContains(
response,
'InnocentCorp is the leading supplier of <span class="deletion">door hinges</span><span class="addition">rubber sprockets</span>',
html=True,
)
self.assertNotContains(
response,
"moon landings",
)
class TestCompareRevisionsWithNonModelField(WagtailTestUtils, TestCase):
"""
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="<p>hello</p>",
)
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 = "<p>I would like very much to go into the forrest.</p>"
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 = "<p>I would like very much to go into the forest.</p>"
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,
'<input type="text" name="code" aria-describedby="panel-child-content-child-code-helptext" required id="id_code" maxlength="5" />',
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,
'<span class="deletion">forrest.</span><span class="addition">forest.</span>',
)
# should not contain the field defined in the formclass used
self.assertNotContains(response, "<h2>Code:</h2>")
class TestRevisionsUnschedule(WagtailTestUtils, TestCase):
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 = (
"<p>Last Christmas I gave you my heart, "
"but the very next day you gave it away</p>"
)
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 = (
"<p>This year, to save me from tears, "
"I'll give it to someone special</p>"
)
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/shared/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 302 redirected response
self.assertEqual(response.status_code, 302)
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 page history
self.assertRedirects(
response,
reverse("wagtailadmin_pages:history", 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
)
def test_unschedule_view_post_with_next_url(self):
"""
This tests that the redirect response follows the "next" parameter
"""
unschedule_url = reverse(
"wagtailadmin_pages:revisions_unschedule",
args=(self.christmas_event.id, self.this_christmas_revision.id),
)
edit_url = reverse("wagtailadmin_pages:edit", args=(self.christmas_event.id,))
# Post to the unschedule page
response = self.client.post(f"{unschedule_url}?next={edit_url}")
# Should be redirected to edit page
self.assertRedirects(response, edit_url)
# 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(WagtailTestUtils, TestCase):
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 = "<p>Some Content</p>"
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/shared/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 page history
self.assertRedirects(
response,
reverse("wagtailadmin_pages:history", 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
)

View File

@@ -0,0 +1,264 @@
from unittest import mock
from django.contrib.auth.models import Permission
from django.http import HttpRequest, HttpResponse
from django.test import TestCase
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from wagtail.models import Page
from wagtail.signals import page_unpublished
from wagtail.test.testapp.models import SimplePage
from wagtail.test.utils import WagtailTestUtils
class TestPageUnpublish(WagtailTestUtils, TestCase):
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 302 redirected response
self.assertEqual(response.status_code, 302)
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)
try:
# 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)
finally:
page_unpublished.disconnect(mock_handler)
def test_after_unpublish_page(self):
def hook_func(request, page):
self.assertIsInstance(request, HttpRequest)
self.assertEqual(page.id, self.page.id)
return HttpResponse("Overridden!")
with self.register_hook("after_unpublish_page", hook_func):
post_data = {}
response = self.client.post(
reverse("wagtailadmin_pages:unpublish", args=(self.page.id,)), post_data
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b"Overridden!")
self.page.refresh_from_db()
self.assertEqual(self.page.status_string, _("draft"))
def test_before_unpublish_page(self):
def hook_func(request, page):
self.assertIsInstance(request, HttpRequest)
self.assertEqual(page.id, self.page.id)
return HttpResponse("Overridden!")
with self.register_hook("before_unpublish_page", hook_func):
post_data = {}
response = self.client.post(
reverse("wagtailadmin_pages:unpublish", args=(self.page.id,)), post_data
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b"Overridden!")
# The hook response is served before unpublish is called.
self.page.refresh_from_db()
self.assertEqual(self.page.status_string, _("live"))
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,
'name="include_descendants"',
)
class TestPageUnpublishIncludingDescendants(WagtailTestUtils, TestCase):
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,
'name="include_descendants"',
)
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 unpublished 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)

View File

@@ -0,0 +1,114 @@
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.models import Page
from wagtail.test.testapp.models import SimplePage, StreamPage
from wagtail.test.utils import WagtailTestUtils
class TestDraftAccess(WagtailTestUtils, TestCase):
"""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)
# Add stream page (which has empty preview_modes, and so doesn't allow viewing draft)
self.stream_page = StreamPage(title="stream page", body=[("text", "hello")])
self.root_page.add_child(instance=self.stream_page)
# create user with admin access (but not draft_view access)
user = self.create_user(username="bob", 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_page_without_preview_modes_is_unauthorised(self):
# Login as admin
self.user = self.login()
# Try getting page draft
response = self.client.get(
reverse("wagtailadmin_pages:view_draft", args=(self.stream_page.id,))
)
# Unauthorised response (because this page type has previewing disabled)
self.assertRedirects(response, "/admin/")
def test_draft_access_unauthorised(self):
"""Test that user without edit/publish permission can't view draft."""
self.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 redirected to the home page
self.assertEqual(response.status_code, 302)
def test_draft_access_authorised(self):
"""Test that user with edit permission can view draft."""
# give user the permission to edit page
user = get_user_model().objects.get(email="bob@example.com")
user.groups.add(Group.objects.get(name="Moderators"))
user.save()
self.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)
def test_show_edit_link_in_userbar(self):
self.login()
response = self.client.get(
reverse("wagtailadmin_pages:view_draft", args=(self.child_page.id,))
)
# Should show edit link in the userbar
# https://github.com/wagtail/wagtail/issues/10002
self.assertContains(response, "Edit this page")
self.assertContains(
response, reverse("wagtailadmin_pages:edit", args=(self.child_page.id,))
)

View File

@@ -0,0 +1,101 @@
from django.contrib.auth.models import Permission
from django.test import TestCase
from django.urls import reverse
from wagtail.models import Page
from wagtail.test.utils import WagtailTestUtils
class TestWorkflowHistoryDetail(WagtailTestUtils, TestCase):
fixtures = ["test.json"]
def setUp(self):
self.user = self.create_test_user()
self.login(self.user)
self.christmas_event = Page.objects.get(
url_path="/home/events/christmas/"
).specific
self.christmas_event.save_revision()
workflow = self.christmas_event.get_workflow()
self.workflow_state = workflow.start(self.christmas_event, self.user)
def test_get_index(self):
response = self.client.get(
reverse(
"wagtailadmin_pages:workflow_history", args=[self.christmas_event.id]
)
)
self.assertEqual(response.status_code, 200)
self.assertContains(
response, reverse("wagtailadmin_pages:edit", args=[self.christmas_event.id])
)
self.assertContains(
response,
reverse(
"wagtailadmin_pages:workflow_history_detail",
args=[self.christmas_event.id, self.workflow_state.id],
),
)
def test_get_index_with_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.get(
reverse(
"wagtailadmin_pages:workflow_history", args=[self.christmas_event.id]
)
)
self.assertEqual(response.status_code, 302)
def test_get_detail(self):
response = self.client.get(
reverse(
"wagtailadmin_pages:workflow_history_detail",
args=[self.christmas_event.id, self.workflow_state.id],
)
)
self.assertEqual(response.status_code, 200)
self.assertContains(
response, reverse("wagtailadmin_pages:edit", args=[self.christmas_event.id])
)
self.assertContains(
response,
reverse(
"wagtailadmin_pages:workflow_history", args=[self.christmas_event.id]
),
)
self.assertContains(response, '<div class="w-tabs" data-tabs>')
self.assertContains(response, '<div class="tab-content">')
def test_get_detail_with_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.get(
reverse(
"wagtailadmin_pages:workflow_history_detail",
args=[self.christmas_event.id, self.workflow_state.id],
)
)
self.assertEqual(response.status_code, 302)