forked from ungleich-public/dynamicweb
848 lines
32 KiB
Python
848 lines
32 KiB
Python
from unittest.mock import Mock, patch
|
|
|
|
from django import forms
|
|
from django.apps import apps
|
|
from django.apps.registry import Apps
|
|
from django.contrib.sites.models import Site
|
|
from django.core.exceptions import ImproperlyConfigured
|
|
from django.forms.models import ModelForm
|
|
from django.template import TemplateSyntaxError
|
|
from django.test.utils import override_settings
|
|
from django.urls import reverse
|
|
from django.utils.encoding import smart_str
|
|
from django.utils.translation import gettext as _
|
|
|
|
from cms import app_registration
|
|
from cms.api import create_page
|
|
from cms.cms_wizards import cms_page_wizard, cms_subpage_wizard
|
|
from cms.constants import MODAL_HTML_REDIRECT, TEMPLATE_INHERITANCE_MAGIC
|
|
from cms.forms.wizards import CreateCMSPageForm, CreateCMSSubPageForm
|
|
from cms.models import Page, PageContent, UserSettings
|
|
from cms.models.permissionmodels import GlobalPagePermission
|
|
from cms.test_utils.project.backwards_wizards.wizards import wizard
|
|
from cms.test_utils.project.sampleapp.cms_wizards import sample_wizard
|
|
from cms.test_utils.testcases import CMSTestCase, TransactionCMSTestCase
|
|
from cms.toolbar.utils import (
|
|
get_object_edit_url,
|
|
get_object_preview_url,
|
|
)
|
|
from cms.utils.conf import get_cms_setting
|
|
from cms.utils.page_permissions import user_can_add_subpage
|
|
from cms.utils.setup import setup_cms_apps
|
|
from cms.utils.urlutils import admin_reverse
|
|
from cms.wizards.forms import WizardStep1Form, WizardStep2BaseForm, step2_form_factory
|
|
from cms.wizards.helpers import get_entries, get_entry
|
|
from cms.wizards.wizard_base import Wizard
|
|
from cms.wizards.wizard_pool import (
|
|
AlreadyRegisteredException,
|
|
entry_choices,
|
|
wizard_pool,
|
|
)
|
|
|
|
CreateCMSPageForm = step2_form_factory(
|
|
mixin_cls=WizardStep2BaseForm,
|
|
entry_form_class=CreateCMSPageForm,
|
|
)
|
|
|
|
CreateCMSSubPageForm = step2_form_factory(
|
|
mixin_cls=WizardStep2BaseForm,
|
|
entry_form_class=CreateCMSSubPageForm,
|
|
)
|
|
|
|
|
|
class WizardForm(forms.Form):
|
|
pass
|
|
|
|
|
|
class ModelWizardForm(ModelForm):
|
|
class Meta:
|
|
model = UserSettings
|
|
exclude = []
|
|
|
|
|
|
class BadModelForm(ModelForm):
|
|
class Meta:
|
|
pass
|
|
|
|
|
|
class WizardTestMixin:
|
|
page_wizard = None
|
|
title_wizard = None
|
|
|
|
def assertSequencesEqual(self, seq_a, seq_b):
|
|
seq_a = list(seq_a)
|
|
seq_b = list(seq_b)
|
|
zipped = list(zip(seq_a, seq_b, strict=False))
|
|
if len(zipped) < len(seq_a) or len(zipped) < len(seq_b):
|
|
self.fail("Sequence lengths are not the same.")
|
|
for idx, (a, b) in enumerate(zipped):
|
|
if a != b:
|
|
self.fail(f"Sequences differ at index {idx}")
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
# This prevents auto-discovery, which would otherwise occur as soon as
|
|
# tests start, creating unexpected starting conditions.
|
|
wizard_pool._discovered = True
|
|
|
|
class PageWizard(Wizard):
|
|
pass
|
|
|
|
# This is a basic Wizard
|
|
cls.page_wizard = PageWizard(
|
|
title=_("Page"),
|
|
weight=100,
|
|
form=WizardForm,
|
|
model=Page,
|
|
template_name="my_template.html", # This doesn't exist anywhere
|
|
)
|
|
|
|
class SettingsWizard(Wizard):
|
|
pass
|
|
|
|
# This is a Wizard that uses a ModelForm to define the model
|
|
cls.user_settings_wizard = SettingsWizard(
|
|
title=_("UserSettings"),
|
|
weight=200,
|
|
form=ModelWizardForm,
|
|
)
|
|
|
|
class PageContentWizard(Wizard):
|
|
pass
|
|
|
|
# This is a bad wizard definition as it neither defines a model, nor
|
|
# uses a ModelForm that has model defined in Meta
|
|
cls.title_wizard = PageContentWizard(
|
|
title=_("Page"),
|
|
weight=100,
|
|
form=BadModelForm,
|
|
template_name="my_template.html", # This doesn't exist anywhere
|
|
)
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.site = Site.objects.get_current()
|
|
|
|
|
|
class TestWizardBase(WizardTestMixin, TransactionCMSTestCase):
|
|
def test_user_has_add_permission(self):
|
|
# Test does not have permission
|
|
user = self.get_staff_user_with_no_permissions()
|
|
self.assertFalse(self.page_wizard.user_has_add_permission(user))
|
|
|
|
# Test has permission
|
|
user = self.get_superuser()
|
|
self.assertTrue(self.page_wizard.user_has_add_permission(user))
|
|
|
|
def test_get_success_url(self):
|
|
user = self.get_superuser()
|
|
page = create_page(
|
|
title="Sample Page",
|
|
template=TEMPLATE_INHERITANCE_MAGIC,
|
|
language="en",
|
|
created_by=smart_str(user),
|
|
parent=None,
|
|
in_navigation=True,
|
|
)
|
|
url = page.get_absolute_url(language="en")
|
|
self.assertEqual(self.page_wizard.get_success_url(page, language="en"), url)
|
|
|
|
# Now again without a language code
|
|
url = page.get_absolute_url()
|
|
self.assertEqual(self.page_wizard.get_success_url(page), url)
|
|
|
|
def test_get_edit_url(self):
|
|
user = self.get_superuser()
|
|
page = create_page(
|
|
title="Sample Page",
|
|
template=TEMPLATE_INHERITANCE_MAGIC,
|
|
language="en",
|
|
created_by=smart_str(user),
|
|
parent=None,
|
|
in_navigation=True,
|
|
)
|
|
|
|
extension = apps.get_app_config("cms").cms_extension
|
|
|
|
with patch.object(extension, "toolbar_enabled_models", {Page: page}):
|
|
url = self.page_wizard.get_success_url(page, language="en")
|
|
self.assertEqual(url, get_object_edit_url(page, language="en"))
|
|
|
|
def test_get_preview_url(self):
|
|
wizard_preview_mode = Wizard(
|
|
title=_("Page"),
|
|
weight=100,
|
|
form=WizardForm,
|
|
model=Page,
|
|
template_name="my_template.html", # This doesn't exist anywhere
|
|
edit_mode_on_success=False,
|
|
)
|
|
|
|
user = self.get_superuser()
|
|
page = create_page(
|
|
title="Sample Page",
|
|
template=TEMPLATE_INHERITANCE_MAGIC,
|
|
language="en",
|
|
created_by=smart_str(user),
|
|
parent=None,
|
|
in_navigation=True,
|
|
)
|
|
|
|
extension = apps.get_app_config("cms").cms_extension
|
|
|
|
with patch.object(extension, "toolbar_enabled_models", {Page: page}):
|
|
url = wizard_preview_mode.get_success_url(page, language="en")
|
|
self.assertEqual(url, get_object_preview_url(page, language="en"))
|
|
|
|
def test_get_model(self):
|
|
self.assertEqual(self.page_wizard.get_model(), Page)
|
|
self.assertEqual(self.user_settings_wizard.get_model(), UserSettings)
|
|
with self.assertRaises(ImproperlyConfigured):
|
|
self.title_wizard.get_model()
|
|
|
|
def test_endpoint_auth_required(self):
|
|
endpoint = reverse("admin:cms_wizard_create")
|
|
staff_active = self._create_user("staff-active", is_staff=True, is_superuser=False, is_active=True)
|
|
|
|
response = self.client.get(endpoint)
|
|
self.assertEqual(response.status_code, 403)
|
|
|
|
with self.login_user_context(staff_active):
|
|
response = self.client.get(endpoint)
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
|
class TestWizardPool(WizardTestMixin, CMSTestCase):
|
|
def tearDown(self):
|
|
# Clean up in case anything has been removed or added to the
|
|
# registered wizards, so other tests don't have problems
|
|
extension = apps.get_app_config("cms").cms_extension
|
|
# Reset the cached property 'wizards'
|
|
if hasattr(extension, "wizards"):
|
|
del extension.wizards
|
|
configs_with_wizards = [
|
|
app.cms_config for app in app_registration.get_cms_config_apps() if hasattr(app.cms_config, "cms_wizards")
|
|
]
|
|
for config in configs_with_wizards:
|
|
extension.configure_wizards(config)
|
|
# Clean up in case cached apps are different than the defaults
|
|
app_registration.get_cms_extension_apps.cache_clear()
|
|
app_registration.get_cms_config_apps.cache_clear()
|
|
|
|
def test_is_registered_for_registered_wizard(self):
|
|
"""
|
|
Test for backwards compatibility of is_registered when checking
|
|
a registered wizard.
|
|
"""
|
|
from django.apps import apps
|
|
|
|
self.assertTrue(apps.get_app_config("cms").cms_extension.wizards)
|
|
is_registered = wizard_pool.is_registered(cms_page_wizard)
|
|
self.assertTrue(is_registered)
|
|
|
|
def test_is_registered_for_unregistered_wizard(self):
|
|
"""
|
|
Test for backwards compatibility of is_registered when checking
|
|
an unregistered wizard.
|
|
"""
|
|
is_registered = wizard_pool.is_registered(self.page_wizard)
|
|
self.assertFalse(is_registered)
|
|
|
|
def test_unregister_registered_wizard(self):
|
|
"""
|
|
Test for backwards compatibility of the unregister method.
|
|
Removes a wizard from the wizards dict.
|
|
"""
|
|
was_unregistered = wizard_pool.unregister(cms_page_wizard)
|
|
registered_wizards = apps.get_app_config("cms").cms_extension.wizards
|
|
self.assertNotIn(cms_page_wizard.id, registered_wizards)
|
|
self.assertTrue(was_unregistered)
|
|
|
|
def test_unregister_unregistered_wizard(self):
|
|
"""
|
|
Test for backwards compatibility of the unregister method.
|
|
Returns False if wizard not found.
|
|
"""
|
|
was_unregistered = wizard_pool.unregister(self.page_wizard)
|
|
self.assertFalse(was_unregistered)
|
|
|
|
def test_register_already_registered_wizard(self):
|
|
"""
|
|
Test for backwards compatibility of the register method.
|
|
Raises AlreadyRegisteredException if the wizard is already
|
|
registered.
|
|
"""
|
|
with self.assertRaises(AlreadyRegisteredException):
|
|
wizard_pool.register(cms_page_wizard)
|
|
|
|
def test_register_unregistered_wizard(self):
|
|
"""
|
|
Test for backwards compatibility of the register method.
|
|
Adds the wizard to the wizards dict.
|
|
"""
|
|
wizard_pool.register(self.page_wizard)
|
|
registered_wizards = apps.get_app_config("cms").cms_extension.wizards
|
|
self.assertIn(self.page_wizard.id, registered_wizards)
|
|
|
|
@patch("cms.wizards.wizard_pool.get_entry")
|
|
def test_get_entry(self, mocked_get_entry):
|
|
"""
|
|
Test for backwards compatibility of wizard_pool.get_entry.
|
|
Checking we use the new get_entry under the hood.
|
|
"""
|
|
wizard_pool.get_entry(cms_page_wizard)
|
|
mocked_get_entry.assert_called_once()
|
|
|
|
def test_old_registration_still_works(self):
|
|
"""
|
|
Integration-like test checking that if you register your wizard
|
|
by adding wizard_pool.register(wizard) to cms_wizards.py it will
|
|
correctly register the wizard. This ensures backwards
|
|
compatibility.
|
|
"""
|
|
# NOTE: Because of how the override_settings decorator works,
|
|
# we can't use it for this test as the app registry first
|
|
# gets loaded with the default apps and then again with
|
|
# the overridden ones.
|
|
INSTALLED_APPS = [
|
|
"cms",
|
|
"treebeard",
|
|
"cms.test_utils.project.backwards_wizards",
|
|
]
|
|
mocked_apps = Apps(installed_apps=INSTALLED_APPS)
|
|
# clear out app registration cache now that installed apps have
|
|
# changed
|
|
app_registration.get_cms_extension_apps.cache_clear()
|
|
app_registration.get_cms_config_apps.cache_clear()
|
|
# Run the setup with the mocked installed apps. Due to the order
|
|
# of imports and other fun things, apps have to be patched
|
|
# in multiple places. If functions are moved around the code
|
|
# this could get worse.
|
|
with patch("cms.app_registration.apps", mocked_apps):
|
|
with patch("cms.wizards.wizard_pool.apps", mocked_apps):
|
|
setup_cms_apps()
|
|
# Check the wizards built into the cms app and the wizard from
|
|
# the backwards_wizards app have been picked up.
|
|
# The backwards_wizards app does not have a cms_config.py but
|
|
# registers its wizard by adding wizard_pool.register(wizard)
|
|
# to cms_wizards.py which is the old way we want backwards
|
|
# compatibility with.
|
|
cms_app = mocked_apps.get_app_config("cms")
|
|
expected_wizards = {
|
|
cms_page_wizard.id: cms_page_wizard,
|
|
cms_subpage_wizard.id: cms_subpage_wizard,
|
|
wizard.id: wizard,
|
|
}
|
|
self.assertDictEqual(cms_app.cms_extension.wizards, expected_wizards)
|
|
|
|
|
|
class TestPageWizard(WizardTestMixin, CMSTestCase):
|
|
def test_str(self):
|
|
self.assertEqual(str(cms_page_wizard), cms_page_wizard.title)
|
|
|
|
def test_repr(self):
|
|
self.assertIn("cms.cms_wizards.CMSPageWizard", repr(cms_page_wizard))
|
|
self.assertIn(f"id={cms_page_wizard.id}", repr(cms_page_wizard))
|
|
self.assertIn(hex(id(cms_page_wizard)), repr(cms_page_wizard))
|
|
|
|
def test_wizard_create_child_page(self):
|
|
superuser = self.get_superuser()
|
|
|
|
with self.login_user_context(superuser):
|
|
request = self.get_request()
|
|
parent_page = create_page(
|
|
title="Parent",
|
|
template=TEMPLATE_INHERITANCE_MAGIC,
|
|
language="en",
|
|
)
|
|
data = {
|
|
"title": "Child",
|
|
"slug": "child",
|
|
"page_type": None,
|
|
}
|
|
form = CreateCMSSubPageForm(
|
|
data=data,
|
|
wizard_page=parent_page,
|
|
wizard_site=self.site,
|
|
wizard_language="en",
|
|
wizard_request=request,
|
|
)
|
|
self.assertTrue(form.is_valid())
|
|
child_page = form.save()
|
|
|
|
self.assertEqual(child_page.depth, 2)
|
|
self.assertEqual(child_page.parent, parent_page)
|
|
self.assertEqual(child_page.get_title("en"), "Child")
|
|
self.assertEqual(child_page.get_path("en"), "parent/child")
|
|
|
|
def test_wizard_create_atomic(self):
|
|
# Ref: https://github.com/divio/django-cms/issues/5652
|
|
# We'll simulate a scenario where a user creates a page with an
|
|
# invalid template which causes Django to throw an error when the
|
|
# template is scanned for placeholders and thus short circuits the
|
|
# creation mechanism.
|
|
superuser = self.get_superuser()
|
|
|
|
with self.login_user_context(superuser):
|
|
request = self.get_request()
|
|
data = {
|
|
"title": "page 1",
|
|
"slug": "page_1",
|
|
"page_type": None,
|
|
}
|
|
form = CreateCMSPageForm(
|
|
data=data,
|
|
wizard_page=None,
|
|
wizard_site=self.site,
|
|
wizard_language="en",
|
|
wizard_request=request,
|
|
)
|
|
|
|
self.assertTrue(form.is_valid())
|
|
self.assertFalse(Page.objects.filter(pagecontent_set__template=TEMPLATE_INHERITANCE_MAGIC).exists())
|
|
|
|
with patch.object(PageContent, "template_choices", [("col_invalid.html", "notvalid")]):
|
|
self.assertRaises(TemplateSyntaxError, form.save)
|
|
# The template raised an exception which should cause the database to roll back
|
|
# instead of committing a page in a partial state.
|
|
self.assertFalse(Page.objects.filter(pagecontent_set__template=TEMPLATE_INHERITANCE_MAGIC).exists())
|
|
|
|
def test_wizard_content_placeholder_setting(self):
|
|
"""
|
|
Tests that the PageWizard respects the
|
|
CMS_PAGE_WIZARD_CONTENT_PLACEHOLDER setting.
|
|
"""
|
|
superuser = self.get_superuser()
|
|
|
|
with self.login_user_context(superuser):
|
|
request = self.get_request()
|
|
templates = get_cms_setting("TEMPLATES")
|
|
# NOTE, there are 4 placeholders on this template, defined in this
|
|
# order: 'header', 'content', 'sub-content', 'footer'.
|
|
# 'footer' is a static-placeholder.
|
|
templates.append(
|
|
(
|
|
"page_wizard.html",
|
|
"page_wizard.html",
|
|
)
|
|
)
|
|
|
|
settings = {
|
|
"CMS_TEMPLATES": templates,
|
|
"CMS_PAGE_WIZARD_DEFAULT_TEMPLATE": "page_wizard.html",
|
|
"CMS_PAGE_WIZARD_CONTENT_PLACEHOLDER": "sub-content",
|
|
}
|
|
|
|
with override_settings(**settings):
|
|
page = create_page("wizard home", "page_wizard.html", "en")
|
|
content = "<p>sub-content content.</p>"
|
|
data = {
|
|
"title": "page 1",
|
|
"slug": "page_1",
|
|
"page_type": None,
|
|
"content": content,
|
|
}
|
|
form = CreateCMSPageForm(
|
|
data=data,
|
|
wizard_page=page,
|
|
wizard_site=self.site,
|
|
wizard_language="en",
|
|
wizard_request=request,
|
|
)
|
|
self.assertTrue(form.is_valid())
|
|
page = form.save()
|
|
|
|
with self.login_user_context(superuser):
|
|
url = page.get_absolute_url("en")
|
|
expected = f'<div class="sub-content">{content}</div>'
|
|
unexpected = f'<div class="content">{content}</div>'
|
|
response = self.client.get(url)
|
|
self.assertContains(response, expected, status_code=200)
|
|
self.assertNotContains(response, unexpected, status_code=200)
|
|
|
|
def test_wizard_content_placeholder_bad_setting(self):
|
|
"""
|
|
Tests that the PageWizard won't respect a 'bad' setting such as
|
|
targeting a static-placeholder. In this case, will just fail to
|
|
add the content (without error).
|
|
"""
|
|
superuser = self.get_superuser()
|
|
|
|
with self.login_user_context(superuser):
|
|
request = self.get_request()
|
|
templates = get_cms_setting("TEMPLATES")
|
|
# NOTE, there are 4 placeholders on this template, defined in this
|
|
# order: 'header', 'content', 'sub-content', 'footer'.
|
|
# 'footer' is a static-placeholder.
|
|
templates.append(
|
|
(
|
|
"page_wizard.html",
|
|
"page_wizard.html",
|
|
)
|
|
)
|
|
|
|
settings = {
|
|
"CMS_TEMPLATES": templates,
|
|
"CMS_PAGE_WIZARD_DEFAULT_TEMPLATE": "page_wizard.html",
|
|
# This is a bad setting.
|
|
"CMS_PAGE_WIZARD_CONTENT_PLACEHOLDER": "footer",
|
|
}
|
|
|
|
with override_settings(**settings):
|
|
page = create_page("wizard home", "page_wizard.html", "en")
|
|
content = "<p>footer content.</p>"
|
|
data = {
|
|
"title": "page 1",
|
|
"slug": "page_1",
|
|
"page_type": None,
|
|
"content": content,
|
|
}
|
|
form = CreateCMSPageForm(
|
|
data=data,
|
|
wizard_page=page,
|
|
wizard_site=self.site,
|
|
wizard_language="en",
|
|
wizard_request=request,
|
|
)
|
|
self.assertTrue(form.is_valid())
|
|
page = form.save()
|
|
|
|
with self.login_user_context(superuser):
|
|
url = page.get_absolute_url("en")
|
|
response = self.client.get(url)
|
|
self.assertNotContains(response, content, status_code=200)
|
|
|
|
def test_create_page_with_empty_fields(self):
|
|
superuser = self.get_superuser()
|
|
|
|
with self.login_user_context(superuser):
|
|
request = self.get_request()
|
|
data = {
|
|
"title": "",
|
|
"slug": "",
|
|
"page_type": None,
|
|
}
|
|
form = CreateCMSPageForm(
|
|
data=data,
|
|
wizard_page=None,
|
|
wizard_site=self.site,
|
|
wizard_language="en",
|
|
wizard_request=request,
|
|
)
|
|
self.assertFalse(form.is_valid())
|
|
|
|
def test_create_page_with_existing_slug(self):
|
|
superuser = self.get_superuser()
|
|
|
|
with self.login_user_context(superuser):
|
|
request = self.get_request()
|
|
data = {
|
|
"title": "page",
|
|
"slug": "page",
|
|
"page_type": None,
|
|
}
|
|
create_page("page", "nav_playground.html", language="en", slug="page")
|
|
|
|
# slug -> page-1
|
|
form = CreateCMSPageForm(
|
|
data=data,
|
|
wizard_page=None,
|
|
wizard_site=self.site,
|
|
wizard_language="en",
|
|
wizard_request=request,
|
|
)
|
|
self.assertTrue(form.is_valid())
|
|
self.assertTrue(form.save().get_urls().filter(slug="page-2"))
|
|
|
|
# slug -> page-2
|
|
form = CreateCMSPageForm(
|
|
data=data,
|
|
wizard_page=None,
|
|
wizard_site=self.site,
|
|
wizard_language="en",
|
|
wizard_request=request,
|
|
)
|
|
self.assertTrue(form.is_valid())
|
|
self.assertTrue(form.save().get_urls().filter(slug="page-3"))
|
|
|
|
# Now explicitly request the page-2 slug
|
|
data["slug"] = "page-2"
|
|
|
|
# slug -> page-2-2
|
|
form = CreateCMSPageForm(
|
|
data=data,
|
|
wizard_page=None,
|
|
wizard_site=self.site,
|
|
wizard_language="en",
|
|
wizard_request=request,
|
|
)
|
|
self.assertTrue(form.is_valid())
|
|
self.assertTrue(form.save().get_urls().filter(slug="page-2-2"))
|
|
|
|
# slug -> page-2-3
|
|
form = CreateCMSPageForm(
|
|
data=data,
|
|
wizard_page=None,
|
|
wizard_site=self.site,
|
|
wizard_language="en",
|
|
wizard_request=request,
|
|
)
|
|
self.assertTrue(form.is_valid())
|
|
self.assertTrue(form.save().get_urls().filter(slug="page-2-3"))
|
|
|
|
class TestPageWizardSubmission(CMSTestCase):
|
|
def test_page_wizard_submission(self):
|
|
from cms.wizards.helpers import get_entries
|
|
|
|
page = create_page("home", "nav_playground.html", "en")
|
|
entry_id = get_entries()[0].id
|
|
endpoint = admin_reverse("cms_wizard_create")
|
|
data_step1 = {
|
|
"wizard_create_view-current_step": "0",
|
|
"0-entry": entry_id,
|
|
"0-language": "en",
|
|
"0-page": page.pk,
|
|
}
|
|
data_step2 = {
|
|
"wizard_create_view-current_step": "1",
|
|
"1-title": "my test page",
|
|
"1-slug": "my-test-page",
|
|
"1-content": "",
|
|
}
|
|
|
|
with self.login_user_context(self.get_superuser()):
|
|
response = self.client.post(endpoint, data_step1, follow=True)
|
|
self.assertEqual(response.status_code, 200)
|
|
response = self.client.post(endpoint, data_step2)
|
|
|
|
edit_endpoint = get_object_edit_url(Page.objects.exclude(pk=page.pk).last().get_content_obj("en"), "en")
|
|
self.assertContains(response, MODAL_HTML_REDIRECT.format(url=edit_endpoint))
|
|
|
|
def test_subpage_wizard_submission(self):
|
|
"""Regression test for #8697: the subpage wizard must run through both
|
|
steps and create the new page as a child of the origin page.
|
|
|
|
The subpage entry is only offered while the wizard knows the origin
|
|
page, so a regression that drops the page between steps both hides the
|
|
entry (step 0 fails to validate) and detaches the created page.
|
|
"""
|
|
parent = create_page("home", "nav_playground.html", "en")
|
|
entry_id = cms_subpage_wizard.id
|
|
endpoint = admin_reverse("cms_wizard_create")
|
|
data_step1 = {
|
|
"wizard_create_view-current_step": "0",
|
|
"0-entry": entry_id,
|
|
"0-language": "en",
|
|
"0-page": parent.pk,
|
|
}
|
|
data_step2 = {
|
|
"wizard_create_view-current_step": "1",
|
|
"1-title": "my child page",
|
|
"1-slug": "my-child-page",
|
|
"1-content": "",
|
|
}
|
|
|
|
with self.login_user_context(self.get_superuser()):
|
|
# Step 0 must advance to step 1 -- i.e. the subpage entry stays a
|
|
# valid choice because the origin page survives the transition.
|
|
response = self.client.post(endpoint, data_step1, follow=True)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertNotContains(response, "Please choose an option")
|
|
response = self.client.post(endpoint, data_step2)
|
|
|
|
new_page = Page.objects.exclude(pk=parent.pk).get()
|
|
self.assertEqual(new_page.parent_id, parent.pk)
|
|
edit_endpoint = get_object_edit_url(new_page.get_content_obj("en"), "en")
|
|
self.assertContains(response, MODAL_HTML_REDIRECT.format(url=edit_endpoint))
|
|
|
|
|
|
@override_settings(
|
|
CMS_PERMISSION=True,
|
|
CMS_LANGUAGES={
|
|
1: [{"code": "en", "name": "English"}],
|
|
2: [{"code": "en", "name": "English"}],
|
|
},
|
|
)
|
|
class TestWizardSiteIsolation(CMSTestCase):
|
|
def setUp(self):
|
|
self.site = Site.objects.get_current()
|
|
self.other_site, _ = Site.objects.get_or_create(
|
|
pk=2, defaults={"domain": "other.example", "name": "Other site"},
|
|
)
|
|
self.local_parent = create_page("Local parent", "nav_playground.html", "en", site=self.site)
|
|
self.other_parent = create_page("Other parent", "nav_playground.html", "en", site=self.other_site)
|
|
self.other_child = create_page(
|
|
"Other child", "nav_playground.html", "en", site=self.other_site, parent=self.other_parent,
|
|
)
|
|
self.writer = self._create_user(
|
|
"site_writer", is_staff=True, permissions=["add_page", "change_page"],
|
|
)
|
|
permission = GlobalPagePermission.objects.create(user=self.writer, can_add=True, can_change=True)
|
|
permission.sites.add(self.site)
|
|
|
|
def test_cross_site_origin_is_rejected(self):
|
|
self.assertFalse(user_can_add_subpage(self.writer, self.other_parent, site=self.other_site))
|
|
endpoint = admin_reverse("cms_wizard_create")
|
|
page_count = Page.objects.count()
|
|
|
|
with self.login_user_context(self.writer):
|
|
response = self.client.post(endpoint, {
|
|
"wizard_create_view-current_step": "0",
|
|
"0-entry": cms_page_wizard.id,
|
|
"0-language": "en",
|
|
"0-page": self.other_child.pk,
|
|
})
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertIn("page", response.context["wizard"]["form"].errors)
|
|
# Posting step 1 directly must not bypass the rejected origin.
|
|
response = self.client.post(endpoint, {
|
|
"wizard_create_view-current_step": "1",
|
|
"1-title": "Cross-site sibling",
|
|
"1-slug": "cross-site-sibling",
|
|
})
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
self.assertEqual(Page.objects.count(), page_count)
|
|
|
|
def test_same_site_subpage_submission(self):
|
|
endpoint = admin_reverse("cms_wizard_create")
|
|
with self.login_user_context(self.writer):
|
|
response = self.client.post(endpoint, {
|
|
"wizard_create_view-current_step": "0",
|
|
"0-entry": cms_subpage_wizard.id,
|
|
"0-language": "en",
|
|
"0-page": self.local_parent.pk,
|
|
})
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(response.context["wizard"]["steps"].current, "1")
|
|
response = self.client.post(endpoint, {
|
|
"wizard_create_view-current_step": "1",
|
|
"1-title": "Local child",
|
|
"1-slug": "local-child",
|
|
})
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
child = Page.objects.get(pagecontent_set__title="Local child")
|
|
self.assertEqual(child.site_id, self.site.pk)
|
|
self.assertEqual(child.parent_id, self.local_parent.pk)
|
|
|
|
def test_cross_site_parent_is_rejected_by_creation_forms(self):
|
|
# Tree integrity must also hold for direct form use, including
|
|
# superusers and projects without CMS per-page permissions.
|
|
with self.login_user_context(self.get_superuser()):
|
|
request = self.get_request()
|
|
for permissions_enabled in (True, False):
|
|
for form_class, origin in (
|
|
(CreateCMSPageForm, self.other_child),
|
|
(CreateCMSSubPageForm, self.other_parent),
|
|
):
|
|
with self.subTest(permissions=permissions_enabled, form=form_class):
|
|
with override_settings(CMS_PERMISSION=permissions_enabled):
|
|
form = form_class(
|
|
data={"title": "Cross-site page", "slug": "cross-site-page"},
|
|
wizard_page=origin,
|
|
wizard_site=self.site,
|
|
wizard_language="en",
|
|
wizard_request=request,
|
|
)
|
|
self.assertFalse(form.is_valid())
|
|
self.assertIn("parent_page", form.errors)
|
|
|
|
|
|
class TestWizardHelpers(CMSTestCase):
|
|
def setUp(self):
|
|
# The results of get_cms_extension_apps and get_cms_config_apps
|
|
# are cached. Clear this cache because installed apps change
|
|
# between tests and therefore unlike in a live environment,
|
|
# results of this function can change between tests
|
|
app_registration.get_cms_extension_apps.cache_clear()
|
|
app_registration.get_cms_config_apps.cache_clear()
|
|
|
|
def test_get_entries_orders_by_weight(self):
|
|
"""
|
|
The get_entries function returns the registered wizards
|
|
ordered by weight.
|
|
"""
|
|
# The test setup registers two wizards from cms itself
|
|
# (cms_page_wizard and cms_subpage_wizard) and one from
|
|
# test_utils.project.sampleapp (sample_wizard)
|
|
# We know these are definitely being ordered by weight if
|
|
# sample_wizard is in the middle because app registration
|
|
# would first add the wizards from cms to a list and then add
|
|
# those from sampleapp, so the sampleapp wizard could not
|
|
# be in the middle by default
|
|
expected = [cms_page_wizard, sample_wizard, cms_subpage_wizard]
|
|
entries = get_entries()
|
|
self.assertListEqual(entries, expected)
|
|
|
|
def test_get_entry_returns_wizard_by_id(self):
|
|
"""
|
|
The get_entry function returns the wizard when a wizard id is
|
|
supplied.
|
|
"""
|
|
entry = get_entry(sample_wizard.id)
|
|
self.assertEqual(entry, sample_wizard)
|
|
|
|
|
|
class TestEntryChoices(CMSTestCase):
|
|
def test_generates_choices_in_weighted_order(self):
|
|
"""
|
|
The entry_choices function returns the wizards ordered by weight
|
|
"""
|
|
user = self.get_superuser()
|
|
page = create_page("home", "nav_playground.html", "en")
|
|
wizard_choices = [option for option in entry_choices(user, page)]
|
|
expected = [
|
|
(cms_page_wizard.id, cms_page_wizard.title),
|
|
(sample_wizard.id, sample_wizard.title),
|
|
(cms_subpage_wizard.id, cms_subpage_wizard.title),
|
|
]
|
|
self.assertListEqual(wizard_choices, expected)
|
|
|
|
@patch.object(cms_page_wizard, "user_has_add_permission", Mock(return_value=False))
|
|
def test_doesnt_generate_choice_if_user_doesnt_have_permission(self):
|
|
"""
|
|
The entry_choices function only returns the wizards that the
|
|
user has permissions for
|
|
"""
|
|
user = self.get_superuser()
|
|
page = create_page("home", "nav_playground.html", "en")
|
|
wizard_choices = [option for option in entry_choices(user, page)]
|
|
expected = [
|
|
# Missing cms_page_wizard entry
|
|
(sample_wizard.id, sample_wizard.title),
|
|
(cms_subpage_wizard.id, cms_subpage_wizard.title),
|
|
]
|
|
self.assertListEqual(wizard_choices, expected)
|
|
|
|
def test_wizard_step1_uses_selected_site_for_root_page_entries(self):
|
|
site1 = Site.objects.get_current()
|
|
site2 = Site.objects.create(domain="second.example.com", name="Second", pk=2)
|
|
user = self._create_user("wizard-staff", is_staff=True, is_superuser=False)
|
|
self.add_permission(user, "add_page")
|
|
self.add_permission(user, "change_page")
|
|
global_permission = self.add_global_permission(user, can_add=True, can_change=True)
|
|
global_permission.sites.set([site1])
|
|
|
|
with self.login_user_context(user):
|
|
request = self.get_request()
|
|
|
|
form_site1 = WizardStep1Form(
|
|
wizard_page=None,
|
|
wizard_site=site1,
|
|
wizard_language="en",
|
|
wizard_request=request,
|
|
)
|
|
form_site2 = WizardStep1Form(
|
|
wizard_page=None,
|
|
wizard_site=site2,
|
|
wizard_language="en",
|
|
wizard_request=request,
|
|
)
|
|
|
|
choices_site1 = [choice[0] for choice in form_site1.fields["entry"].choices]
|
|
choices_site2 = [choice[0] for choice in form_site2.fields["entry"].choices]
|
|
|
|
self.assertIn(cms_page_wizard.id, choices_site1)
|
|
self.assertNotIn(cms_page_wizard.id, choices_site2)
|