dynamicweb/cms/tests/test_page.py

1126 lines
48 KiB
Python

import datetime
import functools
import os.path
from unittest import skipIf
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.sites.models import Site
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.db import models
from django.db.utils import IntegrityError
from django.http import HttpResponse, HttpResponseNotFound
from django.urls import reverse
from django.utils.timezone import now as tz_now
from django.utils.translation import override as force_language
from cms import constants
from cms.api import add_plugin, create_page, create_page_content
from cms.cache.page import get_page_cache
from cms.forms.validators import validate_url_uniqueness
from cms.models import Page, PageContent
from cms.models.placeholdermodel import Placeholder
from cms.models.pluginmodel import CMSPlugin
from cms.sitemaps import CMSSitemap
from cms.test_utils.testcases import CMSTestCase, TransactionCMSTestCase
from cms.utils.conf import get_cms_setting
from cms.utils.page import (
get_available_slug,
get_page_from_request,
)
def _get_current_site():
return Site.objects.get_current()
class PageMigrationTestCase(CMSTestCase):
def test_content_type(self):
"""
Test correct content type is set for Page object
"""
from django.contrib.contenttypes.models import ContentType
self.assertEqual(ContentType.objects.filter(model="page", app_label="cms").count(), 1)
def has_no_custom_user():
return get_user_model().USERNAME_FIELD != "email"
class PagesTestCase(TransactionCMSTestCase):
def tearDown(self):
cache.clear()
def test_absolute_url(self):
"""
Test correct content type is set for Page object
"""
page = self.create_homepage("page", "nav_playground.html", "en")
create_page_content("fr", "french home", page)
page_2 = create_page("inner", "nav_playground.html", "en", parent=page)
create_page_content("fr", "french inner", page_2)
self.assertEqual(page_2.get_absolute_url(), "/en/inner/")
self.assertEqual(page_2.get_absolute_url(language="en"), "/en/inner/")
self.assertEqual(page_2.get_absolute_url(language="fr"), "/fr/french-inner/")
with force_language("fr"):
self.assertEqual(page_2.get_absolute_url(), "/fr/french-inner/")
self.assertEqual(page_2.get_absolute_url(language="en"), "/en/inner/")
self.assertEqual(page_2.get_absolute_url(language="fr"), "/fr/french-inner/")
def test_absolute_url_page_content(self):
page = self.create_homepage("page", "nav_playground.html", "en")
create_page_content("fr", "french home", page)
page_2 = create_page("inner", "nav_playground.html", "en", parent=page)
content_2 = create_page_content("fr", "french inner", page_2)
# content_2 is French
self.assertEqual(content_2.get_absolute_url(), "/fr/french-inner/")
# if language specified, get the language version's url
self.assertEqual(content_2.get_absolute_url(language="en"), "/en/inner/")
# for completeness: specify own language
self.assertEqual(content_2.get_absolute_url(language="fr"), "/fr/french-inner/")
# The above result does not change if language is changed
with force_language("en"):
self.assertEqual(content_2.get_absolute_url(), "/fr/french-inner/")
self.assertEqual(content_2.get_absolute_url(language="en"), "/en/inner/")
self.assertEqual(content_2.get_absolute_url(language="fr"), "/fr/french-inner/")
def test_get_root_page(self):
_create = functools.partial(
create_page,
template="nav_playground.html",
language="en",
)
page_a = _create("page_a")
page_a_a = _create("page_a_a_a", parent=page_a)
page_a_a_a = _create("page_a_a_a", parent=page_a_a)
page_tree_with_root = [
(page_a, page_a),
(page_a_a, page_a),
(page_a_a_a, page_a),
]
for page, root in page_tree_with_root:
self.assertEqual(page.get_root(), root)
def test_treebeard_delete(self):
"""
This is a test for #4102
When deleting a page, parent must be updated too, to reflect the new tree status.
This is handled by MP_NodeQuerySet (which was not used before the fix)
"""
page1 = create_page("home", "nav_playground.html", "en")
page2 = create_page("page2", "nav_playground.html", "en", parent=page1)
page3 = create_page("page3", "nav_playground.html", "en", parent=page2)
self.assertEqual(page1.depth, 1)
self.assertEqual(page1.numchild, 1)
self.assertFalse(page1.is_leaf())
self.assertEqual(page2.depth, 2)
self.assertEqual(page2.numchild, 1)
self.assertFalse(page2.is_leaf())
self.assertEqual(page3.depth, 3)
self.assertEqual(page3.numchild, 0)
self.assertTrue(page3.is_leaf())
page3.delete()
page1.refresh_from_db()
page2.refresh_from_db()
self.assertEqual(page2.depth, 2)
self.assertEqual(page2.numchild, 0)
self.assertTrue(page2.is_leaf())
page3 = create_page("page3", "nav_playground.html", "en", parent=page2, reverse_id="page3")
self.assertEqual(page2.depth, 2)
self.assertEqual(page2.numchild, 1)
self.assertFalse(page2.is_leaf())
self.assertEqual(page3.depth, 3)
self.assertEqual(page3.numchild, 0)
self.assertTrue(page3.is_leaf())
self.assertEqual(page1.depth, 1)
self.assertEqual(page1.numchild, 1)
self.assertFalse(page1.is_leaf())
self.assertEqual(page2.depth, 2)
self.assertEqual(page2.numchild, 1)
self.assertFalse(page2.is_leaf())
self.assertEqual(page3.depth, 3)
self.assertEqual(page3.numchild, 0)
self.assertTrue(page3.is_leaf())
def test_create_page_api(self):
page_data = {
"title": "root",
"slug": "root",
"language": settings.LANGUAGES[0][0],
"template": "nav_playground.html",
}
page = self.create_homepage(**page_data)
self.assertEqual(Page.objects.count(), 1)
self.assertTrue(page.is_home)
self.assertEqual(list(page.get_urls().values_list("path", flat=True)), [""])
@skipIf(has_no_custom_user(), "No custom user")
def test_create_page_api_with_long_username(self):
page_data = {
"title": "root",
"slug": "root",
"language": settings.LANGUAGES[0][0],
"template": "nav_playground.html",
"created_by": self._create_user(
"V" * constants.PAGE_USERNAME_MAX_LENGTH + "ERY-LONG-USERNAME",
is_staff=True,
is_superuser=True,
),
}
page = create_page(**page_data)
self.assertEqual(Page.objects.count(), 1)
self.assertLessEqual(len(page.created_by), constants.PAGE_USERNAME_MAX_LENGTH)
self.assertRegex(page.created_by, r"V+\.{3} \(id=\d+\)")
self.assertLessEqual(len(page.changed_by), constants.PAGE_USERNAME_MAX_LENGTH)
self.assertRegex(page.changed_by, r"V+\.{3} \(id=\d+\)")
self.assertEqual(list(page.get_urls().values_list("path", flat=True)), ["root"])
def test_get_available_slug_recursion(self):
"""Checks cms.utils.page.get_available_slug for infinite recursion"""
site = _get_current_site()
for x in range(0, 12):
create_page("test-page", "nav_playground.html", "en")
new_slug = get_available_slug(site, "test-page", "en")
self.assertEqual(new_slug, "test-page-copy-13") # get_available_slug's suffix default is 'copy'
def test_path_collisions_api_1(self):
"""Checks for slug collisions on sibling pages - uses API to create pages"""
page1 = create_page("test page 1", "nav_playground.html", "en")
create_page("test page 1_1", "nav_playground.html", "en", parent=page1, slug="foo")
# Try to create a sibling page with the same slug - should fail with IntegrityError
with self.assertRaises(IntegrityError):
create_page("test page 1_1", "nav_playground.html", "en", parent=page1, slug="foo")
def test_path_collisions_api_2(self):
"""Checks for slug collisions on root (not home) page and a home page child - uses API to create pages"""
page1 = self.create_homepage("test page 1", "nav_playground.html", "en")
create_page("test page 1_1", "nav_playground.html", "en", parent=page1, slug="foo")
# Try to create a root page with the same slug - should fail with IntegrityError
with self.assertRaises(IntegrityError):
create_page("test page 1_1", "nav_playground.html", "en", slug="foo")
def test_path_collisions_api_3(self):
"""Checks for slug collisions on children of a non root page - uses API to create pages"""
site = _get_current_site()
page1 = create_page("test page 1", "nav_playground.html", "en")
page1_1 = create_page("test page 1_1", "nav_playground.html", "en", parent=page1, slug="foo")
create_page("test page 1_1_1", "nav_playground.html", "en", parent=page1_1, slug="bar")
page1_2 = create_page("test page 1_2", "nav_playground.html", "en", parent=page1, slug="bar")
# Direct children of home has different slug so it's ok.
self.assertTrue(
validate_url_uniqueness(
site,
path=page1_1.get_path("en"),
language="en",
exclude_page=page1_1,
)
)
self.assertTrue(
validate_url_uniqueness(
site,
path=page1_2.get_path("en"),
language="en",
exclude_page=page1_2,
)
)
with self.assertRaises(IntegrityError):
create_page("test page 1_1_1", "nav_playground.html", "en", parent=page1_1, slug="bar")
def test_parent_child_same_slug(self):
"""Test that a parent page and its child can have the same slug."""
parent = create_page("parent", "nav_playground.html", "en", slug="foo")
child = create_page("child", "nav_playground.html", "en", parent=parent, slug="foo")
# Verify the paths are correctly formed and different
self.assertEqual(parent.get_path("en"), "foo")
self.assertEqual(child.get_path("en"), "foo/foo")
self.assertEqual(parent.get_absolute_url("en"), "/en/foo/")
self.assertEqual(child.get_absolute_url("en"), "/en/foo/foo/")
# Verify we can access both pages
response = self.client.get(parent.get_absolute_url("en"))
self.assertEqual(response.status_code, 200)
response = self.client.get(child.get_absolute_url("en"))
self.assertEqual(response.status_code, 200)
def test_validate_url_uniqueness_child_page_sibling_conflict(self):
"""A child page's natural path is checked for conflicts with its siblings (#8902)"""
site = _get_current_site()
parent = create_page("Parent", "nav_playground.html", "en", slug="parent")
create_page("A", "nav_playground.html", "en", parent=parent, slug="a")
page_b = create_page("B", "nav_playground.html", "en", parent=parent, slug="b")
# B may keep its own path
self.assertTrue(validate_url_uniqueness(site, path="parent/b", language="en", exclude_page=page_b))
# but not take A's path
with self.assertRaises(ValidationError):
validate_url_uniqueness(site, path="parent/a", language="en", exclude_page=page_b)
def test_validate_url_uniqueness_parent_child_same_slug(self):
"""A child with the same slug as its parent has a different path and is valid"""
site = _get_current_site()
parent = create_page("parent", "nav_playground.html", "en", slug="foo")
child = create_page("child", "nav_playground.html", "en", parent=parent, slug="foo")
self.assertTrue(validate_url_uniqueness(site, path="foo/foo", language="en", exclude_page=child))
self.assertTrue(validate_url_uniqueness(site, path="foo", language="en", exclude_page=parent))
def test_validate_url_uniqueness_escapes_html_in_error_message(self):
"""
validate_url_uniqueness raises a ValidationError whose message is
wrapped in mark_safe so it can render an <a> link to the conflicting
page in the admin. Any user-controlled fragments interpolated into
that message MUST be HTML-escaped, otherwise a page title containing
markup like ``<script>...</script>`` would render unescaped wherever
Django renders the validation error (admin form errors, toolbar
notifications, ...).
"""
from django.utils.safestring import SafeString
site = _get_current_site()
evil = "<script>alert('xss')</script>"
# Page A: the existing page that owns the conflicting slug. Its title
# ends up inside the <a>...</a> link in the rendered error message.
conflict_page = create_page(f"conflict {evil}", "nav_playground.html", "en", slug="foo")
# Page B: the page being validated. Its title is interpolated into
# the "current page" portion of the message via the `instance` kwarg.
current_page = create_page(f"current {evil}", "nav_playground.html", "en", slug="bar")
with self.assertRaises(ValidationError) as ctx:
validate_url_uniqueness(
site,
path=conflict_page.get_path("en"),
language="en",
exclude_page=current_page,
)
# ValidationError messages are a list; the validator only raises one.
rendered = ctx.exception.messages[0]
self.assertIsInstance(rendered, SafeString)
# The raw <script> tag must NOT survive into the safe message — neither
# from the conflict page's title (escaped explicitly via django.utils.html.escape)
# nor from the current page's title (interpolated via `instance`).
self.assertNotIn("<script>", rendered)
self.assertNotIn("</script>", rendered)
# Both titles should appear in their HTML-escaped form.
self.assertIn("&lt;script&gt;", rendered)
self.assertIn("&lt;/script&gt;", rendered)
# The legitimate <a> tag the validator builds for the admin link is
# still preserved (it's the only HTML the message is allowed to emit).
self.assertIn('<a href="', rendered)
def test_details_view(self):
"""
Test the details view
"""
superuser = self.get_superuser()
self.assertEqual(Page.objects.all().count(), 0)
with self.login_user_context(superuser):
page = self.create_homepage("test page 1", "nav_playground.html", "en")
response = self.client.get(self.get_pages_root())
self.assertEqual(response.status_code, 200)
page2 = create_page("test page 2", "nav_playground.html", "en", parent=page)
homepage = Page.objects.get_home()
self.assertEqual(homepage.get_slug("en"), "test-page-1")
self.assertEqual(page2.get_absolute_url(), "/en/test-page-2/")
response = self.client.get(page2.get_absolute_url())
self.assertEqual(response.status_code, 200)
def test_move_page_regression_left_to_right_5752(self):
# ref: https://github.com/divio/django-cms/issues/5752
# Tests tree integrity when moving sibling pages from left
# to right under the same parent.
home = create_page("Home", "nav_playground.html", "en")
alpha = create_page(
"Alpha",
"nav_playground.html",
"en",
parent=home,
)
beta = create_page(
"Beta",
"nav_playground.html",
"en",
parent=home,
)
beta.move_page(alpha, position="left")
# Draft
self.assertEqual(home.path, "0001")
self.assertEqual(beta.path, "00010001")
self.assertEqual(alpha.path, "00010002")
def test_move_page_regression_right_to_left_5752(self):
# ref: https://github.com/divio/django-cms/issues/5752
# Tests tree integrity when moving sibling pages from right
# to left under the same parent.
home = create_page("Home", "nav_playground.html", "en")
alpha = create_page(
"Alpha",
"nav_playground.html",
"en",
parent=home,
)
beta = create_page(
"Beta",
"nav_playground.html",
"en",
parent=home,
)
beta.move_page(alpha, position="left")
alpha.refresh_from_db()
beta.refresh_from_db()
# Draft
self.assertEqual(home.path, "0001")
self.assertEqual(beta.path, "00010001")
self.assertEqual(alpha.path, "00010002")
def test_move_page_regression_5640(self):
# ref: https://github.com/divio/django-cms/issues/5640
alpha = create_page("Alpha", "nav_playground.html", "en")
beta = create_page("Beta", "nav_playground.html", "en")
alpha.move_page(beta, position="right")
self.assertEqual(beta.path, "0002")
self.assertEqual(alpha.path, "0003")
def test_move_page_regression_nested_5640(self):
# ref: https://github.com/divio/django-cms/issues/5640
alpha = create_page("Alpha", "nav_playground.html", "en")
beta = create_page("Beta", "nav_playground.html", "en")
gamma = create_page("Gamma", "nav_playground.html", "en")
delta = create_page("Delta", "nav_playground.html", "en")
theta = create_page("Theta", "nav_playground.html", "en")
beta.move_page(alpha, position="last-child")
gamma.move_page(beta, position="last-child")
delta.move_page(gamma, position="last-child")
theta.move_page(delta, position="last-child")
tree = [
(alpha, "0001"),
(beta, "00010001"),
(gamma, "000100010001"),
(delta, "0001000100010001"),
(theta, "00010001000100010001"),
]
for page, path in tree:
self.assertEqual(page.path, path)
def test_move_page_inherit(self):
parent = create_page("Parent", "col_three.html", "en")
child = create_page("Child", constants.TEMPLATE_INHERITANCE_MAGIC, "en", parent=parent)
self.assertEqual(child.get_template(), parent.get_template())
child.move_page(parent, "left")
child = Page.objects.get(pk=child.pk)
self.assertEqual(child.get_template(), parent.get_template())
def test_add_placeholder(self):
# create page
page = create_page("Add Placeholder", "nav_playground.html", "en", position="last-child", in_navigation=True)
page.update_translations(template="add_placeholder.html")
url = page.get_absolute_url()
response = self.client.get(url)
self.assertEqual(200, response.status_code)
path = os.path.join(settings.TEMPLATES[0]["DIRS"][0], "add_placeholder.html")
with open(path) as fobj:
old = fobj.read()
try:
new = old.replace("<!-- SECOND_PLACEHOLDER -->", "{% placeholder second_placeholder %}")
with open(path, "w") as fobj:
fobj.write(new)
response = self.client.get(url)
self.assertEqual(200, response.status_code)
finally:
with open(path, "w") as fobj:
fobj.write(old)
def test_sitemap_login_required_pages(self):
"""
Test that CMSSitemap object contains only published,public (login_required=False) pages
"""
create_page("page", "nav_playground.html", "en", login_required=True, in_navigation=True)
self.assertEqual(len(CMSSitemap().items()), 0)
def test_sitemap_includes_last_modification_date(self):
one_day_ago = tz_now() - datetime.timedelta(days=1)
page = create_page("page", "nav_playground.html", "en")
page.creation_date = one_day_ago
page.save()
sitemap = CMSSitemap()
self.assertEqual(len(sitemap.items()), 1)
actual_last_modification_time = sitemap.lastmod(sitemap.items()[0])
self.assertTrue(actual_last_modification_time > one_day_ago)
def test_sitemap_uses_publication_date_when_later_than_modification(self):
now = tz_now()
now -= datetime.timedelta(microseconds=now.microsecond)
one_day_ago = now - datetime.timedelta(days=1)
page = create_page("page", "nav_playground.html", "en")
page.get_content_obj("en")
page.creation_date = one_day_ago
page.changed_date = one_day_ago
page.save()
sitemap = CMSSitemap()
actual_last_modification_time = sitemap.lastmod(sitemap.items().first())
self.assertEqual(actual_last_modification_time.date(), now.date())
def test_templates(self):
"""
Test the inheritance magic for templates
"""
parent = create_page("parent", "nav_playground.html", "en")
child = create_page("child", "nav_playground.html", "en", parent=parent)
grand_child = create_page("grand child", "nav_playground.html", "en", parent=child)
child2 = create_page("child2", "col_two.html", "en", parent=parent)
grand_child2 = create_page("grand child2", "nav_playground.html", "en", parent=child2)
child_content = child.get_content_obj("en")
child2_content = child2.get_content_obj("en")
child_content.template = constants.TEMPLATE_INHERITANCE_MAGIC
grand_child_content = grand_child.get_content_obj("en")
grand_child_content.template = constants.TEMPLATE_INHERITANCE_MAGIC
child_content.save()
grand_child_content.save()
grand_child2_content = grand_child2.get_content_obj("en")
grand_child2_content.template = constants.TEMPLATE_INHERITANCE_MAGIC
grand_child2_content.save()
self.assertFalse(hasattr(grand_child_content, "_template_cache"))
with self.assertNumQueries(0):
self.assertEqual(child_content.template, constants.TEMPLATE_INHERITANCE_MAGIC)
with self.assertNumQueries(2):
self.assertEqual(parent.get_template_name(), grand_child.get_template_name())
# test template cache
with self.assertNumQueries(0):
grand_child.get_template()
self.assertFalse(hasattr(grand_child2_content, "_template_cache"))
with self.assertNumQueries(1):
self.assertEqual(child2_content.template, "col_two.html")
self.assertEqual(child2.get_template_name(), grand_child2.get_template_name())
# test template cache
with self.assertNumQueries(0):
grand_child2.get_template()
parent_content = parent.get_content_obj("en")
parent_content.template = constants.TEMPLATE_INHERITANCE_MAGIC
parent_content.save()
self.assertEqual(parent_content.template, constants.TEMPLATE_INHERITANCE_MAGIC)
self.assertEqual(parent.get_template(), get_cms_setting("TEMPLATES")[0][0])
self.assertEqual(parent.get_template_name(), get_cms_setting("TEMPLATES")[0][1])
def test_delete_with_plugins(self):
"""
Check that plugins and placeholders get correctly deleted when we delete
a page!
"""
Text = self.get_plugin_model("TextPlugin")
home = create_page("home", "nav_playground.html", "en")
page = create_page("page", "nav_playground.html", "en")
placeholder = page.get_placeholders("en")[0]
plugin_base = CMSPlugin.objects.create(
plugin_type="TextPlugin", placeholder=placeholder, position=1, language=settings.LANGUAGES[0][0]
)
plugin = Text(body="")
plugin_base.set_base_attr(plugin)
plugin.save()
self.assertEqual(CMSPlugin.objects.count(), 1)
self.assertEqual(Text.objects.count(), 1)
self.assertTrue(Placeholder.objects.count() > 2)
superuser = self.get_superuser()
home_pl_count = home.get_placeholders("en").count()
page_pl_count = page.get_placeholders("en").count()
expected_pl_count = Placeholder.objects.count() - (home_pl_count + page_pl_count)
with self.login_user_context(superuser):
# Delete page
self.client.post(self.get_admin_url(Page, "delete", page.pk), {"post": "yes"})
with self.login_user_context(superuser):
# Delete home page
self.client.post(self.get_admin_url(Page, "delete", home.pk), {"post": "yes"})
self.assertEqual(CMSPlugin.objects.count(), 0)
self.assertEqual(Text.objects.count(), 0)
self.assertEqual(Placeholder.objects.exclude(slot="clipboard").count(), expected_pl_count)
self.assertEqual(Page.objects.count(), 0)
def test_get_page_from_request_nopage(self):
request = self.get_request("/")
page = get_page_from_request(request)
self.assertEqual(page, None)
def test_get_page_from_request_with_page_404(self):
create_page("page", "nav_playground.html", "en")
request = self.get_request("/does-not-exist/")
found_page = get_page_from_request(request)
self.assertEqual(found_page, None)
def test_get_page_without_final_slash(self):
root = create_page("root", "nav_playground.html", "en", slug="root")
create_page("page", "nav_playground.html", "en", slug="page", parent=root)
request = self.get_request("/en/root/page")
found_page = get_page_from_request(request)
self.assertIsNotNone(found_page)
def test_page_urls(self):
page1 = self.create_homepage("test page 1", "nav_playground.html", "en")
page2 = create_page("test page 2", "nav_playground.html", "en", parent=page1)
page3 = create_page("test page 3", "nav_playground.html", "en", parent=page2)
page4 = create_page("test page 4", "nav_playground.html", "en")
page5 = create_page("test page 5", "nav_playground.html", "en", parent=page4)
page1.refresh_from_db()
page2.refresh_from_db()
page3.refresh_from_db()
page4.refresh_from_db()
page5.refresh_from_db()
self.assertEqual(page3.parent_id, page2.pk)
self.assertEqual(page2.parent_id, page1.pk)
self.assertEqual(page5.parent_id, page4.pk)
self.assertEqual(page1.get_absolute_url(), self.get_pages_root() + "")
self.assertEqual(page2.get_absolute_url(), self.get_pages_root() + "test-page-2/")
self.assertEqual(page3.get_absolute_url(), self.get_pages_root() + "test-page-2/test-page-3/")
self.assertEqual(page4.get_absolute_url(), self.get_pages_root() + "test-page-4/")
self.assertEqual(page5.get_absolute_url(), self.get_pages_root() + "test-page-4/test-page-5/")
page3 = self.move_page(page3, page1)
self.assertEqual(page3.get_absolute_url(), self.get_pages_root() + "test-page-3/")
page3.refresh_from_db()
page2.refresh_from_db()
page5.refresh_from_db()
page5 = self.move_page(page5, page2)
self.assertEqual(page5.get_absolute_url(), self.get_pages_root() + "test-page-2/test-page-5/")
page3.refresh_from_db()
page4.refresh_from_db()
page3 = self.move_page(page3, page4)
self.assertEqual(page3.get_absolute_url(), self.get_pages_root() + "test-page-4/test-page-3/")
def test_page_and_title_repr(self):
non_saved_page = Page()
self.assertIsNone(non_saved_page.pk)
self.assertIn("id=None", repr(non_saved_page))
saved_page = create_page("test saved page", "nav_playground.html", "en")
self.assertIsNotNone(saved_page.pk)
self.assertIn(f"id={saved_page.pk}", repr(saved_page))
non_saved_title = PageContent()
self.assertIsNone(non_saved_title.pk)
self.assertIn("id=None", repr(non_saved_title))
saved_content = saved_page.get_content_obj()
self.assertIsNotNone(saved_content.pk)
self.assertIn(f"id={saved_content.pk}", repr(saved_content))
def test_page_overwrite_urls(self):
page1 = self.create_homepage("test page 1", "nav_playground.html", "en")
page2 = create_page("test page 2", "nav_playground.html", "en", parent=page1)
page3 = create_page(
"test page 3", "nav_playground.html", "en", parent=page2, overwrite_url="i-want-another-url"
)
superuser = self.get_superuser()
self.assertEqual(page2.get_absolute_url(), self.get_pages_root() + "test-page-2/")
self.assertEqual(page3.get_absolute_url(), self.get_pages_root() + "i-want-another-url/")
endpoint = self.get_page_change_uri("en", page2)
with self.login_user_context(superuser):
data = {"title": "test page 2", "slug": "page-test-2", "template": "nav_playground.html"}
response = self.client.post(endpoint, data)
self.assertRedirects(response, self.get_pages_admin_list_uri("en"))
page2 = Page.objects.get(pk=page2.pk)
page3 = Page.objects.get(pk=page3.pk)
self.assertEqual(page2.get_absolute_url(), self.get_pages_root() + "page-test-2/")
self.assertEqual(page3.get_absolute_url(), self.get_pages_root() + "i-want-another-url/")
# tests a bug found in 2.2 where saving an ancestor page
# wiped out the overwrite_url for child pages
page2.save()
self.assertEqual(page3.get_absolute_url(), self.get_pages_root() + "i-want-another-url/")
def test_slug_url_overwrite_clash(self):
"""Tests if a URL-Override clashes with a normal page url"""
site = _get_current_site()
with self.settings(CMS_PERMISSION=False):
create_page("home", "nav_playground.html", "en")
bar = create_page("bar", "nav_playground.html", "en")
foo = create_page("foo", "nav_playground.html", "en")
# Tests to assure is_valid_url is ok on plain pages
self.assertTrue(
validate_url_uniqueness(
site,
path=bar.get_path("en"),
language="en",
exclude_page=bar,
)
)
self.assertTrue(
validate_url_uniqueness(
site,
path=foo.get_path("en"),
language="en",
exclude_page=foo,
)
)
with self.assertRaises(IntegrityError):
foo.update_urls("en", managed=False, path="bar")
def test_valid_url_multisite(self):
site1 = Site.objects.get_current()
site3 = Site.objects.create(domain="sample3.com", name="sample3.com")
home = create_page("home", "nav_playground.html", "de", site=site1)
bar = create_page("bar", "nav_playground.html", "de", slug="bar", parent=home, site=site1)
home_s3 = create_page("home", "nav_playground.html", "de", site=site3)
bar_s3 = create_page("bar", "nav_playground.html", "de", slug="bar", parent=home_s3, site=site3)
self.assertTrue(
validate_url_uniqueness(
site1,
path=bar.get_path("de"),
language="de",
exclude_page=bar,
)
)
self.assertTrue(
validate_url_uniqueness(
site3,
path=bar_s3.get_path("de"),
language="de",
exclude_page=bar_s3,
)
)
def test_home_slug_not_accessible(self):
with self.settings(CMS_PERMISSION=False):
page = self.create_homepage("page", "nav_playground.html", "en")
self.assertEqual(page.get_absolute_url("en"), "/en/")
resp = self.client.get("/en/")
self.assertEqual(resp.status_code, HttpResponse.status_code)
resp = self.client.get("/en/page/")
self.assertEqual(resp.status_code, HttpResponseNotFound.status_code)
def test_plugin_loading_queries(self):
with self.settings(
CMS_TEMPLATES=(("placeholder_tests/base.html", "tpl"),),
):
page = create_page("home", "placeholder_tests/base.html", "en", slug="home")
page.page_content_cache["en"] = page.pagecontent_set.get(language="en")
placeholders = list(page.get_placeholders("en"))
for i, placeholder in enumerate(placeholders):
for j in range(5):
add_plugin(placeholder, "TextPlugin", "en", body="text-%d-%d" % (i, j))
add_plugin(placeholder, "LinkPlugin", "en", name="link-%d-%d" % (i, j))
# trigger the apphook query so that it doesn't get in our way
reverse("pages-root")
# trigger the get_languages query so it doesn't get in our way
context = self.get_context(page=page)
context["request"].current_page.get_languages()
renderer = self.get_content_renderer(context["request"])
with self.assertNumQueries(4):
for i, placeholder in enumerate(placeholders):
content = renderer.render_page_placeholder(
placeholder.slot,
context,
inherit=False,
)
for j in range(5):
self.assertIn("text-%d-%d" % (i, j), content)
self.assertIn("link-%d-%d" % (i, j), content)
def test_xframe_options_allow(self):
"""Test that no X-Frame-Options is set when page's xframe_options is set to allow"""
page = create_page(
title="home",
template="nav_playground.html",
language="en",
slug="home",
xframe_options=constants.X_FRAME_OPTIONS_ALLOW,
)
resp = self.client.get(page.get_absolute_url("en"))
self.assertEqual(resp.get("X-Frame-Options"), None)
def test_xframe_options_sameorigin(self):
"""Test that X-Frame-Options is 'SAMEORIGIN' when xframe_options is set to origin"""
page = create_page(
title="home",
template="nav_playground.html",
language="en",
slug="home",
xframe_options=constants.X_FRAME_OPTIONS_SAMEORIGIN,
)
resp = self.client.get(page.get_absolute_url("en"))
self.assertEqual(resp.get("X-Frame-Options"), "SAMEORIGIN")
def test_xframe_options_deny(self):
"""Test that X-Frame-Options is 'DENY' when xframe_options is set to deny"""
page = create_page(
title="home",
template="nav_playground.html",
language="en",
slug="home",
xframe_options=constants.X_FRAME_OPTIONS_DENY,
)
resp = self.client.get(page.get_absolute_url("en"))
self.assertEqual(resp.get("X-Frame-Options"), "DENY")
def test_xframe_options_inherit_with_parent(self):
"""Test that X-Frame-Options is set to parent page's setting when inherit is set"""
parent = create_page(
title="home",
template="nav_playground.html",
language="en",
slug="home",
xframe_options=constants.X_FRAME_OPTIONS_DENY,
)
child1 = create_page(
title="subpage",
template="nav_playground.html",
language="en",
slug="subpage",
parent=parent,
xframe_options=constants.X_FRAME_OPTIONS_INHERIT,
)
child2 = create_page(
title="subpage",
template="nav_playground.html",
language="en",
slug="subpage",
parent=child1,
xframe_options=constants.X_FRAME_OPTIONS_ALLOW,
)
child3 = create_page(
title="subpage",
template="nav_playground.html",
language="en",
slug="subpage",
parent=child2,
xframe_options=constants.X_FRAME_OPTIONS_INHERIT,
)
resp = self.client.get(parent.get_absolute_url("en"))
self.assertEqual(resp.get("X-Frame-Options"), "DENY")
resp = self.client.get(child1.get_absolute_url("en"))
self.assertEqual(resp.get("X-Frame-Options"), "DENY")
resp = self.client.get(child2.get_absolute_url("en"))
self.assertEqual(resp.get("X-Frame-Options"), None)
resp = self.client.get(child3.get_absolute_url("en"))
self.assertEqual(resp.get("X-Frame-Options"), None)
def test_top_level_page_inherited_xframe_options_are_applied(self):
MIDDLEWARE = settings.MIDDLEWARE + ["django.middleware.clickjacking.XFrameOptionsMiddleware"]
with self.settings(MIDDLEWARE=MIDDLEWARE):
page = create_page("test page 1", "nav_playground.html", "en")
resp = self.client.get(page.get_absolute_url("en"))
self.assertEqual(resp.get("X-Frame-Options"), "SAMEORIGIN")
def test_xframe_options_with_cms_page_cache_and_clickjacking_middleware(self):
# Refs: 6346
if getattr(settings, "MIDDLEWARE", None):
override = {
"MIDDLEWARE": settings.MIDDLEWARE
+ [
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
}
else:
override = {
"MIDDLEWARE_CLASSES": settings.MIDDLEWARE_CLASSES
+ [
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
}
override["CMS_PAGE_CACHE"] = True
with self.settings(**override):
page = create_page(
"test page 1",
"nav_playground.html",
"en",
xframe_options=constants.X_FRAME_OPTIONS_ALLOW,
)
# Normal response from render_page
resp = self.client.get(page.get_absolute_url("en"))
self.assertEqual(resp.get("X-Frame-Options"), None)
# Response from page cache
resp = self.client.get(page.get_absolute_url("en"))
self.assertEqual(resp.get("X-Frame-Options"), None)
def test_xframe_options_inherit_with_cms_page_cache_and_clickjacking_middleware(self):
# A cached "Inherit" page must still receive the clickjacking
# middleware's X-Frame-Options header. Regression: the cached copy used
# to be served with xframe_options_exempt=True, which suppressed the
# middleware and dropped the header, leaving cached inherit pages
# framable while the uncached copy was protected.
if getattr(settings, "MIDDLEWARE", None):
override = {
"MIDDLEWARE": settings.MIDDLEWARE
+ [
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
}
else:
override = {
"MIDDLEWARE_CLASSES": settings.MIDDLEWARE_CLASSES
+ [
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
}
override["CMS_PAGE_CACHE"] = True
override["X_FRAME_OPTIONS"] = "SAMEORIGIN"
with self.settings(**override):
page = create_page(
"inherit page",
"nav_playground.html",
"en",
xframe_options=constants.X_FRAME_OPTIONS_INHERIT,
)
# Fresh response from render_page: the middleware adds the site
# default because the page defers to it (inherit).
resp = self.client.get(page.get_absolute_url("en"))
self.assertEqual(resp.get("X-Frame-Options"), "SAMEORIGIN")
# Response from page cache must carry the same protection.
resp = self.client.get(page.get_absolute_url("en"))
self.assertEqual(resp.get("X-Frame-Options"), "SAMEORIGIN")
def test_page_cache_basic(self):
"""
Test that the page cache works in its basic form
"""
page = self.create_homepage("page", "nav_playground.html", "en")
page_url = page.get_absolute_url()
request = self.get_request(page_url)
# First request - should set cache
response = self.client.get(page_url)
self.assertEqual(response.status_code, 200)
cached_data = get_page_cache(request)
self.assertIsNotNone(cached_data)
# Verify cache contents
cached_content, cached_headers, expires, xframe_options_exempt = cached_data
self.assertEqual(cached_content, response.content)
def test_page_cache_language_handling(self):
"""
Test that the page cache properly handles different languages
"""
page = self.create_homepage("page", "nav_playground.html", "en")
create_page_content("fr", "french home", page)
# Get the page in English
en_url = page.get_absolute_url(language="en")
en_request = self.get_request(en_url)
en_request.LANGUAGE_CODE = "en"
en_response = self.client.get(en_url)
self.assertEqual(en_response.status_code, 200)
# Get the page in French
fr_url = page.get_absolute_url(language="fr")
fr_request = self.get_request(fr_url)
fr_request.LANGUAGE_CODE = "fr"
fr_response = self.client.get(fr_url)
self.assertEqual(fr_response.status_code, 200)
# Verify we have different cache entries for each language
en_cached = get_page_cache(en_request)
fr_cached = get_page_cache(fr_request)
self.assertIsNotNone(en_cached)
self.assertIsNotNone(fr_cached)
self.assertNotEqual(en_cached[0], fr_cached[0]) # Different content for different languages
def test_page_cache_language_fallback(self):
"""
Test that the page cache works correctly with language fallbacks:
- No cache entry should exist for non-existent language versions
- The fallback language version should be cached
"""
page = self.create_homepage("page", "nav_playground.html", "en")
# Request in German (which doesn't exist)
de_url = page.get_absolute_url(language="de")
de_request = self.get_request(de_url)
de_request.LANGUAGE_CODE = "de"
response = self.client.get(de_url)
self.assertEqual(response.status_code, 302) # Should redirect to English
self.assertTrue(response["Location"].startswith("/en/")) # Redirects to English version
# German version should NOT be cached since it doesn't exist
cached_data = get_page_cache(de_request)
self.assertIsNone(cached_data, "Non-existent German version should not be cached")
# English version should be cached after we follow the redirect
en_request = self.get_request(response["Location"])
en_request.LANGUAGE_CODE = "en"
en_response = self.client.get(response["Location"])
self.assertEqual(en_response.status_code, 200)
en_cached = get_page_cache(en_request)
self.assertIsNotNone(en_cached, "Fallback English version should be cached")
def test_page_objects_search(self):
"""
Test the search method of Page.objects
"""
page1 = create_page("test page 1", "nav_playground.html", "en", slug="test-page-1")
create_page_content("de", "some random content", page1)
create_page("another page", "nav_playground.html", "en", slug="another-page")
create_page_content("fr", "some more random content", page1)
# Search by title
results = Page.objects.search("test")
self.assertEqual(len(results), 1)
self.assertEqual(results[0].pk, page1.pk)
# Search by content
results = Page.objects.search("more random")
self.assertEqual(len(results), 1)
self.assertEqual(results[0].pk, page1.pk)
# Search by content and language
results = Page.objects.search("more random", language="fr")
self.assertEqual(len(results), 1)
self.assertEqual(results[0].pk, page1.pk)
# Search with no results
results = Page.objects.search("nonexistent")
self.assertEqual(len(results), 0)
# Search with language that has no content
results = Page.objects.search("more random", language="en")
self.assertEqual(len(results), 0)
class PageTreeTests(CMSTestCase):
def test_rename_node(self):
superuser = self.get_superuser()
create_page("grandpa", "nav_playground.html", "en", slug="home")
parent = create_page("parent", "nav_playground.html", "en", slug="parent")
child = create_page("child", "nav_playground.html", "en", slug="child", parent=parent)
endpoint = self.get_page_change_uri("en", parent)
with self.login_user_context(superuser):
data = {"title": "parent", "slug": "father", "template": "nav_playground.html"}
response = self.client.post(endpoint, data)
self.assertRedirects(response, self.get_pages_admin_list_uri("en"))
child = Page.objects.get(pk=child.pk)
self.assertEqual(child.get_absolute_url(language="en"), "/en/father/child/")
def test_rename_node_alters_descendants(self):
superuser = self.get_superuser()
create_page("grandpa", "nav_playground.html", "en", slug="home")
parent = create_page("parent", "nav_playground.html", "en", slug="parent")
child = create_page("child", "nav_playground.html", "en", slug="child", parent=parent)
grandchild_1 = create_page("grandchild-1", "nav_playground.html", "en", slug="grandchild-1", parent=child)
child.refresh_from_db()
grandchild_2 = create_page("grandchild-2", "nav_playground.html", "en", slug="grandchild-2", parent=child)
child.refresh_from_db()
grandchild_3 = create_page("grandchild-3", "nav_playground.html", "en", slug="grandchild-3", parent=child)
endpoint = self.get_page_change_uri("en", parent)
with self.login_user_context(superuser):
data = {"title": "parent", "slug": "father", "template": "nav_playground.html"}
response = self.client.post(endpoint, data)
self.assertRedirects(response, self.get_pages_admin_list_uri("en"))
self.assertEqual(grandchild_1.get_absolute_url(language="en"), "/en/father/child/grandchild-1/")
self.assertEqual(grandchild_2.get_absolute_url(language="en"), "/en/father/child/grandchild-2/")
self.assertEqual(grandchild_3.get_absolute_url(language="en"), "/en/father/child/grandchild-3/")
def test_move_node(self):
home = create_page("grandpa", "nav_playground.html", "en", slug="home")
parent = create_page("parent", "nav_playground.html", "en", slug="parent")
child = create_page("child", "nav_playground.html", "en", slug="child", parent=home)
child.move_page(parent)
child.refresh_from_db()
self.assertEqual(child.get_absolute_url(language="en"), "/en/parent/child/")
class PageContentTests(CMSTestCase):
def setUp(self):
self.page = create_page("english-page", "nav_playground.html", "en")
self.german_content = create_page_content("de", "german content", self.page)
self.english_content = self.page.get_content_obj("en")
def test_get_content_obj(self):
"""
Cache partially populated, if no hit it should try to populate it
"""
del self.page.page_content_cache["de"]
german_content = self.page.get_content_obj("de")
self.assertEqual(german_content.pk, self.german_content.pk)
def test_page_content_manager(self):
from cms.models.managers import ContentAdminQuerySet
# check if admin_manager exists
self.assertTrue(isinstance(PageContent.admin_manager, models.Manager))
self.assertTrue(isinstance(PageContent.admin_manager.none(), ContentAdminQuerySet))
# setup created to page contents for self.page. Test if admin_manager sees them
self.assertEqual(PageContent.admin_manager.filter(page=self.page).count(), 2)
# test if the current_content_iterator sees both page contents
self.assertEqual(len(list(PageContent.admin_manager.filter(page=self.page).current_content())), 2)