dynamicweb/cms/tests/test_apphooks.py

1544 lines
65 KiB
Python

import sys
import types
import uuid
from django.apps import apps
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site
from django.core import checks
from django.core.cache import cache
from django.core.checks.urls import check_url_config
from django.http import Http404, HttpResponse
from django.shortcuts import render
from django.template import Context, Template
from django.template.response import TemplateResponse
from django.test import RequestFactory, SimpleTestCase
from django.test.utils import override_settings
from django.urls import NoReverseMatch, clear_url_caches, include, path, resolve, reverse
from django.utils.timezone import now
from django.utils.translation import activate, override as force_language
from django.views.generic import TemplateView
from cms.admin.forms import AdvancedSettingsForm
from cms.api import create_page, create_page_content
from cms.app_base import CMSApp
from cms.apphook_pool import apphook_pool
from cms.appresolver import applications_page_check, clear_app_resolvers, get_app_patterns
from cms.middleware.page import get_page
from cms.models import PageContent
from cms.test_utils.project.placeholderapp.models import Example1
from cms.test_utils.testcases import CMSTestCase
from cms.tests.test_menu_utils import DumbPageLanguageUrl
from cms.toolbar.toolbar import CMSToolbar
from cms.views import render_object_edit
from menus.menu_pool import menu_pool
from menus.utils import DefaultLanguageChanger
APP_NAME = 'SampleApp'
NS_APP_NAME = 'NamespacedApp'
APP_MODULE = "cms.test_utils.project.sampleapp.cms_apps"
MENU_MODULE = "cms.test_utils.project.sampleapp.cms_menus"
class BaseApphooksTestCase(CMSTestCase):
"""
Test cases for apphooks
"""
def setUp(self):
clear_app_resolvers()
clear_url_caches()
if APP_MODULE in sys.modules:
del sys.modules[APP_MODULE]
self.reload_urls()
self.apphook_clear()
def tearDown(self):
clear_app_resolvers()
clear_url_caches()
if APP_MODULE in sys.modules:
del sys.modules[APP_MODULE]
self.reload_urls()
self.apphook_clear()
def reload_urls(self):
from django.conf import settings
url_modules = [
'cms.urls',
# TODO: Add here intermediary modules which may
# include() the 'cms.urls' if it isn't included
# directly in the root urlconf.
# '...',
'cms.test_utils.project.second_cms_urls_for_apphook_tests',
'cms.test_utils.project.urls_for_apphook_tests',
APP_MODULE,
settings.ROOT_URLCONF,
]
clear_app_resolvers()
clear_url_caches()
for module in url_modules:
if module in sys.modules:
del sys.modules[module]
class ApphooksTestCase(BaseApphooksTestCase):
def create_base_structure(self, apphook, content_langs, namespace=None):
self.apphook_clear()
superuser = get_user_model().objects.create_superuser('admin', 'admin@admin.com', 'admin')
self.superuser = superuser
page = create_page("home", "nav_playground.html", "en",
created_by=superuser)
create_page_content('de', page.get_title(), page)
child_page = create_page("child_page", "nav_playground.html", "en",
created_by=superuser, parent=page)
create_page_content('de', child_page.get_title(), child_page)
child_child_page = create_page("child_child_page", "nav_playground.html",
"en", created_by=superuser, parent=child_page, apphook=apphook,
apphook_namespace=namespace)
create_page_content("de", child_child_page.get_title(), child_child_page)
# publisher_public is set to draft on publish, issue with onetoone reverse
child_child_page = self.reload(child_child_page)
if isinstance(content_langs, str):
contents = child_child_page.get_content_obj(content_langs)
else:
contents = [child_child_page.get_content_obj(lang) for lang in content_langs]
self.reload_urls()
return contents
@override_settings(ROOT_URLCONF='cms.test_utils.project.fourth_urls_for_apphook_tests')
def test_check_url_config(self):
"""
Test for urls config check.
"""
self.apphook_clear()
result = check_url_config(None)
self.assertEqual(len(result), 0)
@override_settings(CMS_APPHOOKS=[f'{APP_MODULE}.{APP_NAME}'])
def test_explicit_apphooks(self):
"""
Test explicit apphook loading with the CMS_APPHOOKS setting.
"""
self.apphook_clear()
hooks = apphook_pool.get_apphooks()
app_names = [hook[0] for hook in hooks]
self.assertEqual(len(hooks), 1)
self.assertEqual(app_names, [APP_NAME])
self.apphook_clear()
@override_settings(
INSTALLED_APPS=['cms.test_utils.project.sampleapp'],
ROOT_URLCONF='cms.test_utils.project.urls_for_apphook_tests',
)
def test_implicit_apphooks(self):
"""
Test implicit apphook loading with INSTALLED_APPS cms_apps.py
"""
self.apphook_clear()
hooks = apphook_pool.get_apphooks()
app_names = [hook[0] for hook in hooks]
self.assertEqual(len(hooks), 9)
self.assertIn(NS_APP_NAME, app_names)
self.assertIn(APP_NAME, app_names)
self.apphook_clear()
def test_apphook_on_homepage(self):
self.apphook_clear()
superuser = get_user_model().objects.create_superuser('admin', 'admin@admin.com', 'admin')
page = self.create_homepage("apphooked-page", "nav_playground.html", "en",
created_by=superuser, apphook="SampleApp")
create_page("not-apphooked-page", "nav_playground.html", "en",
created_by=superuser, apphook="", slug='blankapp')
english_title = page.pagecontent_set.all()[0]
self.assertEqual(english_title.language, 'en')
create_page_content("de", "aphooked-page-de", page)
with force_language("en"):
response = self.client.get(self.get_pages_root())
self.assertTemplateUsed(response, 'sampleapp/home.html')
self.assertContains(response, '<--noplaceholder-->')
response = self.client.get('/en/blankapp/')
self.assertTemplateUsed(response, 'nav_playground.html')
self.apphook_clear()
def test_pagecontent_get_template_uses_apphook_root_template(self):
"""When the apphook exposes a root template, PageContent.get_template
returns it instead of the page's own template — so the structure board
can show placeholders matching what the apphook actually renders."""
self.apphook_clear()
superuser = get_user_model().objects.create_superuser('admin', 'admin@admin.com', 'admin')
page = create_page(
'apphooked-page', 'nav_playground.html', 'en',
created_by=superuser, apphook=APP_NAME,
)
page_content = page.pagecontent_set.get(language='en')
# SampleApp's root view is a function-based view, so without a custom
# get_root_template override the lookup returns None and PageContent
# falls back to the page's configured template.
self.assertEqual(page_content.get_template(), 'nav_playground.html')
sample_app = apphook_pool.get_apphook(APP_NAME)
original = sample_app.get_root_template
sample_app.get_root_template = lambda **kwargs: 'apphook/from_root_view.html'
try:
del page_content._template_cache
self.assertEqual(
page_content.get_template(),
'apphook/from_root_view.html',
)
finally:
sample_app.get_root_template = original
self.apphook_clear()
def test_apphook_without_landing_page(self):
"""test rendering the apphook without landing page.
tests for https://github.com/django-cms/django-cms/issues/7765
"""
from django.contrib.contenttypes.models import ContentType
from cms import views
from cms.utils.urlutils import admin_reverse
APP_NAME = 'SampleAppWithoutLandingPage'
[content] = self.create_base_structure(
APP_NAME, ["en"], "apphook-without-landing"
)
content_type = ContentType.objects.get(app_label='cms', model='pagecontent')
endpoint = admin_reverse('cms_placeholder_render_object_edit', args=(content_type.pk, content.pk,))
# make sure, we do not get views.details as this always uses the
# published page content
self.assertIsNot(endpoint, views.details)
@override_settings(ROOT_URLCONF='cms.test_utils.project.urls_for_apphook_tests')
def test_apphook_does_not_crash_django_checks(self):
# This test case reproduced the situation causing the error reported in issue #6717.
self.apphook_clear()
superuser = get_user_model().objects.create_superuser('admin', 'admin@admin.com', 'admin')
create_page("apphooked-page", "nav_playground.html", "en",
created_by=superuser, apphook="SampleApp")
self.reload_urls()
checks.run_checks()
self.apphook_clear()
@override_settings(ROOT_URLCONF='cms.test_utils.project.urls_for_apphook_tests')
def test_apphook_on_root_reverse(self):
self.apphook_clear()
superuser = get_user_model().objects.create_superuser('admin', 'admin@admin.com', 'admin')
page = create_page("apphooked-page", "nav_playground.html", "en",
created_by=superuser, apphook="SampleApp")
create_page_content("de", "aphooked-page-de", page)
self.reload_urls()
self.assertFalse(reverse('sample-settings').startswith('//'))
self.apphook_clear()
@override_settings(ROOT_URLCONF='cms.test_utils.project.urls_for_apphook_tests')
def test_multisite_apphooks(self):
self.apphook_clear()
site1, _ = Site.objects.get_or_create(pk=1)
site2, _ = Site.objects.get_or_create(pk=2)
superuser = get_user_model().objects.create_superuser('admin', 'admin@admin.com', 'admin')
request = self.get_request()
request.user = superuser
home_site_1 = create_page(
"home", "nav_playground.html", "en", created_by=superuser, site=site1
)
home_site_2 = create_page(
"home", "nav_playground.html", "de", created_by=superuser, site=site2
)
page_a_1 = create_page(
"apphooked-page", "nav_playground.html", "en", created_by=superuser, parent=home_site_1,
apphook=NS_APP_NAME, apphook_namespace="instance"
)
page_a_2 = create_page(
"apphooked-page", "nav_playground.html", "de", created_by=superuser, parent=home_site_1,
)
page_b_1 = create_page(
"apphooked-page", "nav_playground.html", "de", created_by=superuser, parent=home_site_2,
site=site2
)
# AdvancedSettingsForm expects a _site and _request attribute
# on __init__. These are normally set by the admin.
# In this case, we need to set these in a new class.
Meta = type('Meta', (AdvancedSettingsForm.Meta, object), {})
formClass = type(AdvancedSettingsForm)(
'AdvancedSettingsForm',
(AdvancedSettingsForm,),
{'Meta': Meta, '_site': site1, '_request': request},
)
form = formClass(instance=page_a_1)
self.assertFalse(form._check_unique_namespace_instance("instance"))
form = formClass(instance=page_a_2)
self.assertTrue(form._check_unique_namespace_instance("instance"))
formClass = type(AdvancedSettingsForm)(
'AdvancedSettingsForm',
(AdvancedSettingsForm,),
{'Meta': Meta, '_site': site2, '_request': request},
)
form = formClass(instance=page_b_1)
self.assertFalse(form._check_unique_namespace_instance("instance"))
self.apphook_clear()
@override_settings(ROOT_URLCONF='cms.test_utils.project.second_urls_for_apphook_tests')
def test_get_page_for_apphook(self):
en_title, de_title = self.create_base_structure(APP_NAME, ['en', 'de'])
with force_language("en"):
path = reverse('sample-settings')
request = self.get_request(path)
request.LANGUAGE_CODE = 'en'
attached_to_page = applications_page_check(request)
self.assertEqual(attached_to_page.pk, en_title.page.pk)
response = self.client.get(path)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'sampleapp/home.html')
self.assertContains(response, en_title.title)
with force_language("de"):
path = reverse('sample-settings')
request = self.get_request(path)
request.LANGUAGE_CODE = 'de'
attached_to_page = applications_page_check(request)
self.assertEqual(attached_to_page.pk, de_title.page.pk)
response = self.client.get(path)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'sampleapp/home.html')
self.assertContains(response, de_title.title)
self.apphook_clear()
@override_settings(ROOT_URLCONF='cms.test_utils.project.second_urls_for_apphook_tests')
def test_apphook_on_page_type_is_not_routed(self):
"""Page types are not served on the public site. An apphook attached to
one must not be dispatched either -- its patterns are matched before the
catch-all ``cms.views.details`` route, which would bypass the check
there."""
en_title, de_title = self.create_base_structure(APP_NAME, ['en', 'de'])
with force_language("en"):
path = reverse('sample-settings')
self.assertEqual(self.client.get(path).status_code, 200)
en_title.page.update(is_page_type=True)
self.reload_urls()
self.assertEqual(self.client.get(path).status_code, 404)
self.apphook_clear()
@override_settings(ROOT_URLCONF='cms.test_utils.project.second_urls_for_apphook_tests')
def test_apphook_permissions(self):
en_title, de_title = self.create_base_structure(APP_NAME, ['en', 'de'])
with force_language("en"):
path = reverse('sample-settings')
response = self.client.get(path)
self.assertEqual(response.status_code, 200)
en_title.page.update(login_required=True)
response = self.client.get(path)
self.assertEqual(response.status_code, 302)
self.apphook_clear()
@override_settings(ROOT_URLCONF='cms.test_utils.project.second_urls_for_apphook_tests')
def test_apphook_permissions_preserves_view_name(self):
self.create_base_structure(APP_NAME, ['en', 'de'])
view_names = (
('sample-settings', 'sample_view'),
('sample-class-view', 'ClassView'),
('sample-class-based-view', 'view'),
# Naming convention changed in Django 4
)
with force_language("en"):
for url_name, view_name in view_names:
path = reverse(url_name)
match = resolve(path)
self.assertEqual(match.func.__name__, view_name)
def test_apphooks_with_excluded_permissions(self):
en_title = self.create_base_structure('SampleAppWithExcludedPermissions', 'en')
with force_language("en"):
excluded_path = reverse('excluded:example')
not_excluded_path = reverse('not_excluded:example')
en_title.page.update(login_required=True)
excluded_response = self.client.get(excluded_path)
not_excluded_response = self.client.get(not_excluded_path)
self.assertEqual(excluded_response.status_code, 200)
self.assertEqual(not_excluded_response.status_code, 302)
self.apphook_clear()
@override_settings(CMS_APPHOOKS=[f'{APP_MODULE}.{APP_NAME}'])
def test_apphook_without_name_defaults_to_class_name(self):
"""
Test that an apphook without a name defaults to the class name.
"""
@apphook_pool.register
class AppWithoutName(CMSApp):
def get_urls(self, page=None, language=None, **kwargs):
return ["sampleapp.urls"]
@apphook_pool.register
class AppWithName(CMSApp):
name = "Custom name"
def get_urls(self, page=None, language=None, **kwargs):
return ["sampleapp.urls"]
hooks = apphook_pool.get_apphooks()
hook_names = dict(hooks)
self.assertEqual(hook_names.get('AppWithoutName'), 'AppWithoutName')
self.assertEqual(hook_names.get('AppWithName'), 'Custom name')
@override_settings(ROOT_URLCONF='cms.test_utils.project.urls_3')
def test_get_page_for_apphook_on_preview_or_edit(self):
if get_user_model().USERNAME_FIELD == 'email':
superuser = get_user_model().objects.create_superuser('admin', 'admin@admin.com', 'admin@admin.com')
else:
superuser = get_user_model().objects.create_superuser('admin', 'admin@admin.com', 'admin')
page = create_page("home", "nav_playground.html", "en",
created_by=superuser, apphook=APP_NAME)
create_page_content('de', page.get_title(), page)
with force_language("en"):
path = reverse('sample-settings')
request = self.get_request(path)
request.LANGUAGE_CODE = 'en'
attached_to_page = applications_page_check(request)
self.assertEqual(attached_to_page.pk, page.pk)
with force_language("de"):
path = reverse('sample-settings')
request = self.get_request(path)
request.LANGUAGE_CODE = 'de'
attached_to_page = applications_page_check(request)
self.assertEqual(attached_to_page.pk, page.pk)
with self.login_user_context(superuser):
with force_language("en"):
path = reverse('sample-settings')
request = self.get_request(path)
request.LANGUAGE_CODE = 'en'
attached_to_page = applications_page_check(request)
self.assertEqual(attached_to_page.pk, page.pk)
with force_language("de"):
path = reverse('sample-settings')
request = self.get_request(path)
request.LANGUAGE_CODE = 'de'
attached_to_page = applications_page_check(request)
self.assertEqual(attached_to_page.pk, page.pk)
@override_settings(ROOT_URLCONF='cms.test_utils.project.second_urls_for_apphook_tests')
def test_get_root_page_for_apphook_with_instance_namespace(self):
en_title = self.create_base_structure(NS_APP_NAME, 'en', 'instance_ns')
self.reload_urls()
with force_language("en"):
reverse("example_app:example")
reverse("example1:example")
reverse("example2:example")
path = reverse('namespaced_app_ns:sample-root')
path_instance = reverse('instance_ns:sample-root')
self.assertEqual(path, path_instance)
request = self.get_request(path)
request.LANGUAGE_CODE = 'en'
attached_to_page = applications_page_check(request)
self.assertEqual(attached_to_page.pk, en_title.page.pk)
self.apphook_clear()
@override_settings(ROOT_URLCONF='cms.test_utils.project.second_urls_for_apphook_tests')
def test_get_child_page_for_apphook_with_instance_namespace(self):
en_title = self.create_base_structure(NS_APP_NAME, 'en', 'instance_ns')
with force_language("en"):
path = reverse('namespaced_app_ns:sample-settings')
path_instance1 = reverse('instance_ns:sample-settings')
path_instance2 = reverse('namespaced_app_ns:sample-settings', current_app='instance_ns')
self.assertEqual(path, path_instance1)
self.assertEqual(path, path_instance2)
request = self.get_request(path)
request.LANGUAGE_CODE = 'en'
attached_to_page = applications_page_check(request)
self.assertEqual(attached_to_page.pk, en_title.page_id)
self.apphook_clear()
@override_settings(ROOT_URLCONF='cms.test_utils.project.second_urls_for_apphook_tests')
def test_get_sub_page_for_apphook_with_implicit_current_app(self):
en_title = self.create_base_structure(NS_APP_NAME, 'en', 'namespaced_app_ns')
with force_language("en"):
path = reverse('namespaced_app_ns:current-app')
request = self.get_request(path)
request.LANGUAGE_CODE = 'en'
attached_to_page = applications_page_check(request)
self.assertEqual(attached_to_page.pk, en_title.page.pk)
response = self.client.get(path)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'sampleapp/app.html')
self.assertContains(response, 'namespaced_app_ns')
self.assertContains(response, path)
self.apphook_clear()
@override_settings(ROOT_URLCONF='cms.test_utils.project.second_urls_for_apphook_tests')
def test_default_language_changer_with_implicit_current_app(self):
self.create_base_structure(NS_APP_NAME, ['en', 'de'], 'namespaced_app_ns')
self.reload_urls()
with force_language("en"):
path = reverse('namespaced_app_ns:translated-url')
request = self.get_request(path)
request.LANGUAGE_CODE = 'en'
url = DefaultLanguageChanger(request)('en')
self.assertEqual(url, path)
url = DefaultLanguageChanger(request)('de')
self.assertEqual(url, '/de%s' % path[3:].replace('/page', '/Seite'))
self.apphook_clear()
@override_settings(ROOT_URLCONF='cms.test_utils.project.second_urls_for_apphook_tests')
def test_get_i18n_apphook_with_explicit_current_app(self):
self.apphook_clear()
titles = self.create_base_structure(NS_APP_NAME, ['en', 'de'], 'instance_1')
public_de_title = titles[1]
de_title = PageContent.objects.get(page=public_de_title.page, language="de")
self.reload_urls()
self.apphook_clear()
page2 = create_page("page2",
"nav_playground.html",
language="en",
created_by=self.superuser,
parent=de_title.page.parent,
apphook=NS_APP_NAME,
apphook_namespace="instance_2")
create_page_content("de", "de_title", page2, slug="slug")
clear_app_resolvers()
clear_url_caches()
if APP_MODULE in sys.modules:
del sys.modules[APP_MODULE]
self.reload_urls()
with force_language("de"):
reverse('namespaced_app_ns:current-app', current_app="instance_1")
reverse('namespaced_app_ns:current-app', current_app="instance_2")
reverse('namespaced_app_ns:current-app')
with force_language("en"):
reverse('namespaced_app_ns:current-app', current_app="instance_1")
reverse('namespaced_app_ns:current-app', current_app="instance_2")
reverse('namespaced_app_ns:current-app')
self.apphook_clear()
@override_settings(ROOT_URLCONF='cms.test_utils.project.second_urls_for_apphook_tests')
def test_apphook_include_extra_parameters(self):
self.create_base_structure(NS_APP_NAME, ['en', 'de'], 'instance_1')
with force_language("en"):
path = reverse('namespaced_app_ns:extra_second')
request = self.get_request(path)
request.LANGUAGE_CODE = 'en'
response = self.client.get(path)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'sampleapp/extra.html')
self.assertContains(response, 'someopts')
@override_settings(ROOT_URLCONF='cms.test_utils.project.second_urls_for_apphook_tests')
def test_get_sub_page_for_apphook_with_explicit_current_app(self):
en_title = self.create_base_structure(NS_APP_NAME, 'en', 'instance_ns')
with force_language("en"):
path = reverse('namespaced_app_ns:current-app')
request = self.get_request(path)
request.LANGUAGE_CODE = 'en'
attached_to_page = applications_page_check(request)
self.assertEqual(attached_to_page.pk, en_title.page.pk)
response = self.client.get(path)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'sampleapp/app.html')
self.assertContains(response, 'instance_ns')
self.assertContains(response, path)
self.apphook_clear()
@override_settings(ROOT_URLCONF='cms.test_utils.project.second_urls_for_apphook_tests')
def test_include_urlconf(self):
self.create_base_structure(APP_NAME, 'en')
path = reverse('extra_second')
response = self.client.get(path)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'sampleapp/extra.html')
self.assertContains(response, "test included urlconf")
path = reverse('extra_first')
response = self.client.get(path)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'sampleapp/extra.html')
self.assertContains(response, "test urlconf")
with force_language("de"):
path = reverse('extra_first')
response = self.client.get(path)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'sampleapp/extra.html')
self.assertContains(response, "test urlconf")
with force_language("de"):
path = reverse('extra_second')
response = self.client.get(path)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'sampleapp/extra.html')
self.assertContains(response, "test included urlconf")
self.apphook_clear()
@override_settings(CMS_PERMISSION=False, ROOT_URLCONF='cms.test_utils.project.urls_2')
def test_apphook_breaking_under_home_with_new_path_caching(self):
home = self.create_homepage("home", "nav_playground.html", "en")
child = create_page("child", "nav_playground.html", "en", parent=home)
# not-home is what breaks stuff, because it contains the slug of the home page
not_home = create_page("not-home", "nav_playground.html", "en", parent=child)
create_page("subchild", "nav_playground.html", "en", parent=not_home, apphook='SampleApp')
with force_language("en"):
self.reload_urls()
urlpatterns = get_app_patterns()
resolver = urlpatterns[0]
url = resolver.reverse('sample-root')
self.assertEqual(url, 'child/not-home/subchild/')
@override_settings(CMS_PERMISSION=False, ROOT_URLCONF='cms.test_utils.project.urls_2')
def test_apphook_on_page_without_path_is_skipped(self):
"""A page whose ``PageUrl.path`` is ``None`` must not break url resolution.
Regression test for #8886: ``_get_app_patterns`` built ``page_url.path + "/"``
unconditionally, so a single such page raised ``TypeError`` and took down every
apphook on the site -- including the admin -- until the database was edited by hand.
Unpublishing a page with djangocms-versioning leaves exactly this state.
"""
home = self.create_homepage("home", "nav_playground.html", "en")
create_page("live-app", "nav_playground.html", "en", parent=home, apphook='SampleApp')
pathless = create_page("pathless-app", "nav_playground.html", "en", parent=home, apphook='SampleApp')
# Without page content the url keeps its slug but loses its path -- the core
# equivalent of an unpublished page.
PageContent.objects.filter(page=pathless, language="en").delete()
pathless.update_urls_from_content("en")
self.assertIsNone(pathless.urls.get(language="en").path)
with force_language("en"):
self.reload_urls()
urlpatterns = get_app_patterns() # must not raise TypeError
# the remaining apphook still resolves, i.e. the pathless page was skipped
# rather than the whole pattern list being abandoned
self.assertEqual(urlpatterns[0].reverse('sample-root'), 'live-app/')
@override_settings(ROOT_URLCONF='cms.test_utils.project.urls')
def test_apphook_urlpattern_order(self):
# this one includes the actual cms.urls, so it can be tested if
# they are loaded in the correct order (the cms page pattern must be last)
# (the other testcases replicate the inclusion code and thus don't test this)
self.create_base_structure(APP_NAME, 'en')
path = reverse('extra_second')
response = self.client.get(path)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'sampleapp/extra.html')
self.assertContains(response, "test included urlconf")
@override_settings(ROOT_URLCONF='cms.test_utils.project.urls')
def test_apphooks_receive_url_params(self):
# make sure that urlparams actually reach the apphook views
self.create_base_structure(APP_NAME, 'en')
path = reverse('sample-params', kwargs=dict(my_params='is-my-param-really-in-the-context-QUESTIONMARK'))
response = self.client.get(path)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'sampleapp/home.html')
self.assertContains(response, 'my_params: is-my-param-really-in-the-context-QUESTIONMARK')
@override_settings(ROOT_URLCONF='cms.test_utils.project.third_urls_for_apphook_tests')
def test_multiple_apphooks(self):
# test for #1538
self.apphook_clear()
superuser = get_user_model().objects.create_superuser('admin', 'admin@admin.com', 'admin')
create_page("home", "nav_playground.html", "en", created_by=superuser, )
create_page("apphook1-page", "nav_playground.html", "en",
created_by=superuser, apphook="SampleApp")
create_page("apphook2-page", "nav_playground.html", "en",
created_by=superuser, apphook="SampleApp2")
reverse('sample-root')
reverse('sample2-root')
self.apphook_clear()
@override_settings(ROOT_URLCONF='cms.test_utils.project.fourth_urls_for_apphook_tests')
def test_apphooks_return_urls_directly(self):
self.apphook_clear()
superuser = get_user_model().objects.create_superuser('admin', 'admin@admin.com', 'admin')
create_page("apphooked3-page", "nav_playground.html", "en",
created_by=superuser, apphook="SampleApp3")
self.reload_urls()
path = reverse('sample3-root')
response = self.client.get(path)
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Sample App 3 Response')
self.apphook_clear()
def test_apphook_pool_register_returns_apphook(self):
@apphook_pool.register
class TestApp(CMSApp):
name = "Test App"
self.assertIsNotNone(TestApp)
# Now test the quick return codepath, when apphooks is not empty
apphook_pool.apphooks.append("foo")
@apphook_pool.register
class TestApp2(CMSApp):
name = "Test App 2"
self.assertIsNotNone(TestApp2)
@override_settings(ROOT_URLCONF='cms.test_utils.project.second_urls_for_apphook_tests')
def test_apphook_csrf_exempt_endpoint(self):
self.create_base_structure(NS_APP_NAME, 'en', 'instance_ns')
client = self.client_class(enforce_csrf_checks=True)
with force_language("en"):
path = reverse('namespaced_app_ns:sample-exempt')
response = client.post(path)
# Assert our POST request went through
self.assertEqual(response.status_code, 200)
with force_language("en"):
path = reverse('namespaced_app_ns:sample-account')
response = client.post(path)
# Assert our POST request did not go through
self.assertEqual(response.status_code, 403)
@override_settings(ROOT_URLCONF='cms.test_utils.project.second_urls_for_apphook_tests')
def test_toolbar_current_app_namespace(self):
self.create_base_structure(NS_APP_NAME, 'en', 'instance_ns')
with force_language("en"):
path = reverse('namespaced_app_ns:sample-settings')
request = self.get_request(path)
toolbar = CMSToolbar(request)
self.assertTrue(
toolbar.toolbars['cms.test_utils.project.sampleapp.cms_toolbars.CategoryToolbar'].is_current_app
)
self.assertFalse(
toolbar.toolbars[
'cms.test_utils.project.extensionapp.cms_toolbars.MyPageContentExtensionToolbar'
].is_current_app
)
# Testing a decorated view
with force_language("en"):
path = reverse('namespaced_app_ns:sample-exempt')
request = self.get_request(path)
toolbar = CMSToolbar(request)
self.assertEqual(
toolbar.toolbars['cms.test_utils.project.sampleapp.cms_toolbars.CategoryToolbar'].app_path,
'cms.test_utils.project.sampleapp'
)
self.assertTrue(
toolbar.toolbars['cms.test_utils.project.sampleapp.cms_toolbars.CategoryToolbar'].is_current_app
)
self.assertEqual(
toolbar.toolbars[
'cms.test_utils.project.extensionapp.cms_toolbars.MyPageContentExtensionToolbar'
].app_path,
'cms.test_utils.project.sampleapp'
)
self.assertFalse(
toolbar.toolbars[
'cms.test_utils.project.extensionapp.cms_toolbars.MyPageContentExtensionToolbar'
].is_current_app
)
@override_settings(ROOT_URLCONF='cms.test_utils.project.second_urls_for_apphook_tests')
def test_toolbar_current_app_apphook_with_implicit_current_app(self):
self.create_base_structure(NS_APP_NAME, 'en', 'namespaced_app_ns')
with force_language("en"):
path = reverse('namespaced_app_ns:current-app')
request = self.get_request(path)
toolbar = CMSToolbar(request)
self.assertEqual(
toolbar.toolbars['cms.test_utils.project.sampleapp.cms_toolbars.CategoryToolbar'].app_path,
'cms.test_utils.project.sampleapp'
)
self.assertTrue(
toolbar.toolbars['cms.test_utils.project.sampleapp.cms_toolbars.CategoryToolbar'].is_current_app
)
self.assertEqual(
toolbar.toolbars[
'cms.test_utils.project.extensionapp.cms_toolbars.MyPageContentExtensionToolbar'
].app_path,
'cms.test_utils.project.sampleapp'
)
self.assertFalse(
toolbar.toolbars[
'cms.test_utils.project.extensionapp.cms_toolbars.MyPageContentExtensionToolbar'
].is_current_app
)
@override_settings(ROOT_URLCONF='cms.test_utils.project.placeholderapp_urls')
def test_toolbar_no_namespace(self):
# Test with a basic application with no defined app_name and no namespace
self.create_base_structure(APP_NAME, 'en')
path = reverse('detail', kwargs={'id': 20})
request = self.get_request(path)
toolbar = CMSToolbar(request)
self.assertFalse(
toolbar.toolbars['cms.test_utils.project.sampleapp.cms_toolbars.CategoryToolbar'].is_current_app
)
self.assertFalse(
toolbar.toolbars[
'cms.test_utils.project.extensionapp.cms_toolbars.MyPageContentExtensionToolbar'
].is_current_app
)
self.assertTrue(
toolbar.toolbars['cms.test_utils.project.placeholderapp.cms_toolbars.Example1Toolbar'].is_current_app
)
@override_settings(ROOT_URLCONF='cms.test_utils.project.placeholderapp_urls')
def test_toolbar_multiple_supported_apps(self):
# Test with a basic application with no defined app_name and no namespace
self.create_base_structure(APP_NAME, 'en')
path = reverse('detail', kwargs={'id': 20})
request = self.get_request(path)
toolbar = CMSToolbar(request)
self.assertEqual(toolbar.toolbars['cms.test_utils.project.sampleapp.cms_toolbars.CategoryToolbar'].app_path,
'cms.test_utils.project.placeholderapp')
self.assertFalse(
toolbar.toolbars['cms.test_utils.project.sampleapp.cms_toolbars.CategoryToolbar'].is_current_app
)
self.assertEqual(
toolbar.toolbars[
'cms.test_utils.project.extensionapp.cms_toolbars.MyPageContentExtensionToolbar']
.app_path,
'cms.test_utils.project.placeholderapp'
)
self.assertFalse(
toolbar.toolbars[
'cms.test_utils.project.extensionapp.cms_toolbars.MyPageContentExtensionToolbar'
].is_current_app
)
self.assertEqual(
toolbar.toolbars['cms.test_utils.project.extensionapp.cms_toolbars.MyPageExtensionToolbar'].app_path,
'cms.test_utils.project.placeholderapp'
)
self.assertTrue(
toolbar.toolbars['cms.test_utils.project.extensionapp.cms_toolbars.MyPageExtensionToolbar'].is_current_app
)
self.assertEqual(
toolbar.toolbars['cms.test_utils.project.placeholderapp.cms_toolbars.Example1Toolbar'].app_path,
'cms.test_utils.project.placeholderapp'
)
self.assertTrue(
toolbar.toolbars['cms.test_utils.project.placeholderapp.cms_toolbars.Example1Toolbar'].is_current_app
)
@override_settings(
CMS_APPHOOKS=['cms.test_utils.project.placeholderapp.cms_apps.Example1App'],
ROOT_URLCONF='cms.test_utils.project.placeholderapp_urls',
)
def test_toolbar_staff(self):
# Test that the toolbar contains edit mode switcher if placeholders are available
apphooks = (
'cms.test_utils.project.placeholderapp.cms_apps.Example1App',
)
switcher_id = 'Mode Switcher'
with self.settings(CMS_APPHOOKS=apphooks, ROOT_URLCONF='cms.test_utils.project.placeholderapp_urls'):
self.create_base_structure('Example1App', 'en')
ex1 = Example1.objects.create(char_1='1', char_2='2', char_3='3', char_4='4', date_field=now())
path = reverse('example_detail', kwargs={'pk': ex1.pk})
self.user = self._create_user('admin_staff', True, True)
with self.login_user_context(self.user):
response = self.client.get(path + "?toolbar_on")
request = response.context['request']
toolbar = request.toolbar
items = toolbar.get_right_items()
switchers = [item.identifier for item in items
if getattr(item, 'identifier', '') == switcher_id]
self.assertEqual(len(switchers), 1)
self.user = self._create_user('staff', True, False)
with self.login_user_context(self.user):
response = self.client.get(path + "?toolbar_on")
request = response.context['request']
request.user = get_user_model().objects.get(pk=self.user.pk)
toolbar = request.toolbar
items = toolbar.get_right_items()
switchers = [item.identifier for item in items
if getattr(item, 'identifier', '') == switcher_id]
self.assertEqual(len(switchers), 0)
self.user.user_permissions.add(Permission.objects.get(codename='change_example1'))
with self.login_user_context(self.user):
response = self.client.get(path + "?toolbar_on")
request = response.context['request']
request.user = get_user_model().objects.get(pk=self.user.pk)
toolbar = request.toolbar
items = toolbar.get_right_items()
switchers = [item.identifier for item in items
if getattr(item, 'identifier', '') == switcher_id]
self.assertEqual(len(switchers), 0)
self.user.user_permissions.add(Permission.objects.get(codename='use_structure'))
with self.login_user_context(self.user):
response = self.client.get(path + "?toolbar_on")
request = response.context['request']
request.user = get_user_model().objects.get(pk=self.user.pk)
toolbar = request.toolbar
items = toolbar.get_right_items()
switchers = [item.identifier for item in items
if getattr(item, 'identifier', '') == switcher_id]
self.assertEqual(len(switchers), 1)
self.user = None
def test_nested_apphooks_urls(self):
# make sure that urlparams actually reach the apphook views
with self.settings(ROOT_URLCONF='cms.test_utils.project.urls'):
self.apphook_clear()
superuser = get_user_model().objects.create_superuser('admin', 'admin@admin.com', 'admin')
create_page("home", "nav_playground.html", "en", created_by=superuser, )
parent_page = create_page("parent-apphook-page", "nav_playground.html", "en",
created_by=superuser, apphook="ParentApp")
create_page("child-apphook-page", "nav_playground.html", "en", parent=parent_page,
created_by=superuser, apphook="ChildApp")
parent_app_path = reverse('parentapp_view', kwargs={'path': 'parent/path/'})
child_app_path = reverse('childapp_view', kwargs={'path': 'child-path/'})
# Ensure the page structure is ok before getting responses
self.assertEqual(parent_app_path, '/en/parent-apphook-page/parent/path/')
self.assertEqual(child_app_path, '/en/parent-apphook-page/child-apphook-page/child-path/')
# Get responses for both paths and ensure that the right view will answer
response = self.client.get(parent_app_path)
self.assertContains(response, 'parent app content', status_code=200)
response = self.client.get(child_app_path)
self.assertContains(response, 'child app content', status_code=200)
self.apphook_clear()
@override_settings(ROOT_URLCONF='cms.test_utils.project.second_urls_for_apphook_tests')
def test_get_apps(self):
"""
Check that urlconf are dynamically loaded according to the different page the apphook is
attached to
"""
titles = self.create_base_structure('VariableUrlsApp', ['en', 'de'])
titles[0].page.reverse_id = 'page1'
titles[0].page.save()
self.reload_urls()
# only one urlconf is configured given that only one page is created
with force_language('de'):
reverse('extra_first')
with self.assertRaises(NoReverseMatch):
reverse('sample2-root')
self.reload_urls()
self.apphook_clear()
page2 = create_page('page2', 'nav_playground.html',
'en', created_by=self.superuser,
parent=titles[0].page.parent,
apphook='VariableUrlsApp', reverse_id='page2')
create_page_content('de', 'de_title', page2, slug='slug')
self.reload_urls()
with force_language('de'):
reverse('sample2-root')
reverse('extra_first')
self.apphook_clear()
@override_settings(ROOT_URLCONF='cms.test_utils.project.second_urls_for_apphook_tests')
def test_get_menus(self):
"""
Check that menus are dynamically loaded according to the different page the apphook is
attached to
"""
titles = self.create_base_structure('VariableUrlsApp', ['en', 'de'])
titles[0].page.reverse_id = 'page1'
titles[0].page.save()
cache.clear()
self.reload_urls()
menu_pool.discover_menus()
cache.clear()
request = self.get_request('/')
renderer = menu_pool.get_renderer(request)
nodes = renderer.get_nodes()
nodes_urls = [node.url for node in nodes]
self.assertTrue(reverse('sample-account') in nodes_urls)
self.assertFalse('/en/child_page/page2/' in nodes_urls)
self.reload_urls()
self.apphook_clear()
cache.clear()
self.reload_urls()
page2 = create_page('page2', 'nav_playground.html',
'en', created_by=self.superuser,
parent=titles[0].page.parent,
in_navigation=True,
apphook='VariableUrlsApp', reverse_id='page2')
create_page_content('de', 'de_title', page2, slug='slug')
request = self.get_request('/page2/')
renderer = menu_pool.get_renderer(request)
nodes = renderer.get_nodes()
nodes_urls = [node.url for node in nodes]
self.assertTrue(reverse('sample-account') in nodes_urls)
self.assertTrue(reverse('sample2-root') in nodes_urls)
self.assertTrue('/static/fresh/' in nodes_urls)
self.apphook_clear()
@override_settings(
CMS_APPHOOKS=['cms.test_utils.project.sampleapp.cms_apps.AppWithNoMenu'],
)
def test_menu_node_is_selected_on_app_root(self):
"""
If a user requests a page with an apphook,
the menu should mark the node for that page as selected.
"""
defaults = {
'language': 'en',
'in_navigation': True,
'template': 'nav_playground.html',
}
homepage = create_page('EN-P1', **defaults)
homepage.set_as_homepage()
app_root = create_page('EN-P2', apphook='AppWithNoMenu', apphook_namespace='app_with_no_menu', **defaults)
# Public version
request = self.get_request('/en/en-p2/')
request.current_page = get_page(request)
menu_nodes = menu_pool.get_renderer(request).get_nodes()
self.assertEqual(len(menu_nodes), 2)
self.assertEqual(menu_nodes[0].id, homepage.pk)
self.assertEqual(menu_nodes[0].selected, False)
self.assertEqual(menu_nodes[1].id, app_root.pk)
self.assertEqual(menu_nodes[1].selected, True)
@override_settings(
CMS_APPHOOKS=['cms.test_utils.project.sampleapp.cms_apps.AppWithNoMenu'],
)
def test_menu_node_is_selected_on_app_sub_path(self):
"""
If a user requests a path belonging to an apphook,
the menu should mark the node for the apphook page as selected.
"""
# Refs - https://github.com/divio/django-cms/issues/6336
defaults = {
'language': 'en',
'in_navigation': True,
'template': 'nav_playground.html',
}
homepage = create_page('EN-P1', **defaults)
homepage.set_as_homepage()
app_root = create_page('EN-P2', apphook='AppWithNoMenu', apphook_namespace='app_with_no_menu', **defaults)
request = self.get_request('/en/en-p2/settings/')
request.current_page = get_page(request)
menu_nodes = menu_pool.get_renderer(request).get_nodes()
self.assertEqual(len(menu_nodes), 2)
self.assertEqual(menu_nodes[0].id, homepage.pk)
self.assertEqual(menu_nodes[0].selected, False)
self.assertEqual(menu_nodes[1].id, app_root.pk)
self.assertEqual(menu_nodes[1].selected, True)
@override_settings(CMS_APPHOOKS=['cms.test_utils.project.sampleapp.cms_apps.SampleApp3'],)
def test_apphook_root_page_placeholders_in_endpoints(self):
"""
Test that page content placeholders are available in edit and preview endpoints
for the root page of an apphook.
"""
from django.contrib.contenttypes.models import ContentType
from cms.toolbar.utils import get_object_edit_url, get_object_preview_url, get_object_structure_url
# Create a page with an apphook attached
self.apphook_clear()
superuser = get_user_model().objects.create_superuser('admin', 'admin@admin.com', 'admin')
page = create_page(
"apphook-root",
"nav_playground.html",
"en",
created_by=superuser,
apphook="SampleApp3"
)
content = page.get_content_obj("en")
self.reload_urls()
# Test edit endpoint
edit_url = get_object_edit_url(content, language="en")
with self.login_user_context(superuser):
response = self.client.get(edit_url)
self.assertEqual(response.status_code, 200)
# Verify the apphook view is called
self.assertContains(response, 'Sample App 3 Response')
# Check that toolbar was set with the page content object
self.assertTrue(hasattr(response.wsgi_request, 'toolbar'))
toolbar = response.wsgi_request.toolbar
# Verify the toolbar object is the page content
self.assertIsNotNone(toolbar.obj, "toolbar.obj should be set for edit endpoint")
self.assertEqual(toolbar.obj, content, "toolbar.obj should match the page content")
# Test preview endpoint
preview_url = get_object_preview_url(content, language="en")
with self.login_user_context(superuser):
response = self.client.get(preview_url)
self.assertEqual(response.status_code, 200)
# Verify the apphook view is called
self.assertContains(response, 'Sample App 3 Response')
# Check that toolbar was set with the page content object
self.assertTrue(hasattr(response.wsgi_request, 'toolbar'))
toolbar = response.wsgi_request.toolbar
# Verify the toolbar object is the page content
self.assertIsNotNone(toolbar.obj, "toolbar.obj should be set for preview endpoint")
self.assertEqual(toolbar.obj, content, "toolbar.obj should match the page content")
# Test structure endpoint
structure_url = get_object_structure_url(content, language="en")
with self.login_user_context(superuser):
response = self.client.get(structure_url)
self.assertEqual(response.status_code, 200)
# Check that toolbar was set with the page content object
self.assertTrue(hasattr(response.wsgi_request, 'toolbar'))
toolbar = response.wsgi_request.toolbar
# Verify the toolbar object is the page content
self.assertIsNotNone(toolbar.obj, "toolbar.obj should be set for structure endpoint")
self.assertEqual(toolbar.obj, content, "toolbar.obj should match the page content")
# Verify that page content placeholders are present in the markup
for placeholder in content.get_placeholders():
# Check that placeholder ID is in the response content
self.assertContains(
response,
f'"placeholder_id": "{placeholder.pk}"',
msg_prefix=f"Placeholder {placeholder.slot} should be present in structure markup"
)
self.apphook_clear()
class ApphooksPageLanguageUrlTestCase(CMSTestCase):
def setUp(self):
clear_app_resolvers()
clear_url_caches()
if APP_MODULE in sys.modules:
del sys.modules[APP_MODULE]
self.reload_urls()
def tearDown(self):
clear_app_resolvers()
clear_url_caches()
if APP_MODULE in sys.modules:
del sys.modules[APP_MODULE]
self.apphook_clear()
def reload_urls(self):
from django.conf import settings
url_modules = [
'cms.urls',
'cms.test_utils.project.second_cms_urls_for_apphook_tests',
settings.ROOT_URLCONF,
]
clear_app_resolvers()
clear_url_caches()
for module in url_modules:
if module in sys.modules:
del sys.modules[module]
def test_page_language_url_for_apphook(self):
self.apphook_clear()
superuser = get_user_model().objects.create_superuser('admin', 'admin@admin.com', 'admin')
page = self.create_homepage("home", "nav_playground.html", "en", created_by=superuser)
create_page_content('de', page.get_title(), page)
child_page = create_page("child_page", "nav_playground.html", "en",
created_by=superuser, parent=page)
create_page_content('de', child_page.get_title(), child_page)
child_page.refresh_from_db()
child_child_page = create_page("child_child_page", "nav_playground.html",
"en", created_by=superuser, parent=child_page, apphook='SampleApp')
create_page_content("de", f'{child_child_page.get_title()}_de', child_child_page)
child_child_page.refresh_from_db()
with force_language("en"):
path = reverse('extra_first')
request = self.get_request(path)
request.LANGUAGE_CODE = 'en'
request.current_page = child_child_page
fake_context = {'request': request}
tag = DumbPageLanguageUrl()
output = tag.get_context(fake_context, 'en')
url = output['content']
self.assertEqual(url, '/en/child_page/child_child_page/extra_1/')
output = tag.get_context(fake_context, 'de')
url = output['content']
# look the extra "_de"
self.assertEqual(url, '/de/child_page/child_child_page_de/extra_1/')
output = tag.get_context(fake_context, 'fr')
url = output['content']
self.assertEqual(url, '/en/child_page/child_child_page/extra_1/')
self.apphook_clear()
@override_settings(SITE_ID="")
class ApphooksSiteTestCase(BaseApphooksTestCase):
"""
Test cases for apphooks in a multisite configuration using a single instance. Sites
are mocked for the test, in reality either taken from the request or from a middleware.
These test cases verify that apphooks are only accessible on their respective sites
when multiple sites are configured in Django CMS.
"""
def create_pages(self):
self.apphook_clear()
self.site1 = Site.objects.get(pk=1)
self.site2 = Site.objects.create(domain='otherserver', name='Site 2')
superuser = self.get_superuser()
self.page1 = create_page("Page 1", "nav_playground.html", "de", site=self.site1, apphook='SampleApp', created_by=superuser)
create_page_content("en", "en_title", self.page1, slug="page-1")
self.page2 = create_page("Page 2", "nav_playground.html", "de", site=self.site1, created_by=superuser)
self.page3 = create_page("Page 3", "nav_playground.html", "de", site=self.site2, apphook='SampleApp', created_by=superuser)
self.page4 = create_page("Page 4", "nav_playground.html", "de", site=self.site2, created_by=superuser)
self.page2.set_as_homepage()
self.page4.set_as_homepage()
activate("de")
def get_for_site(self, site, url):
resolver_match = resolve(url)
request = self.get_request(url)
request.META["host"] = f"{site.domain}:80"
request.site = site
request.resolver_match = resolver_match
try:
return resolver_match.func(request, *resolver_match.args, **resolver_match.kwargs)
except Http404 as e:
return HttpResponse(str(e), status=404)
def test_apphook_access_on_site1(self):
self.create_pages()
# Verify that the cms_site_filter decorator was called
url = self.page1.get_absolute_url("de")
response = self.get_for_site(self.site1, url)
self.assertEqual(response.status_code, 200)
response = self.get_for_site(self.site1, '/de/page-3/')
self.assertEqual(response.status_code, 404) # Sollte nicht erreichbar sein
def test_apphook_access_on_site2(self):
self.create_pages()
# Verify that the cms_site_filter decorator was called
url = self.page3.get_absolute_url("de")
response = self.get_for_site(self.site2, url)
self.assertEqual(response.status_code, 200)
response = self.get_for_site(self.site2, '/de/page-1/')
self.assertEqual(response.status_code, 404) # Sollte nicht erreichbar sein
@override_settings(ROOT_URLCONF='cms.test_utils.project.second_urls_for_apphook_tests')
def test_apphook_site_filter_preserves_view_name(self):
self.create_pages()
# Force a fresh import of the urlconf now that the apphook pages exist so
# that the global APP_RESOLVERS are rebuilt for the overridden
# ROOT_URLCONF. Without this, reverse() below depends on whether prior
# tests left the url modules cached in sys.modules (order dependence).
self.reload_urls()
view_names = (
('sample-settings', 'sample_view'),
('sample-class-view', 'ClassView'),
('sample-class-based-view', 'view'),
)
with force_language("de"):
for url_name, view_name in view_names:
path = reverse(url_name)
match = resolve(path)
self.assertEqual(match.func.__name__, view_name)
class ApphookFrontendEditingTests(BaseApphooksTestCase):
"""
Tests for rendering external models attached via apphooks in frontend editing mode.
Ensures that context remains consistent (e.g., current_page is resolved).
"""
@override_settings(ROOT_URLCONF='cms.test_utils.project.urls')
def test_current_page_resolution_in_render_object_edit(self):
from django.contrib.sessions.backends.base import SessionBase
from cms.test_utils.project.sampleapp.models import Category
# 1. Setup: Create an apphooked page
superuser = self.get_superuser()
create_page(
title="Apphook Page",
template="nav_playground.html",
language="en",
created_by=superuser,
apphook="SampleApp"
)
self.reload_urls()
# 2. Setup: Create an external model instance (Category)
category = Category.add_root(name="Test Category")
ct = ContentType.objects.get_for_model(Category)
# 3. Setup: Register a mock renderer
cms_extension = apps.get_app_config('cms').cms_extension
def mock_render_category(request, obj):
t = Template("{% load cms_tags %}[{% page_attribute 'page_title' %}]")
return HttpResponse(t.render(Context({"request": request})))
original_renderer = cms_extension.toolbar_enabled_models.get(Category)
cms_extension.toolbar_enabled_models[Category] = mock_render_category
try:
# 4. Action: Create a request
url = f"/admin/cms/placeholder/render-object-edit/{ct.pk}/{category.pk}/"
request = RequestFactory().get(url)
request.user = superuser
request.session = SessionBase()
request.toolbar = CMSToolbar(request)
# 5. Execution
response = render_object_edit(request, ct.pk, category.pk)
content = response.content.decode('utf-8')
# 6. Verification
self.assertEqual(content, "[Apphook Page]")
finally:
if original_renderer:
cms_extension.toolbar_enabled_models[Category] = original_renderer
elif Category in cms_extension.toolbar_enabled_models:
del cms_extension.toolbar_enabled_models[Category]
def _register_root_template_urlconf(patterns):
"""Build a unique, throwaway urlconf module so it can be referenced by
string path (which is what ``django.urls.get_resolver`` expects)."""
name = f'cms.tests._root_template_test_urlconf_{uuid.uuid4().hex}'
module = types.ModuleType(name)
module.urlpatterns = patterns
sys.modules[name] = module
return name
class GetRootTemplateTests(SimpleTestCase):
"""Unit tests for :meth:`cms.app_base.CMSApp.get_root_template`."""
def setUp(self):
clear_url_caches()
def tearDown(self):
clear_url_caches()
def _make_app(self, urlconf):
class _App(CMSApp):
_urls = [urlconf]
return _App()
def test_class_based_view_with_class_level_template_name(self):
class HomeView(TemplateView):
template_name = 'apphook/cbv_home.html'
urlconf = _register_root_template_urlconf([path('', HomeView.as_view())])
self.assertEqual(
self._make_app(urlconf).get_root_template(),
'apphook/cbv_home.html',
)
def test_class_based_view_with_template_name_via_as_view_kwargs(self):
# ``TemplateView.as_view(template_name=...)`` stashes the template on
# ``view_initkwargs``; the lookup must pick it up from there.
urlconf = _register_root_template_urlconf([
path('', TemplateView.as_view(template_name='apphook/inline.html')),
])
self.assertEqual(
self._make_app(urlconf).get_root_template(),
'apphook/inline.html',
)
def test_fbv_returning_template_response_returns_none(self):
# The template name lives inside the response, not on the callable.
# Without rendering the view, get_root_template can't see it.
def home(request):
return TemplateResponse(request, 'apphook/fbv_tr.html', {})
urlconf = _register_root_template_urlconf([path('', home)])
self.assertIsNone(self._make_app(urlconf).get_root_template())
def test_fbv_using_render_shortcut_returns_none(self):
# ``render()`` returns a plain HttpResponse with the body already
# rendered — by the time we'd inspect a response, the template name
# is gone.
def home(request):
return render(request, 'apphook/fbv_render.html')
urlconf = _register_root_template_urlconf([path('', home)])
self.assertIsNone(self._make_app(urlconf).get_root_template())
def test_subclass_can_override_get_root_template(self):
# The advertised escape hatch when inference doesn't fit: override
# the method on the apphook subclass.
class _App(CMSApp):
_urls = []
def get_root_template(self, page=None, language=None, **kwargs):
return 'apphook/manual.html'
self.assertEqual(_App().get_root_template(), 'apphook/manual.html')
def test_returns_none_when_no_root_pattern(self):
class DetailView(TemplateView):
template_name = 'apphook/detail.html'
urlconf = _register_root_template_urlconf([
path('detail/', DetailView.as_view()),
])
self.assertIsNone(self._make_app(urlconf).get_root_template())
def test_root_pattern_via_include(self):
# ``path('', include(...))`` — the root pattern lives inside the
# included urlconf and must still be discovered.
class HomeView(TemplateView):
template_name = 'apphook/inner_home.html'
inner = _register_root_template_urlconf([path('', HomeView.as_view())])
outer = _register_root_template_urlconf([path('', include(inner))])
self.assertEqual(
self._make_app(outer).get_root_template(),
'apphook/inner_home.html',
)
def test_non_root_pattern_template_is_ignored(self):
# When the root view doesn't expose a template, we must NOT fall back
# to a sibling pattern's template — that would be misleading.
def root_fbv(request):
return TemplateResponse(request, 'apphook/root.html', {})
class AboutView(TemplateView):
template_name = 'apphook/about.html'
urlconf = _register_root_template_urlconf([
path('', root_fbv),
path('about/', AboutView.as_view()),
])
self.assertIsNone(self._make_app(urlconf).get_root_template())
def test_inferred_template_is_not_cached(self):
# Inferred templates must NOT be cached on the apphook instance:
# apphook instances are shared singletons, and apphooks like
# VariableUrlsApp return different urlconfs depending on page/language.
# Caching the first inferred template would lock it in for every
# subsequent (possibly differently-routed) call.
class HomeOne(TemplateView):
template_name = 'apphook/one.html'
class HomeTwo(TemplateView):
template_name = 'apphook/two.html'
urlconf_one = _register_root_template_urlconf([path('', HomeOne.as_view())])
urlconf_two = _register_root_template_urlconf([path('', HomeTwo.as_view())])
urlconfs = iter([urlconf_one, urlconf_two])
class _App(CMSApp):
def get_urls(self, page=None, language=None, **kwargs):
return [next(urlconfs)]
app = _App()
self.assertEqual(app.get_root_template(), 'apphook/one.html')
self.assertEqual(app.get_root_template(), 'apphook/two.html')
def test_invalid_urlconf_string_returns_none(self):
# A urlconf that fails to import shouldn't crash the lookup.
class _App(CMSApp):
_urls = ['cms.tests._does_not_exist_xyz']
self.assertIsNone(_App().get_root_template())