3997 lines
158 KiB
Python
3997 lines
158 KiB
Python
import datetime
|
|
import unittest
|
|
from unittest.mock import Mock
|
|
|
|
from django.conf import settings
|
|
from django.contrib.auth import get_user_model
|
|
from django.contrib.auth.models import AnonymousUser, Group
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.core.exceptions import ValidationError
|
|
from django.http import Http404
|
|
from django.test import Client, TestCase, override_settings
|
|
from django.test.client import RequestFactory
|
|
from django.utils import timezone, translation
|
|
from freezegun import freeze_time
|
|
|
|
from wagtail.actions.copy_for_translation import ParentNotTranslatedError
|
|
from wagtail.coreutils import get_dummy_request
|
|
from wagtail.locks import BasicLock, ScheduledForPublishLock, WorkflowLock
|
|
from wagtail.models import (
|
|
Comment,
|
|
GroupApprovalTask,
|
|
Locale,
|
|
Page,
|
|
PageLogEntry,
|
|
PageManager,
|
|
PageViewRestriction,
|
|
Site,
|
|
Workflow,
|
|
WorkflowTask,
|
|
get_page_models,
|
|
get_translatable_models,
|
|
)
|
|
from wagtail.signals import page_published
|
|
from wagtail.test.testapp.models import (
|
|
AbstractPage,
|
|
Advert,
|
|
AlwaysShowInMenusPage,
|
|
BlogCategory,
|
|
BlogCategoryBlogPage,
|
|
BusinessChild,
|
|
BusinessIndex,
|
|
BusinessNowherePage,
|
|
BusinessSubIndex,
|
|
CustomManager,
|
|
CustomManagerPage,
|
|
CustomPageQuerySet,
|
|
EventCategory,
|
|
EventIndex,
|
|
EventPage,
|
|
EventPageSpeaker,
|
|
GenericSnippetPage,
|
|
ManyToManyBlogPage,
|
|
MTIBasePage,
|
|
MTIChildPage,
|
|
MyCustomPage,
|
|
OneToOnePage,
|
|
PageWithExcludedCopyField,
|
|
PageWithGenericRelation,
|
|
RelatedGenericRelation,
|
|
SimpleChildPage,
|
|
SimplePage,
|
|
SimpleParentPage,
|
|
SingleEventPage,
|
|
SingletonPage,
|
|
StandardIndex,
|
|
StreamPage,
|
|
TaggedGrandchildPage,
|
|
TaggedPage,
|
|
)
|
|
from wagtail.test.utils import WagtailTestUtils
|
|
from wagtail.url_routing import RouteResult
|
|
|
|
|
|
def get_ct(model):
|
|
return ContentType.objects.get_for_model(model)
|
|
|
|
|
|
class TestValidation(TestCase):
|
|
fixtures = ["test.json"]
|
|
|
|
def test_can_create(self):
|
|
"""
|
|
Check that basic page creation works
|
|
"""
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
hello_page = SimplePage(
|
|
title="Hello world", slug="hello-world", content="hello"
|
|
)
|
|
homepage.add_child(instance=hello_page)
|
|
|
|
# check that hello_page exists in the db
|
|
retrieved_page = Page.objects.get(id=hello_page.id)
|
|
self.assertEqual(retrieved_page.title, "Hello world")
|
|
|
|
def test_title_is_required(self):
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
|
|
hello_page = SimplePage(slug="hello-world", content="hello")
|
|
with self.assertRaises(ValidationError):
|
|
homepage.add_child(instance=hello_page)
|
|
|
|
hello_page = SimplePage(title="", slug="hello-world", content="hello")
|
|
with self.assertRaises(ValidationError):
|
|
homepage.add_child(instance=hello_page)
|
|
|
|
def test_slug_is_autogenerated(self):
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
|
|
# slug should be auto-assigned to a slugified version of the title
|
|
hello_page = SimplePage(title="Hello world", content="hello")
|
|
homepage.add_child(instance=hello_page)
|
|
retrieved_page = Page.objects.get(id=hello_page.id)
|
|
self.assertEqual(retrieved_page.slug, "hello-world")
|
|
|
|
# auto-generated slug should receive a suffix to make it unique
|
|
events_page = SimplePage(title="Events", content="hello")
|
|
homepage.add_child(instance=events_page)
|
|
retrieved_page = Page.objects.get(id=events_page.id)
|
|
self.assertEqual(retrieved_page.slug, "events-2")
|
|
|
|
def test_slug_must_be_unique_within_parent(self):
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
|
|
events_page = SimplePage(title="Events", slug="events", content="hello")
|
|
with self.assertRaises(ValidationError):
|
|
homepage.add_child(instance=events_page)
|
|
|
|
def test_slug_can_duplicate_other_sections(self):
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
|
|
# the Events section has a page with slug='christmas', but we still allow
|
|
# it as a slug elsewhere
|
|
christmas_page = SimplePage(
|
|
title="Christmas", slug="christmas", content="hello"
|
|
)
|
|
homepage.add_child(instance=christmas_page)
|
|
self.assertTrue(Page.objects.filter(id=christmas_page.id).exists())
|
|
|
|
@override_settings(WAGTAIL_ALLOW_UNICODE_SLUGS=True)
|
|
def test_slug_generation_respects_unicode_setting_true(self):
|
|
page = Page(title="A mööse bit me önce")
|
|
Page.get_first_root_node().add_child(instance=page)
|
|
self.assertEqual(page.slug, "a-mööse-bit-me-önce")
|
|
|
|
@override_settings(WAGTAIL_ALLOW_UNICODE_SLUGS=False)
|
|
def test_slug_generation_respects_unicode_setting_false(self):
|
|
page = Page(title="A mööse bit me önce")
|
|
Page.get_first_root_node().add_child(instance=page)
|
|
self.assertEqual(page.slug, "a-moose-bit-me-once")
|
|
|
|
def test_get_admin_display_title(self):
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
self.assertEqual(homepage.draft_title, homepage.get_admin_display_title())
|
|
|
|
def test_get_admin_display_title_with_blank_draft_title(self):
|
|
# Display title should fall back on the live title if draft_title is blank;
|
|
# this can happen if the page was created through fixtures or migrations that
|
|
# didn't explicitly account for draft_title
|
|
# (since draft_title doesn't get populated automatically on save in those cases)
|
|
Page.objects.filter(url_path="/home/").update(
|
|
title="live title", draft_title=""
|
|
)
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
|
|
self.assertEqual(homepage.get_admin_display_title(), "live title")
|
|
|
|
def test_draft_title_is_autopopulated(self):
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
|
|
hello_page = SimplePage(title="Hello world", content="hello")
|
|
homepage.add_child(instance=hello_page)
|
|
retrieved_page = Page.objects.get(id=hello_page.id)
|
|
self.assertEqual(retrieved_page.draft_title, "Hello world")
|
|
|
|
hello_page = SimplePage(
|
|
title="Hello world", draft_title="Hello world edited", content="hello"
|
|
)
|
|
homepage.add_child(instance=hello_page)
|
|
retrieved_page = Page.objects.get(id=hello_page.id)
|
|
self.assertEqual(retrieved_page.draft_title, "Hello world edited")
|
|
|
|
|
|
@override_settings(
|
|
ALLOWED_HOSTS=[
|
|
"localhost",
|
|
"events.example.com",
|
|
"about.example.com",
|
|
"unknown.site.com",
|
|
]
|
|
)
|
|
class TestSiteRouting(TestCase):
|
|
fixtures = ["test.json"]
|
|
|
|
def setUp(self):
|
|
self.default_site = Site.objects.get(is_default_site=True)
|
|
events_page = Page.objects.get(url_path="/home/events/")
|
|
about_page = Page.objects.get(url_path="/home/about-us/")
|
|
self.events_site = Site.objects.create(
|
|
hostname="events.example.com", root_page=events_page
|
|
)
|
|
self.alternate_port_events_site = Site.objects.create(
|
|
hostname="events.example.com", root_page=events_page, port="8765"
|
|
)
|
|
self.about_site = Site.objects.create(
|
|
hostname="about.example.com", root_page=about_page
|
|
)
|
|
self.alternate_port_default_site = Site.objects.create(
|
|
hostname=self.default_site.hostname,
|
|
port="8765",
|
|
root_page=self.default_site.root_page,
|
|
)
|
|
self.unrecognised_port = "8000"
|
|
self.unrecognised_hostname = "unknown.site.com"
|
|
|
|
def test_route_for_request_query_count(self):
|
|
request = get_dummy_request(site=self.events_site)
|
|
with self.assertNumQueries(2):
|
|
# expect queries for site & page
|
|
Page.route_for_request(request, request.path)
|
|
with self.assertNumQueries(0):
|
|
# subsequent lookups should be cached on the request
|
|
Page.route_for_request(request, request.path)
|
|
|
|
def test_route_for_request_value(self):
|
|
request = get_dummy_request(site=self.events_site)
|
|
self.assertFalse(hasattr(request, "_wagtail_route_for_request"))
|
|
result = Page.route_for_request(request, request.path)
|
|
self.assertTrue(isinstance(result, RouteResult))
|
|
self.assertEqual(
|
|
(result[0], result[1], result[2]),
|
|
(self.events_site.root_page.specific, [], {}),
|
|
)
|
|
self.assertTrue(hasattr(request, "_wagtail_route_for_request"))
|
|
self.assertIs(request._wagtail_route_for_request, result)
|
|
|
|
def test_route_for_request_cached(self):
|
|
request = get_dummy_request(site=self.events_site)
|
|
m = Mock()
|
|
request._wagtail_route_for_request = m
|
|
with self.assertNumQueries(0):
|
|
self.assertEqual(Page.route_for_request(request, request.path), m)
|
|
|
|
def test_route_for_request_suppresses_404(self):
|
|
request = get_dummy_request(path="does-not-exist", site=self.events_site)
|
|
self.assertIsNone(Page.route_for_request(request, request.path))
|
|
|
|
def test_find_for_request(self):
|
|
request_200 = get_dummy_request(site=self.events_site)
|
|
self.assertEqual(
|
|
Page.find_for_request(request_200, request_200.path),
|
|
self.events_site.root_page.specific,
|
|
)
|
|
request_404 = get_dummy_request(path="does-not-exist", site=self.events_site)
|
|
self.assertEqual(
|
|
Page.find_for_request(request_404, request_404.path),
|
|
None,
|
|
)
|
|
|
|
def test_valid_headers_route_to_specific_site(self):
|
|
# requests with a known Host: header should be directed to the specific site
|
|
request = get_dummy_request(site=self.events_site)
|
|
with self.assertNumQueries(1):
|
|
self.assertEqual(Site.find_for_request(request), self.events_site)
|
|
|
|
def test_ports_in_request_headers_are_respected(self):
|
|
# ports in the Host: header should be respected
|
|
request = get_dummy_request(site=self.alternate_port_events_site)
|
|
with self.assertNumQueries(1):
|
|
self.assertEqual(
|
|
Site.find_for_request(request), self.alternate_port_events_site
|
|
)
|
|
|
|
def test_unrecognised_host_header_routes_to_default_site(self):
|
|
# requests with an unrecognised Host: header should be directed to the default site
|
|
request = get_dummy_request()
|
|
request.META["HTTP_HOST"] = self.unrecognised_hostname
|
|
with self.assertNumQueries(1):
|
|
self.assertEqual(Site.find_for_request(request), self.default_site)
|
|
|
|
def test_unrecognised_port_and_default_host_routes_to_default_site(self):
|
|
# requests to the default host on an unrecognised port should be directed to the default site
|
|
request = get_dummy_request(site=self.default_site)
|
|
request.META["SERVER_PORT"] = self.unrecognised_port
|
|
with self.assertNumQueries(1):
|
|
self.assertEqual(Site.find_for_request(request), self.default_site)
|
|
|
|
def test_unrecognised_port_and_unrecognised_host_routes_to_default_site(self):
|
|
# requests with an unrecognised Host: header _and_ an unrecognised port
|
|
# should be directed to the default site
|
|
request = get_dummy_request()
|
|
request.META["HTTP_HOST"] = self.unrecognised_hostname
|
|
request.META["SERVER_PORT"] = self.unrecognised_port
|
|
with self.assertNumQueries(1):
|
|
self.assertEqual(Site.find_for_request(request), self.default_site)
|
|
|
|
def test_unrecognised_port_on_known_hostname_routes_there_if_no_ambiguity(self):
|
|
# requests on an unrecognised port should be directed to the site with
|
|
# matching hostname if there is no ambiguity
|
|
request = get_dummy_request()
|
|
request.META["HTTP_HOST"] = self.about_site.hostname
|
|
request.META["SERVER_PORT"] = self.unrecognised_port
|
|
with self.assertNumQueries(1):
|
|
self.assertEqual(Site.find_for_request(request), self.about_site)
|
|
|
|
def test_unrecognised_port_on_known_hostname_routes_to_default_site_if_ambiguity(
|
|
self,
|
|
):
|
|
# requests on an unrecognised port should be directed to the default
|
|
# site, even if their hostname (but not port) matches more than one
|
|
# other entry
|
|
request = get_dummy_request(site=self.events_site)
|
|
request.META["SERVER_PORT"] = self.unrecognised_port
|
|
with self.assertNumQueries(1):
|
|
self.assertEqual(Site.find_for_request(request), self.default_site)
|
|
|
|
def test_port_in_http_host_header_is_ignored(self):
|
|
# port in the HTTP_HOST header is ignored
|
|
request = get_dummy_request()
|
|
request.META["HTTP_HOST"] = "{}:{}".format(
|
|
self.events_site.hostname,
|
|
self.events_site.port,
|
|
)
|
|
request.META["SERVER_PORT"] = self.alternate_port_events_site.port
|
|
with self.assertNumQueries(1):
|
|
self.assertEqual(
|
|
Site.find_for_request(request), self.alternate_port_events_site
|
|
)
|
|
|
|
|
|
class TestRouting(TestCase):
|
|
fixtures = ["test.json"]
|
|
|
|
# need to clear urlresolver caches before/after tests, because we override ROOT_URLCONF
|
|
# in some tests here
|
|
def setUp(self):
|
|
from django.urls import clear_url_caches
|
|
|
|
clear_url_caches()
|
|
|
|
def tearDown(self):
|
|
from django.urls import clear_url_caches
|
|
|
|
clear_url_caches()
|
|
|
|
def test_urls(self):
|
|
default_site = Site.objects.get(is_default_site=True)
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
christmas_page = Page.objects.get(url_path="/home/events/christmas/")
|
|
|
|
# Basic installation only has one site configured, so page.url will return local URLs
|
|
self.assertEqual(
|
|
homepage.get_url_parts(), (default_site.id, "http://localhost", "/")
|
|
)
|
|
self.assertEqual(homepage.full_url, "http://localhost/")
|
|
self.assertEqual(homepage.url, "/")
|
|
self.assertEqual(homepage.relative_url(default_site), "/")
|
|
self.assertEqual(homepage.get_site(), default_site)
|
|
|
|
self.assertEqual(
|
|
christmas_page.get_url_parts(),
|
|
(default_site.id, "http://localhost", "/events/christmas/"),
|
|
)
|
|
self.assertEqual(christmas_page.full_url, "http://localhost/events/christmas/")
|
|
self.assertEqual(christmas_page.url, "/events/christmas/")
|
|
self.assertEqual(
|
|
christmas_page.relative_url(default_site), "/events/christmas/"
|
|
)
|
|
self.assertEqual(christmas_page.get_site(), default_site)
|
|
|
|
def test_page_with_no_url(self):
|
|
root = Page.objects.get(url_path="/")
|
|
default_site = Site.objects.get(is_default_site=True)
|
|
|
|
self.assertIsNone(root.get_url_parts())
|
|
self.assertIsNone(root.full_url)
|
|
self.assertIsNone(root.url)
|
|
self.assertIsNone(root.relative_url(default_site))
|
|
self.assertIsNone(root.get_site())
|
|
|
|
@override_settings(
|
|
ALLOWED_HOSTS=[
|
|
"localhost",
|
|
"testserver",
|
|
"events.example.com",
|
|
"second-events.example.com",
|
|
]
|
|
)
|
|
def test_urls_with_multiple_sites(self):
|
|
events_page = Page.objects.get(url_path="/home/events/")
|
|
events_site = Site.objects.create(
|
|
hostname="events.example.com", root_page=events_page
|
|
)
|
|
|
|
# An underscore is not valid according to RFC 1034/1035
|
|
# and will raise a DisallowedHost Exception
|
|
second_events_site = Site.objects.create(
|
|
hostname="second-events.example.com", root_page=events_page
|
|
)
|
|
|
|
default_site = Site.objects.get(is_default_site=True)
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
christmas_page = Page.objects.get(url_path="/home/events/christmas/")
|
|
|
|
# with multiple sites, page.url will return full URLs to ensure that
|
|
# they work across sites
|
|
self.assertEqual(
|
|
homepage.get_url_parts(), (default_site.id, "http://localhost", "/")
|
|
)
|
|
self.assertEqual(homepage.full_url, "http://localhost/")
|
|
self.assertEqual(homepage.url, "http://localhost/")
|
|
self.assertEqual(homepage.relative_url(default_site), "/")
|
|
self.assertEqual(homepage.relative_url(events_site), "http://localhost/")
|
|
self.assertEqual(homepage.get_site(), default_site)
|
|
|
|
self.assertEqual(
|
|
christmas_page.get_url_parts(),
|
|
(events_site.id, "http://events.example.com", "/christmas/"),
|
|
)
|
|
self.assertEqual(
|
|
christmas_page.full_url, "http://events.example.com/christmas/"
|
|
)
|
|
self.assertEqual(christmas_page.url, "http://events.example.com/christmas/")
|
|
self.assertEqual(
|
|
christmas_page.relative_url(default_site),
|
|
"http://events.example.com/christmas/",
|
|
)
|
|
self.assertEqual(christmas_page.relative_url(events_site), "/christmas/")
|
|
self.assertEqual(christmas_page.get_site(), events_site)
|
|
|
|
request = get_dummy_request(site=events_site)
|
|
|
|
self.assertEqual(
|
|
christmas_page.get_url_parts(request=request),
|
|
(events_site.id, "http://events.example.com", "/christmas/"),
|
|
)
|
|
|
|
request2 = get_dummy_request(site=second_events_site)
|
|
self.assertEqual(
|
|
christmas_page.get_url_parts(request=request2),
|
|
(second_events_site.id, "http://second-events.example.com", "/christmas/"),
|
|
)
|
|
|
|
@override_settings(ROOT_URLCONF="wagtail.test.non_root_urls")
|
|
def test_urls_with_non_root_urlconf(self):
|
|
default_site = Site.objects.get(is_default_site=True)
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
christmas_page = Page.objects.get(url_path="/home/events/christmas/")
|
|
|
|
# Basic installation only has one site configured, so page.url will return local URLs
|
|
self.assertEqual(
|
|
homepage.get_url_parts(), (default_site.id, "http://localhost", "/site/")
|
|
)
|
|
self.assertEqual(homepage.full_url, "http://localhost/site/")
|
|
self.assertEqual(homepage.url, "/site/")
|
|
self.assertEqual(homepage.relative_url(default_site), "/site/")
|
|
self.assertEqual(homepage.get_site(), default_site)
|
|
|
|
self.assertEqual(
|
|
christmas_page.get_url_parts(),
|
|
(default_site.id, "http://localhost", "/site/events/christmas/"),
|
|
)
|
|
self.assertEqual(
|
|
christmas_page.full_url, "http://localhost/site/events/christmas/"
|
|
)
|
|
self.assertEqual(christmas_page.url, "/site/events/christmas/")
|
|
self.assertEqual(
|
|
christmas_page.relative_url(default_site), "/site/events/christmas/"
|
|
)
|
|
self.assertEqual(christmas_page.get_site(), default_site)
|
|
|
|
@override_settings(ROOT_URLCONF="wagtail.test.headless_urls")
|
|
def test_urls_headless(self):
|
|
default_site = Site.objects.get(is_default_site=True)
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
|
|
# The page should not be routable because wagtail_serve is not registered
|
|
# However it is still associated with a site
|
|
self.assertEqual(homepage.get_url_parts(), (default_site.id, None, None))
|
|
self.assertIsNone(homepage.full_url)
|
|
self.assertIsNone(homepage.url)
|
|
|
|
def test_request_routing(self):
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
christmas_page = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
|
|
request = get_dummy_request(path="/events/christmas/")
|
|
(found_page, args, kwargs) = homepage.route(request, ["events", "christmas"])
|
|
self.assertEqual(found_page, christmas_page)
|
|
|
|
def test_request_serving(self):
|
|
christmas_page = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
|
|
request = get_dummy_request(site=Site.objects.first())
|
|
request.user = AnonymousUser()
|
|
|
|
response = christmas_page.serve(request)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(response.context_data["self"], christmas_page)
|
|
# confirm that the event_page.html template was used
|
|
self.assertContains(response, "<h2>Event</h2>")
|
|
|
|
def test_route_to_unknown_page_returns_404(self):
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
|
|
request = get_dummy_request(path="/events/quinquagesima/")
|
|
with self.assertRaises(Http404):
|
|
homepage.route(request, ["events", "quinquagesima"])
|
|
|
|
def test_route_to_unpublished_page_returns_404(self):
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
|
|
request = get_dummy_request(path="/events/tentative-unpublished-event/")
|
|
with self.assertRaises(Http404):
|
|
homepage.route(request, ["events", "tentative-unpublished-event"])
|
|
|
|
# Override CACHES so we don't generate any cache-related SQL queries (tests use DatabaseCache
|
|
# otherwise) and so cache.get will always return None.
|
|
@override_settings(
|
|
CACHES={"default": {"BACKEND": "django.core.cache.backends.dummy.DummyCache"}}
|
|
)
|
|
@override_settings(ALLOWED_HOSTS=["dummy"])
|
|
def test_request_scope_site_root_paths_cache(self):
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
christmas_page = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
|
|
# without a request, get_url should only issue 1 SQL query
|
|
with self.assertNumQueries(1):
|
|
self.assertEqual(homepage.get_url(), "/")
|
|
# subsequent calls with the same page should generate no SQL queries
|
|
with self.assertNumQueries(0):
|
|
self.assertEqual(homepage.get_url(), "/")
|
|
# subsequent calls with a different page will still generate 1 SQL query
|
|
with self.assertNumQueries(1):
|
|
self.assertEqual(christmas_page.get_url(), "/events/christmas/")
|
|
|
|
# with a request, the first call to get_url should issue 1 SQL query
|
|
request = get_dummy_request()
|
|
# first call with "balnk" request issues a extra query for the Site.find_for_request() call
|
|
with self.assertNumQueries(2):
|
|
self.assertEqual(homepage.get_url(request=request), "/")
|
|
# subsequent calls should issue no SQL queries
|
|
with self.assertNumQueries(0):
|
|
self.assertEqual(homepage.get_url(request=request), "/")
|
|
# even if called on a different page
|
|
with self.assertNumQueries(0):
|
|
self.assertEqual(
|
|
christmas_page.get_url(request=request), "/events/christmas/"
|
|
)
|
|
|
|
def test_cached_parent_obj_set(self):
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
christmas_page = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
|
|
request = get_dummy_request(path="/events/christmas/")
|
|
(found_page, args, kwargs) = homepage.route(request, ["events", "christmas"])
|
|
self.assertEqual(found_page, christmas_page)
|
|
|
|
# parent cache should be set
|
|
events_page = Page.objects.get(url_path="/home/events/").specific
|
|
with self.assertNumQueries(0):
|
|
parent = found_page.get_parent(update=False)
|
|
self.assertEqual(parent, events_page)
|
|
|
|
|
|
@override_settings(
|
|
ROOT_URLCONF="wagtail.test.urls_multilang",
|
|
LANGUAGE_CODE="en",
|
|
WAGTAIL_I18N_ENABLED=True,
|
|
LANGUAGES=[
|
|
("en", "English"),
|
|
("en-us", "English (United States)"),
|
|
("fr", "French"),
|
|
],
|
|
WAGTAIL_CONTENT_LANGUAGES=[("en", "English"), ("fr", "French")],
|
|
)
|
|
class TestRoutingWithI18N(TestRouting):
|
|
# This inherits from TestRouting so contains all the same test cases
|
|
# Only the test cases that behave differently under internationalisation are overridden here
|
|
|
|
def test_urls(self, expected_language_code="en"):
|
|
default_site = Site.objects.get(is_default_site=True)
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
christmas_page = Page.objects.get(url_path="/home/events/christmas/")
|
|
|
|
# Basic installation only has one site configured, so page.url will return local URLs
|
|
# self.assertEqual(
|
|
# homepage.get_url_parts(),
|
|
# (default_site.id, 'http://localhost', f'/{expected_language_code}/')
|
|
# )
|
|
self.assertEqual(
|
|
homepage.full_url, f"http://localhost/{expected_language_code}/"
|
|
)
|
|
self.assertEqual(homepage.url, f"/{expected_language_code}/")
|
|
self.assertEqual(
|
|
homepage.relative_url(default_site), f"/{expected_language_code}/"
|
|
)
|
|
self.assertEqual(homepage.get_site(), default_site)
|
|
|
|
self.assertEqual(
|
|
christmas_page.get_url_parts(),
|
|
(
|
|
default_site.id,
|
|
"http://localhost",
|
|
f"/{expected_language_code}/events/christmas/",
|
|
),
|
|
)
|
|
self.assertEqual(
|
|
christmas_page.full_url,
|
|
f"http://localhost/{expected_language_code}/events/christmas/",
|
|
)
|
|
self.assertEqual(
|
|
christmas_page.url, f"/{expected_language_code}/events/christmas/"
|
|
)
|
|
self.assertEqual(
|
|
christmas_page.relative_url(default_site),
|
|
f"/{expected_language_code}/events/christmas/",
|
|
)
|
|
self.assertEqual(christmas_page.get_site(), default_site)
|
|
|
|
def test_urls_with_translation_activated(self):
|
|
# This should have no effect as the URL is determined from the page's locale
|
|
# and not the active locale
|
|
with translation.override("fr"):
|
|
self.test_urls()
|
|
|
|
def test_urls_with_region_specific_translation_activated(self):
|
|
# One exception to the above rule is when the active locale
|
|
# is a more specific one to what the page was authored in
|
|
# and the active locale is not in WAGTAIL_CONTENT_LANGUAGES
|
|
|
|
# This is because, in this situation, the same page will be
|
|
# served under both /en/ and /en-us/ prefixes
|
|
with translation.override("en-us"):
|
|
self.test_urls(expected_language_code="en-us")
|
|
|
|
@override_settings(
|
|
WAGTAIL_CONTENT_LANGUAGES=[
|
|
("en", "English"),
|
|
("en-us", "English (United States)"),
|
|
("fr", "French"),
|
|
]
|
|
)
|
|
def test_urls_with_region_specific_translation_activated_thats_in_wagtail_content_languages(
|
|
self,
|
|
):
|
|
# But, if en-us is also a content language, then this rule doesn't apply
|
|
# because that page won't be served under /en-us/.
|
|
with translation.override("en-us"):
|
|
self.test_urls()
|
|
|
|
def test_urls_with_language_not_in_wagtail_content_languages(self):
|
|
# If the active locale doesn't map to anything in WAGTAIL_CONTENT_LANGUAGES,
|
|
# URL prefixes should remain the same as the page's reported locale
|
|
with translation.override("se"):
|
|
self.test_urls()
|
|
|
|
def test_urls_with_different_language_tree(self):
|
|
default_site = Site.objects.get(is_default_site=True)
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
christmas_page = Page.objects.get(url_path="/home/events/christmas/")
|
|
|
|
fr_locale = Locale.objects.create(language_code="fr")
|
|
fr_homepage = homepage.copy_for_translation(fr_locale)
|
|
fr_christmas_page = christmas_page.copy_for_translation(
|
|
fr_locale, copy_parents=True
|
|
)
|
|
fr_christmas_page.slug = "noel"
|
|
fr_christmas_page.save(update_fields=["slug"])
|
|
|
|
# Basic installation only has one site configured, so page.url will return local URLs
|
|
self.assertEqual(
|
|
fr_homepage.get_url_parts(), (default_site.id, "http://localhost", "/fr/")
|
|
)
|
|
self.assertEqual(fr_homepage.full_url, "http://localhost/fr/")
|
|
self.assertEqual(fr_homepage.url, "/fr/")
|
|
self.assertEqual(fr_homepage.relative_url(default_site), "/fr/")
|
|
self.assertEqual(fr_homepage.get_site(), default_site)
|
|
|
|
self.assertEqual(
|
|
fr_christmas_page.get_url_parts(),
|
|
(default_site.id, "http://localhost", "/fr/events/noel/"),
|
|
)
|
|
self.assertEqual(fr_christmas_page.full_url, "http://localhost/fr/events/noel/")
|
|
self.assertEqual(fr_christmas_page.url, "/fr/events/noel/")
|
|
self.assertEqual(
|
|
fr_christmas_page.relative_url(default_site), "/fr/events/noel/"
|
|
)
|
|
self.assertEqual(fr_christmas_page.get_site(), default_site)
|
|
|
|
@override_settings(
|
|
ALLOWED_HOSTS=[
|
|
"localhost",
|
|
"testserver",
|
|
"events.example.com",
|
|
"second-events.example.com",
|
|
]
|
|
)
|
|
def test_urls_with_multiple_sites(self):
|
|
events_page = Page.objects.get(url_path="/home/events/")
|
|
events_site = Site.objects.create(
|
|
hostname="events.example.com", root_page=events_page
|
|
)
|
|
|
|
# An underscore is not valid according to RFC 1034/1035
|
|
# and will raise a DisallowedHost Exception
|
|
second_events_site = Site.objects.create(
|
|
hostname="second-events.example.com", root_page=events_page
|
|
)
|
|
|
|
default_site = Site.objects.get(is_default_site=True)
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
christmas_page = Page.objects.get(url_path="/home/events/christmas/")
|
|
|
|
# with multiple sites, page.url will return full URLs to ensure that
|
|
# they work across sites
|
|
self.assertEqual(
|
|
homepage.get_url_parts(), (default_site.id, "http://localhost", "/en/")
|
|
)
|
|
self.assertEqual(homepage.full_url, "http://localhost/en/")
|
|
self.assertEqual(homepage.url, "http://localhost/en/")
|
|
self.assertEqual(homepage.relative_url(default_site), "/en/")
|
|
self.assertEqual(homepage.relative_url(events_site), "http://localhost/en/")
|
|
self.assertEqual(homepage.get_site(), default_site)
|
|
|
|
self.assertEqual(
|
|
christmas_page.get_url_parts(),
|
|
(events_site.id, "http://events.example.com", "/en/christmas/"),
|
|
)
|
|
self.assertEqual(
|
|
christmas_page.full_url, "http://events.example.com/en/christmas/"
|
|
)
|
|
self.assertEqual(christmas_page.url, "http://events.example.com/en/christmas/")
|
|
self.assertEqual(
|
|
christmas_page.relative_url(default_site),
|
|
"http://events.example.com/en/christmas/",
|
|
)
|
|
self.assertEqual(christmas_page.relative_url(events_site), "/en/christmas/")
|
|
self.assertEqual(christmas_page.get_site(), events_site)
|
|
|
|
request = get_dummy_request(site=events_site)
|
|
|
|
self.assertEqual(
|
|
christmas_page.get_url_parts(request=request),
|
|
(events_site.id, "http://events.example.com", "/en/christmas/"),
|
|
)
|
|
|
|
request2 = get_dummy_request(site=second_events_site)
|
|
|
|
self.assertEqual(
|
|
christmas_page.get_url_parts(request=request2),
|
|
(
|
|
second_events_site.id,
|
|
"http://second-events.example.com",
|
|
"/en/christmas/",
|
|
),
|
|
)
|
|
|
|
# Override CACHES so we don't generate any cache-related SQL queries (tests use DatabaseCache
|
|
# otherwise) and so cache.get will always return None.
|
|
@override_settings(
|
|
CACHES={"default": {"BACKEND": "django.core.cache.backends.dummy.DummyCache"}}
|
|
)
|
|
@override_settings(ALLOWED_HOSTS=["dummy"])
|
|
def test_request_scope_site_root_paths_cache(self):
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
christmas_page = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
|
|
# without a request, get_url should only issue 2 SQL queries
|
|
with self.assertNumQueries(2):
|
|
self.assertEqual(homepage.get_url(), "/en/")
|
|
# subsequent calls with the same page should generate no SQL queries
|
|
with self.assertNumQueries(0):
|
|
self.assertEqual(homepage.get_url(), "/en/")
|
|
# subsequent calls with a different page will still generate 2 SQL queries
|
|
with self.assertNumQueries(2):
|
|
self.assertEqual(christmas_page.get_url(), "/en/events/christmas/")
|
|
|
|
# with a request, the first call to get_url should issue 1 SQL query
|
|
request = get_dummy_request()
|
|
# first call with "balnk" request issues a extra query for the Site.find_for_request() call
|
|
with self.assertNumQueries(3):
|
|
self.assertEqual(homepage.get_url(request=request), "/en/")
|
|
# subsequent calls should issue no SQL queries
|
|
with self.assertNumQueries(0):
|
|
self.assertEqual(homepage.get_url(request=request), "/en/")
|
|
# even if called on a different page
|
|
with self.assertNumQueries(0):
|
|
self.assertEqual(
|
|
christmas_page.get_url(request=request), "/en/events/christmas/"
|
|
)
|
|
|
|
|
|
class TestServeView(TestCase):
|
|
fixtures = ["test.json"]
|
|
|
|
def setUp(self):
|
|
# Explicitly clear the cache of site root paths. Normally this would be kept
|
|
# in sync by the Site.save logic, but this is bypassed when the database is
|
|
# rolled back between tests using transactions.
|
|
Site.clear_site_root_paths_cache()
|
|
|
|
# also need to clear urlresolver caches before/after tests, because we override
|
|
# ROOT_URLCONF in some tests here
|
|
from django.urls import clear_url_caches
|
|
|
|
clear_url_caches()
|
|
|
|
def tearDown(self):
|
|
from django.urls import clear_url_caches
|
|
|
|
clear_url_caches()
|
|
|
|
def test_serve(self):
|
|
response = self.client.get("/events/christmas/")
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(response.templates[0].name, "tests/event_page.html")
|
|
christmas_page = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
self.assertEqual(response.context["self"], christmas_page)
|
|
|
|
self.assertContains(response, "<h1>Christmas</h1>")
|
|
self.assertContains(response, "<h2>Event</h2>")
|
|
|
|
@override_settings(ROOT_URLCONF="wagtail.test.non_root_urls")
|
|
def test_serve_with_non_root_urls(self):
|
|
response = self.client.get("/site/events/christmas/")
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(response.templates[0].name, "tests/event_page.html")
|
|
christmas_page = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
self.assertEqual(response.context["self"], christmas_page)
|
|
|
|
self.assertContains(response, "<h1>Christmas</h1>")
|
|
self.assertContains(response, "<h2>Event</h2>")
|
|
|
|
def test_serve_unknown_page_returns_404(self):
|
|
response = self.client.get("/events/quinquagesima/")
|
|
self.assertEqual(response.status_code, 404)
|
|
|
|
def test_serve_unpublished_page_returns_404(self):
|
|
response = self.client.get("/events/tentative-unpublished-event/")
|
|
self.assertEqual(response.status_code, 404)
|
|
|
|
@override_settings(ALLOWED_HOSTS=["localhost", "events.example.com"])
|
|
def test_serve_with_multiple_sites(self):
|
|
events_page = Page.objects.get(url_path="/home/events/")
|
|
Site.objects.create(hostname="events.example.com", root_page=events_page)
|
|
|
|
response = self.client.get("/christmas/", HTTP_HOST="events.example.com")
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(response.templates[0].name, "tests/event_page.html")
|
|
christmas_page = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
self.assertEqual(response.context["self"], christmas_page)
|
|
|
|
self.assertContains(response, "<h1>Christmas</h1>")
|
|
self.assertContains(response, "<h2>Event</h2>")
|
|
|
|
# same request to the default host should return a 404
|
|
c = Client()
|
|
response = c.get("/christmas/", HTTP_HOST="localhost")
|
|
self.assertEqual(response.status_code, 404)
|
|
|
|
def test_serve_with_custom_context_name(self):
|
|
EventPage.context_object_name = "event_page"
|
|
christmas_page = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
|
|
response = self.client.get("/events/christmas/")
|
|
|
|
# Context should contain context_object_name key along with standard page keys
|
|
self.assertEqual(response.context["event_page"], christmas_page)
|
|
self.assertEqual(response.context["page"], christmas_page)
|
|
self.assertEqual(response.context["self"], christmas_page)
|
|
|
|
def test_serve_with_custom_context(self):
|
|
response = self.client.get("/events/")
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
# should render the whole page
|
|
self.assertContains(response, "<h1>Events</h1>")
|
|
|
|
# response should contain data from the custom 'events' context variable
|
|
self.assertContains(response, '<a href="/events/christmas/">Christmas</a>')
|
|
|
|
def test_ajax_response(self):
|
|
response = self.client.get("/events/", HTTP_X_REQUESTED_WITH="XMLHttpRequest")
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
# should only render the content of includes/event_listing.html, not the whole page
|
|
self.assertNotContains(response, "<h1>Events</h1>")
|
|
self.assertContains(response, '<a href="/events/christmas/">Christmas</a>')
|
|
|
|
def test_before_serve_hook(self):
|
|
response = self.client.get("/events/", HTTP_USER_AGENT="GoogleBot")
|
|
self.assertContains(response, "bad googlebot no cookie")
|
|
|
|
|
|
class TestMovePage(TestCase):
|
|
fixtures = ["test.json"]
|
|
|
|
def test_move_page(self):
|
|
about_us_page = SimplePage.objects.get(url_path="/home/about-us/")
|
|
events_index = EventIndex.objects.get(url_path="/home/events/")
|
|
|
|
events_index.move(about_us_page, pos="last-child")
|
|
|
|
# re-fetch events index to confirm that db fields have been updated
|
|
events_index = EventIndex.objects.get(id=events_index.id)
|
|
self.assertEqual(events_index.url_path, "/home/about-us/events/")
|
|
self.assertEqual(events_index.depth, 4)
|
|
self.assertEqual(events_index.get_parent().id, about_us_page.id)
|
|
|
|
# children of events_index should also have been updated
|
|
christmas = events_index.get_children().get(slug="christmas")
|
|
self.assertEqual(christmas.depth, 5)
|
|
self.assertEqual(christmas.url_path, "/home/about-us/events/christmas/")
|
|
|
|
|
|
class TestPrevNextSiblings(TestCase):
|
|
fixtures = ["test.json"]
|
|
|
|
def test_get_next_siblings(self):
|
|
christmas_event = Page.objects.get(url_path="/home/events/christmas/")
|
|
self.assertTrue(
|
|
christmas_event.get_next_siblings()
|
|
.filter(url_path="/home/events/final-event/")
|
|
.exists()
|
|
)
|
|
|
|
def test_get_next_siblings_inclusive(self):
|
|
christmas_event = Page.objects.get(url_path="/home/events/christmas/")
|
|
|
|
# First element must always be the current page
|
|
self.assertEqual(
|
|
christmas_event.get_next_siblings(inclusive=True).first(), christmas_event
|
|
)
|
|
|
|
def test_get_prev_siblings(self):
|
|
final_event = Page.objects.get(url_path="/home/events/final-event/")
|
|
self.assertTrue(
|
|
final_event.get_prev_siblings()
|
|
.filter(url_path="/home/events/christmas/")
|
|
.exists()
|
|
)
|
|
|
|
# First element must always be the current page
|
|
self.assertEqual(
|
|
final_event.get_prev_siblings(inclusive=True).first(), final_event
|
|
)
|
|
|
|
|
|
class TestSaveRevision(TestCase):
|
|
fixtures = ["test.json"]
|
|
|
|
def test_raises_error_if_non_specific_page_used(self):
|
|
christmas_event = Page.objects.get(url_path="/home/events/christmas/")
|
|
|
|
with self.assertRaises(RuntimeError) as e:
|
|
christmas_event.save_revision()
|
|
|
|
self.assertEqual(
|
|
e.exception.args[0],
|
|
"page.save_revision() must be called on the specific version of the page. Call page.specific.save_revision() instead.",
|
|
)
|
|
|
|
|
|
class TestLiveRevision(TestCase):
|
|
fixtures = ["test.json"]
|
|
|
|
@freeze_time("2017-01-01 12:00:00")
|
|
def test_publish_method_will_set_live_revision(self):
|
|
page = Page.objects.get(id=2)
|
|
|
|
revision = page.save_revision()
|
|
revision.publish()
|
|
|
|
page.refresh_from_db()
|
|
self.assertEqual(page.live_revision, revision)
|
|
if settings.USE_TZ:
|
|
self.assertEqual(
|
|
page.last_published_at,
|
|
datetime.datetime(2017, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc),
|
|
)
|
|
# first_published_at should not change
|
|
self.assertEqual(
|
|
page.first_published_at,
|
|
datetime.datetime(2014, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc),
|
|
)
|
|
else:
|
|
self.assertEqual(
|
|
# interpret the "2017-01-01 12:00:00" in freeze_time above as a naive local date
|
|
page.last_published_at,
|
|
datetime.datetime(2017, 1, 1, 12, 0, 0),
|
|
)
|
|
# first_published_at should not change
|
|
self.assertEqual(
|
|
# convert the "2014-01-01T12:00:00.000Z" in the test fixture to a naive local time
|
|
page.first_published_at,
|
|
timezone.make_naive(
|
|
datetime.datetime(
|
|
2014, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc
|
|
)
|
|
),
|
|
)
|
|
|
|
@freeze_time("2017-01-01 12:00:00")
|
|
def test_unpublish_method_will_clean_live_revision(self):
|
|
page = Page.objects.get(id=2)
|
|
|
|
revision = page.save_revision()
|
|
revision.publish()
|
|
|
|
page.refresh_from_db()
|
|
|
|
page.unpublish()
|
|
|
|
page.refresh_from_db()
|
|
self.assertIsNone(page.live_revision)
|
|
# first_published_at / last_published_at should remain unchanged on unpublish
|
|
if settings.USE_TZ:
|
|
self.assertEqual(
|
|
page.first_published_at,
|
|
datetime.datetime(2014, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc),
|
|
)
|
|
self.assertEqual(
|
|
page.last_published_at,
|
|
datetime.datetime(2017, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc),
|
|
)
|
|
else:
|
|
self.assertEqual(
|
|
# convert the "2014-01-01T12:00:00.000Z" in the test fixture to a naive local time
|
|
page.first_published_at,
|
|
timezone.make_naive(
|
|
datetime.datetime(
|
|
2014, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc
|
|
)
|
|
),
|
|
)
|
|
self.assertEqual(
|
|
# interpret the "2017-01-01 12:00:00" in freeze_time above as a naive local date
|
|
page.last_published_at,
|
|
datetime.datetime(2017, 1, 1, 12, 0, 0),
|
|
)
|
|
|
|
@freeze_time("2017-01-01 12:00:00")
|
|
def test_copy_method_with_keep_live_will_update_live_revision(self):
|
|
about_us = SimplePage.objects.get(url_path="/home/about-us/")
|
|
revision = about_us.save_revision()
|
|
revision.publish()
|
|
|
|
new_about_us = about_us.copy(
|
|
keep_live=True,
|
|
update_attrs={"title": "New about us", "slug": "new-about-us"},
|
|
)
|
|
self.assertIsNotNone(new_about_us.live_revision)
|
|
self.assertNotEqual(about_us.live_revision, new_about_us.live_revision)
|
|
|
|
# first_published_at / last_published_at should reflect the current time,
|
|
# not the source page's publish dates, since the copied page is being published
|
|
# for the first time
|
|
if settings.USE_TZ:
|
|
self.assertEqual(
|
|
new_about_us.first_published_at,
|
|
datetime.datetime(2017, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc),
|
|
)
|
|
self.assertEqual(
|
|
new_about_us.last_published_at,
|
|
datetime.datetime(2017, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc),
|
|
)
|
|
else:
|
|
self.assertEqual(
|
|
new_about_us.first_published_at, datetime.datetime(2017, 1, 1, 12, 0, 0)
|
|
)
|
|
self.assertEqual(
|
|
new_about_us.last_published_at, datetime.datetime(2017, 1, 1, 12, 0, 0)
|
|
)
|
|
|
|
new_about_us.refresh_from_db()
|
|
self.assertEqual(new_about_us.title, "New about us")
|
|
self.assertEqual(new_about_us.draft_title, "New about us")
|
|
|
|
def test_copy_method_without_keep_live_will_not_update_live_revision(self):
|
|
about_us = SimplePage.objects.get(url_path="/home/about-us/")
|
|
revision = about_us.save_revision()
|
|
revision.publish()
|
|
about_us.refresh_from_db()
|
|
self.assertIsNotNone(about_us.live_revision)
|
|
|
|
new_about_us = about_us.copy(
|
|
keep_live=False,
|
|
update_attrs={"title": "New about us", "slug": "new-about-us"},
|
|
)
|
|
self.assertIsNone(new_about_us.live_revision)
|
|
# first_published_at / last_published_at should be blank, because the copied article
|
|
# has not been published
|
|
self.assertIsNone(new_about_us.first_published_at)
|
|
self.assertIsNone(new_about_us.last_published_at)
|
|
new_about_us.refresh_from_db()
|
|
self.assertEqual(new_about_us.title, "New about us")
|
|
self.assertEqual(new_about_us.draft_title, "New about us")
|
|
|
|
def test_copy_method_copies_latest_revision(self):
|
|
about_us = SimplePage.objects.get(url_path="/home/about-us/")
|
|
|
|
# make another revision
|
|
about_us.content = "We are even better than before"
|
|
about_us.save_revision()
|
|
about_us.refresh_from_db()
|
|
|
|
self.assertEqual(
|
|
about_us.get_latest_revision_as_object().content,
|
|
"We are even better than before",
|
|
)
|
|
|
|
new_about_us = about_us.copy(
|
|
keep_live=False,
|
|
update_attrs={"title": "New about us", "slug": "new-about-us"},
|
|
)
|
|
new_about_us_draft = new_about_us.get_latest_revision_as_object()
|
|
self.assertEqual(new_about_us_draft.content, "We are even better than before")
|
|
new_about_us.refresh_from_db()
|
|
self.assertEqual(new_about_us.title, "New about us")
|
|
self.assertEqual(new_about_us.draft_title, "New about us")
|
|
|
|
@freeze_time("2017-01-01 12:00:00")
|
|
def test_publish_with_future_go_live_does_not_set_live_revision(self):
|
|
about_us = SimplePage.objects.get(url_path="/home/about-us/")
|
|
if settings.USE_TZ:
|
|
about_us.go_live_at = datetime.datetime(
|
|
2018, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc
|
|
)
|
|
else:
|
|
about_us.go_live_at = datetime.datetime(2018, 1, 1, 12, 0, 0)
|
|
revision = about_us.save_revision()
|
|
revision.publish()
|
|
about_us.refresh_from_db()
|
|
|
|
self.assertFalse(about_us.live)
|
|
self.assertIsNone(about_us.live_revision)
|
|
|
|
# first_published_at / last_published_at should remain unchanged
|
|
if settings.USE_TZ:
|
|
self.assertEqual(
|
|
about_us.first_published_at,
|
|
datetime.datetime(2014, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc),
|
|
)
|
|
self.assertEqual(
|
|
about_us.last_published_at,
|
|
datetime.datetime(2014, 2, 1, 12, 0, 0, tzinfo=datetime.timezone.utc),
|
|
)
|
|
else:
|
|
self.assertEqual(
|
|
about_us.first_published_at,
|
|
timezone.make_naive(
|
|
datetime.datetime(
|
|
2014, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc
|
|
)
|
|
),
|
|
)
|
|
self.assertEqual(
|
|
about_us.last_published_at,
|
|
timezone.make_naive(
|
|
datetime.datetime(
|
|
2014, 2, 1, 12, 0, 0, tzinfo=datetime.timezone.utc
|
|
)
|
|
),
|
|
)
|
|
|
|
|
|
class TestPageGetSpecific(TestCase):
|
|
fixtures = ["test.json"]
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.page = Page.objects.get(url_path="/home/about-us/")
|
|
self.page.foo = "ABC"
|
|
self.page.bar = {"key": "value"}
|
|
self.page.baz = 999
|
|
|
|
def test_default(self):
|
|
# Field values are fetched from the database, hence the query
|
|
with self.assertNumQueries(1):
|
|
result = self.page.get_specific()
|
|
|
|
# The returned instance is the correct type
|
|
self.assertIsInstance(result, SimplePage)
|
|
|
|
# Generic page field values can be accessed for free
|
|
with self.assertNumQueries(0):
|
|
self.assertEqual(result.id, self.page.id)
|
|
self.assertEqual(result.title, self.page.title)
|
|
|
|
# Specific model fields values are available without additional queries
|
|
with self.assertNumQueries(0):
|
|
self.assertTrue(result.content)
|
|
|
|
# All non-field attributes should have been copied over...
|
|
for attr in ("foo", "bar", "baz"):
|
|
with self.subTest(attribute=attr):
|
|
self.assertIs(getattr(result, attr), getattr(self.page, attr))
|
|
|
|
def test_deferred(self):
|
|
# Field values are NOT fetched from the database, hence no query
|
|
with self.assertNumQueries(0):
|
|
result = self.page.get_specific(deferred=True)
|
|
|
|
# The returned instance is the correct type
|
|
self.assertIsInstance(result, SimplePage)
|
|
|
|
# Generic page field values can be accessed for free
|
|
with self.assertNumQueries(0):
|
|
self.assertEqual(result.id, self.page.id)
|
|
self.assertEqual(result.title, self.page.title)
|
|
|
|
# But, specific model fields values are NOT available without additional queries
|
|
with self.assertNumQueries(1):
|
|
self.assertTrue(result.content)
|
|
|
|
# All non-field attributes should have been copied over...
|
|
for attr in ("foo", "bar", "baz"):
|
|
with self.subTest(attribute=attr):
|
|
self.assertIs(getattr(result, attr), getattr(self.page, attr))
|
|
|
|
def test_copy_attrs(self):
|
|
result = self.page.get_specific(copy_attrs=["foo", "bar"])
|
|
|
|
# foo and bar should have been copied over
|
|
self.assertIs(result.foo, self.page.foo)
|
|
self.assertIs(result.bar, self.page.bar)
|
|
|
|
# but baz should not have been
|
|
self.assertFalse(hasattr(result, "baz"))
|
|
|
|
def test_copy_attrs_with_empty_list(self):
|
|
result = self.page.get_specific(copy_attrs=())
|
|
|
|
# No non-field attributes should have been copied over...
|
|
for attr in ("foo", "bar", "baz"):
|
|
with self.subTest(attribute=attr):
|
|
self.assertFalse(hasattr(result, attr))
|
|
|
|
def test_copy_attrs_exclude(self):
|
|
result = self.page.get_specific(copy_attrs_exclude=["baz"])
|
|
|
|
# foo and bar should have been copied over
|
|
self.assertIs(result.foo, self.page.foo)
|
|
self.assertIs(result.bar, self.page.bar)
|
|
|
|
# but baz should not have been
|
|
self.assertFalse(hasattr(result, "baz"))
|
|
|
|
def test_copy_attrs_exclude_with_empty_list(self):
|
|
result = self.page.get_specific(copy_attrs_exclude=())
|
|
|
|
# All non-field attributes should have been copied over...
|
|
for attr in ("foo", "bar", "baz"):
|
|
with self.subTest(attribute=attr):
|
|
self.assertIs(getattr(result, attr), getattr(self.page, attr))
|
|
|
|
def test_specific_cached_property(self):
|
|
# invoking several times to demonstrate that field values
|
|
# are fetched only once from the database, and each time the
|
|
# same object is returned
|
|
with self.assertNumQueries(1):
|
|
result = self.page.specific
|
|
result_2 = self.page.specific
|
|
result_3 = self.page.specific
|
|
self.assertIs(result, result_2)
|
|
self.assertIs(result, result_3)
|
|
|
|
self.assertIsInstance(result, SimplePage)
|
|
# Specific model fields values are available without additional queries
|
|
with self.assertNumQueries(0):
|
|
self.assertTrue(result.content)
|
|
|
|
def test_specific_deferred_cached_property(self):
|
|
# invoking several times to demonstrate that the property
|
|
# returns the same object (without any queries)
|
|
with self.assertNumQueries(0):
|
|
result = self.page.specific_deferred
|
|
result_2 = self.page.specific_deferred
|
|
result_3 = self.page.specific_deferred
|
|
self.assertIs(result, result_2)
|
|
self.assertIs(result, result_3)
|
|
|
|
self.assertIsInstance(result, SimplePage)
|
|
# Specific model fields values are not available without additional queries
|
|
with self.assertNumQueries(1):
|
|
self.assertTrue(result.content)
|
|
|
|
|
|
class TestCopyPage(TestCase):
|
|
fixtures = ["test.json"]
|
|
|
|
def test_copy_page_copies(self):
|
|
about_us = SimplePage.objects.get(url_path="/home/about-us/")
|
|
|
|
# Copy it
|
|
new_about_us = about_us.copy(
|
|
update_attrs={"title": "New about us", "slug": "new-about-us"}
|
|
)
|
|
|
|
# Check that new_about_us is correct
|
|
self.assertIsInstance(new_about_us, SimplePage)
|
|
self.assertEqual(new_about_us.title, "New about us")
|
|
self.assertEqual(new_about_us.slug, "new-about-us")
|
|
|
|
# Check that new_about_us is a different page
|
|
self.assertNotEqual(about_us.id, new_about_us.id)
|
|
|
|
# Check that the url path was updated
|
|
self.assertEqual(new_about_us.url_path, "/home/new-about-us/")
|
|
|
|
def test_copy_page_copies_child_objects(self):
|
|
christmas_event = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
|
|
# Copy it
|
|
new_christmas_event = christmas_event.copy(
|
|
update_attrs={"title": "New christmas event", "slug": "new-christmas-event"}
|
|
)
|
|
|
|
# Check that the speakers were copied
|
|
self.assertEqual(
|
|
new_christmas_event.speakers.count(), 1, "Child objects weren't copied"
|
|
)
|
|
|
|
# Check that the speakers weren't removed from old page
|
|
self.assertEqual(
|
|
christmas_event.speakers.count(),
|
|
1,
|
|
"Child objects were removed from the original page",
|
|
)
|
|
|
|
# Check that advert placements were also copied (there's a gotcha here, since the advert_placements
|
|
# relation is defined on Page, not EventPage)
|
|
self.assertEqual(
|
|
new_christmas_event.advert_placements.count(),
|
|
1,
|
|
"Child objects defined on the superclass weren't copied",
|
|
)
|
|
self.assertEqual(
|
|
christmas_event.advert_placements.count(),
|
|
1,
|
|
"Child objects defined on the superclass were removed from the original page",
|
|
)
|
|
|
|
def test_copy_page_copies_parental_relations(self):
|
|
"""Test that a page will be copied with parental many to many relations intact."""
|
|
christmas_event = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
summer_category = EventCategory.objects.create(name="Summer")
|
|
holiday_category = EventCategory.objects.create(name="Holidays")
|
|
|
|
# add parental many to many relations
|
|
christmas_event.categories = (summer_category, holiday_category)
|
|
christmas_event.save()
|
|
|
|
# Copy it
|
|
new_christmas_event = christmas_event.copy(
|
|
update_attrs={"title": "New christmas event", "slug": "new-christmas-event"}
|
|
)
|
|
|
|
# check that original eventt is untouched
|
|
self.assertEqual(
|
|
christmas_event.categories.count(),
|
|
2,
|
|
"Child objects (parental many to many) defined on the superclass were removed from the original page",
|
|
)
|
|
|
|
# check that parental many to many are copied
|
|
self.assertEqual(
|
|
new_christmas_event.categories.count(),
|
|
2,
|
|
"Child objects (parental many to many) weren't copied",
|
|
)
|
|
|
|
# check that the original and copy are related to the same categories
|
|
self.assertEqual(
|
|
new_christmas_event.categories.all().in_bulk(),
|
|
christmas_event.categories.all().in_bulk(),
|
|
)
|
|
|
|
def test_copy_page_does_not_copy_comments(self):
|
|
christmas_event = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
|
|
christmas_event.wagtail_admin_comments = [
|
|
Comment(text="test", user=christmas_event.owner)
|
|
]
|
|
christmas_event.save()
|
|
|
|
# Copy the page as in `test_copy_page_copies_child_objects()``, but using exclude_fields
|
|
# to prevent 'advert_placements' from being copied to the new version
|
|
new_christmas_event = christmas_event.copy(
|
|
update_attrs={"title": "New christmas event", "slug": "new-christmas-event"}
|
|
)
|
|
|
|
# Check that the comments weren't removed from old page
|
|
self.assertEqual(
|
|
christmas_event.wagtail_admin_comments.count(),
|
|
1,
|
|
"Comments were removed from the original page",
|
|
)
|
|
|
|
# Check that comments were NOT copied over
|
|
self.assertFalse(
|
|
new_christmas_event.wagtail_admin_comments.exists(),
|
|
msg="Comments were copied",
|
|
)
|
|
|
|
def test_copy_page_does_not_copy_child_objects_if_accessor_name_in_exclude_fields(
|
|
self,
|
|
):
|
|
christmas_event = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
|
|
# Copy the page as in `test_copy_page_copies_child_objects()``, but using exclude_fields
|
|
# to prevent 'advert_placements' from being copied to the new version
|
|
new_christmas_event = christmas_event.copy(
|
|
update_attrs={
|
|
"title": "New christmas event",
|
|
"slug": "new-christmas-event",
|
|
},
|
|
exclude_fields=["advert_placements"],
|
|
)
|
|
|
|
# Check that the speakers were copied
|
|
self.assertEqual(
|
|
new_christmas_event.speakers.count(), 1, "Child objects weren't copied"
|
|
)
|
|
|
|
# Check that the speakers weren't removed from old page
|
|
self.assertEqual(
|
|
christmas_event.speakers.count(),
|
|
1,
|
|
"Child objects were removed from the original page",
|
|
)
|
|
|
|
# Check that advert placements were NOT copied over, but were not removed from the old page
|
|
self.assertFalse(
|
|
new_christmas_event.advert_placements.exists(),
|
|
msg="Child objects were copied despite accessor_name being specified in `exclude_fields`",
|
|
)
|
|
self.assertEqual(
|
|
christmas_event.advert_placements.count(),
|
|
1,
|
|
"Child objects defined on the superclass were removed from the original page",
|
|
)
|
|
|
|
def test_copy_page_with_process_child_object_supplied(self):
|
|
# We'll provide this when copying and test that it gets called twice:
|
|
# Once for the single speaker, and another for the single advert_placement
|
|
modify_child = Mock()
|
|
|
|
old_event = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
|
|
# Create a child event
|
|
child_event = old_event.copy(
|
|
update_attrs={
|
|
"title": "Child christmas event",
|
|
"slug": "child-christmas-event",
|
|
}
|
|
)
|
|
child_event.move(old_event, pos="last-child")
|
|
|
|
new_event = old_event.copy(
|
|
update_attrs={
|
|
"title": "New christmas event",
|
|
"slug": "new-christmas-event",
|
|
},
|
|
process_child_object=modify_child,
|
|
recursive=True,
|
|
)
|
|
|
|
# The method should have been called with these arguments when copying
|
|
# the advert placement
|
|
relationship = EventPage._meta.get_field("advert_placements")
|
|
child_object = new_event.advert_placements.get()
|
|
modify_child.assert_any_call(old_event, new_event, relationship, child_object)
|
|
|
|
# And again with these arguments when copying the speaker
|
|
relationship = EventPage._meta.get_field("speaker")
|
|
child_object = new_event.speakers.get()
|
|
modify_child.assert_any_call(old_event, new_event, relationship, child_object)
|
|
|
|
# Check that process_child_object was run on the child event page as well
|
|
new_child_event = new_event.get_children().get().specific
|
|
child_object = new_child_event.speakers.get()
|
|
modify_child.assert_any_call(
|
|
child_event, new_child_event, relationship, child_object
|
|
)
|
|
|
|
def test_copy_page_copies_revisions(self):
|
|
christmas_event = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
christmas_event.save_revision()
|
|
|
|
# Copy it
|
|
new_christmas_event = christmas_event.copy(
|
|
update_attrs={"title": "New christmas event", "slug": "new-christmas-event"}
|
|
)
|
|
|
|
# Check that the revisions were copied
|
|
# Copying creates a new revision so we're expecting the new page to have two revisions
|
|
self.assertEqual(new_christmas_event.revisions.count(), 2)
|
|
|
|
# Check that the revisions weren't removed from old page
|
|
self.assertEqual(
|
|
christmas_event.revisions.count(),
|
|
1,
|
|
"Revisions were removed from the original page",
|
|
)
|
|
|
|
# Check that the attributes were updated in the latest revision
|
|
latest_revision = new_christmas_event.get_latest_revision_as_object()
|
|
self.assertEqual(latest_revision.title, "New christmas event")
|
|
self.assertEqual(latest_revision.slug, "new-christmas-event")
|
|
|
|
# get_latest_revision_as_object might bypass the revisions table if it determines
|
|
# that there are no draft edits since publish - so retrieve it explicitly from the
|
|
# revision data, to ensure it's been updated there too
|
|
latest_revision = new_christmas_event.get_latest_revision().as_object()
|
|
self.assertEqual(latest_revision.title, "New christmas event")
|
|
self.assertEqual(latest_revision.slug, "new-christmas-event")
|
|
|
|
# Check that the ids within the revision were updated correctly
|
|
new_revision = new_christmas_event.revisions.first()
|
|
new_revision_content = new_revision.content
|
|
self.assertEqual(new_revision_content["pk"], new_christmas_event.id)
|
|
self.assertEqual(
|
|
new_revision_content["speakers"][0]["page"], new_christmas_event.id
|
|
)
|
|
|
|
# Also, check that the child objects in the new revision are given new IDs
|
|
old_speakers_ids = set(christmas_event.speakers.values_list("id", flat=True))
|
|
new_speakers_ids = {
|
|
speaker["pk"] for speaker in new_revision_content["speakers"]
|
|
}
|
|
self.assertFalse(
|
|
old_speakers_ids.intersection(new_speakers_ids),
|
|
msg="Child objects in revisions were not given a new primary key",
|
|
)
|
|
|
|
def test_copy_page_copies_revisions_and_doesnt_change_created_at(self):
|
|
christmas_event = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
christmas_event.save_revision()
|
|
|
|
# Set the created_at of the revision to a time in the past
|
|
revision = christmas_event.get_latest_revision()
|
|
revision.created_at = datetime.datetime(
|
|
2014, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc
|
|
)
|
|
revision.save()
|
|
|
|
# Copy it
|
|
new_christmas_event = christmas_event.copy(
|
|
update_attrs={"title": "New christmas event", "slug": "new-christmas-event"}
|
|
)
|
|
|
|
# Check that the created_at time is the same
|
|
christmas_event_created_at = (
|
|
christmas_event.revisions.order_by("created_at").first().created_at
|
|
)
|
|
new_christmas_event_created_at = (
|
|
new_christmas_event.revisions.order_by("created_at").first().created_at
|
|
)
|
|
self.assertEqual(christmas_event_created_at, new_christmas_event_created_at)
|
|
|
|
def test_copy_page_copies_revisions_and_doesnt_schedule(self):
|
|
christmas_event = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
if settings.USE_TZ:
|
|
christmas_event.save_revision(
|
|
approved_go_live_at=datetime.datetime(
|
|
2014, 9, 16, 9, 12, 00, tzinfo=datetime.timezone.utc
|
|
)
|
|
)
|
|
else:
|
|
christmas_event.save_revision(
|
|
approved_go_live_at=datetime.datetime(2014, 9, 16, 9, 12, 00)
|
|
)
|
|
|
|
# Copy it
|
|
new_christmas_event = christmas_event.copy(
|
|
update_attrs={"title": "New christmas event", "slug": "new-christmas-event"}
|
|
)
|
|
|
|
# Check that the old revision is still scheduled
|
|
if settings.USE_TZ:
|
|
self.assertEqual(
|
|
christmas_event.revisions.order_by("created_at")
|
|
.first()
|
|
.approved_go_live_at,
|
|
datetime.datetime(2014, 9, 16, 9, 12, 00, tzinfo=datetime.timezone.utc),
|
|
)
|
|
else:
|
|
self.assertEqual(
|
|
christmas_event.revisions.order_by("created_at")
|
|
.first()
|
|
.approved_go_live_at,
|
|
datetime.datetime(2014, 9, 16, 9, 12, 00),
|
|
)
|
|
|
|
# Check that the new revision is not scheduled
|
|
self.assertIsNone(
|
|
new_christmas_event.revisions.order_by("created_at")
|
|
.first()
|
|
.approved_go_live_at
|
|
)
|
|
|
|
def test_copy_page_doesnt_copy_revisions_if_told_not_to_do_so(self):
|
|
christmas_event = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
christmas_event.save_revision()
|
|
|
|
# Copy it
|
|
new_christmas_event = christmas_event.copy(
|
|
update_attrs={
|
|
"title": "New christmas event",
|
|
"slug": "new-christmas-event",
|
|
},
|
|
copy_revisions=False,
|
|
)
|
|
|
|
# Check that the revisions weren't copied
|
|
# Copying creates a new revision so we're expecting the new page to have one revision
|
|
self.assertEqual(new_christmas_event.revisions.count(), 1)
|
|
|
|
# Check that the revisions weren't removed from old page
|
|
self.assertEqual(
|
|
christmas_event.revisions.count(),
|
|
1,
|
|
"Revisions were removed from the original page",
|
|
)
|
|
|
|
def test_copy_page_copies_child_objects_with_nonspecific_class(self):
|
|
# Get chrismas page as Page instead of EventPage
|
|
christmas_event = Page.objects.get(url_path="/home/events/christmas/")
|
|
|
|
# Copy it
|
|
new_christmas_event = christmas_event.copy(
|
|
update_attrs={"title": "New christmas event", "slug": "new-christmas-event"}
|
|
)
|
|
|
|
# Check that the type of the new page is correct
|
|
self.assertIsInstance(new_christmas_event, EventPage)
|
|
|
|
# Check that the speakers were copied
|
|
self.assertEqual(
|
|
new_christmas_event.speakers.count(), 1, "Child objects weren't copied"
|
|
)
|
|
|
|
def test_copy_page_copies_recursively(self):
|
|
events_index = EventIndex.objects.get(url_path="/home/events/")
|
|
|
|
# Copy it
|
|
new_events_index = events_index.copy(
|
|
recursive=True,
|
|
update_attrs={"title": "New events index", "slug": "new-events-index"},
|
|
)
|
|
|
|
# Get christmas event
|
|
old_christmas_event = (
|
|
events_index.get_children().filter(slug="christmas").first()
|
|
)
|
|
new_christmas_event = (
|
|
new_events_index.get_children().filter(slug="christmas").first()
|
|
)
|
|
|
|
# Check that the event exists in both places
|
|
self.assertIsNotNone(new_christmas_event, "Child pages weren't copied")
|
|
self.assertIsNotNone(
|
|
old_christmas_event, "Child pages were removed from original page"
|
|
)
|
|
|
|
# Check that the url path was updated
|
|
self.assertEqual(
|
|
new_christmas_event.url_path, "/home/new-events-index/christmas/"
|
|
)
|
|
|
|
def test_copy_page_copies_recursively_with_child_objects(self):
|
|
events_index = EventIndex.objects.get(url_path="/home/events/")
|
|
|
|
# Copy it
|
|
new_events_index = events_index.copy(
|
|
recursive=True,
|
|
update_attrs={"title": "New events index", "slug": "new-events-index"},
|
|
)
|
|
|
|
# Get christmas event
|
|
old_christmas_event = (
|
|
events_index.get_children().filter(slug="christmas").first()
|
|
)
|
|
new_christmas_event = (
|
|
new_events_index.get_children().filter(slug="christmas").first()
|
|
)
|
|
|
|
# Check that the speakers were copied
|
|
self.assertEqual(
|
|
new_christmas_event.specific.speakers.count(),
|
|
1,
|
|
"Child objects weren't copied",
|
|
)
|
|
|
|
# Check that the speakers weren't removed from old page
|
|
self.assertEqual(
|
|
old_christmas_event.specific.speakers.count(),
|
|
1,
|
|
"Child objects were removed from the original page",
|
|
)
|
|
|
|
def test_copy_page_copies_recursively_with_revisions(self):
|
|
events_index = EventIndex.objects.get(url_path="/home/events/")
|
|
old_christmas_event = (
|
|
events_index.get_children().filter(slug="christmas").first().specific
|
|
)
|
|
old_christmas_event.save_revision()
|
|
|
|
# Copy it
|
|
new_events_index = events_index.copy(
|
|
recursive=True,
|
|
update_attrs={"title": "New events index", "slug": "new-events-index"},
|
|
)
|
|
|
|
# Get christmas event
|
|
new_christmas_event = (
|
|
new_events_index.get_children().filter(slug="christmas").first()
|
|
)
|
|
|
|
# Check that the revisions were copied
|
|
# Copying creates a new revision so we're expecting the new page to have two revisions
|
|
self.assertEqual(new_christmas_event.specific.revisions.count(), 2)
|
|
|
|
# Check that the revisions weren't removed from old page
|
|
self.assertEqual(
|
|
old_christmas_event.specific.revisions.count(),
|
|
1,
|
|
"Revisions were removed from the original page",
|
|
)
|
|
|
|
def test_copy_page_copies_recursively_but_doesnt_copy_revisions_if_told_not_to_do_so(
|
|
self,
|
|
):
|
|
events_index = EventIndex.objects.get(url_path="/home/events/")
|
|
old_christmas_event = (
|
|
events_index.get_children().filter(slug="christmas").first()
|
|
)
|
|
old_christmas_event.specific.save_revision()
|
|
|
|
# Copy it
|
|
new_events_index = events_index.copy(
|
|
recursive=True,
|
|
update_attrs={"title": "New events index", "slug": "new-events-index"},
|
|
copy_revisions=False,
|
|
)
|
|
|
|
# Get christmas event
|
|
new_christmas_event = (
|
|
new_events_index.get_children().filter(slug="christmas").first()
|
|
)
|
|
|
|
# Check that the revisions weren't copied
|
|
# Copying creates a new revision so we're expecting the new page to have one revision
|
|
self.assertEqual(new_christmas_event.specific.revisions.count(), 1)
|
|
|
|
# Check that the revisions weren't removed from old page
|
|
self.assertEqual(
|
|
old_christmas_event.specific.revisions.count(),
|
|
1,
|
|
"Revisions were removed from the original page",
|
|
)
|
|
|
|
def test_copy_page_copies_recursively_to_the_same_tree(self):
|
|
events_index = EventIndex.objects.get(url_path="/home/events/")
|
|
old_christmas_event = (
|
|
events_index.get_children().filter(slug="christmas").first().specific
|
|
)
|
|
old_christmas_event.save_revision()
|
|
|
|
with self.assertRaises(Exception) as exception:
|
|
events_index.copy(
|
|
recursive=True,
|
|
update_attrs={"title": "New events index", "slug": "new-events-index"},
|
|
to=events_index,
|
|
)
|
|
self.assertEqual(
|
|
str(exception.exception),
|
|
"You cannot copy a tree branch recursively into itself",
|
|
)
|
|
|
|
def test_copy_page_updates_user(self):
|
|
event_moderator = get_user_model().objects.get(
|
|
email="eventmoderator@example.com"
|
|
)
|
|
christmas_event = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
christmas_event.save_revision()
|
|
|
|
# Copy it
|
|
new_christmas_event = christmas_event.copy(
|
|
update_attrs={
|
|
"title": "New christmas event",
|
|
"slug": "new-christmas-event",
|
|
},
|
|
user=event_moderator,
|
|
)
|
|
|
|
# Check that the owner has been updated
|
|
self.assertEqual(new_christmas_event.owner, event_moderator)
|
|
|
|
# Check that the user on the last revision is correct
|
|
self.assertEqual(
|
|
new_christmas_event.get_latest_revision().user, event_moderator
|
|
)
|
|
|
|
def test_copy_multi_table_inheritance(self):
|
|
saint_patrick_event = SingleEventPage.objects.get(
|
|
url_path="/home/events/saint-patrick/"
|
|
)
|
|
|
|
# Copy it
|
|
new_saint_patrick_event = saint_patrick_event.copy(
|
|
update_attrs={"slug": "new-saint-patrick"}
|
|
)
|
|
|
|
# Check that new_saint_patrick_event is correct
|
|
self.assertIsInstance(new_saint_patrick_event, SingleEventPage)
|
|
self.assertEqual(new_saint_patrick_event.excerpt, saint_patrick_event.excerpt)
|
|
|
|
# Check that new_saint_patrick_event is a different page, including parents from both EventPage and Page
|
|
self.assertNotEqual(saint_patrick_event.id, new_saint_patrick_event.id)
|
|
self.assertNotEqual(
|
|
saint_patrick_event.eventpage_ptr.id,
|
|
new_saint_patrick_event.eventpage_ptr.id,
|
|
)
|
|
self.assertNotEqual(
|
|
saint_patrick_event.eventpage_ptr.page_ptr.id,
|
|
new_saint_patrick_event.eventpage_ptr.page_ptr.id,
|
|
)
|
|
|
|
# Check that the url path was updated
|
|
self.assertEqual(
|
|
new_saint_patrick_event.url_path, "/home/events/new-saint-patrick/"
|
|
)
|
|
|
|
# Check that both parent instance exists
|
|
self.assertIsInstance(
|
|
EventPage.objects.get(id=new_saint_patrick_event.id), EventPage
|
|
)
|
|
self.assertIsInstance(Page.objects.get(id=new_saint_patrick_event.id), Page)
|
|
|
|
def test_copy_page_copies_tags(self):
|
|
# create and publish a TaggedPage under Events
|
|
event_index = Page.objects.get(url_path="/home/events/")
|
|
tagged_page = TaggedPage(title="My tagged page", slug="my-tagged-page")
|
|
tagged_page.tags.add("wagtail", "bird")
|
|
event_index.add_child(instance=tagged_page)
|
|
tagged_page.save_revision().publish()
|
|
|
|
old_tagged_item_ids = [item.id for item in tagged_page.tagged_items.all()]
|
|
# there should be two items here, with defined (truthy) IDs
|
|
self.assertEqual(len(old_tagged_item_ids), 2)
|
|
self.assertTrue(all(old_tagged_item_ids))
|
|
|
|
# copy to underneath homepage
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
new_tagged_page = tagged_page.copy(to=homepage)
|
|
|
|
self.assertNotEqual(tagged_page.id, new_tagged_page.id)
|
|
|
|
# new page should also have two tags
|
|
new_tagged_item_ids = [item.id for item in new_tagged_page.tagged_items.all()]
|
|
self.assertEqual(len(new_tagged_item_ids), 2)
|
|
self.assertTrue(all(new_tagged_item_ids))
|
|
|
|
# new tagged_item IDs should differ from old ones
|
|
self.assertTrue(
|
|
all(item_id not in old_tagged_item_ids for item_id in new_tagged_item_ids)
|
|
)
|
|
|
|
def test_copy_subclassed_page_copies_tags(self):
|
|
# create and publish a TaggedGrandchildPage under Events
|
|
event_index = Page.objects.get(url_path="/home/events/")
|
|
sub_tagged_page = TaggedGrandchildPage(
|
|
title="My very special tagged page", slug="my-special-tagged-page"
|
|
)
|
|
sub_tagged_page.tags.add("wagtail", "bird")
|
|
event_index.add_child(instance=sub_tagged_page)
|
|
sub_tagged_page.save_revision().publish()
|
|
|
|
old_tagged_item_ids = [item.id for item in sub_tagged_page.tagged_items.all()]
|
|
# there should be two items here, with defined (truthy) IDs
|
|
self.assertEqual(len(old_tagged_item_ids), 2)
|
|
self.assertTrue(all(old_tagged_item_ids))
|
|
|
|
# copy to underneath homepage
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
new_sub_tagged_page = sub_tagged_page.copy(to=homepage)
|
|
|
|
self.assertNotEqual(sub_tagged_page.id, new_sub_tagged_page.id)
|
|
|
|
# new page should also have two tags
|
|
new_tagged_item_ids = [
|
|
item.id for item in new_sub_tagged_page.tagged_items.all()
|
|
]
|
|
self.assertEqual(len(new_tagged_item_ids), 2)
|
|
self.assertTrue(all(new_tagged_item_ids))
|
|
|
|
# new tagged_item IDs should differ from old ones
|
|
self.assertTrue(
|
|
all(item_id not in old_tagged_item_ids for item_id in new_tagged_item_ids)
|
|
)
|
|
|
|
def test_copy_page_with_m2m_relations(self):
|
|
# create and publish a ManyToManyBlogPage under Events
|
|
event_index = Page.objects.get(url_path="/home/events/")
|
|
category = BlogCategory.objects.create(name="Birds")
|
|
advert = Advert.objects.create(
|
|
url="http://www.heinz.com/", text="beanz meanz heinz"
|
|
)
|
|
|
|
blog_page = ManyToManyBlogPage(title="My blog page", slug="my-blog-page")
|
|
event_index.add_child(instance=blog_page)
|
|
|
|
blog_page.adverts.add(advert)
|
|
BlogCategoryBlogPage.objects.create(category=category, page=blog_page)
|
|
blog_page.save_revision().publish()
|
|
|
|
# copy to underneath homepage
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
new_blog_page = blog_page.copy(to=homepage)
|
|
|
|
# M2M relations are not formally supported, so for now we're only interested in
|
|
# the copy operation as a whole succeeding, rather than the child objects being copied
|
|
self.assertNotEqual(blog_page.id, new_blog_page.id)
|
|
|
|
def test_copy_page_with_generic_foreign_key(self):
|
|
# create and publish a GenericSnippetPage under Events
|
|
event_index = Page.objects.get(url_path="/home/events/")
|
|
advert = Advert.objects.create(
|
|
url="http://www.heinz.com/", text="beanz meanz heinz"
|
|
)
|
|
|
|
page = GenericSnippetPage(title="My snippet page", slug="my-snippet-page")
|
|
page.snippet_content_object = advert
|
|
event_index.add_child(instance=page)
|
|
|
|
page.save_revision().publish()
|
|
|
|
# copy to underneath homepage
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
new_page = page.copy(to=homepage)
|
|
|
|
self.assertNotEqual(page.id, new_page.id)
|
|
self.assertEqual(new_page.snippet_content_object, advert)
|
|
|
|
def test_copy_page_with_o2o_relation(self):
|
|
event_index = Page.objects.get(url_path="/home/events/")
|
|
|
|
page = OneToOnePage(title="My page", slug="my-page")
|
|
|
|
event_index.add_child(instance=page)
|
|
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
new_page = page.copy(to=homepage)
|
|
|
|
self.assertNotEqual(page.id, new_page.id)
|
|
|
|
def test_copy_page_with_additional_excluded_fields(self):
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
page = homepage.add_child(
|
|
instance=PageWithExcludedCopyField(
|
|
title="Discovery",
|
|
slug="disco",
|
|
content="NCC-1031",
|
|
special_field="Context is for Kings",
|
|
)
|
|
)
|
|
page.save_revision()
|
|
new_page = page.copy(to=homepage, update_attrs={"slug": "disco-2"})
|
|
exclude_field = new_page.latest_revision.content["special_field"]
|
|
|
|
self.assertEqual(page.title, new_page.title)
|
|
self.assertNotEqual(page.id, new_page.id)
|
|
self.assertNotEqual(page.path, new_page.path)
|
|
# special_field is in the list to be excluded
|
|
self.assertNotEqual(page.special_field, new_page.special_field)
|
|
self.assertEqual(new_page.special_field, exclude_field)
|
|
|
|
def test_page_with_generic_relation(self):
|
|
"""Test that a page with a GenericRelation will have that relation ignored when
|
|
copying.
|
|
"""
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
original_page = homepage.add_child(
|
|
instance=PageWithGenericRelation(
|
|
title="PageWithGenericRelation",
|
|
slug="page-with-generic-relation",
|
|
live=True,
|
|
has_unpublished_changes=False,
|
|
)
|
|
)
|
|
RelatedGenericRelation.objects.create(content_object=original_page)
|
|
self.assertIsNotNone(original_page.generic_relation.first())
|
|
page_copy = original_page.copy(
|
|
to=homepage, update_attrs={"slug": f"{original_page.slug}-2"}
|
|
)
|
|
self.assertIsNone(page_copy.generic_relation.first())
|
|
|
|
def test_copy_page_with_excluded_parental_and_child_relations(self):
|
|
"""Test that a page will be copied with parental and child relations removed if excluded."""
|
|
|
|
try:
|
|
# modify excluded fields for this test
|
|
EventPage.exclude_fields_in_copy = [
|
|
"advert_placements",
|
|
"categories",
|
|
"signup_link",
|
|
]
|
|
|
|
# set up data
|
|
christmas_event = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
summer_category = EventCategory.objects.create(name="Summer")
|
|
holiday_category = EventCategory.objects.create(name="Holidays")
|
|
|
|
# add URL (to test excluding a basic field)
|
|
christmas_event.signup_link = "https://christmas-is-awesome.com/rsvp"
|
|
|
|
# add parental many to many relations
|
|
christmas_event.categories = (summer_category, holiday_category)
|
|
christmas_event.save()
|
|
|
|
# Copy it
|
|
new_christmas_event = christmas_event.copy(
|
|
update_attrs={
|
|
"title": "New christmas event",
|
|
"slug": "new-christmas-event",
|
|
}
|
|
)
|
|
|
|
# check that the signup_link was NOT copied
|
|
self.assertEqual(
|
|
christmas_event.signup_link, "https://christmas-is-awesome.com/rsvp"
|
|
)
|
|
self.assertEqual(new_christmas_event.signup_link, "")
|
|
|
|
# check that original event is untouched
|
|
self.assertEqual(
|
|
christmas_event.categories.count(),
|
|
2,
|
|
"Child objects (parental many to many) defined on the superclass were removed from the original page",
|
|
)
|
|
|
|
# check that parental many to many are NOT copied
|
|
self.assertEqual(
|
|
new_christmas_event.categories.count(),
|
|
0,
|
|
"Child objects (parental many to many) were copied but should be excluded",
|
|
)
|
|
|
|
# check that child objects on original event were left untouched
|
|
self.assertEqual(
|
|
christmas_event.advert_placements.count(),
|
|
1,
|
|
"Child objects defined on the original superclass were edited when copied",
|
|
)
|
|
|
|
# check that child objects were NOT copied
|
|
self.assertEqual(
|
|
new_christmas_event.advert_placements.count(),
|
|
0,
|
|
"Child objects defined on the superclass were copied and should not be",
|
|
)
|
|
|
|
finally:
|
|
# reset excluded fields for future tests
|
|
EventPage.exclude_fields_in_copy = []
|
|
|
|
def test_copy_unsaved_page(self):
|
|
"""Test that unsaved page will not be copied."""
|
|
new_page = SimplePage(slug="testpurp", title="testpurpose")
|
|
with self.assertRaises(RuntimeError):
|
|
new_page.copy()
|
|
|
|
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.
|
|
"""
|
|
christmas_page = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
christmas_page.speakers.add(
|
|
EventPageSpeaker(
|
|
first_name="Santa",
|
|
last_name="Claus",
|
|
)
|
|
)
|
|
christmas_page.save()
|
|
# ensure there's a revision (which should capture the new speaker orderables)
|
|
christmas_page.save_revision().publish()
|
|
|
|
new_page = christmas_page.copy(
|
|
update_attrs={
|
|
"title": "Orderable Page",
|
|
"slug": "translated-orderable-page",
|
|
},
|
|
)
|
|
new_page.save_revision().publish()
|
|
self.assertNotEqual(
|
|
christmas_page.speakers.first().translation_key,
|
|
new_page.speakers.first().translation_key,
|
|
)
|
|
|
|
def test_copy_published_emits_signal(self):
|
|
"""Test that copying of a published page emits a page_published signal."""
|
|
christmas_page = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
|
|
signal_fired = False
|
|
signal_page = None
|
|
|
|
def page_published_handler(sender, instance, **kwargs):
|
|
nonlocal signal_fired
|
|
nonlocal signal_page
|
|
signal_fired = True
|
|
signal_page = instance
|
|
|
|
page_published.connect(page_published_handler)
|
|
try:
|
|
copy_page = christmas_page.copy(
|
|
update_attrs={"title": "New christmas", "slug": "new-christmas"},
|
|
)
|
|
|
|
self.assertTrue(signal_fired)
|
|
self.assertEqual(signal_page, copy_page)
|
|
finally:
|
|
page_published.disconnect(page_published_handler)
|
|
|
|
def test_copy_unpublished_not_emits_signal(self):
|
|
"""Test that copying of an unpublished page not emits a page_published signal."""
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
homepage.live = False
|
|
homepage.save()
|
|
|
|
signal_fired = False
|
|
|
|
def page_published_handler(sender, instance, **kwargs):
|
|
nonlocal signal_fired
|
|
signal_fired = True
|
|
|
|
page_published.connect(page_published_handler)
|
|
|
|
try:
|
|
homepage.copy(update_attrs={"slug": "new_slug"})
|
|
self.assertFalse(signal_fired)
|
|
finally:
|
|
page_published.disconnect(page_published_handler)
|
|
|
|
def test_copy_keep_live_false_not_emits_signal(self):
|
|
"""Test that copying of a live page with keep_live=False not emits a page_published signal."""
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
signal_fired = False
|
|
|
|
def page_published_handler(sender, instance, **kwargs):
|
|
nonlocal signal_fired
|
|
signal_fired = True
|
|
|
|
try:
|
|
page_published.connect(page_published_handler)
|
|
|
|
homepage.copy(keep_live=False, update_attrs={"slug": "new_slug"})
|
|
self.assertFalse(signal_fired)
|
|
finally:
|
|
page_published.disconnect(page_published_handler)
|
|
|
|
def test_copy_alias_page(self):
|
|
about_us = SimplePage.objects.get(url_path="/home/about-us/")
|
|
about_us_alias = about_us.create_alias(update_slug="about-us-alias")
|
|
|
|
about_us_alias_copy = about_us_alias.copy(
|
|
update_attrs={"slug": "about-us-alias-copy"}
|
|
)
|
|
|
|
self.assertIsInstance(about_us_alias_copy, SimplePage)
|
|
self.assertEqual(about_us_alias_copy.slug, "about-us-alias-copy")
|
|
self.assertNotEqual(about_us_alias_copy.id, about_us.id)
|
|
self.assertEqual(about_us_alias_copy.url_path, "/home/about-us-alias-copy/")
|
|
|
|
# The copy should just be a copy of the original page, not an alias
|
|
self.assertIsNone(about_us_alias_copy.alias_of)
|
|
|
|
def test_copy_page_copies_restriction(self):
|
|
"""Test that view restrictions attached to a page are copied along with the page"""
|
|
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
child_page_1 = SimplePage(
|
|
title="Child Page 1", slug="child-page-1", content="hello child page 1"
|
|
)
|
|
homepage.add_child(instance=child_page_1)
|
|
|
|
# Add PageViewRestriction to child_page_1
|
|
PageViewRestriction.objects.create(page=child_page_1, password="hello")
|
|
|
|
child_page_2 = child_page_1.copy(
|
|
update_attrs={"title": "Child Page 2", "slug": "child-page-2"}
|
|
)
|
|
|
|
# check that the copied page child_page_2 has a view restriction
|
|
self.assertTrue(PageViewRestriction.objects.filter(page=child_page_2).exists())
|
|
|
|
def test_copy_page_does_not_copy_restrictions_from_parent(self):
|
|
"""Test that view restrictions on a page's ancestor are NOT copied along with the page"""
|
|
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
|
|
origin_parent = SimplePage(
|
|
title="Parent 1", slug="parent-1", content="hello parent 1"
|
|
)
|
|
homepage.add_child(instance=origin_parent)
|
|
PageViewRestriction.objects.create(page=origin_parent, password="hello")
|
|
|
|
destination_parent = SimplePage(
|
|
title="Parent 2", slug="parent-2", content="hello parent 2"
|
|
)
|
|
homepage.add_child(instance=destination_parent)
|
|
|
|
child_page_1 = SimplePage(
|
|
title="Child Page 1", slug="child-page-1", content="hello child page 1"
|
|
)
|
|
origin_parent.add_child(instance=child_page_1)
|
|
|
|
child_page_2 = child_page_1.copy(
|
|
to=destination_parent,
|
|
update_attrs={"title": "Child Page 2", "slug": "child-page-2"},
|
|
)
|
|
# check that the copied page child_page_2 does not have a view restriction
|
|
self.assertFalse(PageViewRestriction.objects.filter(page=child_page_2).exists())
|
|
|
|
def test_copy_page_does_not_copy_restrictions_when_new_parent_has_one_already(self):
|
|
"""Test that view restrictions on a page's ancestor are NOT copied along with the page"""
|
|
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
|
|
origin_parent = SimplePage(
|
|
title="Parent 1", slug="parent-1", content="hello parent 1"
|
|
)
|
|
homepage.add_child(instance=origin_parent)
|
|
|
|
destination_parent = SimplePage(
|
|
title="Parent 2", slug="parent-2", content="hello parent 2"
|
|
)
|
|
homepage.add_child(instance=destination_parent)
|
|
PageViewRestriction.objects.create(page=destination_parent, password="hello")
|
|
|
|
child_page_1 = SimplePage(
|
|
title="Child Page 1", slug="child-page-1", content="hello child page 1"
|
|
)
|
|
origin_parent.add_child(instance=child_page_1)
|
|
PageViewRestriction.objects.create(page=child_page_1, password="hello")
|
|
|
|
child_page_2 = child_page_1.copy(
|
|
to=destination_parent,
|
|
update_attrs={"title": "Child Page 2", "slug": "child-page-2"},
|
|
)
|
|
# check that the copied page child_page_2 does not have a view restriction
|
|
self.assertFalse(PageViewRestriction.objects.filter(page=child_page_2).exists())
|
|
|
|
|
|
class TestCreateAlias(TestCase):
|
|
fixtures = ["test.json"]
|
|
|
|
def test_create_alias(self):
|
|
about_us = SimplePage.objects.get(url_path="/home/about-us/")
|
|
|
|
# Set a different draft title, aliases are not supposed to
|
|
# have a different draft_title because they don't have revisions.
|
|
# This should be corrected when copying
|
|
about_us.draft_title = "Draft title"
|
|
about_us.save(update_fields=["draft_title"])
|
|
|
|
# Copy it
|
|
new_about_us = about_us.create_alias(update_slug="new-about-us")
|
|
|
|
# Check that new_about_us is correct
|
|
self.assertIsInstance(new_about_us, SimplePage)
|
|
self.assertEqual(new_about_us.slug, "new-about-us")
|
|
# Draft title should be changed to match the live title
|
|
self.assertEqual(new_about_us.draft_title, "About us")
|
|
|
|
# Check that new_about_us is a different page
|
|
self.assertNotEqual(about_us.id, new_about_us.id)
|
|
|
|
# Check that the url path was updated
|
|
self.assertEqual(new_about_us.url_path, "/home/new-about-us/")
|
|
|
|
# Check that the alias_of field was filled in
|
|
self.assertEqual(new_about_us.alias_of, about_us)
|
|
|
|
def test_create_alias_copies_child_objects(self):
|
|
christmas_event = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
|
|
# Copy it
|
|
new_christmas_event = christmas_event.create_alias(
|
|
update_slug="new-christmas-event"
|
|
)
|
|
|
|
# Check that the speakers were copied
|
|
self.assertEqual(
|
|
new_christmas_event.speakers.count(), 1, "Child objects weren't copied"
|
|
)
|
|
|
|
# Check that the speakers weren't removed from old page
|
|
self.assertEqual(
|
|
christmas_event.speakers.count(),
|
|
1,
|
|
"Child objects were removed from the original page",
|
|
)
|
|
|
|
# Check that advert placements were also copied (there's a gotcha here, since the advert_placements
|
|
# relation is defined on Page, not EventPage)
|
|
self.assertEqual(
|
|
new_christmas_event.advert_placements.count(),
|
|
1,
|
|
"Child objects defined on the superclass weren't copied",
|
|
)
|
|
self.assertEqual(
|
|
christmas_event.advert_placements.count(),
|
|
1,
|
|
"Child objects defined on the superclass were removed from the original page",
|
|
)
|
|
|
|
def test_create_alias_copies_parental_relations(self):
|
|
"""Test that a page will be copied with parental many to many relations intact."""
|
|
christmas_event = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
summer_category = EventCategory.objects.create(name="Summer")
|
|
holiday_category = EventCategory.objects.create(name="Holidays")
|
|
|
|
# add parental many to many relations
|
|
christmas_event.categories = (summer_category, holiday_category)
|
|
christmas_event.save()
|
|
|
|
# Copy it
|
|
new_christmas_event = christmas_event.create_alias(
|
|
update_slug="new-christmas-event"
|
|
)
|
|
|
|
# check that original eventt is untouched
|
|
self.assertEqual(
|
|
christmas_event.categories.count(),
|
|
2,
|
|
"Child objects (parental many to many) defined on the superclass were removed from the original page",
|
|
)
|
|
|
|
# check that parental many to many are copied
|
|
self.assertEqual(
|
|
new_christmas_event.categories.count(),
|
|
2,
|
|
"Child objects (parental many to many) weren't copied",
|
|
)
|
|
|
|
# check that the original and copy are related to the same categories
|
|
self.assertEqual(
|
|
new_christmas_event.categories.all().in_bulk(),
|
|
christmas_event.categories.all().in_bulk(),
|
|
)
|
|
|
|
def test_create_alias_doesnt_copy_revisions(self):
|
|
christmas_event = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
christmas_event.save_revision()
|
|
|
|
# Copy it
|
|
new_christmas_event = christmas_event.create_alias(
|
|
update_slug="new-christmas-event"
|
|
)
|
|
|
|
# Check that no revisions were created
|
|
self.assertEqual(new_christmas_event.revisions.count(), 0)
|
|
|
|
def test_create_alias_copies_child_objects_with_nonspecific_class(self):
|
|
# Get chrismas page as Page instead of EventPage
|
|
christmas_event = Page.objects.get(url_path="/home/events/christmas/")
|
|
|
|
# Copy it
|
|
new_christmas_event = christmas_event.create_alias(
|
|
update_slug="new-christmas-event"
|
|
)
|
|
|
|
# Check that the type of the new page is correct
|
|
self.assertIsInstance(new_christmas_event, EventPage)
|
|
|
|
# Check that the speakers were copied
|
|
self.assertEqual(
|
|
new_christmas_event.speakers.count(), 1, "Child objects weren't copied"
|
|
)
|
|
|
|
def test_create_alias_copies_recursively(self):
|
|
events_index = EventIndex.objects.get(url_path="/home/events/")
|
|
|
|
# Copy it
|
|
new_events_index = events_index.create_alias(
|
|
recursive=True, update_slug="new-events-index"
|
|
)
|
|
|
|
# Get christmas event
|
|
old_christmas_event = (
|
|
events_index.get_children().filter(slug="christmas").first()
|
|
)
|
|
new_christmas_event = (
|
|
new_events_index.get_children().filter(slug="christmas").first()
|
|
)
|
|
|
|
# Check that the event exists in both places
|
|
self.assertIsNotNone(new_christmas_event, "Child pages weren't copied")
|
|
self.assertIsNotNone(
|
|
old_christmas_event, "Child pages were removed from original page"
|
|
)
|
|
|
|
# Check that the url path was updated
|
|
self.assertEqual(
|
|
new_christmas_event.url_path, "/home/new-events-index/christmas/"
|
|
)
|
|
|
|
# Check that the children were also created as aliases
|
|
self.assertEqual(new_christmas_event.alias_of, old_christmas_event)
|
|
|
|
def test_create_alias_copies_recursively_with_child_objects(self):
|
|
events_index = EventIndex.objects.get(url_path="/home/events/")
|
|
|
|
# Copy it
|
|
new_events_index = events_index.create_alias(
|
|
recursive=True, update_slug="new-events-index"
|
|
)
|
|
|
|
# Get christmas event
|
|
old_christmas_event = (
|
|
events_index.get_children().filter(slug="christmas").first()
|
|
)
|
|
new_christmas_event = (
|
|
new_events_index.get_children().filter(slug="christmas").first()
|
|
)
|
|
|
|
# Check that the speakers were copied
|
|
self.assertEqual(
|
|
new_christmas_event.specific.speakers.count(),
|
|
1,
|
|
"Child objects weren't copied",
|
|
)
|
|
|
|
# Check that the speakers weren't removed from old page
|
|
self.assertEqual(
|
|
old_christmas_event.specific.speakers.count(),
|
|
1,
|
|
"Child objects were removed from the original page",
|
|
)
|
|
|
|
def test_create_alias_doesnt_copy_recursively_to_the_same_tree(self):
|
|
events_index = EventIndex.objects.get(url_path="/home/events/")
|
|
old_christmas_event = (
|
|
events_index.get_children().filter(slug="christmas").first().specific
|
|
)
|
|
old_christmas_event.save_revision()
|
|
|
|
with self.assertRaises(Exception) as exception:
|
|
events_index.create_alias(recursive=True, parent=events_index)
|
|
|
|
self.assertEqual(
|
|
str(exception.exception),
|
|
"You cannot copy a tree branch recursively into itself",
|
|
)
|
|
|
|
def test_create_alias_updates_user(self):
|
|
event_moderator = get_user_model().objects.get(
|
|
email="eventmoderator@example.com"
|
|
)
|
|
christmas_event = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
christmas_event.save_revision()
|
|
|
|
# Copy it
|
|
new_christmas_event = christmas_event.create_alias(
|
|
update_slug="new-christmas-event", user=event_moderator
|
|
)
|
|
|
|
# Check that the owner has been updated
|
|
self.assertEqual(new_christmas_event.owner, event_moderator)
|
|
|
|
def test_create_alias_multi_table_inheritance(self):
|
|
saint_patrick_event = SingleEventPage.objects.get(
|
|
url_path="/home/events/saint-patrick/"
|
|
)
|
|
|
|
# Copy it
|
|
new_saint_patrick_event = saint_patrick_event.create_alias(
|
|
update_slug="new-saint-patrick"
|
|
)
|
|
|
|
# Check that new_saint_patrick_event is correct
|
|
self.assertIsInstance(new_saint_patrick_event, SingleEventPage)
|
|
self.assertEqual(new_saint_patrick_event.excerpt, saint_patrick_event.excerpt)
|
|
|
|
# Check that new_saint_patrick_event is a different page, including parents from both EventPage and Page
|
|
self.assertNotEqual(saint_patrick_event.id, new_saint_patrick_event.id)
|
|
self.assertNotEqual(
|
|
saint_patrick_event.eventpage_ptr.id,
|
|
new_saint_patrick_event.eventpage_ptr.id,
|
|
)
|
|
self.assertNotEqual(
|
|
saint_patrick_event.eventpage_ptr.page_ptr.id,
|
|
new_saint_patrick_event.eventpage_ptr.page_ptr.id,
|
|
)
|
|
|
|
# Check that the url path was updated
|
|
self.assertEqual(
|
|
new_saint_patrick_event.url_path, "/home/events/new-saint-patrick/"
|
|
)
|
|
|
|
# Check that both parent instance exists
|
|
self.assertIsInstance(
|
|
EventPage.objects.get(id=new_saint_patrick_event.id), EventPage
|
|
)
|
|
self.assertIsInstance(Page.objects.get(id=new_saint_patrick_event.id), Page)
|
|
|
|
def test_create_alias_copies_tags(self):
|
|
# create and publish a TaggedPage under Events
|
|
event_index = Page.objects.get(url_path="/home/events/")
|
|
tagged_page = TaggedPage(title="My tagged page", slug="my-tagged-page")
|
|
tagged_page.tags.add("wagtail", "bird")
|
|
event_index.add_child(instance=tagged_page)
|
|
tagged_page.save_revision().publish()
|
|
|
|
old_tagged_item_ids = [item.id for item in tagged_page.tagged_items.all()]
|
|
# there should be two items here, with defined (truthy) IDs
|
|
self.assertEqual(len(old_tagged_item_ids), 2)
|
|
self.assertTrue(all(old_tagged_item_ids))
|
|
|
|
# copy to underneath homepage
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
new_tagged_page = tagged_page.create_alias(parent=homepage)
|
|
|
|
self.assertNotEqual(tagged_page.id, new_tagged_page.id)
|
|
|
|
# new page should also have two tags
|
|
new_tagged_item_ids = [item.id for item in new_tagged_page.tagged_items.all()]
|
|
self.assertEqual(len(new_tagged_item_ids), 2)
|
|
self.assertTrue(all(new_tagged_item_ids))
|
|
|
|
# new tagged_item IDs should differ from old ones
|
|
self.assertTrue(
|
|
all(item_id not in old_tagged_item_ids for item_id in new_tagged_item_ids)
|
|
)
|
|
|
|
def test_create_alias_with_m2m_relations(self):
|
|
# create and publish a ManyToManyBlogPage under Events
|
|
event_index = Page.objects.get(url_path="/home/events/")
|
|
category = BlogCategory.objects.create(name="Birds")
|
|
advert = Advert.objects.create(
|
|
url="http://www.heinz.com/", text="beanz meanz heinz"
|
|
)
|
|
|
|
blog_page = ManyToManyBlogPage(title="My blog page", slug="my-blog-page")
|
|
event_index.add_child(instance=blog_page)
|
|
|
|
blog_page.adverts.add(advert)
|
|
BlogCategoryBlogPage.objects.create(category=category, page=blog_page)
|
|
blog_page.save_revision().publish()
|
|
|
|
# copy to underneath homepage
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
new_blog_page = blog_page.create_alias(parent=homepage)
|
|
|
|
# M2M relations are not formally supported, so for now we're only interested in
|
|
# the copy operation as a whole succeeding, rather than the child objects being copied
|
|
self.assertNotEqual(blog_page.id, new_blog_page.id)
|
|
|
|
def test_create_alias_with_generic_foreign_key(self):
|
|
# create and publish a GenericSnippetPage under Events
|
|
event_index = Page.objects.get(url_path="/home/events/")
|
|
advert = Advert.objects.create(
|
|
url="http://www.heinz.com/", text="beanz meanz heinz"
|
|
)
|
|
|
|
page = GenericSnippetPage(title="My snippet page", slug="my-snippet-page")
|
|
page.snippet_content_object = advert
|
|
event_index.add_child(instance=page)
|
|
|
|
page.save_revision().publish()
|
|
|
|
# copy to underneath homepage
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
new_page = page.create_alias(parent=homepage)
|
|
|
|
self.assertNotEqual(page.id, new_page.id)
|
|
self.assertEqual(new_page.snippet_content_object, advert)
|
|
|
|
def test_create_alias_with_o2o_relation(self):
|
|
event_index = Page.objects.get(url_path="/home/events/")
|
|
|
|
page = OneToOnePage(title="My page", slug="my-page")
|
|
|
|
event_index.add_child(instance=page)
|
|
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
new_page = page.create_alias(parent=homepage)
|
|
|
|
self.assertNotEqual(page.id, new_page.id)
|
|
|
|
@unittest.expectedFailure
|
|
def test_create_alias_with_additional_excluded_fields(self):
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
page = homepage.add_child(
|
|
instance=PageWithExcludedCopyField(
|
|
title="Discovery",
|
|
slug="disco",
|
|
content="NCC-1031",
|
|
special_field="Context is for Kings",
|
|
)
|
|
)
|
|
new_page = page.create_alias(parent=homepage, update_slug="disco-2")
|
|
|
|
self.assertEqual(page.title, new_page.title)
|
|
self.assertNotEqual(page.id, new_page.id)
|
|
self.assertNotEqual(page.path, new_page.path)
|
|
# special_field is in the list to be excluded
|
|
self.assertNotEqual(page.special_field, new_page.special_field)
|
|
|
|
@unittest.expectedFailure
|
|
def test_create_alias_with_excluded_parental_and_child_relations(self):
|
|
"""Test that a page will be copied with parental and child relations removed if excluded."""
|
|
|
|
try:
|
|
# modify excluded fields for this test
|
|
EventPage.exclude_fields_in_copy = [
|
|
"advert_placements",
|
|
"categories",
|
|
"signup_link",
|
|
]
|
|
|
|
# set up data
|
|
christmas_event = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
summer_category = EventCategory.objects.create(name="Summer")
|
|
holiday_category = EventCategory.objects.create(name="Holidays")
|
|
|
|
# add URL (to test excluding a basic field)
|
|
christmas_event.signup_link = "https://christmas-is-awesome.com/rsvp"
|
|
|
|
# add parental many to many relations
|
|
christmas_event.categories = (summer_category, holiday_category)
|
|
christmas_event.save()
|
|
|
|
# Copy it
|
|
new_christmas_event = christmas_event.create_alias(
|
|
update_slug="new-christmas-event"
|
|
)
|
|
|
|
# check that the signup_link was NOT copied
|
|
self.assertEqual(
|
|
christmas_event.signup_link, "https://christmas-is-awesome.com/rsvp"
|
|
)
|
|
self.assertEqual(new_christmas_event.signup_link, "")
|
|
|
|
# check that original event is untouched
|
|
self.assertEqual(
|
|
christmas_event.categories.count(),
|
|
2,
|
|
"Child objects (parental many to many) defined on the superclass were removed from the original page",
|
|
)
|
|
|
|
# check that parental many to many are NOT copied
|
|
self.assertEqual(
|
|
new_christmas_event.categories.count(),
|
|
0,
|
|
"Child objects (parental many to many) were copied but should be excluded",
|
|
)
|
|
|
|
# check that child objects on original event were left untouched
|
|
self.assertEqual(
|
|
christmas_event.advert_placements.count(),
|
|
1,
|
|
"Child objects defined on the original superclass were edited when copied",
|
|
)
|
|
|
|
# check that child objects were NOT copied
|
|
self.assertEqual(
|
|
new_christmas_event.advert_placements.count(),
|
|
0,
|
|
"Child objects defined on the superclass were copied and should not be",
|
|
)
|
|
|
|
finally:
|
|
# reset excluded fields for future tests
|
|
EventPage.exclude_fields_in_copy = []
|
|
|
|
def test_alias_page_copies_restriction(self):
|
|
"""Test that view restrictions attached to a page are copied along with the page"""
|
|
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
child_page_1 = SimplePage(
|
|
title="Child Page 1", slug="child-page-1", content="hello child page 1"
|
|
)
|
|
homepage.add_child(instance=child_page_1)
|
|
|
|
# Add PageViewRestriction to child_page_1
|
|
group = Group.objects.create(name="Test Group")
|
|
restriction = PageViewRestriction.objects.create(
|
|
page=child_page_1, restriction_type=PageViewRestriction.GROUPS
|
|
)
|
|
restriction.groups.add(group)
|
|
|
|
child_page_2 = child_page_1.create_alias(update_slug="child-page-2")
|
|
|
|
# check that the copied page child_page_2 has a view restriction
|
|
copied_restriction = PageViewRestriction.objects.get(page=child_page_2)
|
|
# check that the copied restriction has the same groups as the original
|
|
self.assertEqual(
|
|
list(copied_restriction.groups.values_list("id", flat=True)), [group.pk]
|
|
)
|
|
|
|
def test_alias_page_does_not_copy_restrictions_from_parent(self):
|
|
"""Test that view restrictions on a page's ancestor are NOT copied along with the page"""
|
|
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
|
|
origin_parent = SimplePage(
|
|
title="Parent 1", slug="parent-1", content="hello parent 1"
|
|
)
|
|
homepage.add_child(instance=origin_parent)
|
|
PageViewRestriction.objects.create(page=origin_parent, password="hello")
|
|
|
|
destination_parent = SimplePage(
|
|
title="Parent 2", slug="parent-2", content="hello parent 2"
|
|
)
|
|
homepage.add_child(instance=destination_parent)
|
|
|
|
child_page_1 = SimplePage(
|
|
title="Child Page 1", slug="child-page-1", content="hello child page 1"
|
|
)
|
|
origin_parent.add_child(instance=child_page_1)
|
|
|
|
child_page_2 = child_page_1.create_alias(
|
|
parent=destination_parent,
|
|
update_slug="child-page-2",
|
|
)
|
|
# check that the copied page child_page_2 does not have a view restriction
|
|
self.assertFalse(PageViewRestriction.objects.filter(page=child_page_2).exists())
|
|
|
|
def test_alias_page_does_not_copy_restrictions_when_new_parent_has_one_already(
|
|
self,
|
|
):
|
|
"""Test that view restrictions on a page's ancestor are NOT copied along with the page"""
|
|
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
|
|
origin_parent = SimplePage(
|
|
title="Parent 1", slug="parent-1", content="hello parent 1"
|
|
)
|
|
homepage.add_child(instance=origin_parent)
|
|
|
|
destination_parent = SimplePage(
|
|
title="Parent 2", slug="parent-2", content="hello parent 2"
|
|
)
|
|
homepage.add_child(instance=destination_parent)
|
|
PageViewRestriction.objects.create(page=destination_parent, password="hello")
|
|
|
|
child_page_1 = SimplePage(
|
|
title="Child Page 1", slug="child-page-1", content="hello child page 1"
|
|
)
|
|
origin_parent.add_child(instance=child_page_1)
|
|
PageViewRestriction.objects.create(page=child_page_1, password="hello")
|
|
|
|
child_page_2 = child_page_1.create_alias(
|
|
parent=destination_parent,
|
|
update_slug="child-page-2",
|
|
)
|
|
# check that the copied page child_page_2 does not have a view restriction
|
|
self.assertFalse(PageViewRestriction.objects.filter(page=child_page_2).exists())
|
|
|
|
|
|
class TestUpdateAliases(TestCase):
|
|
fixtures = ["test.json"]
|
|
|
|
def test_update_aliases(self):
|
|
event_page = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
alias = event_page.create_alias(update_slug="new-event-page")
|
|
alias_alias = alias.create_alias(update_slug="new-event-page-2")
|
|
|
|
# Update the title and add a speaker
|
|
event_page.title = "Updated title"
|
|
event_page.draft_title = "A different draft title"
|
|
event_page.speakers.add(
|
|
EventPageSpeaker(
|
|
first_name="Ted",
|
|
last_name="Crilly",
|
|
)
|
|
)
|
|
event_page.save()
|
|
|
|
# Nothing should've happened yet
|
|
alias.refresh_from_db()
|
|
alias_alias.refresh_from_db()
|
|
self.assertEqual(alias.title, "Christmas")
|
|
self.assertEqual(alias_alias.title, "Christmas")
|
|
self.assertEqual(alias.speakers.count(), 1)
|
|
self.assertEqual(alias_alias.speakers.count(), 1)
|
|
|
|
PageLogEntry.objects.all().delete()
|
|
|
|
event_page.update_aliases()
|
|
|
|
# Check that the aliases have been updated
|
|
alias.refresh_from_db()
|
|
alias_alias.refresh_from_db()
|
|
self.assertEqual(alias.title, "Updated title")
|
|
self.assertEqual(alias_alias.title, "Updated title")
|
|
self.assertEqual(alias.speakers.count(), 2)
|
|
self.assertEqual(alias_alias.speakers.count(), 2)
|
|
|
|
# Draft titles shouldn't update as alias pages do not have drafts
|
|
self.assertEqual(alias.draft_title, "Updated title")
|
|
self.assertEqual(alias_alias.draft_title, "Updated title")
|
|
|
|
# Check no log entries were created for the aliases
|
|
self.assertFalse(
|
|
PageLogEntry.objects.filter(page=alias, action="wagtail.publish").exists()
|
|
)
|
|
self.assertFalse(
|
|
PageLogEntry.objects.filter(
|
|
page=alias_alias, action="wagtail.publish"
|
|
).exists()
|
|
)
|
|
|
|
def test_update_aliases_publishes_drafts(self):
|
|
event_page = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
|
|
# Unpublish the event page so that the aliases will be created in draft
|
|
event_page.live = False
|
|
event_page.has_unpublished_changes = True
|
|
event_page.save(clean=False)
|
|
|
|
alias = event_page.create_alias(update_slug="new-event-page")
|
|
alias_alias = alias.create_alias(update_slug="new-event-page-2")
|
|
|
|
self.assertFalse(alias.live)
|
|
self.assertFalse(alias_alias.live)
|
|
|
|
# Publish the event page
|
|
event_page.live = True
|
|
event_page.has_unpublished_changes = False
|
|
event_page.save(clean=False)
|
|
|
|
# Nothing should've happened yet
|
|
alias.refresh_from_db()
|
|
alias_alias.refresh_from_db()
|
|
self.assertFalse(alias.live)
|
|
self.assertFalse(alias_alias.live)
|
|
|
|
PageLogEntry.objects.all().delete()
|
|
|
|
event_page.update_aliases()
|
|
|
|
# Check that the aliases have been updated
|
|
alias.refresh_from_db()
|
|
alias_alias.refresh_from_db()
|
|
self.assertTrue(alias.live)
|
|
self.assertTrue(alias_alias.live)
|
|
|
|
# Check no log entries were created for the aliases
|
|
self.assertFalse(
|
|
PageLogEntry.objects.filter(page=alias, action="wagtail.publish").exists()
|
|
)
|
|
self.assertFalse(
|
|
PageLogEntry.objects.filter(
|
|
page=alias_alias, action="wagtail.publish"
|
|
).exists()
|
|
)
|
|
|
|
|
|
class TestCopyForTranslation(TestCase):
|
|
fixtures = ["test.json"]
|
|
|
|
def setUp(self):
|
|
self.en_homepage = Page.objects.get(url_path="/home/").specific
|
|
self.en_eventindex = EventIndex.objects.get(url_path="/home/events/")
|
|
self.en_eventpage = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
self.root_page = self.en_homepage.get_parent()
|
|
self.fr_locale = Locale.objects.create(language_code="fr")
|
|
|
|
def test_copy_homepage(self):
|
|
fr_homepage = self.en_homepage.copy_for_translation(self.fr_locale)
|
|
|
|
self.assertNotEqual(self.en_homepage.id, fr_homepage.id)
|
|
self.assertEqual(fr_homepage.locale, self.fr_locale)
|
|
self.assertEqual(fr_homepage.translation_key, self.en_homepage.translation_key)
|
|
|
|
# At the top level, the language code should be appended to the slug
|
|
self.assertEqual(fr_homepage.slug, "home-fr")
|
|
|
|
# Translation must be in draft
|
|
self.assertFalse(fr_homepage.live)
|
|
self.assertTrue(fr_homepage.has_unpublished_changes)
|
|
|
|
# Check log
|
|
log_entry = PageLogEntry.objects.get(action="wagtail.copy_for_translation")
|
|
self.assertEqual(log_entry.data["source_locale"]["language_code"], "en")
|
|
self.assertEqual(log_entry.data["page"]["locale"]["language_code"], "fr")
|
|
self.assertEqual(
|
|
log_entry.message, "Copied for translation from Root (English)"
|
|
)
|
|
|
|
def test_copy_homepage_slug_exists(self):
|
|
# This test is the same as test_copy_homepage, but we will create another page with
|
|
# the slug "home-fr" before translating. copy_for_translation should pick a different slug
|
|
self.root_page.add_child(
|
|
instance=SimplePage(
|
|
title="Old french homepage", slug="home-fr", content="Test content"
|
|
)
|
|
)
|
|
|
|
fr_homepage = self.en_homepage.copy_for_translation(self.fr_locale)
|
|
self.assertEqual(fr_homepage.slug, "home-fr-1")
|
|
|
|
def test_copy_childpage(self):
|
|
# Create translated homepage manually
|
|
fr_homepage = self.root_page.add_child(
|
|
instance=Page(
|
|
title="french homepage",
|
|
slug="home-fr",
|
|
locale=self.fr_locale,
|
|
translation_key=self.en_homepage.translation_key,
|
|
)
|
|
)
|
|
|
|
fr_eventindex = self.en_eventindex.copy_for_translation(self.fr_locale)
|
|
|
|
self.assertNotEqual(self.en_eventindex.id, fr_eventindex.id)
|
|
self.assertEqual(fr_eventindex.locale, self.fr_locale)
|
|
self.assertEqual(
|
|
fr_eventindex.translation_key, self.en_eventindex.translation_key
|
|
)
|
|
|
|
# Check that the fr event index was created under the fr homepage
|
|
self.assertEqual(fr_eventindex.get_parent(), fr_homepage)
|
|
|
|
# The slug should be the same when copying to another tree
|
|
self.assertEqual(self.en_eventindex.slug, fr_eventindex.slug)
|
|
|
|
# Check log
|
|
log_entry = PageLogEntry.objects.get(action="wagtail.copy_for_translation")
|
|
self.assertEqual(log_entry.data["source_locale"]["language_code"], "en")
|
|
self.assertEqual(log_entry.data["page"]["locale"]["language_code"], "fr")
|
|
self.assertEqual(
|
|
log_entry.message,
|
|
"Copied for translation from Welcome to the Wagtail test site! (English)",
|
|
)
|
|
|
|
def test_copy_childpage_without_parent(self):
|
|
# This test is the same as test_copy_childpage but we won't create the parent page first
|
|
|
|
with self.assertRaises(ParentNotTranslatedError):
|
|
self.en_eventindex.copy_for_translation(self.fr_locale)
|
|
|
|
def test_copy_childpage_with_copy_parents(self):
|
|
# This time we will set copy_parents
|
|
fr_eventindex = self.en_eventindex.copy_for_translation(
|
|
self.fr_locale, copy_parents=True
|
|
)
|
|
|
|
self.assertNotEqual(self.en_eventindex.id, fr_eventindex.id)
|
|
self.assertEqual(fr_eventindex.locale, self.fr_locale)
|
|
self.assertEqual(
|
|
fr_eventindex.translation_key, self.en_eventindex.translation_key
|
|
)
|
|
self.assertEqual(self.en_eventindex.slug, fr_eventindex.slug)
|
|
|
|
# This should create the homepage as well
|
|
fr_homepage = fr_eventindex.get_parent()
|
|
|
|
self.assertNotEqual(self.en_homepage.id, fr_homepage.id)
|
|
self.assertEqual(fr_homepage.locale, self.fr_locale)
|
|
self.assertEqual(fr_homepage.translation_key, self.en_homepage.translation_key)
|
|
self.assertEqual(fr_homepage.slug, "home-fr")
|
|
|
|
def test_copy_page_with_translatable_child_objects(self):
|
|
# Create translated homepage and event index manually
|
|
fr_homepage = self.root_page.add_child(
|
|
instance=Page(
|
|
title="french homepage",
|
|
slug="home-fr",
|
|
locale=self.fr_locale,
|
|
translation_key=self.en_homepage.translation_key,
|
|
)
|
|
)
|
|
|
|
fr_homepage.add_child(
|
|
instance=EventIndex(
|
|
title="Events",
|
|
slug="events",
|
|
locale=self.fr_locale,
|
|
translation_key=self.en_eventindex.translation_key,
|
|
)
|
|
)
|
|
|
|
# Add an award to the speaker
|
|
# TODO: Nested child objects not supported by page copy
|
|
en_speaker = self.en_eventpage.speakers.get()
|
|
# en_award = EventPageSpeakerAward.objects.create(
|
|
# speaker=en_speaker,
|
|
# name="Golden Globe"
|
|
# )
|
|
|
|
fr_eventpage = self.en_eventpage.copy_for_translation(self.fr_locale)
|
|
|
|
# Check that the speakers and awards were copied for translation properly
|
|
fr_speaker = fr_eventpage.speakers.get()
|
|
self.assertEqual(fr_speaker.locale, self.fr_locale)
|
|
self.assertEqual(fr_speaker.translation_key, en_speaker.translation_key)
|
|
self.assertEqual(list(fr_speaker.get_translations()), [en_speaker])
|
|
|
|
# TODO: Nested child objects not supported by page copy
|
|
# fr_award = fr_speaker.awards.get()
|
|
# self.assertEqual(ffr_award.locale, self.fr_locale)
|
|
# self.assertEqual(ffr_award.translation_key, en_award.translation_key)
|
|
# self.assertEqual(list(fr_award.get_translations()), [en_award])
|
|
|
|
def test_copies_missing_parents_as_aliases(self):
|
|
fr_eventpage = self.en_eventpage.copy_for_translation(
|
|
self.fr_locale, copy_parents=True
|
|
)
|
|
fr_eventindex = fr_eventpage.get_parent()
|
|
|
|
# Check parent is a translation of its English original
|
|
self.assertEqual(fr_eventindex.locale, self.fr_locale)
|
|
self.assertEqual(
|
|
fr_eventindex.translation_key, self.en_eventindex.translation_key
|
|
)
|
|
|
|
# Check parent is also an alias of its English original
|
|
self.assertEqual(fr_eventindex.alias_of, self.en_eventindex)
|
|
|
|
|
|
class TestSubpageTypeBusinessRules(WagtailTestUtils, TestCase):
|
|
def test_allowed_subpage_models(self):
|
|
# SimplePage does not define any restrictions on subpage types
|
|
# SimplePage is a valid subpage of SimplePage
|
|
self.assertIn(SimplePage, SimplePage.allowed_subpage_models())
|
|
# BusinessIndex is a valid subpage of SimplePage
|
|
self.assertIn(BusinessIndex, SimplePage.allowed_subpage_models())
|
|
# BusinessSubIndex is not valid, because it explicitly omits SimplePage from parent_page_types
|
|
self.assertNotIn(BusinessSubIndex, SimplePage.allowed_subpage_models())
|
|
|
|
# BusinessChild has an empty subpage_types list, so does not allow anything
|
|
self.assertNotIn(SimplePage, BusinessChild.allowed_subpage_models())
|
|
self.assertNotIn(BusinessIndex, BusinessChild.allowed_subpage_models())
|
|
self.assertNotIn(BusinessSubIndex, BusinessChild.allowed_subpage_models())
|
|
|
|
# BusinessSubIndex only allows BusinessChild as subpage type
|
|
self.assertNotIn(SimplePage, BusinessSubIndex.allowed_subpage_models())
|
|
self.assertIn(BusinessChild, BusinessSubIndex.allowed_subpage_models())
|
|
|
|
def test_allowed_parent_page_models(self):
|
|
# SimplePage does not define any restrictions on parent page types
|
|
# SimplePage is a valid parent page of SimplePage
|
|
self.assertIn(SimplePage, SimplePage.allowed_parent_page_models())
|
|
# BusinessChild cannot be a parent of anything
|
|
self.assertNotIn(BusinessChild, SimplePage.allowed_parent_page_models())
|
|
|
|
# BusinessNowherePage does not allow anything as a parent
|
|
self.assertNotIn(SimplePage, BusinessNowherePage.allowed_parent_page_models())
|
|
self.assertNotIn(
|
|
StandardIndex, BusinessNowherePage.allowed_parent_page_models()
|
|
)
|
|
|
|
# BusinessSubIndex only allows BusinessIndex as a parent
|
|
self.assertNotIn(SimplePage, BusinessSubIndex.allowed_parent_page_models())
|
|
self.assertIn(BusinessIndex, BusinessSubIndex.allowed_parent_page_models())
|
|
|
|
def test_can_exist_under(self):
|
|
self.assertTrue(SimplePage.can_exist_under(SimplePage()))
|
|
|
|
# StandardIndex should only be allowed under a Page
|
|
self.assertTrue(StandardIndex.can_exist_under(Page()))
|
|
self.assertFalse(StandardIndex.can_exist_under(SimplePage()))
|
|
|
|
# The Business pages are quite restrictive in their structure
|
|
self.assertTrue(BusinessSubIndex.can_exist_under(BusinessIndex()))
|
|
self.assertTrue(BusinessChild.can_exist_under(BusinessIndex()))
|
|
self.assertTrue(BusinessChild.can_exist_under(BusinessSubIndex()))
|
|
|
|
self.assertFalse(BusinessSubIndex.can_exist_under(SimplePage()))
|
|
self.assertFalse(BusinessSubIndex.can_exist_under(BusinessSubIndex()))
|
|
self.assertFalse(BusinessChild.can_exist_under(SimplePage()))
|
|
|
|
def test_can_create_at(self):
|
|
# Pages are not `is_creatable`, and should not be creatable
|
|
self.assertFalse(Page.can_create_at(Page()))
|
|
|
|
# SimplePage can be created under a simple page
|
|
self.assertTrue(SimplePage.can_create_at(SimplePage()))
|
|
|
|
# StandardIndex can be created under a Page, but not a SimplePage
|
|
self.assertTrue(StandardIndex.can_create_at(Page()))
|
|
self.assertFalse(StandardIndex.can_create_at(SimplePage()))
|
|
|
|
# The Business pages are quite restrictive in their structure
|
|
self.assertTrue(BusinessSubIndex.can_create_at(BusinessIndex()))
|
|
self.assertTrue(BusinessChild.can_create_at(BusinessIndex()))
|
|
self.assertTrue(BusinessChild.can_create_at(BusinessSubIndex()))
|
|
|
|
self.assertFalse(BusinessChild.can_create_at(SimplePage()))
|
|
self.assertFalse(BusinessSubIndex.can_create_at(SimplePage()))
|
|
|
|
def test_can_create_at_with_max_count_per_parent_limited_to_one(self):
|
|
root_page = Page.objects.get(url_path="/home/")
|
|
|
|
# Create 2 parent pages for our limited page model
|
|
parent1 = root_page.add_child(
|
|
instance=SimpleParentPage(title="simple parent", slug="simple-parent")
|
|
)
|
|
parent2 = root_page.add_child(
|
|
instance=SimpleParentPage(title="simple parent", slug="simple-parent-2")
|
|
)
|
|
|
|
# Add a child page to one of the pages (assert just to be sure)
|
|
self.assertTrue(SimpleChildPage.can_create_at(parent1))
|
|
parent1.add_child(
|
|
instance=SimpleChildPage(title="simple child", slug="simple-child")
|
|
)
|
|
|
|
# We already have a `SimpleChildPage` as a child of `parent1`, and since it is limited
|
|
# to have only 1 child page, we cannot create another one. However, we should still be able
|
|
# to create an instance for this page at a different location (as child of `parent2`)
|
|
self.assertFalse(SimpleChildPage.can_create_at(parent1))
|
|
self.assertTrue(SimpleChildPage.can_create_at(parent2))
|
|
|
|
def test_can_move_to(self):
|
|
self.assertTrue(SimplePage().can_move_to(SimplePage()))
|
|
|
|
# StandardIndex should only be allowed under a Page
|
|
self.assertTrue(StandardIndex().can_move_to(Page()))
|
|
self.assertFalse(StandardIndex().can_move_to(SimplePage()))
|
|
|
|
# The Business pages are quite restrictive in their structure
|
|
self.assertTrue(BusinessSubIndex().can_move_to(BusinessIndex()))
|
|
self.assertTrue(BusinessChild().can_move_to(BusinessIndex()))
|
|
self.assertTrue(BusinessChild().can_move_to(BusinessSubIndex()))
|
|
|
|
self.assertFalse(BusinessChild().can_move_to(SimplePage()))
|
|
self.assertFalse(BusinessSubIndex().can_move_to(SimplePage()))
|
|
|
|
def test_singleton_page_creation(self):
|
|
root_page = Page.objects.get(url_path="/home/")
|
|
|
|
# A single singleton page should be creatable
|
|
self.assertTrue(SingletonPage.can_create_at(root_page))
|
|
|
|
# Create a singleton page
|
|
root_page.add_child(instance=SingletonPage(title="singleton", slug="singleton"))
|
|
|
|
# A second singleton page should not be creatable
|
|
self.assertFalse(SingletonPage.can_create_at(root_page))
|
|
|
|
|
|
class TestIssue735(TestCase):
|
|
"""
|
|
Issue 735 reports that URL paths of child pages are not
|
|
updated correctly when slugs of parent pages are updated
|
|
"""
|
|
|
|
fixtures = ["test.json"]
|
|
|
|
def test_child_urls_updated_on_parent_publish(self):
|
|
event_index = Page.objects.get(url_path="/home/events/").specific
|
|
christmas_event = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
|
|
# Change the event index slug and publish it
|
|
event_index.slug = "old-events"
|
|
event_index.save_revision().publish()
|
|
|
|
# Check that the christmas events url path updated correctly
|
|
new_christmas_event = EventPage.objects.get(id=christmas_event.id)
|
|
self.assertEqual(new_christmas_event.url_path, "/home/old-events/christmas/")
|
|
|
|
|
|
class TestIssue756(TestCase):
|
|
"""
|
|
Issue 756 reports that the latest_revision_created_at
|
|
field was getting clobbered whenever a revision was published
|
|
"""
|
|
|
|
def test_publish_revision_doesnt_remove_latest_revision_created_at(self):
|
|
# Create a revision
|
|
revision = Page.objects.get(id=1).save_revision()
|
|
|
|
# Check that latest_revision_created_at is set
|
|
self.assertIsNotNone(Page.objects.get(id=1).latest_revision_created_at)
|
|
|
|
# Publish the revision
|
|
revision.publish()
|
|
|
|
# Check that latest_revision_created_at is still set
|
|
self.assertIsNotNone(Page.objects.get(id=1).latest_revision_created_at)
|
|
|
|
|
|
class TestIssue1216(TestCase):
|
|
"""
|
|
Test that url paths greater than 255 characters are supported
|
|
"""
|
|
|
|
fixtures = ["test.json"]
|
|
|
|
def test_url_path_can_exceed_255_characters(self):
|
|
event_index = Page.objects.get(url_path="/home/events/").specific
|
|
christmas_event = EventPage.objects.get(
|
|
url_path="/home/events/christmas/"
|
|
).specific
|
|
|
|
# Change the christmas_event slug first - this way, we test that the process for
|
|
# updating child url paths also handles >255 character paths correctly
|
|
new_christmas_slug = "christmas-%s-christmas" % ("0123456789" * 20)
|
|
christmas_event.slug = new_christmas_slug
|
|
christmas_event.save_revision().publish()
|
|
|
|
# Change the event index slug and publish it
|
|
new_event_index_slug = "events-%s-events" % ("0123456789" * 20)
|
|
event_index.slug = new_event_index_slug
|
|
event_index.save_revision().publish()
|
|
|
|
# Check that the url path updated correctly
|
|
new_christmas_event = EventPage.objects.get(id=christmas_event.id)
|
|
expected_url_path = "/home/{}/{}/".format(
|
|
new_event_index_slug, new_christmas_slug
|
|
)
|
|
self.assertEqual(new_christmas_event.url_path, expected_url_path)
|
|
|
|
|
|
class TestIsCreatable(TestCase):
|
|
def test_is_creatable_default(self):
|
|
"""By default, pages should be creatable"""
|
|
self.assertTrue(SimplePage.is_creatable)
|
|
self.assertIn(SimplePage, get_page_models())
|
|
|
|
def test_is_creatable_false(self):
|
|
"""Page types should be able to disable their creation"""
|
|
self.assertFalse(MTIBasePage.is_creatable)
|
|
# non-creatable pages should still appear in the get_page_models list
|
|
self.assertIn(MTIBasePage, get_page_models())
|
|
|
|
def test_is_creatable_not_inherited(self):
|
|
"""
|
|
is_creatable should not be inherited in the normal manner, and should
|
|
default to True unless set otherwise
|
|
"""
|
|
self.assertTrue(MTIChildPage.is_creatable)
|
|
self.assertIn(MTIChildPage, get_page_models())
|
|
|
|
def test_abstract_pages(self):
|
|
"""
|
|
Abstract models should not be creatable
|
|
"""
|
|
self.assertFalse(AbstractPage.is_creatable)
|
|
self.assertNotIn(AbstractPage, get_page_models())
|
|
|
|
|
|
class TestDeferredPageClasses(TestCase):
|
|
def test_deferred_page_classes_are_not_registered(self):
|
|
"""
|
|
In Django <1.10, a call to `defer` such as `SimplePage.objects.defer('content')`
|
|
will dynamically create a subclass of SimplePage. Ensure that these subclasses
|
|
are not registered in the get_page_models() list
|
|
"""
|
|
list(SimplePage.objects.defer("content"))
|
|
simplepage_subclasses = [
|
|
cls for cls in get_page_models() if issubclass(cls, SimplePage)
|
|
]
|
|
self.assertEqual(simplepage_subclasses, [SimplePage])
|
|
|
|
|
|
class TestPageManager(TestCase):
|
|
def test_page_manager(self):
|
|
"""
|
|
Assert that the Page class uses PageManager
|
|
"""
|
|
self.assertIs(type(Page.objects), PageManager)
|
|
|
|
def test_page_subclass_manager(self):
|
|
"""
|
|
Assert that Page subclasses get a PageManager without having to do
|
|
anything special. MTI subclasses do *not* inherit their parents Manager
|
|
by default.
|
|
"""
|
|
self.assertIs(type(SimplePage.objects), PageManager)
|
|
|
|
def test_custom_page_manager(self):
|
|
"""
|
|
Subclasses should be able to override their default Manager, and
|
|
Wagtail should respect this. It is up to the developer to ensure their
|
|
custom Manager inherits from PageManager.
|
|
"""
|
|
self.assertIs(type(CustomManagerPage.objects), CustomManager)
|
|
|
|
def test_custom_page_queryset(self):
|
|
"""
|
|
Managers that are constructed from a custom PageQuerySet
|
|
(via PageManager.from_queryset(CustomPageQuerySet)) should return
|
|
querysets of that type
|
|
"""
|
|
self.assertIs(type(CustomManagerPage.objects.all()), CustomPageQuerySet)
|
|
self.assertIs(type(CustomManagerPage.objects.about_spam()), CustomPageQuerySet)
|
|
self.assertIs(
|
|
type(CustomManagerPage.objects.all().about_spam()), CustomPageQuerySet
|
|
)
|
|
self.assertIs(
|
|
type(CustomManagerPage.objects.about_spam().all()), CustomPageQuerySet
|
|
)
|
|
|
|
def test_abstract_base_page_manager(self):
|
|
"""
|
|
Abstract base classes should be able to override their default Manager,
|
|
and Wagtail should respect this. It is up to the developer to ensure
|
|
their custom Manager inherits from PageManager.
|
|
"""
|
|
self.assertIs(type(MyCustomPage.objects), CustomManager)
|
|
|
|
|
|
class TestIssue2024(TestCase):
|
|
"""
|
|
This tests that deleting a content type can't delete any Page objects.
|
|
"""
|
|
|
|
fixtures = ["test.json"]
|
|
|
|
def test_delete_content_type(self):
|
|
event_index = Page.objects.get(url_path="/home/events/")
|
|
|
|
# Delete the content type
|
|
event_index_content_type = event_index.content_type
|
|
event_index_content_type.delete()
|
|
|
|
# Fetch the page again, it should still exist
|
|
event_index = Page.objects.get(url_path="/home/events/")
|
|
|
|
# Check that the content_type changed to Page
|
|
self.assertEqual(
|
|
event_index.content_type, ContentType.objects.get_for_model(Page)
|
|
)
|
|
|
|
|
|
class TestMakePreviewRequest(TestCase):
|
|
fixtures = ["test.json"]
|
|
|
|
def test_make_preview_request_for_accessible_page(self):
|
|
event_index = Page.objects.get(url_path="/home/events/")
|
|
response = event_index.make_preview_request()
|
|
self.assertEqual(response.status_code, 200)
|
|
request = response.context_data["request"]
|
|
|
|
# request should have the correct path and hostname for this page
|
|
self.assertEqual(request.path, "/events/")
|
|
self.assertEqual(request.headers["host"], "localhost")
|
|
|
|
# check other env vars required by the WSGI spec
|
|
self.assertEqual(request.META["REQUEST_METHOD"], "GET")
|
|
self.assertEqual(request.META["SCRIPT_NAME"], "")
|
|
self.assertEqual(request.META["PATH_INFO"], "/events/")
|
|
self.assertEqual(request.META["SERVER_NAME"], "localhost")
|
|
self.assertEqual(request.META["SERVER_PORT"], 80)
|
|
self.assertEqual(request.META["SERVER_PROTOCOL"], "HTTP/1.1")
|
|
self.assertEqual(request.META["wsgi.version"], (1, 0))
|
|
self.assertEqual(request.META["wsgi.url_scheme"], "http")
|
|
self.assertIn("wsgi.input", request.META)
|
|
self.assertIn("wsgi.errors", request.META)
|
|
self.assertIn("wsgi.multithread", request.META)
|
|
self.assertIn("wsgi.multiprocess", request.META)
|
|
self.assertIn("wsgi.run_once", request.META)
|
|
|
|
def test_make_preview_request_for_accessible_page_https(self):
|
|
Site.objects.update(port=443)
|
|
|
|
event_index = Page.objects.get(url_path="/home/events/")
|
|
response = event_index.make_preview_request()
|
|
self.assertEqual(response.status_code, 200)
|
|
request = response.context_data["request"]
|
|
|
|
# request should have the correct path and hostname for this page
|
|
self.assertEqual(request.path, "/events/")
|
|
self.assertEqual(request.headers["host"], "localhost")
|
|
|
|
# check other env vars required by the WSGI spec
|
|
self.assertEqual(request.META["REQUEST_METHOD"], "GET")
|
|
self.assertEqual(request.META["SCRIPT_NAME"], "")
|
|
self.assertEqual(request.META["PATH_INFO"], "/events/")
|
|
self.assertEqual(request.META["SERVER_NAME"], "localhost")
|
|
self.assertEqual(request.META["SERVER_PORT"], 443)
|
|
self.assertEqual(request.META["SERVER_PROTOCOL"], "HTTP/1.1")
|
|
self.assertEqual(request.META["wsgi.version"], (1, 0))
|
|
self.assertEqual(request.META["wsgi.url_scheme"], "https")
|
|
self.assertIn("wsgi.input", request.META)
|
|
self.assertIn("wsgi.errors", request.META)
|
|
self.assertIn("wsgi.multithread", request.META)
|
|
self.assertIn("wsgi.multiprocess", request.META)
|
|
self.assertIn("wsgi.run_once", request.META)
|
|
|
|
def test_make_preview_request_for_accessible_page_non_standard_port(self):
|
|
Site.objects.update(port=8888)
|
|
|
|
event_index = Page.objects.get(url_path="/home/events/")
|
|
response = event_index.make_preview_request()
|
|
self.assertEqual(response.status_code, 200)
|
|
request = response.context_data["request"]
|
|
|
|
# request should have the correct path and hostname for this page
|
|
self.assertEqual(request.path, "/events/")
|
|
self.assertEqual(request.headers["host"], "localhost:8888")
|
|
|
|
# check other env vars required by the WSGI spec
|
|
self.assertEqual(request.META["REQUEST_METHOD"], "GET")
|
|
self.assertEqual(request.META["SCRIPT_NAME"], "")
|
|
self.assertEqual(request.META["PATH_INFO"], "/events/")
|
|
self.assertEqual(request.META["SERVER_NAME"], "localhost")
|
|
self.assertEqual(request.META["SERVER_PORT"], 8888)
|
|
self.assertEqual(request.META["SERVER_PROTOCOL"], "HTTP/1.1")
|
|
self.assertEqual(request.META["wsgi.version"], (1, 0))
|
|
self.assertEqual(request.META["wsgi.url_scheme"], "http")
|
|
self.assertIn("wsgi.input", request.META)
|
|
self.assertIn("wsgi.errors", request.META)
|
|
self.assertIn("wsgi.multithread", request.META)
|
|
self.assertIn("wsgi.multiprocess", request.META)
|
|
self.assertIn("wsgi.run_once", request.META)
|
|
|
|
def test_make_preview_request_for_accessible_page_with_original_request(self):
|
|
event_index = Page.objects.get(url_path="/home/events/")
|
|
original_headers = {
|
|
"REMOTE_ADDR": "192.168.0.1",
|
|
"HTTP_X_FORWARDED_FOR": "192.168.0.2,192.168.0.3",
|
|
"HTTP_COOKIE": "test=1;blah=2",
|
|
"HTTP_USER_AGENT": "Test Agent",
|
|
"HTTP_AUTHORIZATION": "Basic V2FndGFpbDpXYWd0YWlsCg==",
|
|
}
|
|
factory = RequestFactory(**original_headers)
|
|
original_request = factory.get("/home/events/")
|
|
response = event_index.make_preview_request(original_request)
|
|
self.assertEqual(response.status_code, 200)
|
|
request = response.context_data["request"]
|
|
|
|
# request should have the all the special headers we set in original_request
|
|
self.assertEqual(
|
|
request.META["REMOTE_ADDR"], original_request.META["REMOTE_ADDR"]
|
|
)
|
|
self.assertEqual(
|
|
request.headers["x-forwarded-for"],
|
|
original_request.META["HTTP_X_FORWARDED_FOR"],
|
|
)
|
|
self.assertEqual(
|
|
request.headers["cookie"], original_request.META["HTTP_COOKIE"]
|
|
)
|
|
self.assertEqual(
|
|
request.headers["user-agent"], original_request.META["HTTP_USER_AGENT"]
|
|
)
|
|
self.assertEqual(
|
|
request.headers["authorization"],
|
|
original_request.META["HTTP_AUTHORIZATION"],
|
|
)
|
|
|
|
# check other env vars required by the WSGI spec
|
|
self.assertEqual(request.META["REQUEST_METHOD"], "GET")
|
|
self.assertEqual(request.META["SCRIPT_NAME"], "")
|
|
self.assertEqual(request.META["PATH_INFO"], "/events/")
|
|
self.assertEqual(request.META["SERVER_NAME"], "localhost")
|
|
self.assertEqual(request.META["SERVER_PORT"], 80)
|
|
self.assertEqual(request.META["SERVER_PROTOCOL"], "HTTP/1.1")
|
|
self.assertEqual(request.META["wsgi.version"], (1, 0))
|
|
self.assertEqual(request.META["wsgi.url_scheme"], "http")
|
|
self.assertIn("wsgi.input", request.META)
|
|
self.assertIn("wsgi.errors", request.META)
|
|
self.assertIn("wsgi.multithread", request.META)
|
|
self.assertIn("wsgi.multiprocess", request.META)
|
|
self.assertIn("wsgi.run_once", request.META)
|
|
|
|
@override_settings(ALLOWED_HOSTS=["production.example.com"])
|
|
def test_make_preview_request_for_inaccessible_page_should_use_valid_host(self):
|
|
root_page = Page.objects.get(url_path="/")
|
|
response = root_page.make_preview_request()
|
|
self.assertEqual(response.status_code, 200)
|
|
request = response.context_data["request"]
|
|
|
|
# in the absence of an actual Site record where we can access this page,
|
|
# make_preview_request should still provide a hostname that Django's host header
|
|
# validation won't reject
|
|
self.assertEqual(request.headers["host"], "production.example.com")
|
|
|
|
@override_settings(ALLOWED_HOSTS=["*"])
|
|
def test_make_preview_request_for_inaccessible_page_with_wildcard_allowed_hosts(
|
|
self,
|
|
):
|
|
root_page = Page.objects.get(url_path="/")
|
|
response = root_page.make_preview_request()
|
|
self.assertEqual(response.status_code, 200)
|
|
request = response.context_data["request"]
|
|
|
|
# '*' is not a valid hostname, so ensure that we replace it with something sensible
|
|
self.assertNotEqual(request.headers["host"], "*")
|
|
|
|
def test_is_previewable(self):
|
|
event_index = Page.objects.get(url_path="/home/events/")
|
|
stream_page = StreamPage(title="stream page", body=[("text", "hello")])
|
|
event_index.add_child(instance=stream_page)
|
|
plain_stream_page = Page.objects.get(id=stream_page.id)
|
|
|
|
# StreamPage sets preview_modes to an empty list, so stream_page is not previewable
|
|
with self.assertNumQueries(0):
|
|
self.assertFalse(stream_page.is_previewable())
|
|
|
|
# is_previewable should also cope with being called on a base Page object, at the
|
|
# cost of an extra query to access the specific object
|
|
with self.assertNumQueries(1):
|
|
self.assertFalse(plain_stream_page.is_previewable())
|
|
|
|
# event_index is a plain Page object, but we should recognise that preview_modes
|
|
# has not been overridden on EventIndexPage and avoid the extra query
|
|
with self.assertNumQueries(0):
|
|
self.assertTrue(event_index.is_previewable())
|
|
|
|
|
|
class TestShowInMenusDefaultOption(TestCase):
|
|
"""
|
|
This tests that a page model can define the default for 'show_in_menus'
|
|
"""
|
|
|
|
fixtures = ["test.json"]
|
|
|
|
def test_show_in_menus_default(self):
|
|
# Create a page that does not have the default init
|
|
page = Page(title="My Awesome Page", slug="my-awesome-page")
|
|
|
|
# Check that the page instance creates with show_in_menu as False
|
|
self.assertFalse(page.show_in_menus)
|
|
|
|
def test_show_in_menus_default_override(self):
|
|
# Create a page that does have the default init
|
|
page = AlwaysShowInMenusPage(title="My Awesome Page", slug="my-awesome-page")
|
|
|
|
# Check that the page instance creates with show_in_menu as True
|
|
self.assertTrue(page.show_in_menus)
|
|
|
|
|
|
class TestPageWithContentJSON(TestCase):
|
|
fixtures = ["test.json"]
|
|
|
|
def test_with_content_json_preserves_values(self):
|
|
original_page = SimplePage.objects.get(url_path="/home/about-us/")
|
|
eventpage_content_type = ContentType.objects.get_for_model(EventPage)
|
|
|
|
# Take a json representation of the page and update it
|
|
# with some alternative values
|
|
content = original_page.serializable_data()
|
|
content.update(
|
|
title="About them",
|
|
draft_title="About them",
|
|
slug="about-them",
|
|
url_path="/home/some-section/about-them/",
|
|
pk=original_page.pk + 999,
|
|
numchild=original_page.numchild + 999,
|
|
depth=original_page.depth + 999,
|
|
path=original_page.path + "ABCDEF",
|
|
content="<p>They are not as good</p>",
|
|
first_published_at="2000-01-01T00:00:00Z",
|
|
last_published_at="2000-01-01T00:00:00Z",
|
|
live=not original_page.live,
|
|
locked=True,
|
|
locked_by=1,
|
|
locked_at="2000-01-01T00:00:00Z",
|
|
has_unpublished_changes=not original_page.has_unpublished_changes,
|
|
content_type=eventpage_content_type.id,
|
|
show_in_menus=not original_page.show_in_menus,
|
|
owner=1,
|
|
)
|
|
|
|
# Pass the values to with_content_json() to get an updated version of the page
|
|
updated_page = original_page.with_content_json(content)
|
|
|
|
# The following attributes values should have changed
|
|
for attr_name in ("title", "slug", "content", "url_path", "show_in_menus"):
|
|
self.assertNotEqual(
|
|
getattr(original_page, attr_name), getattr(updated_page, attr_name)
|
|
)
|
|
|
|
# The following attribute values should have been preserved,
|
|
# despite new values being provided in content
|
|
for attr_name in (
|
|
"pk",
|
|
"path",
|
|
"depth",
|
|
"numchild",
|
|
"content_type",
|
|
"draft_title",
|
|
"live",
|
|
"has_unpublished_changes",
|
|
"owner",
|
|
"locked",
|
|
"locked_by",
|
|
"locked_at",
|
|
"latest_revision_created_at",
|
|
"first_published_at",
|
|
):
|
|
self.assertEqual(
|
|
getattr(original_page, attr_name), getattr(updated_page, attr_name)
|
|
)
|
|
|
|
# The url_path should reflect the new slug value, but the
|
|
# rest of the path should have remained unchanged
|
|
self.assertEqual(updated_page.url_path, "/home/about-them/")
|
|
|
|
|
|
class TestUnpublish(TestCase):
|
|
fixtures = ["test.json"]
|
|
|
|
def test_unpublish_doesnt_call_full_clean_before_save(self):
|
|
root_page = Page.objects.get(id=1)
|
|
home_page = root_page.add_child(
|
|
instance=SimplePage(title="Homepage", slug="home2", content="hello")
|
|
)
|
|
# Empty the content - bypassing validation which would otherwise prevent it
|
|
home_page.save(clean=False)
|
|
# This shouldn't fail with a ValidationError.
|
|
home_page.unpublish()
|
|
|
|
def test_unpublish_also_unpublishes_aliases(self):
|
|
event_page = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
alias = event_page.create_alias(update_slug="new-event-page")
|
|
alias_alias = alias.create_alias(update_slug="new-event-page-2")
|
|
|
|
self.assertTrue(event_page.live)
|
|
self.assertTrue(alias.live)
|
|
self.assertTrue(alias_alias.live)
|
|
|
|
PageLogEntry.objects.all().delete()
|
|
|
|
# Unpublish the event page
|
|
event_page.unpublish()
|
|
|
|
alias.refresh_from_db()
|
|
alias_alias.refresh_from_db()
|
|
self.assertFalse(event_page.live)
|
|
self.assertFalse(alias.live)
|
|
self.assertFalse(alias_alias.live)
|
|
|
|
# Check no log entries were created for the aliases
|
|
self.assertFalse(
|
|
PageLogEntry.objects.filter(page=alias, action="wagtail.unpublish").exists()
|
|
)
|
|
self.assertFalse(
|
|
PageLogEntry.objects.filter(
|
|
page=alias_alias, action="wagtail.unpublish"
|
|
).exists()
|
|
)
|
|
|
|
|
|
class TestCachedContentType(TestCase):
|
|
"""Tests for Page.cached_content_type"""
|
|
|
|
def setUp(self):
|
|
root_page = Page.objects.first()
|
|
self.page = root_page.add_child(
|
|
instance=SimplePage(title="Test1", slug="test1", content="test")
|
|
)
|
|
self.specific_page_ctype = ContentType.objects.get_for_model(SimplePage)
|
|
|
|
def test_golden_path(self):
|
|
"""
|
|
The return value should match the value you'd get
|
|
if fetching the ContentType from the database,
|
|
and shouldn't trigger any database queries when
|
|
the ContentType is already in memory.
|
|
"""
|
|
with self.assertNumQueries(0):
|
|
result = self.page.cached_content_type
|
|
self.assertEqual(result, ContentType.objects.get(id=self.page.content_type_id))
|
|
|
|
|
|
class TestGetTranslatableModels(TestCase):
|
|
def test_get_translatable_models(self):
|
|
translatable_models = get_translatable_models()
|
|
|
|
# Only root translatable models should be included by default
|
|
self.assertNotIn(EventPage, translatable_models)
|
|
|
|
self.assertIn(Page, translatable_models)
|
|
self.assertIn(EventPageSpeaker, translatable_models)
|
|
self.assertNotIn(Site, translatable_models)
|
|
self.assertNotIn(Advert, translatable_models)
|
|
|
|
def test_get_translatable_models_include_subclasses(self):
|
|
translatable_models = get_translatable_models(include_subclasses=True)
|
|
|
|
self.assertIn(EventPage, translatable_models)
|
|
|
|
self.assertIn(Page, translatable_models)
|
|
self.assertIn(EventPageSpeaker, translatable_models)
|
|
self.assertNotIn(Site, translatable_models)
|
|
self.assertNotIn(Advert, translatable_models)
|
|
|
|
|
|
class TestDefaultLocale(TestCase):
|
|
def setUp(self):
|
|
self.root_page = Page.objects.first()
|
|
|
|
def test_default_locale(self):
|
|
page = self.root_page.add_child(
|
|
instance=SimplePage(title="Test1", slug="test1", content="test")
|
|
)
|
|
|
|
self.assertEqual(page.locale, self.root_page.locale)
|
|
|
|
def test_override_default_locale(self):
|
|
fr_locale = Locale.objects.create(language_code="fr")
|
|
|
|
page = self.root_page.add_child(
|
|
instance=SimplePage(
|
|
title="Test1", slug="test1", content="test", locale=fr_locale
|
|
)
|
|
)
|
|
|
|
self.assertEqual(page.locale, fr_locale)
|
|
|
|
def test_always_defaults_to_parent_locale(self):
|
|
fr_locale = Locale.objects.create(language_code="fr")
|
|
|
|
fr_page = self.root_page.add_child(
|
|
instance=SimplePage(
|
|
title="Test1", slug="test1", content="test", locale=fr_locale
|
|
)
|
|
)
|
|
|
|
page = fr_page.add_child(
|
|
instance=SimplePage(title="Test1", slug="test1", content="test")
|
|
)
|
|
|
|
self.assertEqual(page.locale, fr_locale)
|
|
|
|
|
|
@override_settings(WAGTAIL_I18N_ENABLED=True)
|
|
class TestLocalized(TestCase):
|
|
fixtures = ["test.json"]
|
|
|
|
def setUp(self):
|
|
self.fr_locale = Locale.objects.create(language_code="fr")
|
|
self.event_page = Page.objects.get(url_path="/home/events/christmas/")
|
|
self.fr_event_page = self.event_page.copy_for_translation(
|
|
self.fr_locale, copy_parents=True
|
|
)
|
|
self.fr_event_page.title = "Noël"
|
|
self.fr_event_page.save(update_fields=["title"])
|
|
self.fr_event_page.save_revision().publish()
|
|
|
|
def test_localized_same_language(self):
|
|
self.assertEqual(self.event_page.localized, self.event_page)
|
|
self.assertEqual(self.event_page.localized_draft, self.event_page)
|
|
|
|
def test_localized_different_language(self):
|
|
with translation.override("fr"):
|
|
self.assertEqual(self.event_page.localized, self.fr_event_page.page_ptr)
|
|
self.assertEqual(
|
|
self.event_page.localized_draft, self.fr_event_page.page_ptr
|
|
)
|
|
|
|
@override_settings(WAGTAIL_I18N_ENABLED=False)
|
|
def test_localized_different_language_with_wagtail_i18n_enabled_false(self):
|
|
"""Should return the same page if WAGTAIL_I18N_ENABLED is False"""
|
|
with translation.override("fr"):
|
|
self.assertEqual(self.event_page.localized, self.event_page)
|
|
self.assertEqual(self.event_page.localized_draft, self.event_page)
|
|
|
|
def test_localized_different_language_unpublished(self):
|
|
# We shouldn't autolocalize if the translation is unpublished
|
|
self.fr_event_page.unpublish()
|
|
self.fr_event_page.save()
|
|
|
|
with translation.override("fr"):
|
|
self.assertEqual(self.event_page.localized, self.event_page)
|
|
self.assertEqual(
|
|
self.event_page.localized_draft, self.fr_event_page.page_ptr
|
|
)
|
|
|
|
def test_localized_with_non_content_active_locale(self):
|
|
# if active locale does not have a Locale record, use default locale
|
|
with translation.override("de"):
|
|
self.assertEqual(self.event_page.localized, self.event_page)
|
|
self.assertEqual(self.fr_event_page.localized, self.event_page.specific)
|
|
self.assertEqual(self.event_page.localized_draft, self.event_page)
|
|
self.assertEqual(
|
|
self.fr_event_page.localized_draft, self.event_page.specific
|
|
)
|
|
|
|
def test_localized_with_missing_default_locale(self):
|
|
# if neither active locale nor default language code have a Locale record, return self
|
|
|
|
# Change the 'en' locale to 'pl', so that no locale record for LANGUAGE_CODE exists.
|
|
# This replicates a scenario where a site was originally built with LANGUAGE_CODE='pl'
|
|
# but subsequently changed to LANGUAGE_CODE='en' (a change which was not reflected in
|
|
# the database).
|
|
en_locale = Locale.objects.get(language_code="en")
|
|
en_locale.language_code = "pl"
|
|
en_locale.save()
|
|
|
|
with translation.override("de"):
|
|
self.assertEqual(self.event_page.localized, self.event_page)
|
|
self.assertEqual(self.fr_event_page.localized, self.fr_event_page)
|
|
self.assertEqual(self.event_page.localized_draft, self.event_page)
|
|
self.assertEqual(self.fr_event_page.localized_draft, self.fr_event_page)
|
|
|
|
|
|
class TestGetLock(TestCase):
|
|
fixtures = ["test.json"]
|
|
|
|
def test_when_unlocked(self):
|
|
christmas_event = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
|
|
self.assertIsNone(christmas_event.get_lock())
|
|
|
|
def test_when_locked(self):
|
|
moderator = get_user_model().objects.get(email="eventmoderator@example.com")
|
|
|
|
christmas_event = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
christmas_event.locked = True
|
|
christmas_event.locked_by = moderator
|
|
if settings.USE_TZ:
|
|
christmas_event.locked_at = datetime.datetime(
|
|
2022, 7, 29, 12, 19, 0, tzinfo=datetime.timezone.utc
|
|
)
|
|
else:
|
|
christmas_event.locked_at = datetime.datetime(2022, 7, 29, 12, 19, 0)
|
|
|
|
lock = christmas_event.get_lock()
|
|
self.assertIsInstance(lock, BasicLock)
|
|
self.assertTrue(lock.for_user(christmas_event.owner))
|
|
self.assertFalse(lock.for_user(moderator))
|
|
|
|
if settings.USE_TZ:
|
|
# the default timezone is "Asia/Tokyo", so we expect UTC +9
|
|
expected_date_string = "July 29, 2022, 9:19 p.m."
|
|
else:
|
|
expected_date_string = "July 29, 2022, 12:19 p.m."
|
|
|
|
self.assertEqual(
|
|
lock.get_message(christmas_event.owner),
|
|
f"<b>'Christmas' was locked</b> by <b>{str(moderator)}</b> on <b>{expected_date_string}</b>.",
|
|
)
|
|
self.assertEqual(
|
|
lock.get_message(moderator),
|
|
f"<b>'Christmas' was locked</b> by <b>you</b> on <b>{expected_date_string}</b>.",
|
|
)
|
|
|
|
def test_when_locked_without_locked_at(self):
|
|
moderator = get_user_model().objects.get(email="eventmoderator@example.com")
|
|
|
|
christmas_event = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
christmas_event.locked = True
|
|
christmas_event.locked_by = moderator
|
|
|
|
lock = christmas_event.get_lock()
|
|
self.assertEqual(
|
|
lock.get_message(christmas_event.owner),
|
|
"<b>'Christmas' is locked</b>.",
|
|
)
|
|
self.assertEqual(
|
|
lock.get_message(moderator),
|
|
"<b>'Christmas' is locked</b> by <b>you</b>.",
|
|
)
|
|
|
|
@override_settings(WAGTAILADMIN_GLOBAL_EDIT_LOCK=True)
|
|
def test_when_locked_globally(self):
|
|
moderator = get_user_model().objects.get(email="eventmoderator@example.com")
|
|
|
|
christmas_event = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
christmas_event.locked = True
|
|
christmas_event.locked_by = moderator
|
|
if settings.USE_TZ:
|
|
christmas_event.locked_at = datetime.datetime(
|
|
2022, 7, 29, 12, 19, 0, tzinfo=datetime.timezone.utc
|
|
)
|
|
else:
|
|
christmas_event.locked_at = datetime.datetime(2022, 7, 29, 12, 19, 0)
|
|
|
|
lock = christmas_event.get_lock()
|
|
self.assertIsInstance(lock, BasicLock)
|
|
self.assertTrue(lock.for_user(christmas_event.owner))
|
|
self.assertTrue(lock.for_user(moderator))
|
|
|
|
if settings.USE_TZ:
|
|
# the default timezone is "Asia/Tokyo", so we expect UTC +9
|
|
expected_date_string = "July 29, 2022, 9:19 p.m."
|
|
else:
|
|
expected_date_string = "July 29, 2022, 12:19 p.m."
|
|
|
|
self.assertEqual(
|
|
lock.get_message(christmas_event.owner),
|
|
f"<b>'Christmas' was locked</b> by <b>{str(moderator)}</b> on <b>{expected_date_string}</b>.",
|
|
)
|
|
self.assertEqual(
|
|
lock.get_message(moderator),
|
|
f"<b>'Christmas' was locked</b> by <b>you</b> on <b>{expected_date_string}</b>.",
|
|
)
|
|
|
|
def test_when_locked_by_workflow(self):
|
|
moderator = get_user_model().objects.get(email="eventmoderator@example.com")
|
|
|
|
christmas_event = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
christmas_event.save_revision()
|
|
|
|
workflow = Workflow.objects.create(name="test_workflow")
|
|
task = GroupApprovalTask.objects.create(name="test_task")
|
|
task.groups.add(Group.objects.get(name="Event moderators"))
|
|
WorkflowTask.objects.create(workflow=workflow, task=task, sort_order=1)
|
|
workflow.start(christmas_event, moderator)
|
|
|
|
lock = christmas_event.get_lock()
|
|
self.assertIsInstance(lock, WorkflowLock)
|
|
self.assertTrue(lock.for_user(christmas_event.owner))
|
|
self.assertFalse(lock.for_user(moderator))
|
|
self.assertEqual(
|
|
lock.get_message(christmas_event.owner),
|
|
"This page is currently awaiting moderation. Only reviewers for this task can edit the page.",
|
|
)
|
|
self.assertIsNone(lock.get_message(moderator))
|
|
|
|
# When visiting a page in a workflow with multiple tasks, the message displayed to users changes to show the current task the page is on
|
|
|
|
# Add a second task to the workflow
|
|
other_task = GroupApprovalTask.objects.create(name="another_task")
|
|
WorkflowTask.objects.create(workflow=workflow, task=other_task, sort_order=2)
|
|
|
|
lock = christmas_event.get_lock()
|
|
self.assertEqual(
|
|
lock.get_message(christmas_event.owner),
|
|
"This page is awaiting <b>'test_task'</b> in the <b>'test_workflow'</b> workflow. Only reviewers for this task can edit the page.",
|
|
)
|
|
|
|
def test_when_scheduled_for_publish(self):
|
|
christmas_event = EventPage.objects.get(url_path="/home/events/christmas/")
|
|
if settings.USE_TZ:
|
|
christmas_event.go_live_at = datetime.datetime(
|
|
2030, 7, 29, 16, 32, 0, tzinfo=datetime.timezone.utc
|
|
)
|
|
else:
|
|
christmas_event.go_live_at = datetime.datetime(2030, 7, 29, 16, 32, 0)
|
|
rvn = christmas_event.save_revision()
|
|
rvn.publish()
|
|
|
|
lock = christmas_event.get_lock()
|
|
self.assertIsInstance(lock, ScheduledForPublishLock)
|
|
self.assertTrue(lock.for_user(christmas_event.owner))
|
|
|
|
if settings.USE_TZ:
|
|
# the default timezone is "Asia/Tokyo", so we expect UTC +9
|
|
expected_date_string = "July 30, 2030, 1:32 a.m."
|
|
else:
|
|
expected_date_string = "July 29, 2030, 4:32 p.m."
|
|
|
|
self.assertEqual(
|
|
lock.get_message(christmas_event.owner),
|
|
f"Page 'Christmas' is locked and has been scheduled to go live at {expected_date_string}",
|
|
)
|
|
|
|
# Not even superusers can break this lock
|
|
# This is because it shouldn't be possible to create a separate draft from what is scheduled to be published
|
|
superuser = get_user_model().objects.get(email="superuser@example.com")
|
|
self.assertTrue(lock.for_user(superuser))
|
|
|
|
|
|
class TestPageCacheKey(TestCase):
|
|
fixtures = ["test.json"]
|
|
|
|
def setUp(self):
|
|
self.page = Page.objects.last()
|
|
self.other_page = Page.objects.first()
|
|
|
|
def test_cache_key_consistent(self):
|
|
self.assertEqual(self.page.cache_key, self.page.cache_key)
|
|
self.assertEqual(self.other_page.cache_key, self.other_page.cache_key)
|
|
|
|
def test_no_queries(self):
|
|
with self.assertNumQueries(0):
|
|
self.page.cache_key
|
|
self.other_page.cache_key
|
|
|
|
def test_changes_when_slug_changes(self):
|
|
original_cache_key = self.page.cache_key
|
|
self.page.slug = "something-else"
|
|
self.page.save()
|
|
self.assertNotEqual(self.page.cache_key, original_cache_key)
|
|
|
|
|
|
class TestPageCachedParentObjExists(TestCase):
|
|
fixtures = ["test.json"]
|
|
|
|
def test_cached_parent_obj_exists(self):
|
|
# https://github.com/wagtail/wagtail/pull/11737
|
|
|
|
# Test if _cached_parent_obj is set after using page.get_parent()
|
|
# This is treebeard specific, we don't know if their API will change.
|
|
homepage = Page.objects.get(url_path="/home/")
|
|
homepage._cached_parent_obj = "_cached_parent_obj_exists"
|
|
parent = homepage.get_parent(update=False)
|
|
self.assertEqual(
|
|
parent,
|
|
"_cached_parent_obj_exists",
|
|
"Page.get_parent() (treebeard) no longer uses _cached_parent_obj to cache the parent object",
|
|
)
|