dynamicweb/cms/tests/test_toolbar.py

3043 lines
132 KiB
Python

import datetime
import re
from types import SimpleNamespace
from unittest.mock import patch
import iptools
from django.conf import settings
from django.contrib import admin
from django.contrib.admin import site
from django.contrib.auth.models import AnonymousUser, Permission
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site
from django.http import HttpResponse, QueryDict
from django.template.defaultfilters import date as date_filter, truncatewords
from django.test import TestCase
from django.test.client import RequestFactory
from django.test.utils import override_settings
from django.urls import reverse
from django.utils.encoding import force_str
from django.utils.functional import lazy
from django.utils.html import escape
from django.utils.http import urlencode
from django.utils.translation import get_language, gettext_lazy as _, override
from cms.admin.forms import RequestToolbarForm
from cms.api import (
add_plugin,
assign_user_to_page,
create_page,
create_page_content,
)
from cms.cms_toolbars import (
ADMIN_MENU_IDENTIFIER,
ADMINISTRATION_BREAK,
DEFAULT_HELP_MENU_ITEMS,
HELP_MENU_IDENTIFIER,
LANGUAGE_MENU_IDENTIFIER,
PAGE_MENU_IDENTIFIER,
PageToolbar,
get_user_model,
)
from cms.models import PagePermission, Placeholder, UserSettings
from cms.test_utils.project.placeholderapp.models import CharPksExample, Example1
from cms.test_utils.project.placeholderapp.views import ClassDetail, detail_view
from cms.test_utils.testcases import URL_CMS_USERSETTINGS, CMSTestCase
from cms.test_utils.util.context_managers import UserLoginContext, override_placeholder_conf
from cms.toolbar import utils
from cms.toolbar.items import (
AjaxItem,
Break,
ItemSearchResult,
LinkItem,
SubMenu,
ToolbarAPIMixin,
)
from cms.toolbar.toolbar import CMSToolbar, EmptyToolbar
from cms.toolbar.utils import (
add_live_url_querystring_param,
get_object_edit_url,
get_object_for_language,
get_object_live_url,
get_object_preview_url,
get_object_structure_url,
get_plugin_tree,
get_toolbar_from_request,
)
from cms.toolbar_pool import toolbar_pool
from cms.utils.conf import get_cms_setting
from cms.utils.i18n import get_language_tuple
from cms.utils.urlutils import admin_reverse
from cms.views import details
class ToolbarTestBase(CMSTestCase):
def get_anon(self):
return AnonymousUser()
def get_staff(self):
staff = self._create_user("staff", True, False)
staff.user_permissions.add(Permission.objects.get(codename="change_page"))
return staff
def get_nonstaff(self):
nonstaff = self._create_user("nonstaff")
nonstaff.user_permissions.add(Permission.objects.get(codename="change_page"))
return nonstaff
def get_superuser(self):
superuser = self._create_user("superuser", True, True)
return superuser
def _get_example_obj(self):
obj = Example1.objects.create(char_1="one", char_2="two", char_3="tree", char_4="four")
obj.save()
return obj
@override_settings(ROOT_URLCONF="cms.test_utils.project.nonroot_urls")
class ToolbarMiddlewareTest(ToolbarTestBase):
@override_settings(CMS_TOOLBAR_HIDE=False)
def test_no_app_setted_show_toolbar_in_non_cms_urls(self):
request = self.get_page_request(None, self.get_anon(), "/en/example/")
self.assertTrue(hasattr(request, "toolbar"))
@override_settings(CMS_TOOLBAR_HIDE=False)
def test_no_app_setted_show_toolbar_in_cms_urls(self):
page = create_page("foo", "col_two.html", "en")
request = self.get_page_request(page, self.get_anon())
self.assertTrue(hasattr(request, "toolbar"))
@override_settings(CMS_TOOLBAR_HIDE=False)
def test_app_setted_hide_toolbar_in_non_cms_urls_toolbar_hide_unsetted(self):
request = self.get_page_request(None, self.get_anon(), "/en/example/")
self.assertTrue(hasattr(request, "toolbar"))
@override_settings(CMS_TOOLBAR_HIDE=True)
def test_app_setted_hide_toolbar_in_non_cms_urls(self):
request = self.get_page_request(None, self.get_anon(), "/en/example/")
self.assertFalse(hasattr(request, "toolbar"))
@override_settings(CMS_TOOLBAR_HIDE=False)
def test_app_setted_show_toolbar_in_cms_urls(self):
page = create_page("foo", "col_two.html", "en")
page = create_page("foo", "col_two.html", "en", parent=page)
request = self.get_page_request(page, self.get_anon())
self.assertTrue(hasattr(request, "toolbar"))
@override_settings(CMS_TOOLBAR_HIDE=True)
def test_app_setted_show_toolbar_in_cms_urls_subpage(self):
page = create_page("foo", "col_two.html", "en")
page = create_page("foo", "col_two.html", "en", parent=page)
request = self.get_page_request(page, self.get_anon())
self.assertTrue(hasattr(request, "toolbar"))
@override_settings(CMS_TOOLBAR_HIDE=True)
def test_app_setted_show_toolbar_in_admin_endpoints(self):
from cms.middleware.toolbar import ToolbarMiddleware
page = create_page("foo", "col_two.html", "en")
content = page.pagecontent_set.first()
# edit endpoint
request = self.get_request(get_object_edit_url(content))
mid = ToolbarMiddleware(lambda req: HttpResponse(""))
mid(request)
self.assertTrue(hasattr(request, "toolbar"))
# structure endpoint
request = self.get_request(get_object_structure_url(content))
mid = ToolbarMiddleware(lambda req: HttpResponse(""))
mid(request)
self.assertTrue(hasattr(request, "toolbar"))
# preview endpoint
request = self.get_request(get_object_preview_url(content))
mid = ToolbarMiddleware(lambda req: HttpResponse(""))
mid(request)
self.assertTrue(hasattr(request, "toolbar"))
# edit-field endpoint
request = self.get_request(admin_reverse("cms_placeholder_edit_field", args=(content.pk, "en")))
mid = ToolbarMiddleware(lambda req: HttpResponse(""))
mid(request)
self.assertTrue(hasattr(request, "toolbar"))
@override_settings(CMS_TOOLBAR_HIDE=True)
def test_app_setted_provide_toolbar_obj_to_edit_actions(self):
from cms.middleware.toolbar import ToolbarMiddleware
# Add plugin
request = self.get_request(admin_reverse("cms_placeholder_add_plugin"))
mid = ToolbarMiddleware(lambda req: HttpResponse(""))
mid(request)
self.assertTrue(hasattr(request, "toolbar"))
# Edit plugin
request = self.get_request(admin_reverse("cms_placeholder_edit_plugin", args=(1,)))
mid = ToolbarMiddleware(lambda req: HttpResponse(""))
mid(request)
self.assertTrue(hasattr(request, "toolbar"))
# clear placeholder
request = self.get_request(admin_reverse("cms_placeholder_clear_placeholder", args=(1,)))
mid = ToolbarMiddleware(lambda req: HttpResponse(""))
mid(request)
self.assertTrue(hasattr(request, "toolbar"))
# move plugin
request = self.get_request(admin_reverse("cms_placeholder_move_plugin"))
mid = ToolbarMiddleware(lambda req: HttpResponse(""))
mid(request)
self.assertTrue(hasattr(request, "toolbar"))
# copy plugin
request = self.get_request(admin_reverse("cms_placeholder_copy_plugins"))
mid = ToolbarMiddleware(lambda req: HttpResponse(""))
mid(request)
self.assertTrue(hasattr(request, "toolbar"))
def test_cms_internal_ips_unset(self):
with self.settings(CMS_INTERNAL_IPS=[]):
request = self.get_page_request(None, self.get_staff(), "/en/example/")
self.assertTrue(hasattr(request, "toolbar"))
def test_cms_internal_ips_set_no_match(self):
with self.settings(
CMS_INTERNAL_IPS=[
"123.45.67.89",
]
):
request = self.get_page_request(None, self.get_staff(), "/en/example/")
self.assertFalse(hasattr(request, "toolbar"))
def test_cms_internal_ips_set_match(self):
with self.settings(
CMS_INTERNAL_IPS=[
"127.0.0.0",
"127.0.0.1",
"127.0.0.2",
]
):
request = self.get_page_request(None, self.get_staff(), "/en/example/")
self.assertTrue(hasattr(request, "toolbar"))
def test_cms_internal_ips_iptools(self):
with self.settings(CMS_INTERNAL_IPS=iptools.IpRangeList(("127.0.0.0", "127.0.0.255"))):
request = self.get_page_request(None, self.get_staff(), "/en/example/")
self.assertTrue(hasattr(request, "toolbar"))
def test_cms_internal_ips_iptools_bad_range(self):
with self.settings(CMS_INTERNAL_IPS=iptools.IpRangeList(("128.0.0.0", "128.0.0.255"))):
request = self.get_page_request(None, self.get_staff(), "/en/example/")
self.assertFalse(hasattr(request, "toolbar"))
class MissingToolbarTest(CMSTestCase):
"""
The plugin endpoints must degrade instead of crashing when the toolbar is
absent, e.g. because CMS_INTERNAL_IPS excludes the client or because
ToolbarMiddleware is not installed. They enforce their own permissions and
must not depend on the middleware having run.
"""
def get_request_without_toolbar(self):
request = RequestFactory().post("/")
request.user = self.get_superuser()
self.assertFalse(hasattr(request, "toolbar"))
return request
def test_empty_toolbar_has_a_clipboard_attribute(self):
toolbar = EmptyToolbar(RequestFactory().get("/"))
self.assertIsNone(toolbar.clipboard)
def test_get_clipboard_from_request_without_toolbar(self):
self.assertIsNone(get_toolbar_from_request(self.get_request_without_toolbar()).clipboard)
def test_get_plugin_tree_without_toolbar(self):
page = create_page("home", "nav_playground.html", "en")
placeholder = page.get_placeholders("en")[0]
plugin = add_plugin(placeholder, "LinkPlugin", "en", name="link", external_link="http://example.com")
tree = get_plugin_tree(self.get_request_without_toolbar(), [plugin])
self.assertIn("html", tree)
self.assertEqual(len(tree["plugins"]), 1)
def test_copy_plugins_permission_denied_without_clipboard(self):
"""Without a clipboard there is nothing to copy into, so deny rather than crash."""
page = create_page("home", "nav_playground.html", "en")
placeholder = page.get_placeholders("en")[0]
plugin = add_plugin(placeholder, "LinkPlugin", "en", name="link", external_link="http://example.com")
admin_instance = site._registry[Placeholder]
self.assertFalse(admin_instance.has_copy_plugins_permission(self.get_request_without_toolbar(), [plugin]))
def test_add_plugin_endpoint_does_not_crash_without_toolbar(self):
"""Regression: rendering the close frame used to raise AttributeError."""
page = create_page("home", "nav_playground.html", "en")
placeholder = page.get_placeholders("en")[0]
data = {
"plugin_type": "LinkPlugin",
"placeholder_id": placeholder.pk,
"plugin_language": "en",
"plugin_position": 1,
"cms_path": "/en/",
}
url = admin_reverse("cms_placeholder_add_plugin") + "?" + urlencode(data)
# CMS_INTERNAL_IPS excludes the test client, so no toolbar is attached.
with self.settings(CMS_INTERNAL_IPS=["203.0.113.10"]):
with self.login_user_context(self.get_superuser()):
response = self.client.post(
url, {"name": "link", "external_link": "http://example.com"}
)
self.assertEqual(response.status_code, 200)
self.assertEqual(placeholder.get_plugins("en").count(), 1)
@override_settings(CMS_PERMISSION=False)
class ToolbarTests(ToolbarTestBase):
def get_page_item(self, toolbar):
items = toolbar.get_left_items() + toolbar.get_right_items()
page_item = [item for item in items if force_str(getattr(item, "name", None)) == "Page"]
self.assertEqual(len(page_item), 1)
return page_item[0]
def test_toolbar_login(self):
admin = self.get_superuser()
endpoint = reverse("cms_login") + "?next=/en/admin/"
username = getattr(admin, get_user_model().USERNAME_FIELD)
password = getattr(admin, get_user_model().USERNAME_FIELD)
response = self.client.post(endpoint, data={"username": username, "password": password})
self.assertRedirects(response, "/en/admin/")
self.assertTrue(settings.SESSION_COOKIE_NAME in response.cookies)
def test_toolbar_login_error(self):
admin = self.get_superuser()
endpoint = reverse("cms_login") + "?next=/en/admin/"
username = getattr(admin, get_user_model().USERNAME_FIELD)
response = self.client.post(endpoint, data={"username": username, "password": "invalid"})
self.assertRedirects(response, "/en/admin/?cms_toolbar_login_error=1", target_status_code=302)
self.assertFalse(settings.SESSION_COOKIE_NAME in response.cookies)
def test_toolbar_login_invalid_redirect_to(self):
admin = self.get_superuser()
endpoint = reverse("cms_login") + "?next=http://example.com"
username = getattr(admin, get_user_model().USERNAME_FIELD)
password = getattr(admin, get_user_model().USERNAME_FIELD)
response = self.client.post(endpoint, data={"username": username, "password": password})
self.assertRedirects(response, "/en/")
self.assertTrue(settings.SESSION_COOKIE_NAME in response.cookies)
def test_toolbar_login_error_preserves_query_string(self):
# The error flag must be appended without clobbering an existing query.
admin = self.get_superuser()
endpoint = reverse("cms_login") + "?next=/en/admin/%3Ffoo%3Dbar"
username = getattr(admin, get_user_model().USERNAME_FIELD)
response = self.client.post(endpoint, data={"username": username, "password": "invalid"})
self.assertRedirects(
response,
"/en/admin/?foo=bar&cms_toolbar_login_error=1",
target_status_code=302,
)
self.assertFalse(settings.SESSION_COOKIE_NAME in response.cookies)
def test_toolbar_login_unresolvable_redirect_to(self):
# A same-host path that does not resolve against the URL configuration
# must not be used as a redirect target (open redirect protection).
admin = self.get_superuser()
endpoint = reverse("cms_login") + "?next=/not-a-language/does-not-resolve@@/"
username = getattr(admin, get_user_model().USERNAME_FIELD)
password = getattr(admin, get_user_model().USERNAME_FIELD)
response = self.client.post(endpoint, data={"username": username, "password": password})
self.assertRedirects(response, "/en/")
self.assertTrue(settings.SESSION_COOKIE_NAME in response.cookies)
def test_toolbar_login_resolvable_redirect_to(self):
# A same-host path that resolves to a project view is accepted.
admin = self.get_superuser()
endpoint = reverse("cms_login") + "?next=/en/admin/"
username = getattr(admin, get_user_model().USERNAME_FIELD)
password = getattr(admin, get_user_model().USERNAME_FIELD)
response = self.client.post(endpoint, data={"username": username, "password": password})
self.assertRedirects(response, "/en/admin/", target_status_code=200)
self.assertTrue(settings.SESSION_COOKIE_NAME in response.cookies)
@override_settings(CMS_TOOLBARS=["cms.test_utils.project.sampleapp.cms_toolbars.ToolbarWithMedia"])
def test_toolbar_media(self):
"""
Toolbar classes can declare a media class or property
to be rendered along with the toolbar.
"""
old_pool = toolbar_pool.toolbars
toolbar_pool.clear()
cms_page = create_page("toolbar-page", "col_two.html", "en")
page_content = self.get_pagecontent_obj(cms_page)
endpoint = get_object_edit_url(page_content)
with self.login_user_context(self.get_superuser()):
response = self.client.get(endpoint)
self.assertContains(response, 'src="/static/samplemap/js/sampleapp.js"')
self.assertContains(response, 'href="/static/samplemap/css/sampleapp.css"')
toolbar_pool.toolbars = old_pool
toolbar_pool._discovered = True
def test_toolbar_request_endpoint_validation(self):
endpoint = self.get_admin_url(UserSettings, "get_toolbar")
cms_page = create_page("toolbar-page", "col_two.html", "en")
page_content = self.get_pagecontent_obj(cms_page)
with self.login_user_context(self.get_superuser()):
response = self.client.get(
endpoint,
data={
"obj_id": page_content.pk,
"obj_type": "cms.pagecontent",
"cms_path": get_object_edit_url(page_content),
},
)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Clipboard")
response = self.client.get(
endpoint,
data={
"obj_id": page_content.pk,
"obj_type": "cms.pagecontent",
"cms_path": get_object_edit_url(page_content) + "q", # Invalid
},
)
self.assertEqual(response.status_code, 200)
# No clipboard exposed to invalid cms_path
self.assertNotContains(response, "Clipboard")
# Invalid app / model
response = self.client.get(
endpoint,
data={"obj_id": cms_page.pk, "obj_type": "cms.somemodel", "cms_path": cms_page.get_absolute_url("en")},
)
self.assertEqual(response.status_code, 400)
@override_settings(CMS_PERMISSION=True)
def test_toolbar_request_endpoint_enforces_object_view_permission(self):
# The obj_id/obj_type come straight from the client, so the endpoint
# must enforce an object-level view check before rendering a toolbar
# (which discloses the object's title/breadcrumb/edit URLs). A staff
# user who cannot view a restricted page must be denied.
endpoint = self.get_admin_url(UserSettings, "get_toolbar")
cms_page = create_page("secret-page", "col_two.html", "en")
page_content = self.get_pagecontent_obj(cms_page)
# Grant view to one user only -> the page is now view-restricted.
assign_user_to_page(cms_page, self.get_standard_user(), can_view=True)
data = {
"obj_id": page_content.pk,
"obj_type": "cms.pagecontent",
"cms_path": get_object_edit_url(page_content),
}
# A staff user without view permission is denied with the same generic
# response as an unknown object (no existence oracle).
with self.login_user_context(self.get_staff_user_with_no_permissions()):
response = self.client.get(endpoint, data=data)
self.assertEqual(response.status_code, 400)
self.assertNotContains(response, "Clipboard", status_code=400)
# A superuser can still render the toolbar.
with self.login_user_context(self.get_superuser()):
response = self.client.get(endpoint, data=data)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Clipboard")
def test_toolbar_request_form(self):
cms_page = create_page("toolbar-page", "col_two.html", "en")
generic_obj = self._get_example_obj()
# Valid forms
form = RequestToolbarForm(
{
"obj_id": cms_page.pk,
"obj_type": "cms.page",
"cms_path": cms_page.get_absolute_url("en"),
}
)
self.assertTrue(form.is_valid())
self.assertEqual(form.cleaned_data["attached_obj"], cms_page)
form = RequestToolbarForm(
{
"obj_id": generic_obj.pk,
"obj_type": "placeholderapp.example1",
"cms_path": cms_page.get_absolute_url("en"),
}
)
self.assertTrue(form.is_valid())
self.assertEqual(form.cleaned_data["attached_obj"], generic_obj)
# Invalid forms
form = RequestToolbarForm(
{
"obj_id": 1000,
"obj_type": "cms.page",
"cms_path": cms_page.get_absolute_url("en"),
}
)
self.assertFalse(form.is_valid())
form = RequestToolbarForm(
{
"obj_id": cms_page.pk,
"obj_type": "cms.somemodel",
"cms_path": cms_page.get_absolute_url("en"),
}
)
self.assertFalse(form.is_valid())
form = RequestToolbarForm(
{
"obj_id": cms_page.pk,
"obj_type": "cms.page",
"cms_path": "https://example.com/some-path/",
}
)
self.assertFalse(form.is_valid())
def test_no_page_anon(self):
request = self.get_page_request(None, self.get_anon(), "/")
toolbar = CMSToolbar(request)
toolbar.populate()
toolbar.post_template_populate()
items = toolbar.get_left_items() + toolbar.get_right_items()
self.assertEqual(len(items), 0)
def test_no_page_staff(self):
request = self.get_page_request(None, self.get_staff(), "/")
toolbar = CMSToolbar(request)
toolbar.populate()
toolbar.post_template_populate()
items = toolbar.get_left_items() + toolbar.get_right_items()
self.assertEqual(len(items), 5, items)
admin_items = toolbar.get_or_create_menu(ADMIN_MENU_IDENTIFIER, "Test").get_items()
self.assertEqual(len(admin_items), 12, admin_items)
def test_no_page_superuser(self):
request = self.get_page_request(None, self.get_superuser(), "/")
toolbar = CMSToolbar(request)
toolbar.populate()
toolbar.post_template_populate()
items = toolbar.get_left_items() + toolbar.get_right_items()
self.assertEqual(len(items), 5)
admin_items = toolbar.get_or_create_menu(ADMIN_MENU_IDENTIFIER, "Test").get_items()
self.assertEqual(len(admin_items), 13, admin_items)
def test_anon(self):
page = create_page("test", "nav_playground.html", "en")
request = self.get_page_request(page, self.get_anon())
toolbar = CMSToolbar(request)
items = toolbar.get_left_items() + toolbar.get_right_items()
self.assertEqual(len(items), 0)
def test_nonstaff(self):
page = create_page("test", "nav_playground.html", "en")
request = self.get_page_request(page, self.get_nonstaff())
toolbar = CMSToolbar(request)
items = toolbar.get_left_items() + toolbar.get_right_items()
self.assertEqual(len(items), 0)
@override_settings(CMS_PERMISSION=True)
def test_template_change_permission(self):
"""The template lives on the page content, so the *change* permission
governs it -- not *change advanced settings*.
"""
page = create_page("test", "nav_playground.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
def get_template_items(user):
request = self.get_page_request(page, user, edit_url)
page_item = self.get_page_item(CMSToolbar(request))
return [item for item in page_item.items if force_str(getattr(item, "name", "")) == "Templates"]
# Staff user with change page permissions only -- no advanced settings.
staff_user = self.get_staff_user_with_no_permissions()
self.add_permission(staff_user, "change_page")
global_permission = self.add_global_permission(staff_user, can_change=True, can_delete=True)
template_item = get_template_items(staff_user)
self.assertEqual(len(template_item), 1)
self.assertFalse(template_item[0].disabled)
# Granting advanced settings on top changes nothing.
global_permission.can_change_advanced_settings = True
global_permission.save()
staff_user = self.reload(staff_user)
template_item = get_template_items(staff_user)
self.assertEqual(len(template_item), 1)
self.assertFalse(template_item[0].disabled)
def test_markup(self):
page = create_page("toolbar-page", "nav_playground.html", "en")
page_content = self.get_pagecontent_obj(page)
page_edit_url = get_object_edit_url(page_content)
superuser = self.get_superuser()
with self.login_user_context(superuser):
response = self.client.get(page_edit_url)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "nav_playground.html")
self.assertContains(response, '<div id="cms-top"')
self.assertContains(response, "cms.base.css")
def test_placeholder_buttons_on_app_page(self):
"""
Checks that the "edit page" button shows up
on non-cms pages with app placeholders.
"""
superuser = self.get_superuser()
ex1 = self._get_example_obj()
# Test for Edit button
obj_edit_url = get_object_edit_url(ex1)
with self.login_user_context(superuser):
response = self.client.get("/en/example/latest/")
self.assertEqual(response.status_code, 200)
toolbar = response.wsgi_request.toolbar
self.assertEqual(len(toolbar.get_right_items()[2].buttons), 1)
edit_button = toolbar.get_right_items()[2].buttons[0]
self.assertEqual(edit_button.name, "Edit")
self.assertEqual(edit_button.url, obj_edit_url)
self.assertEqual(edit_button.extra_classes, ["cms-btn", "cms-btn-action", "cms-btn-switch-edit"])
# Test for Preview button
obj_preview_url = get_object_preview_url(ex1)
with self.login_user_context(superuser):
response = self.client.get(obj_edit_url)
self.assertEqual(response.status_code, 200)
toolbar = response.wsgi_request.toolbar
self.assertEqual(len(toolbar.get_right_items()[2].buttons), 1)
preview_button = toolbar.get_right_items()[2].buttons[0]
self.assertEqual(preview_button.name, "Preview")
self.assertEqual(preview_button.url, obj_preview_url)
self.assertEqual(preview_button.extra_classes, ["cms-btn", "cms-btn-switch-save"])
def test_placeholder_buttons_preserve_get_parameters(self):
"""
Verifies that the edit and preview buttons in the toolbar
preserve GET parameters in their URLs.
"""
superuser = self.get_superuser()
ex1 = self._get_example_obj()
# Test that GET parameters are preserved in edit button
with self.login_user_context(superuser):
response = self.client.get("/en/example/latest/?param1=value1&param2=value2")
self.assertEqual(response.status_code, 200)
toolbar = response.wsgi_request.toolbar
edit_button = toolbar.get_right_items()[2].buttons[0]
self.assertEqual(edit_button.name, "Edit")
self.assertIn("param1=value1", edit_button.url)
self.assertIn("param2=value2", edit_button.url)
# Test that GET parameters are preserved in preview button
obj_edit_url = get_object_edit_url(ex1)
with self.login_user_context(superuser):
response = self.client.get(obj_edit_url + "?param1=value1&param2=value2")
self.assertEqual(response.status_code, 200)
toolbar = response.wsgi_request.toolbar
preview_button = toolbar.get_right_items()[2].buttons[0]
self.assertEqual(preview_button.name, "Preview")
self.assertIn("param1=value1", preview_button.url)
self.assertIn("param2=value2", preview_button.url)
def test_markup_generic_module(self):
page = create_page("toolbar-page", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
page_structure_url = get_object_structure_url(page_content)
superuser = self.get_superuser()
with self.login_user_context(superuser):
response = self.client.get(page_structure_url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, '<div class="cms-submenu-item cms-submenu-item-title"><span>Generic</span>')
def test_markup_link_custom_module(self):
superuser = self.get_superuser()
page = create_page("toolbar-page", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
page_structure_url = get_object_structure_url(page_content)
with self.login_user_context(superuser):
response = self.client.get(page_structure_url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'href="LinkPlugin">')
self.assertContains(
response, '<div class="cms-submenu-item cms-submenu-item-title"><span>Different Grouper</span>'
)
def test_placeholder_menu_items(self):
superuser = self.get_superuser()
page = create_page("toolbar-page", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
page_structure_url = get_object_structure_url(page_content)
placeholders = page_content.get_placeholders()
self.assertEqual(len(placeholders), 2) # we get two placeholders
with self.login_user_context(superuser):
response = self.client.get(page_structure_url)
self.assertEqual(response.status_code, 200)
expected_bits = (
# Copy all in placeholder menu
'<div class="cms-submenu-item"><a data-cms-icon="copy" data-rel="copy" href="#">Copy all</a></div>',
# Past in placeholder menu
'<div class="cms-submenu-item"><a data-cms-icon="paste" data-rel="paste" href="#">Paste</a></div>',
# Empty all placeholder menu (for both placeholders)
'<div class="cms-submenu-item"><a data-cms-icon="bin" data-rel="modal" href="'
+ reverse("admin:cms_placeholder_clear_placeholder", args=(placeholders[0].id,)),
'<div class="cms-submenu-item"><a data-cms-icon="bin" data-rel="modal" href="'
+ reverse("admin:cms_placeholder_clear_placeholder", args=(placeholders[1].id,)),
'data-name="sidebar column">Empty all</a></div>',
# Extra items in placeholder menu
'<div class="cms-submenu-item"><a href="/some/url/" data-rel="ajax"',
'data-on-success="REFRESH_PAGE" data-cms-icon="whatever" >Data item - not usable</a></div>',
'<div class="cms-submenu-item"><a href="/some/other/url/" data-rel="ajax_add"',
)
for bit in expected_bits:
self.assertContains(response, bit)
def test_markup_plugin_template(self):
page = create_page("toolbar-page-1", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
page_edit_url = get_object_edit_url(page_content)
plugin_1 = add_plugin(
page.get_placeholders("en").get(slot="col_left"),
language="en",
plugin_type="TestPluginAlpha",
alpha="alpha",
)
superuser = self.get_superuser()
with self.login_user_context(superuser):
response = self.client.get(page_edit_url)
self.assertEqual(response.status_code, 200)
response_text = response.render().rendered_content
self.assertTrue(
re.search("edit_plugin.+/en/admin/cms/placeholder/edit-plugin/%s" % plugin_1.pk, response_text),
f"/en/admin/cms/placeholder/edit-plugin/{plugin_1.pk} not found in {response_text}",
)
self.assertTrue(re.search("move_plugin.+/en/admin/cms/placeholder/move-plugin/", response_text))
self.assertTrue(
re.search("delete_plugin.+/en/admin/cms/placeholder/delete-plugin/%s/" % plugin_1.pk, response_text)
)
self.assertTrue(re.search("add_plugin.+/en/admin/cms/placeholder/add-plugin/", response_text))
self.assertTrue(re.search("copy_plugin.+/en/admin/cms/placeholder/copy-plugins/", response_text))
def test_show_toolbar_to_staff(self):
page = create_page("toolbar-page", "nav_playground.html", "en")
staff = self.get_staff()
assert staff.user_permissions.get().name == "Can change page"
request = self.get_page_request(page, staff, "/")
toolbar = CMSToolbar(request)
self.assertTrue(toolbar.show_toolbar)
def test_remove_and_copy_urls_are_correctly_associated_with_pagecontent(self):
"""
The urls to copy and remove translations should be linked correctly.
"""
def get_delete_url(pk):
return admin_reverse("cms_pagecontent_delete", args=(pk,))
page = create_page("english-page", "nav_playground.html", "en")
german_content = create_page_content("de", "german content", page)
english_content = page.get_content_obj("en")
page_languages = page.get_languages()
edit_url = get_object_edit_url(english_content)
staff = self.get_staff()
self.client.force_login(staff)
response = self.client.get(edit_url)
menus = response.context["cms_toolbar"].menus
language_menu = menus["language-menu"]
delete = language_menu.items[-2]
german_delete = delete.items[page_languages.index("de")]
english_delete = delete.items[page_languages.index("en")]
copy = language_menu.items[-1]
copy_german = copy.items[0]
copy_german_context = copy_german.get_context()
self.assertEqual(delete.name.lower(), "delete translation")
self.assertEqual(german_delete.name.lower(), "german...")
self.assertEqual(german_delete.url.split("?")[0], get_delete_url(german_content.pk))
self.assertEqual(english_delete.name.lower(), "english...")
self.assertEqual(english_delete.url.split("?")[0], get_delete_url(english_content.pk))
self.assertEqual(copy_german.name.lower(), "from german")
self.assertEqual(
copy_german_context["action"], admin_reverse("cms_pagecontent_copy_language", args=(german_content.pk,))
)
def _get_delete_page_item(self, toolbar):
page_menu = toolbar.get_menu(PAGE_MENU_IDENTIFIER)
for item in page_menu.items:
if force_str(getattr(item, "name", "")).startswith("Delete page"):
return item
return None
def test_delete_redirect_not_computed_when_delete_disabled(self):
"""
Regression test: the on-delete redirect URL was computed on every
toolbar render of a page with a parent, issuing two parent-page
queries even when the delete item is disabled (e.g. outside edit
mode). It must only be computed when the delete action is available.
"""
parent = create_page("parent", "nav_playground.html", "en")
child = create_page("child", "nav_playground.html", "en", parent=parent)
with patch.object(PageToolbar, "get_on_delete_redirect_url", autospec=True) as mocked:
# View mode: the delete item is disabled, so no redirect is needed.
self.get_page_request(child, self.get_superuser(), child.get_absolute_url())
mocked.assert_not_called()
def test_delete_redirect_computed_when_delete_enabled(self):
"""The redirect URL is still resolved (to the parent) when deletion is possible."""
parent = create_page("parent", "nav_playground.html", "en")
child = create_page("child", "nav_playground.html", "en", parent=parent)
edit_url = get_object_edit_url(child.get_content_obj("en"))
request = self.get_page_request(child, self.get_superuser(), edit_url)
delete_item = self._get_delete_page_item(request.toolbar)
self.assertIsNotNone(delete_item)
self.assertFalse(delete_item.disabled)
self.assertEqual(
delete_item.on_close,
get_object_preview_url(parent.get_admin_content("en")),
)
def test_show_toolbar_staff(self):
page = create_page("toolbar-page", "nav_playground.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
request = self.get_page_request(page, self.get_staff(), edit_url)
toolbar = CMSToolbar(request)
self.assertTrue(toolbar.show_toolbar)
def test_hide_toolbar_non_staff(self):
page = create_page("toolbar-page", "nav_playground.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
request = self.get_page_request(page, self.get_nonstaff(), edit_url)
toolbar = CMSToolbar(request)
self.assertFalse(toolbar.show_toolbar)
def test_hide_toolbar_disabled(self):
page = create_page("toolbar-page", "nav_playground.html", "en")
# Edit mode should re-enable the toolbar in any case
request = self.get_page_request(page, self.get_staff(), disable=True)
self.assertTrue(request.session.get("cms_toolbar_disabled"))
toolbar = CMSToolbar(request)
self.assertFalse(toolbar.edit_mode_active)
self.assertFalse(toolbar.structure_mode_active)
self.assertFalse(toolbar.preview_mode_active)
def test_show_disabled_toolbar_with_edit(self):
page = create_page("toolbar-page", "nav_playground.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
request = self.get_page_request(page, self.get_staff(), edit_url, disable=True)
self.assertFalse(request.session.get("cms_toolbar_disabled"))
toolbar = CMSToolbar(request)
self.assertTrue(toolbar.show_toolbar)
def test_hide_toolbar_disabled_no_persist(self):
page = create_page("toolbar-page", "nav_playground.html", "en")
request = self.get_page_request(page, self.get_staff(), disable=True, persist=False)
self.assertFalse(request.session.get("cms_toolbar_disabled"))
toolbar = CMSToolbar(request)
self.assertFalse(toolbar.show_toolbar)
def test_toolbar_login_redirect_validation(self):
user = self._create_user("toolbar", True, True)
username = getattr(user, user.USERNAME_FIELD)
page = create_page("toolbar-page", "nav_playground.html", "en")
page.set_as_homepage()
login_url = reverse("cms_login")
endpoint = f"{login_url}?next=https://notyourdomain.com"
response = self.client.post(endpoint, {"username": username, "password": username})
self.assertRedirects(response, page.get_absolute_url(), fetch_redirect_response=False)
@override_settings(CMS_TOOLBAR_ANONYMOUS_ON=True)
def test_show_toolbar_login_anonymous(self):
page = create_page("toolbar-page", "nav_playground.html", "en")
page_url = "{}?{}".format(page.get_absolute_url(), get_cms_setting("TOOLBAR_URL__ENABLE"))
response = self.client.get(page_url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "cms-toolbar")
@override_settings(CMS_TOOLBAR_ANONYMOUS_ON=False)
def test_hide_toolbar_login_anonymous_setting(self):
page = create_page("toolbar-page", "nav_playground.html", "en")
response = self.client.get(page.get_absolute_url())
self.assertEqual(response.status_code, 200)
self.assertNotContains(response, "cms-toolbar")
def test_admin_logout_staff(self):
with override_settings(CMS_PERMISSION=True):
with self.login_user_context(self.get_staff()):
response = self.client.post("/en/admin/logout/")
self.assertTrue(response.status_code, 200)
def test_toolbar_hidden_when_admin_not_available(self):
"""
Test that the toolbar is hidden when admin URLs are not available.
When admin_reverse raises NoReverseMatch (e.g., admin not in urlpatterns),
the toolbar should be disabled even for staff users.
"""
from unittest.mock import patch
from django.urls import NoReverseMatch
page = create_page("toolbar-page", "nav_playground.html", "en")
staff_user = self.get_staff()
# Create request manually without going through get_page_request
request = self.get_request("/", page=page)
request.user = staff_user
request.session = {}
request.current_page = page
# Mock admin_reverse to raise NoReverseMatch only for 'admin:index'
with patch("cms.toolbar.toolbar.admin_reverse") as mock_admin_reverse:
mock_admin_reverse.side_effect = NoReverseMatch("No admin URLs")
# Initialize toolbar - this should catch NoReverseMatch and set show_toolbar=False
toolbar = CMSToolbar(request)
# Toolbar should be hidden because admin is not available
self.assertFalse(toolbar.show_toolbar)
def test_show_toolbar_without_edit(self):
page = create_page("toolbar-page", "nav_playground.html", "en")
request = self.get_page_request(page, AnonymousUser())
toolbar = CMSToolbar(request)
self.assertFalse(toolbar.show_toolbar)
def test_no_change_button(self):
page = create_page("test", "nav_playground.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
user = self.get_staff()
user.user_permissions.all().delete()
request = self.get_page_request(page, user, edit_url, disable=False)
request.toolbar.post_template_populate()
self.assertFalse(page.has_change_permission(request.user))
items = request.toolbar.get_left_items() + request.toolbar.get_right_items()
self.assertEqual(len(items), 6, items)
page_items = items[1].get_items()
# The page menu should only have the "Create page" item enabled.
self.assertFalse(page_items[0].disabled)
self.assertTrue(all(item.disabled for item in page_items[1:] if hasattr(item, "disabled")))
admin_items = request.toolbar.get_or_create_menu(ADMIN_MENU_IDENTIFIER, "Test").get_items()
self.assertEqual(len(admin_items), 14, admin_items)
def test_button_consistency_staff(self):
"""
Tests that the buttons remain even when the language changes.
"""
user = self.get_staff()
cms_page = create_page("test-en", "nav_playground.html", "en")
page_content_en = self.get_pagecontent_obj(cms_page)
edit_url_en = get_object_edit_url(page_content_en)
page_content_de = create_page_content("de", "test-de", cms_page)
edit_url_de = get_object_edit_url(page_content_de)
en_request = self.get_page_request(cms_page, user, edit_url_en)
en_toolbar = CMSToolbar(en_request)
en_toolbar.set_object(page_content_en)
en_toolbar.populate()
en_toolbar.post_template_populate()
self.assertEqual(len(en_toolbar.get_left_items() + en_toolbar.get_right_items()), 7)
de_request = self.get_page_request(cms_page, user, edit_url_de, lang_code="de")
de_toolbar = CMSToolbar(de_request)
de_toolbar.set_object(page_content_de)
de_toolbar.populate()
de_toolbar.post_template_populate()
self.assertEqual(len(de_toolbar.get_left_items() + de_toolbar.get_right_items()), 7)
def test_double_menus(self):
"""
Tests that even called multiple times, admin and language buttons are not duplicated
"""
user = self.get_staff()
page = create_page("test", "nav_playground.html", "en")
for code, verbose in get_language_tuple(Site.objects.get_current().pk):
if code != "en":
create_page_content(code, f"test {code}", page)
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
en_request = self.get_page_request(None, user, edit_url)
toolbar = CMSToolbar(en_request)
toolbar.set_object(page_content)
toolbar.populated = False
toolbar.populate()
toolbar.populated = False
toolbar.populate()
toolbar.populated = False
toolbar.post_template_populate()
get_object_for_language(page_content, "de")
admin_menu = toolbar.get_left_items()[0]
help_menu = toolbar.get_left_items()[1]
lang_menu = toolbar.get_left_items()[2]
self.assertEqual(len(admin_menu.get_items()), 15)
self.assertEqual(
list(DEFAULT_HELP_MENU_ITEMS),
[(item.name, item.url) for item in help_menu.get_items()],
)
self.assertEqual(
[item.name for item in lang_menu.get_items()],
[language_name for _, language_name in get_language_tuple(Site.objects.get_current().pk)],
)
self.assertIn(edit_url, [item.url for item in lang_menu.get_items()]) # Edit urls returned
@override_placeholder_conf(CMS_PLACEHOLDER_CONF={"col_left": {"name": "Charles Babbage"}})
def test_placeholder_name(self):
superuser = self.get_superuser()
page = create_page("toolbar-page", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
page_edit_url = get_object_edit_url(page_content)
with self.login_user_context(superuser):
response = self.client.get(page_edit_url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Charles Babbage") # Configured placeholder name
self.assertContains(response, "Col_Sidebar") # Fall back placeholder name
def test_user_settings(self):
superuser = self.get_superuser()
with self.login_user_context(superuser):
response = self.client.get(URL_CMS_USERSETTINGS)
self.assertEqual(response.status_code, 200)
def test_remove_lang(self):
page = create_page("test", "nav_playground.html", "en")
page_content = self.get_pagecontent_obj(page)
page_edit_url = get_object_edit_url(page_content)
superuser = self.get_superuser()
with self.login_user_context(superuser):
response = self.client.get(page_edit_url)
self.assertEqual(response.status_code, 200)
setting = UserSettings.objects.get(user=superuser)
setting.language = "it"
setting.save()
with self.settings(LANGUAGES=(("en", "english"),)):
response = self.client.get(page_edit_url)
self.assertEqual(response.status_code, 200)
self.assertNotContains(response, "/it/")
def test_get_alphabetical_insert_position(self):
page = create_page("toolbar-page", "nav_playground.html", "en")
request = self.get_page_request(page, self.get_staff(), "/")
toolbar = CMSToolbar(request)
toolbar.get_left_items()
toolbar.get_right_items()
admin_menu = toolbar.get_or_create_menu(ADMIN_MENU_IDENTIFIER, "TestAppMenu")
# Insert alpha
alpha_position = admin_menu.get_alphabetical_insert_position("menu-alpha", SubMenu, None)
# As this will be the first item added to this, this use should return the default, or namely None
if not alpha_position:
alpha_position = admin_menu.find_first(Break, identifier=ADMINISTRATION_BREAK) + 1
admin_menu.get_or_create_menu("menu-alpha", "menu-alpha", position=alpha_position)
# Insert gamma (should return alpha_position + 1)
gamma_position = admin_menu.get_alphabetical_insert_position("menu-gamma", SubMenu)
self.assertEqual(int(gamma_position), int(alpha_position) + 1)
admin_menu.get_or_create_menu("menu-gamma", "menu-gamma", position=gamma_position)
# Where should beta go? It should go right where gamma is now...
beta_position = admin_menu.get_alphabetical_insert_position("menu-beta", SubMenu)
self.assertEqual(beta_position, gamma_position)
def test_out_of_order(self):
page = create_page("toolbar-page", "nav_playground.html", "en")
request = self.get_page_request(page, self.get_staff(), "/")
toolbar = CMSToolbar(request)
menu1 = toolbar.get_or_create_menu("test")
menu2 = toolbar.get_or_create_menu("test", "Test", side=toolbar.RIGHT, position=2)
self.assertEqual(menu1, menu2)
self.assertEqual(menu1.name, "Test")
self.assertEqual(len(toolbar.get_right_items()), 2) # Including color scheme switch
def test_negative_position_left(self):
page = create_page("toolbar-page", "nav_playground.html", "en")
request = self.get_page_request(page, self.get_staff(), "/")
toolbar = CMSToolbar(request)
# Starting point: [Menu:Example, Menu:Page, Menu:Language]
# Example @ 1, Page @ 2, Language @ -1
menu1 = toolbar.get_or_create_menu("menu1", "Menu1", side=toolbar.LEFT, position=-2)
menu2 = toolbar.get_or_create_menu("menu2", "Menu2", side=toolbar.LEFT, position=-3)
self.assertEqual(toolbar.get_left_items().index(menu1), 3)
self.assertEqual(toolbar.get_left_items().index(menu2), 2)
def test_negative_position_right(self):
page = create_page("toolbar-page", "nav_playground.html", "en")
request = self.get_page_request(page, self.get_staff(), "/")
toolbar = CMSToolbar(request)
# Starting point: [] (empty)
# Add a couple of "normal" menus
toolbar.get_or_create_menu("menu1", "Menu1", side=toolbar.RIGHT)
toolbar.get_or_create_menu("menu2", "Menu2", side=toolbar.RIGHT)
menu3 = toolbar.get_or_create_menu("menu3", "Menu3", side=toolbar.RIGHT, position=-1)
menu4 = toolbar.get_or_create_menu("menu4", "Menu4", side=toolbar.RIGHT, position=-2)
self.assertEqual(toolbar.get_right_items().index(menu3), 4) # Including color scheme
self.assertEqual(toolbar.get_right_items().index(menu4), 3) # Including color scheme
def assertMenuItems(self, request, menu_id, name, items=None):
toolbar = CMSToolbar(request)
toolbar.populate()
menu = {force_str(getattr(item, "name", "|")): item for item in toolbar.get_menu(menu_id).get_items()}
self.assertIn(name, list(menu))
if items is not None:
sub_menu = list(force_str(getattr(item, "name", "|")) for item in menu[name].get_items())
self.assertEqual(sorted(sub_menu), sorted(items))
def test_remove_language(self):
page = create_page("toolbar-page", "nav_playground.html", "en")
create_page_content(title="de page", language="de", page=page)
create_page_content(title="fr page", language="fr", page=page)
page_content_en = self.get_pagecontent_obj(page)
edit_url_en = get_object_edit_url(page_content_en)
request = self.get_page_request(page, self.get_staff(), edit_url_en)
self.assertMenuItems(
request, LANGUAGE_MENU_IDENTIFIER, "Delete Translation", ["German...", "English...", "French..."]
)
reduced_langs = {
1: [
{
"code": "en",
"name": "English",
"fallbacks": ["fr", "de"],
"public": True,
},
{
"code": "fr",
"name": "French",
"public": True,
},
]
}
with self.settings(CMS_LANGUAGES=reduced_langs):
self.assertMenuItems(request, LANGUAGE_MENU_IDENTIFIER, "Delete Translation", ["English...", "French..."])
def test_add_language(self):
page = create_page("tbp", "nav_playground.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
request = self.get_page_request(page, self.get_staff(), edit_url)
self.assertMenuItems(
request,
LANGUAGE_MENU_IDENTIFIER,
"Add Translation",
["German...", "Brazilian Portuguese...", "French...", "Espa\xf1ol..."],
)
create_page_content(title="de page", language="de", page=page)
create_page_content(title="fr page", language="fr", page=page)
self.assertMenuItems(
request, LANGUAGE_MENU_IDENTIFIER, "Add Translation", ["Brazilian Portuguese...", "Espa\xf1ol..."]
)
def test_copy_plugins(self):
page = create_page("tbp", "nav_playground.html", "en")
title_en = self.get_pagecontent_obj(page)
edit_url_en = get_object_edit_url(title_en)
title_de = create_page_content("de", "de page", page, template="nav_playground.html")
edit_url_de = get_object_edit_url(title_de)
add_plugin(title_de.placeholders.get(slot="body"), "TextPlugin", "de", body="de body")
title_fr = create_page_content("fr", "fr page", page, template="nav_playground.html")
add_plugin(title_fr.placeholders.get(slot="body"), "TextPlugin", "fr", body="fr body")
staff = self.get_staff()
request = self.get_page_request(page, staff, edit_url_en)
self.assertMenuItems(request, LANGUAGE_MENU_IDENTIFIER, _("Copy all plugins"), ["from German", "from French"])
request = self.get_page_request(page, staff, edit_url_de, lang_code="de")
request.toolbar.toolbar_language = "en"
self.assertMenuItems(
request,
LANGUAGE_MENU_IDENTIFIER,
_("Copy all plugins"),
[
"from English",
"from French",
],
)
def get_username(self, user=None, default=""):
user = user or self.request.user
try:
name = user.get_full_name()
if name:
return name
else:
return user.get_username()
except (AttributeError, NotImplementedError):
return default
def test_toolbar_logout(self):
"""
Tests that the Logout menu item includes the user's full name, if the
relevant fields were populated in auth.User, else the user's username.
"""
superuser = self.get_superuser()
# Ensure that some other test hasn't set the name fields
if superuser.get_full_name():
# Looks like it has been set, clear them
superuser.first_name = ""
superuser.last_name = ""
superuser.save()
page = create_page("home", "nav_playground.html", "en")
page_content = self.get_pagecontent_obj(page)
page_edit_url = get_object_edit_url(page_content)
self.get_page_request(page, superuser, "/")
#
# Test that the logout shows the username of the logged-in user if
# first_name and last_name haven't been provided.
#
with self.login_user_context(superuser):
response = self.client.get(page_edit_url)
toolbar = response.context["request"].toolbar
admin_menu = toolbar.get_or_create_menu(ADMIN_MENU_IDENTIFIER)
self.assertTrue(admin_menu.find_first(AjaxItem, name=_("Logout %s") % self.get_username(superuser)))
#
# Test that the logout shows the logged-in user's name, if it was
# populated in auth.User.
#
superuser.first_name = "Super"
superuser.last_name = "User"
superuser.save()
# Sanity check...
self.get_page_request(page, superuser, "/")
page_content = self.get_pagecontent_obj(page)
page_edit_url = get_object_edit_url(page_content)
with self.login_user_context(superuser):
response = self.client.get(page_edit_url)
toolbar = response.context["request"].toolbar
admin_menu = toolbar.get_or_create_menu(ADMIN_MENU_IDENTIFIER)
self.assertTrue(admin_menu.find_first(AjaxItem, name=_("Logout %s") % self.get_username(superuser)))
@override_settings(CMS_PERMISSION=True)
def test_toolbar_logout_redirect(self):
"""
Tests the logount AjaxItem on_success parameter in four different conditions:
* published page: no redirect
* unpublished page: redirect to the home page
* published page with login_required: redirect to the home page
* published page with view permissions: redirect to the home page
"""
superuser = self.get_superuser()
page0 = create_page("home", "nav_playground.html", "en")
page1 = create_page("internal", "nav_playground.html", "en", parent=page0)
page1_content = self.get_pagecontent_obj(page1)
page1_edit_url = get_object_edit_url(page1_content)
create_page("unpublished", "nav_playground.html", "en", parent=page0)
page3 = create_page("login_restricted", "nav_playground.html", "en", parent=page0, login_required=True)
page3_content = self.get_pagecontent_obj(page3)
page3_edit_url = get_object_edit_url(page3_content)
page4 = create_page("view_restricted", "nav_playground.html", "en", parent=page0)
page4_content = self.get_pagecontent_obj(page4)
page4_edit_url = get_object_edit_url(page4_content)
PagePermission.objects.create(page=page4, can_view=True, user=superuser)
self.get_page_request(page4, superuser, "/")
with self.login_user_context(superuser):
# Published page, no redirect
response = self.client.get(page1_edit_url)
toolbar = response.context["request"].toolbar
menu_name = _("Logout %s") % self.get_username(superuser)
admin_menu = toolbar.get_or_create_menu(ADMIN_MENU_IDENTIFIER)
self.assertTrue(admin_menu.find_first(AjaxItem, name=menu_name).item.on_success)
# Published page with login restrictions, redirect
response = self.client.get(page3_edit_url)
toolbar = response.context["request"].toolbar
admin_menu = toolbar.get_or_create_menu(ADMIN_MENU_IDENTIFIER)
self.assertEqual(admin_menu.find_first(AjaxItem, name=menu_name).item.on_success, "/")
# Published page with view permissions, redirect
response = self.client.get(page4_edit_url)
toolbar = response.context["request"].toolbar
admin_menu = toolbar.get_or_create_menu(ADMIN_MENU_IDENTIFIER)
self.assertEqual(admin_menu.find_first(AjaxItem, name=menu_name).item.on_success, "/")
class ToolbarModeTests(ToolbarTestBase):
def setUp(self):
super().setUp()
self.page = create_page("home", "nav_playground.html", "en")
self.page_content = self.get_pagecontent_obj(self.page)
def tearDown(self):
self.page.delete()
super().tearDown()
def test_edit_mode(self):
"""Only edit mode is active"""
page_edit_url = get_object_edit_url(self.page_content)
with self.login_user_context(self.get_superuser()):
response = self.client.get(page_edit_url)
toolbar = response.context["request"].toolbar
self.assertTrue(toolbar.edit_mode_active)
self.assertFalse(toolbar.structure_mode_active)
self.assertFalse(toolbar.preview_mode_active)
def test_preview_mode(self):
"""Only preview mode is active"""
page_preview_url = get_object_preview_url(self.page_content)
with self.login_user_context(self.get_superuser()):
response = self.client.get(page_preview_url)
toolbar = response.context["request"].toolbar
self.assertFalse(toolbar.edit_mode_active)
self.assertFalse(toolbar.structure_mode_active)
self.assertTrue(toolbar.preview_mode_active)
def test_structure_mode(self):
"""Structure AND edit mode is active"""
page_structure_url = get_object_structure_url(self.page_content)
with self.login_user_context(self.get_superuser()):
response = self.client.get(page_structure_url)
toolbar = response.context["request"].toolbar
self.assertTrue(toolbar.edit_mode_active)
self.assertTrue(toolbar.structure_mode_active)
self.assertFalse(toolbar.preview_mode_active)
def _use_structure_permission(self):
return Permission.objects.get(
content_type__app_label="cms", codename="use_structure"
)
def test_structure_switcher_shown_to_view_only_user_with_use_structure(self):
"""Structure mode is a read affordance: view permission is enough.
Viewing the structure board only requires view permission (mutations
stay gated by change permission at the plugin endpoints), so a user with
``cms.use_structure`` who may view — but not change — an unrestricted
page is still offered the structure switcher.
"""
page = create_page("normal", "nav_playground.html", "en")
page_content = page.get_content_obj("en")
user = self.get_staff_user_with_no_permissions()
user.user_permissions.add(self._use_structure_permission())
with self.login_user_context(user):
response = self.client.get(get_object_edit_url(page_content, language="en"))
self.assertContains(response, 'title="Toggle structure"')
def test_structure_switcher_hidden_without_use_structure(self):
"""Without ``cms.use_structure`` the switcher stays hidden."""
page = create_page("normal", "nav_playground.html", "en")
page_content = page.get_content_obj("en")
user = self.get_staff_user_with_no_permissions()
with self.login_user_context(user):
response = self.client.get(get_object_edit_url(page_content, language="en"))
self.assertNotContains(response, 'title="Toggle structure"')
@override_settings(CMS_EXTRA_HELP_MENU_ITEMS=(("google", "www.google.com"),))
def test_help_menu(self):
page = create_page("help-page", "nav_playground.html", "en")
staff = self.get_staff()
assert staff.user_permissions.get().name == "Can change page"
request = self.get_page_request(page, staff, "/")
toolbar = CMSToolbar(request)
help_menu = toolbar.get_menu(HELP_MENU_IDENTIFIER)
custom_link = help_menu.get_items()[-1]
self.assertEqual(custom_link.name, "google")
self.assertEqual(custom_link.url, "www.google.com")
@override_settings(CMS_ENABLE_HELP=False)
def test_help_menu_disabled(self):
page = create_page("help-page", "nav_playground.html", "en")
staff = self.get_staff()
assert staff.user_permissions.get().name == "Can change page"
request = self.get_page_request(page, staff, "/")
toolbar = CMSToolbar(request)
help_menu = toolbar.get_menu(HELP_MENU_IDENTIFIER)
self.assertIsNone(help_menu)
@override_settings(ROOT_URLCONF="cms.test_utils.project.placeholderapp_urls")
class EditModelTemplateTagTest(ToolbarTestBase):
edit_fields_rx = "(\\?|&amp;|\\\\u0026)edit_fields=%s"
def tearDown(self):
Example1.objects.all().delete()
super().tearDown()
def test_markup_toolbar_url_model(self):
superuser = self.get_superuser()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
preview_url = get_object_preview_url(page_content)
ex1 = self._get_example_obj()
# object
# check when in draft mode
request = self.get_page_request(page, superuser, edit_url)
response = detail_view(request, ex1.pk)
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'href="%s"' % get_object_preview_url(ex1))
# check when in live mode
request = self.get_page_request(page, superuser, preview_url)
response = detail_view(request, ex1.pk)
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'href="%s"' % get_object_edit_url(ex1))
def test_anon(self):
user = self.get_anon()
page = create_page("Test", "col_two.html", "en")
ex1 = self._get_example_obj()
request = self.get_page_request(page, user)
response = detail_view(request, ex1.pk)
self.assertContains(response, "<h1>one</h1>")
self.assertNotContains(response, "CMS.API")
def test_noedit(self):
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
ex1 = self._get_example_obj()
request = self.get_page_request(page, user)
response = detail_view(request, ex1.pk)
self.assertContains(response, "<h1>one</h1>")
self.assertContains(response, '<script data-cms-config id="cms-config-json" type="application/json">')
def test_edit(self):
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = self._get_example_obj()
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk)
self.assertContains(
response,
'<h1><template class="cms-plugin cms-plugin-start cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"one"
'<template class="cms-plugin cms-plugin-end cms-plugin-{0}-{1}-{2}-{3} cms-render-model">'
"</template></h1>".format("placeholderapp", "example1", "char_1", ex1.pk),
)
def test_invalid_item(self):
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = self._get_example_obj()
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
<h1>{% render_model fake "char_1" %}</h1>
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(
response,
'<template class="cms-plugin cms-plugin-start cms-plugin-%s cms-render-model"></template>' % ex1.pk,
)
self.assertContains(
response, '<template class="cms-plugin cms-plugin-end cms-plugin-%s cms-render-model"></template>' % ex1.pk
)
def test_as_varname(self):
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = self._get_example_obj()
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
<h1>{% render_model instance "char_1" as tempvar %}</h1>
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertNotContains(
response,
'<template class="cms-plugin cms-plugin-start cms-plugin-%s cms-render-model"></template>' % ex1.pk,
)
self.assertNotContains(
response, '<template class="cms-plugin cms-plugin-end cms-plugin-%s cms-render-model"></template>' % ex1.pk
)
def test_edit_render_placeholder(self):
"""
Tests the {% render_placeholder %} templatetag.
"""
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = self._get_example_obj()
render_placeholder_body = "I'm the render placeholder body"
plugin = add_plugin(ex1.placeholder, "TextPlugin", "en", body=render_placeholder_body)
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
<h1>{% render_placeholder instance.placeholder %}</h1>
<h2>{% render_placeholder instance.placeholder as tempvar %}</h2>
<h3>{{ tempvar }}</h3>
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(response, f'<div class="cms-placeholder cms-placeholder-{ex1.placeholder.pk}"></div>')
self.assertContains(
response,
f'<h1><template class="cms-plugin cms-plugin-start cms-plugin-{plugin.pk}" '
f'data-cms-placeholder="{plugin.placeholder_id}"></template>'
f"{render_placeholder_body}"
f'<template class="cms-plugin cms-plugin-end cms-plugin-{plugin.pk}"></template>',
)
self.assertContains(
response,
"<h2></h2>",
)
#
# NOTE: Using the render_placeholder "as" form should /not/ render
# frontend placeholder editing support.
#
self.assertContains(response, f"<h3>{render_placeholder_body}</h3>")
self.assertContains(response, f'<script data-cms-plugin id="cms-plugin-{plugin.pk}" type="application/json">')
self.assertContains(
response,
f'<script data-cms-placeholder id="cms-placeholder-{ex1.placeholder.pk}" type="application/json">',
)
def test_filters(self):
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = Example1(
char_1="char_1, <p>hello</p>, <p>hello</p>, <p>hello</p>, <p>hello</p>",
char_2="char_2",
char_3="char_3",
char_4="char_4",
)
ex1.save()
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
<h1>{% render_model instance "char_1" "" "" 'truncatewords:2' %}</h1>
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(
response,
"<h1>"
'<template class="cms-plugin cms-plugin-start cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"{4}"
'<template class="cms-plugin cms-plugin-end cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"</h1>".format("placeholderapp", "example1", "char_1", ex1.pk, truncatewords(escape(ex1.char_1), 2)),
)
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
<h1>{% render_model instance "char_1" "" "" "truncatewords:2|safe" %}</h1>
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(
response,
"<h1>"
'<template class="cms-plugin cms-plugin-start cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"{4}"
'<template class="cms-plugin cms-plugin-end cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"</h1>".format("placeholderapp", "example1", "char_1", ex1.pk, truncatewords(ex1.char_1, 2)),
)
def test_setting_override(self):
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
<h1>{% render_model instance "char_1" "" "" 'truncatewords:2' %}</h1>
{% endblock content %}
"""
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = Example1(
char_1="char_1, <p>hello</p>, <p>hello</p>, <p>hello</p>, <p>hello</p>",
char_2="char_2",
char_3="char_3",
char_4="char_4",
)
ex1.save()
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(
response,
"<h1>"
'<template class="cms-plugin cms-plugin-start cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"{4}"
'<template class="cms-plugin cms-plugin-end cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"</h1>".format("placeholderapp", "example1", "char_1", ex1.pk, truncatewords(escape(ex1.char_1), 2)),
)
def test_filters_date(self):
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = Example1(
char_1="char_1, <p>hello</p>, <p>hello</p>, <p>hello</p>, <p>hello</p>",
char_2="char_2",
char_3="char_3",
char_4="char_4",
date_field=datetime.date(2012, 1, 2),
)
ex1.save()
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
<h1>{% render_model instance "date_field" %}</h1>
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(
response,
"<h1>"
'<template class="cms-plugin cms-plugin-start cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"{4}"
'<template class="cms-plugin cms-plugin-end cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"</h1>".format(
"placeholderapp",
"example1",
"date_field",
ex1.pk,
date_filter(ex1.date_field),
),
)
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
<h1>{% render_model instance "date_field" "" "" "safe" %}</h1>
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(
response,
"<h1>"
'<template class="cms-plugin cms-plugin-start cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"{4}"
'<template class="cms-plugin cms-plugin-end cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"</h1>".format(
"placeholderapp", "example1", "date_field", ex1.pk, ex1.date_field.strftime("%Y-%m-%d")
),
)
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
<h1>{% render_model instance "date_field" "" "" 'date:"Y m d"' %}</h1>
{% endblock content %}
"""
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(
response,
"<h1>"
'<template class="cms-plugin cms-plugin-start cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"{4}"
'<template class="cms-plugin cms-plugin-end cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"</h1>".format(
"placeholderapp", "example1", "date_field", ex1.pk, ex1.date_field.strftime("%Y %m %d")
),
)
def test_filters_notoolbar(self):
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
ex1 = Example1(
char_1="char_1, <p>hello</p>, <p>hello</p>, <p>hello</p>, <p>hello</p>",
char_2="char_2",
char_3="char_3",
char_4="char_4",
)
ex1.save()
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
<h1>{% render_model instance "char_1" "" "" 'truncatewords:2' %}</h1>
{% endblock content %}
"""
request = self.get_page_request(page, user)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(response, "<h1>%s</h1>" % truncatewords(escape(ex1.char_1), 2))
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
<h1>{% render_model instance "char_1" "" "" 'truncatewords:2|safe' "" "" %}</h1>
{% endblock content %}
"""
request = self.get_page_request(page, user)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(response, "<h1>%s</h1>" % truncatewords(ex1.char_1, 2))
def test_no_cms(self):
user = self.get_staff()
ex1 = self._get_example_obj()
edit_url = get_object_edit_url(ex1)
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
{% render_model_icon instance %}
{% endblock content %}
"""
request = self.get_page_request(None, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(
response,
'<template class="cms-plugin cms-plugin-start cms-plugin-{0}-{1}-{2} cms-render-model-icon"></template>'
'<img src="/static/cms/img/toolbar/render_model_placeholder.png">'
'<template class="cms-plugin cms-plugin-end cms-plugin-{0}-{1}-{2} cms-render-model-icon">'
"</template>".format("placeholderapp", "example1", ex1.pk),
)
self.assertContains(response, '"onClose": "REFRESH_PAGE",')
def test_icon_tag(self):
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = self._get_example_obj()
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
{% render_model_icon instance %}
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(
response,
'<template class="cms-plugin cms-plugin-start cms-plugin-{0}-{1}-{2} cms-render-model-icon"></template>'
'<img src="/static/cms/img/toolbar/render_model_placeholder.png">'
'<template class="cms-plugin cms-plugin-end cms-plugin-{0}-{1}-{2} cms-render-model-icon">'
"</template>".format("placeholderapp", "example1", ex1.pk),
)
def test_icon_followed_by_render_model_block_tag(self):
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = self._get_example_obj()
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
{% render_model_icon instance "char_1" %}
{% render_model_block instance "char_2" %}
{{ instance }}
<h1>{{ instance.char_1 }} - {{ instance.char_2 }}</h1>
<span class="date">{{ instance.date_field|date:"Y" }}</span>
{% if instance.char_1 %}
<a href="{% url 'detail' instance.pk %}">successful if</a>
{% endif %}
{% endrender_model_block %}
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(
response,
f'<script id="cms-plugin-placeholderapp-example1-char_1-{ex1.pk}" type="application/json" data-cms-general>',
)
self.assertContains(
response,
f'<script id="cms-plugin-placeholderapp-example1-char_2-{ex1.pk}" type="application/json" data-cms-general>',
)
def test_add_tag(self):
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = self._get_example_obj()
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
{% render_model_add instance %}
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(
response,
'<template class="cms-plugin cms-plugin-start cms-plugin-{0}-{1}-add-{2} cms-render-model-add"></template>'
'<img src="/static/cms/img/toolbar/render_model_placeholder.png">'
'<template class="cms-plugin cms-plugin-end cms-plugin-{0}-{1}-add-{2} cms-render-model-add">'
"</template>".format("placeholderapp", "example1", ex1.pk),
)
def test_add_tag_class(self):
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = self._get_example_obj()
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
{% render_model_add instance_class %}
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(
response,
'<template class="cms-plugin cms-plugin-start cms-plugin-{0}-{1}-add-{2} cms-render-model-add"></template>'
'<img src="/static/cms/img/toolbar/render_model_placeholder.png">'
'<template class="cms-plugin cms-plugin-end cms-plugin-{0}-{1}-add-{2} cms-render-model-add">'
"</template>".format("placeholderapp", "example1", "0"),
)
def test_add_tag_classview(self):
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = self._get_example_obj()
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
{% render_model_add instance_class %}
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
view_func = ClassDetail.as_view(template_string=template_text)
response = view_func(request, pk=ex1.pk, template_string=template_text)
self.assertContains(
response,
'<template class="cms-plugin cms-plugin-start cms-plugin-{0}-{1}-add-{2} cms-render-model-add"></template>'
'<img src="/static/cms/img/toolbar/render_model_placeholder.png">'
'<template class="cms-plugin cms-plugin-end cms-plugin-{0}-{1}-add-{2} cms-render-model-add">'
"</template>".format("placeholderapp", "example1", "0"),
)
def test_block_tag(self):
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = Example1(
char_1="char_1", char_2="char_2", char_3="char_3", char_4="char_4", date_field=datetime.date(2012, 1, 1)
)
ex1.save()
# This template does not render anything as content is saved in a
# variable and never inserted in the page
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
{% render_model_block instance as rendered_model %}
{{ instance }}
<h1>{{ instance.char_1 }} - {{ instance.char_2 }}</h1>
{{ instance.date_field|date:"Y" }}
{% if instance.char_1 %}
<a href="{% url 'detail' instance.pk %}">successful if</a>
{% endif %}
{% endrender_model_block %}
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertNotContains(
response,
'<template class="cms-plugin cms-plugin-start cms-plugin-{0}-{1}-{2} cms-render-model-icon"></template>'
'<img src="/static/cms/img/toolbar/render_model_icon.png">'
'<template class="cms-plugin cms-plugin-end cms-plugin-{0}-{1}-{2} cms-render-model-icon">'
"</template>".format("placeholderapp", "example1", ex1.pk),
)
# This template does not render anything as content is saved in a
# variable and inserted in the page afterwards
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
{% render_model_block instance as rendered_model %}
{{ instance }}
<h1>{{ instance.char_1 }} - {{ instance.char_2 }}</h1>
<span class="date">{{ instance.date_field|date:"Y" }}</span>
{% if instance.char_1 %}
<a href="{% url 'detail' instance.pk %}">successful if</a>
{% endif %}
{% endrender_model_block %}
{{ rendered_model }}
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
# Assertions on the content of the block tag
self.assertContains(
response,
'<template class="cms-plugin cms-plugin-start cms-plugin-{}-{}-{} cms-render-model '
'cms-render-model-block">'.format("placeholderapp", "example1", ex1.pk),
)
self.assertContains(response, f"<h1>{ex1.char_1} - {ex1.char_2}</h1>")
self.assertContains(response, '<span class="date">%s</span>' % (ex1.date_field.strftime("%Y")))
self.assertContains(
response, '<a href="%s">successful if</a>\n \n<template' % (reverse("detail", args=(ex1.pk,)))
)
# This template is rendered directly
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
{% render_model_block instance %}
{{ instance }}
<h1>{{ instance.char_1 }} - {{ instance.char_2 }}</h1>
<span class="date">{{ instance.date_field|date:"Y" }}</span>
{% if instance.char_1 %}
<a href="{% url 'detail' instance.pk %}">successful if</a>
{% endif %}
{% endrender_model_block %}
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
# Assertions on the content of the block tag
self.assertContains(
response,
'<template class="cms-plugin cms-plugin-start cms-plugin-{}-{}-{} cms-render-model '
'cms-render-model-block">'.format("placeholderapp", "example1", ex1.pk),
)
self.assertContains(response, f"<h1>{ex1.char_1} - {ex1.char_2}</h1>")
self.assertContains(response, '<span class="date">%s</span>' % (ex1.date_field.strftime("%Y")))
self.assertContains(
response, '<a href="%s">successful if</a>\n \n<template' % (reverse("detail", args=(ex1.pk,)))
)
# Changelist check
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
{% render_model_block instance 'changelist' %}
{{ instance }}
{% endrender_model_block %}
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
# Assertions on the content of the block tag
self.assertContains(
response,
'<template class="cms-plugin cms-plugin-start cms-plugin-{}-{}-changelist-{} cms-render-model '
'cms-render-model-block"></template>'.format("placeholderapp", "example1", ex1.pk),
)
self.assertContains(
response,
'"edit_plugin": "{}?language={}&amp;edit_fields=changelist"'.format(
admin_reverse("placeholderapp_example1_changelist"), "en"
),
)
def test_changeform_url_is_marked_as_popup(self):
"""The frontend editing modal opens the plain admin changeform, which only
renders without the admin chrome - and only closes the modal upon saving -
when the ``_popup`` flag is set."""
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = self._get_example_obj()
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
{% render_model_block instance %}
{{ instance }}
{% endrender_model_block %}
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(
response,
'"edit_plugin": "{}?language=en&amp;_popup=1"'.format(
admin_reverse("placeholderapp_example1_change", args=(ex1.pk,))
),
)
def test_add_form_url_is_marked_as_popup(self):
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = self._get_example_obj()
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
{% render_model_add instance %}
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(
response,
'"edit_plugin": "{}?_popup=1"'.format(admin_reverse("placeholderapp_example1_add")),
)
def test_edit_field_url_is_not_marked_as_popup(self):
"""``edit_field`` is a cms view rendering its own chrome-less template: it
neither needs nor understands the ``_popup`` flag."""
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = self._get_example_obj()
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
<h1>{% render_model instance "char_1" "char_1" %}</h1>
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(
response,
'"edit_plugin": "{}?language=en&amp;edit_fields=char_1"'.format(
admin_reverse("placeholderapp_example1_edit_field", args=(ex1.pk, "en"))
),
)
def test_invalid_attribute(self):
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = self._get_example_obj()
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
<h1>{% render_model instance "fake_field" %}</h1>
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(
response,
'<template class="cms-plugin cms-plugin-start cms-plugin-{}-{}-{}-{} cms-render-model">'
"</template>".format("placeholderapp", "example1", "fake_field", ex1.pk),
)
self.assertContains(
response,
'<template class="cms-plugin cms-plugin-end cms-plugin-{}-{}-{}-{} cms-render-model">'
"</template>".format("placeholderapp", "example1", "fake_field", ex1.pk),
)
# no attribute
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
<h1>{% render_model instance "" %}</h1>
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(
response, f'<template class="cms-plugin cms-plugin-start cms-plugin-{ex1.pk} cms-render-model"></template>'
)
self.assertContains(
response, f'<template class="cms-plugin cms-plugin-end cms-plugin-{ex1.pk} cms-render-model"></template>'
)
def test_callable_item(self):
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = self._get_example_obj()
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
<h1>{% render_model instance "callable_item" %}</h1>
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(
response,
'<h1><template class="cms-plugin cms-plugin-start cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"one"
'<template class="cms-plugin cms-plugin-end cms-plugin-{0}-{1}-{2}-{3} cms-render-model">'
"</template></h1>".format("placeholderapp", "example1", "callable_item", ex1.pk),
)
def test_view_method(self):
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = self._get_example_obj()
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
<h1>{% render_model instance "callable_item" "char_1,char_2" "en" "" "" "dynamic_url" %}</h1>
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(response, '"edit_plugin": "/admin/placeholderapp/example1/edit-field/%s/en/' % ex1.pk)
def test_edit_field_respects_content_admin_mixin(self):
user = self.get_staff()
ex1 = self._get_example_obj()
edit_url = admin_reverse("placeholderapp_example1_edit_field", args=(ex1.pk, "en"))
with patch("cms.test_utils.project.placeholderapp.models.Example1.admin_manager.get") as get_mock:
with self.login_user_context(user):
get_mock.return_value = ex1
self.client.get(edit_url + "?edit_fields=char_1")
self.assertEqual(edit_url, f"/admin/placeholderapp/example1/edit-field/{ex1.pk}/en/")
Example1.admin_manager.get.assert_called_once_with(pk=str(ex1.pk))
def test_view_url(self):
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = self._get_example_obj()
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
<h1>{% render_model instance "callable_item" "char_1,char_2" "en" "" "admin:placeholderapp_example1_edit_field" %}</h1>
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(response, '"edit_plugin": "/admin/placeholderapp/example1/edit-field/%s/en/' % ex1.pk)
def test_method_attribute(self):
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = self._get_example_obj()
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
<h1>{% render_model instance "callable_item" "char_1,char_2" "en" "" "" "static_admin_url" %}</h1>
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
ex1.set_static_url(request)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(
response,
"<h1>"
'<template class="cms-plugin cms-plugin-start cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"one"
'<template class="cms-plugin cms-plugin-end cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"</h1>".format("placeholderapp", "example1", "callable_item", ex1.pk),
)
def test_admin_url(self):
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = self._get_example_obj()
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
<h1>{% render_model instance "callable_item" "char_1" "en" "" "admin:placeholderapp_example1_edit_field" %}</h1>
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
expected_output = (
"<h1>"
'<template class="cms-plugin cms-plugin-start cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"one"
'<template class="cms-plugin cms-plugin-end cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"</h1>"
).format("placeholderapp", "example1", "callable_item", ex1.pk)
self.assertContains(response, expected_output)
def test_admin_url_extra_field(self):
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = self._get_example_obj()
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
<h1>{% render_model instance "callable_item" "char_2" %}</h1>
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(
response,
"<h1>"
'<template class="cms-plugin cms-plugin-start cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"one"
'<template class="cms-plugin cms-plugin-end cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"</h1>".format("placeholderapp", "example1", "callable_item", ex1.pk),
)
self.assertContains(response, "/admin/placeholderapp/example1/edit-field/%s/en/" % ex1.pk)
self.assertTrue(re.search(self.edit_fields_rx % "char_2", response.content.decode("utf8")))
def test_admin_url_multiple_fields(self):
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = self._get_example_obj()
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
<h1>{% render_model instance "callable_item" "char_1,char_2" "en" "" "admin:placeholderapp_example1_edit_field" %}</h1>
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(
response,
"<h1>"
'<template class="cms-plugin cms-plugin-start cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"one"
'<template class="cms-plugin cms-plugin-end cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"</h1>".format("placeholderapp", "example1", "callable_item", ex1.pk),
)
self.assertContains(response, "/admin/placeholderapp/example1/edit-field/%s/en/" % ex1.pk)
self.assertTrue(re.search(self.edit_fields_rx % "char_1", response.content.decode("utf8")))
self.assertTrue(re.search(self.edit_fields_rx % "char_1%2Cchar_2", response.content.decode("utf8")))
def test_instance_method(self):
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = self._get_example_obj()
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
<h1>{% render_model instance "callable_item" %}</h1>
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text)
self.assertContains(
response,
"<h1>"
'<template class="cms-plugin cms-plugin-start cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"one"
'<template class="cms-plugin cms-plugin-end cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"</h1>".format("placeholderapp", "example1", "callable_item", ex1.pk),
)
def test_item_from_context(self):
user = self.get_staff()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = self._get_example_obj()
template_text = """{% extends "base.html" %}
{% load cms_tags %}
{% block content %}
<h1>{% render_model instance item_name %}</h1>
{% endblock content %}
"""
request = self.get_page_request(page, user, edit_url)
response = detail_view(request, ex1.pk, template_string=template_text, item_name="callable_item")
self.assertContains(
response,
"<h1>"
'<template class="cms-plugin cms-plugin-start cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"one"
'<template class="cms-plugin cms-plugin-end cms-plugin-{0}-{1}-{2}-{3} cms-render-model"></template>'
"</h1>".format("placeholderapp", "example1", "callable_item", ex1.pk),
)
def test_edit_field(self):
from django.contrib.admin import site
exadmin = site._registry[Example1]
user = self.get_superuser()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = self._get_example_obj()
request = self.get_page_request(page, user, edit_url)
request.GET["edit_fields"] = "char_1"
response = exadmin.edit_field(request, ex1.pk, "en")
self.assertContains(response, 'id="id_char_1"')
self.assertContains(response, 'value="one"')
def test_edit_field_not_allowed(self):
from django.contrib.admin import site
exadmin = site._registry[Example1]
user = self.get_superuser()
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex1 = self._get_example_obj()
request = self.get_page_request(page, user, edit_url)
request.GET["edit_fields"] = "char_3"
response = exadmin.edit_field(request, ex1.pk, "en")
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Field char_3 not found")
def test_edit_page(self):
language = "en"
user = self.get_superuser()
page = create_page("Test", "col_two.html", language)
title = page.get_content_obj(language)
title.menu_title = "Menu Test"
title.page_title = "Page Test"
title.title = "Main Test"
title.save()
page.reload()
edit_url = get_object_edit_url(title)
request = self.get_page_request(page, user, edit_url)
response = details(request, page.get_path(language))
response.render()
self.assertContains(
response,
f'<template class="cms-plugin cms-plugin-start cms-plugin-cms-page-get_page_title-{page.pk} cms-render-model">'
"</template>"
f"{page.get_page_title(language)}"
f'<template class="cms-plugin cms-plugin-end cms-plugin-cms-page-get_page_title-{page.pk} cms-render-model">'
"</template>",
)
self.assertContains(
response,
f'<template class="cms-plugin cms-plugin-start cms-plugin-cms-page-get_menu_title-{page.pk} cms-render-model">'
"</template>"
f"{page.get_menu_title(language)}"
f'<template class="cms-plugin cms-plugin-end cms-plugin-cms-page-get_menu_title-{page.pk} cms-render-model">'
"</template>",
)
self.assertContains(
response,
f'<template class="cms-plugin cms-plugin-start cms-plugin-cms-page-get_title-{page.pk} cms-render-model">'
"</template>"
f"{page.get_title(language)}"
f'<template class="cms-plugin cms-plugin-end cms-plugin-cms-page-get_title-{page.pk} cms-render-model">'
"</template>",
)
self.assertContains(
response,
""
'<template class="cms-plugin cms-plugin-start cms-plugin-cms-page-changelist-%s cms-render-model '
'cms-render-model-block"></template>\n <h3>Menu</h3>' % page.pk,
)
class ToolbarUtilsTestCase(ToolbarTestBase):
def test_get_plugin_content_uses_plugin_language(self):
page = create_page("test page", "col_two.html", "de")
placeholder = page.get_placeholders("de").get(slot="col_left")
plugin = add_plugin(placeholder, "TextPlugin", "de", body="test")
request = self.get_page_request(page, self.get_superuser(), "/de/")
with patch.object(
request.toolbar.content_renderer,
"render_plugin",
side_effect=lambda *args, **kwargs: get_language(),
), override("en"):
content = utils.get_plugin_content(request, plugin)
self.assertEqual(content[0]["html"], "de")
self.assertEqual(get_language(), "en")
@override_settings(CMS_ENDPOINT_LIVE_URL_QUERYSTRING_PARAM_ENABLED=True)
@override_settings(CMS_ENDPOINT_LIVE_URL_QUERYSTRING_PARAM="test-live-link")
def test_add_live_url_querystring_param_no_querystring(self):
"""
When the endpoint returns a value without a querystring param, one should be added to the
url returned
"""
page = create_page("home", "nav_playground.html", "en")
page_content = page.get_content_obj()
live_url = page.get_absolute_url()
edit_url = get_object_edit_url(page_content)
preview_url = get_object_preview_url(page_content)
self.assertIn(f"?test-live-link={live_url}", edit_url)
self.assertIn(f"?test-live-link={live_url}", preview_url)
self.assertEqual(edit_url.count("?"), 1)
self.assertEqual(preview_url.count("?"), 1)
self.assertEqual(edit_url.count("&"), 0)
self.assertEqual(preview_url.count("&"), 0)
@override_settings(CMS_ENDPOINT_LIVE_URL_QUERYSTRING_PARAM_ENABLED=True)
@override_settings(CMS_ENDPOINT_LIVE_URL_QUERYSTRING_PARAM="test-live-link")
@patch("cms.utils.urlutils.admin_reverse")
def test_add_live_url_querystring_param_with_querystring(self, patched_admin_reverse):
"""
With the endpoint returning an existing querystring param, the additional param should be appended
to the existing with &.
"""
page = create_page("home", "nav_playground.html", "en")
page_content = page.get_content_obj()
app_label = page_content._meta.app_label
model_name = page_content._meta.model_name
live_url = page.get_absolute_url()
content_type = ContentType.objects.get(app_label=app_label, model=model_name)
# Get the original edit endpoint url, and patch it with additional querystring parameter
base_edit_url = admin_reverse("cms_placeholder_render_object_edit", args=[content_type.pk, page_content.pk])
with patch.object(utils, "admin_reverse", return_value=f"{base_edit_url}?base_qsp=base_value"):
edit_url = get_object_edit_url(page_content)
# Get the original edit endpoint url, and patch it with additional querystring parameter
base_preview_url = admin_reverse(
"cms_placeholder_render_object_preview", args=[content_type.pk, page_content.pk]
)
with patch.object(utils, "admin_reverse", return_value=f"{base_preview_url}?base_qsp=base_value"):
preview_url = get_object_edit_url(page_content)
self.assertIn(f"?base_qsp=base_value&test-live-link={live_url}", edit_url)
self.assertIn(f"?base_qsp=base_value&test-live-link={live_url}", preview_url)
self.assertEqual(edit_url.count("?"), 1)
self.assertEqual(preview_url.count("?"), 1)
self.assertEqual(edit_url.count("&"), 1)
self.assertEqual(preview_url.count("&"), 1)
def test_add_live_url_querystring_param_no_querystring_setting_disabled(self):
"""
With the querystring param configured, but CMS_ENDPOINT_LIVE_URL_QUERYSTRING_PARAM_ENABLED not set True,
don't add the querystring params
"""
page = create_page("home", "nav_playground.html", "en")
page_content = page.get_content_obj()
content_type = ContentType.objects.get_for_model(page_content)
language = get_language()
with override(language):
expected_edit_url = admin_reverse(
"cms_placeholder_render_object_edit", args=[content_type.pk, page_content.pk]
)
expected_preview_url = admin_reverse(
"cms_placeholder_render_object_preview", args=[content_type.pk, page_content.pk]
)
edit_url = get_object_edit_url(page_content)
preview_url = get_object_preview_url(page_content)
self.assertEqual(edit_url, expected_edit_url)
self.assertEqual(preview_url, expected_preview_url)
self.assertEqual(edit_url.count("?"), 0)
self.assertEqual(preview_url.count("?"), 0)
self.assertEqual(edit_url.count("&"), 0)
self.assertEqual(preview_url.count("&"), 0)
@override_settings(CMS_ENDPOINT_LIVE_URL_QUERYSTRING_PARAM="test-live-link")
@override_settings(CMS_ENDPOINT_LIVE_URL_QUERYSTRING_PARAM_ENABLED=True)
def test_add_live_url_querystring_param_handles_wrong_content_type(self):
"""
With CMS_ENDPOINT_LIVE_URL_QUERYSTRING_PARAM_ENABLED set True, and
CMS_ENDPOINT_LIVE_URL_QUERYSTRING_PARAM provided, but a content type that isn't PageContent provided,
don't add the querystring params
"""
test_obj = self._get_example_obj()
content_type = ContentType.objects.get_for_model(test_obj)
language = get_language()
with override(language):
expected_edit_url = admin_reverse(
"cms_placeholder_render_object_edit", args=[content_type.pk, test_obj.pk]
)
expected_preview_url = admin_reverse(
"cms_placeholder_render_object_preview", args=[content_type.pk, test_obj.pk]
)
edit_url = add_live_url_querystring_param(test_obj, expected_edit_url)
preview_url = add_live_url_querystring_param(test_obj, expected_preview_url)
self.assertEqual(edit_url, expected_edit_url)
self.assertEqual(preview_url, expected_preview_url)
self.assertEqual(edit_url.count("?"), 0)
self.assertEqual(preview_url.count("?"), 0)
def test_get_object_for_language_one_language(self):
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page, "en")
self.assertEqual(page_content, get_object_for_language(page_content, "en"))
self.assertIsNone(get_object_for_language(page_content, "de"))
def test_get_object_for_language_multiple_languages(self):
page = create_page("Test", "col_two.html", "en")
# Additional pages to ensure not a page content of another page is returned
for code, verbose in get_language_tuple(Site.objects.get_current().pk):
create_page(f"Not this page ({verbose})", "col_two.html", code)
page_content = {"en": self.get_pagecontent_obj(page, "en")}
for code, verbose in get_language_tuple(Site.objects.get_current().pk):
if code != "en":
page_content[code] = create_page_content(code, verbose, page)
self.assertEqual(page_content["en"], get_object_for_language(page_content["en"], "en"))
self.assertEqual(get_object_for_language(page_content["en"], "de"), page_content["de"])
@patch("cms.toolbar.utils.get_cms_setting")
@patch("cms.toolbar.utils.get_language_from_path")
@patch("cms.toolbar.utils.admin_reverse")
@patch("cms.toolbar.utils.ContentType")
def test_get_object_edit_url_appends_language_if_no_i18n_prefix(
self, mock_contenttype, mock_admin_reverse, mock_get_lang_from_path, mock_get_cms_setting
):
mock_get_cms_setting.return_value = False
mock_get_lang_from_path.return_value = None
mock_admin_reverse.return_value = "/admin/cms/placeholder/render/object/edit/1/"
mock_contenttype.objects.get_for_model.return_value = SimpleNamespace(pk=42)
obj = SimpleNamespace(pk=1)
url = get_object_edit_url(obj, language="de")
self.assertIn("?language=de", url)
@patch("cms.toolbar.utils.get_cms_setting")
@patch("cms.toolbar.utils.get_language_from_path")
@patch("cms.toolbar.utils.admin_reverse")
@patch("cms.toolbar.utils.ContentType")
def test_get_object_preview_url_appends_language_if_no_i18n_prefix(
self, mock_contenttype, mock_admin_reverse, mock_get_lang_from_path, mock_get_cms_setting
):
mock_get_cms_setting.return_value = False
mock_get_lang_from_path.return_value = None
mock_admin_reverse.return_value = "/admin/cms/placeholder/render/object/preview/1/"
mock_contenttype.objects.get_for_model.return_value = SimpleNamespace(pk=99)
obj = SimpleNamespace(pk=2)
url = get_object_preview_url(obj, language="fr")
self.assertIn("?language=fr", url)
@patch("cms.toolbar.utils.get_language_from_path")
@patch("cms.toolbar.utils.admin_reverse")
@patch("cms.toolbar.utils.ContentType")
def test_get_object_structure_url_appends_language_if_no_i18n_prefix(
self, mock_contenttype, mock_admin_reverse, mock_get_lang_from_path
):
mock_get_lang_from_path.return_value = None
mock_admin_reverse.return_value = "/admin/cms/placeholder/render/object/structure/1/"
mock_contenttype.objects.get_for_model.return_value = SimpleNamespace(pk=7)
obj = SimpleNamespace(pk=3)
url = get_object_structure_url(obj, language="es")
self.assertIn("?language=es", url)
# --- Tests for GET parameter forwarding (params argument) ---
@patch("cms.toolbar.utils.get_cms_setting")
@patch("cms.toolbar.utils.get_language_from_path")
@patch("cms.toolbar.utils.admin_reverse")
@patch("cms.toolbar.utils.ContentType")
def test_get_object_edit_url_appends_extra_params(
self, mock_contenttype, mock_admin_reverse, mock_get_lang_from_path, mock_get_cms_setting
):
"""Extra GET parameters passed via a QueryDict are appended to the edit URL."""
mock_get_cms_setting.return_value = False
mock_get_lang_from_path.return_value = None
mock_admin_reverse.return_value = "/admin/cms/placeholder/render/object/edit/1/"
mock_contenttype.objects.get_for_model.return_value = SimpleNamespace(pk=42)
params = QueryDict("foo=bar&baz=qux")
obj = SimpleNamespace(pk=1)
url = get_object_edit_url(obj, language="de", params=params)
self.assertIn("foo=bar", url)
self.assertIn("baz=qux", url)
self.assertIn("language=de", url)
@patch("cms.toolbar.utils.get_cms_setting")
@patch("cms.toolbar.utils.get_language_from_path")
@patch("cms.toolbar.utils.admin_reverse")
@patch("cms.toolbar.utils.ContentType")
def test_get_object_preview_url_appends_extra_params(
self, mock_contenttype, mock_admin_reverse, mock_get_lang_from_path, mock_get_cms_setting
):
"""Extra GET parameters passed via a QueryDict are appended to the preview URL."""
mock_get_cms_setting.return_value = False
mock_get_lang_from_path.return_value = None
mock_admin_reverse.return_value = "/admin/cms/placeholder/render/object/preview/1/"
mock_contenttype.objects.get_for_model.return_value = SimpleNamespace(pk=99)
params = QueryDict("alpha=1&beta=2")
obj = SimpleNamespace(pk=2)
url = get_object_preview_url(obj, language="fr", params=params)
self.assertIn("alpha=1", url)
self.assertIn("beta=2", url)
self.assertIn("language=fr", url)
@patch("cms.toolbar.utils.get_language_from_path")
@patch("cms.toolbar.utils.admin_reverse")
@patch("cms.toolbar.utils.ContentType")
def test_get_object_structure_url_appends_extra_params(
self, mock_contenttype, mock_admin_reverse, mock_get_lang_from_path
):
"""Extra GET parameters passed via a QueryDict are appended to the structure URL."""
mock_get_lang_from_path.return_value = None
mock_admin_reverse.return_value = "/admin/cms/placeholder/render/object/structure/1/"
mock_contenttype.objects.get_for_model.return_value = SimpleNamespace(pk=7)
params = QueryDict("x=10&y=20")
obj = SimpleNamespace(pk=3)
url = get_object_structure_url(obj, language="es", params=params)
self.assertIn("x=10", url)
self.assertIn("y=20", url)
self.assertIn("language=es", url)
@patch("cms.toolbar.utils.get_cms_setting")
@patch("cms.toolbar.utils.get_language_from_path")
@patch("cms.toolbar.utils.admin_reverse")
@patch("cms.toolbar.utils.ContentType")
def test_get_object_url_without_params_has_no_extra_querystring(
self, mock_contenttype, mock_admin_reverse, mock_get_lang_from_path, mock_get_cms_setting
):
"""When params is None, only the language parameter (if needed) is appended."""
mock_get_cms_setting.return_value = False
mock_get_lang_from_path.return_value = None
mock_admin_reverse.return_value = "/admin/cms/placeholder/render/object/edit/1/"
mock_contenttype.objects.get_for_model.return_value = SimpleNamespace(pk=42)
obj = SimpleNamespace(pk=1)
url = get_object_edit_url(obj, language="de", params=None)
self.assertEqual(url, "/admin/cms/placeholder/render/object/edit/1/?language=de")
@patch("cms.toolbar.utils.get_cms_setting")
@patch("cms.toolbar.utils.get_language_from_path")
@patch("cms.toolbar.utils.admin_reverse")
@patch("cms.toolbar.utils.ContentType")
def test_get_object_url_does_not_mutate_original_params(
self, mock_contenttype, mock_admin_reverse, mock_get_lang_from_path, mock_get_cms_setting
):
"""Calling get_object_*_url must not mutate the original QueryDict."""
mock_get_cms_setting.return_value = False
mock_get_lang_from_path.return_value = None
mock_admin_reverse.return_value = "/admin/cms/placeholder/render/object/edit/1/"
mock_contenttype.objects.get_for_model.return_value = SimpleNamespace(pk=42)
params = QueryDict("keep=me")
obj = SimpleNamespace(pk=1)
get_object_edit_url(obj, language="de", params=params)
# The original immutable QueryDict must not have been altered
self.assertNotIn("language", params)
self.assertEqual(params["keep"], "me")
# --- Integration tests: toolbar methods forward request.GET ---
def test_toolbar_get_object_edit_url_passes_request_get(self):
"""CMSToolbar.get_object_edit_url forwards request.GET to the utility function."""
page = create_page("home", "nav_playground.html", "en")
page_content = page.get_content_obj()
superuser = self.get_superuser()
edit_url = get_object_edit_url(page_content)
request = self.get_page_request(page, superuser, edit_url + "?custom=value")
toolbar = CMSToolbar(request)
toolbar.set_object(page_content)
url = toolbar.get_object_edit_url()
self.assertIn("custom=value", url)
def test_toolbar_get_object_preview_url_passes_request_get(self):
"""CMSToolbar.get_object_preview_url forwards request.GET to the utility function."""
page = create_page("home", "nav_playground.html", "en")
page_content = page.get_content_obj()
superuser = self.get_superuser()
edit_url = get_object_edit_url(page_content)
request = self.get_page_request(page, superuser, edit_url + "?custom=value")
toolbar = CMSToolbar(request)
toolbar.set_object(page_content)
url = toolbar.get_object_preview_url()
self.assertIn("custom=value", url)
def test_toolbar_get_object_structure_url_passes_request_get(self):
"""CMSToolbar.get_object_structure_url forwards request.GET to the utility function."""
page = create_page("home", "nav_playground.html", "en")
page_content = page.get_content_obj()
superuser = self.get_superuser()
edit_url = get_object_edit_url(page_content)
request = self.get_page_request(page, superuser, edit_url + "?custom=value")
toolbar = CMSToolbar(request)
toolbar.set_object(page_content)
url = toolbar.get_object_structure_url()
self.assertIn("custom=value", url)
# --- Template integration test: URLs with params are correctly escaped in JS config ---
def test_toolbar_javascript_template_contains_escaped_urls_with_params(self):
"""
The toolbar_javascript.html template renders cms_edit_url, cms_preview_url,
and cms_structure_url through the escapejs filter so that query-string
ampersands appear as \\u0026 inside the JSON string literals.
"""
page = create_page("home", "nav_playground.html", "en")
page_content = page.get_content_obj()
superuser = self.get_superuser()
edit_url = get_object_edit_url(page_content)
with self.login_user_context(superuser):
response = self.client.get(edit_url + "?myparam=myval")
self.assertEqual(response.status_code, 200)
content = response.content.decode()
# The rendered JSON should contain the custom parameter in all three URLs.
# The template uses {{ ... | escapejs }} so '&' becomes '\\u0026'.
self.assertIn("myparam=myval", content)
# Verify the edit, edit_off (preview), and structure keys exist with the param
import json
# Extract the JSON config block from the <script> tag
match = re.search(
r'<script[^>]*id="cms-config-json"[^>]*>\s*(\{.*?\})\s*</script>',
content,
re.DOTALL,
)
self.assertIsNotNone(match, "Could not find cms-config-json script block")
config = json.loads(match.group(1))
self.assertIn("myparam=myval", config["settings"]["edit"])
self.assertIn("myparam=myval", config["settings"]["edit_off"])
self.assertIn("myparam=myval", config["settings"]["structure"])
class GetObjectLiveUrlTests(CMSTestCase):
"""
Tests for get_object_live_url:
- no prefix if no target site is given
- no prefix if target site equals object's site
- protocol-relative domain prefix if target site differs from object's site
- no prefix if the object has no site information
"""
class _Dummy:
def __init__(self, url="/en/dummy/", language="en", site=None):
self.language = language
if site is not None:
self.site = site # optional site attribute
self._url = url
def get_absolute_url(self):
return self._url
def setUp(self):
# create two sites with distinct domains
Site.objects.all().delete()
self.site1 = Site.objects.create(domain="site1.test", name="Site 1")
self.site2 = Site.objects.create(domain="site2.test", name="Site 2")
def test_always_prefix_when_site_argument_is_none(self):
obj = self._Dummy(url="/en/dummy/", language="en", site=self.site1)
url = get_object_live_url(obj, language="en", site=None)
self.assertEqual(url, f"//{self.site1.domain}/en/dummy/")
def test_no_prefix_when_target_site_equals_object_site(self):
obj = self._Dummy(url="/en/dummy/", language="en", site=self.site1)
url = get_object_live_url(obj, language="en", site=self.site1)
self.assertEqual(url, "/en/dummy/")
def test_prefix_added_when_target_site_differs_from_object_site(self):
obj = self._Dummy(url="/en/dummy/", language="en", site=self.site1)
url = get_object_live_url(obj, language="en", site=self.site2)
self.assertEqual(url, f"//{self.site1.domain}/en/dummy/")
def test_no_prefix_when_object_has_no_site_info(self):
# No .site attribute and no cms_extension mapping -> site param ignored
obj = self._Dummy(url="/en/dummy/", language="en", site=None)
# Remove site attribute if present
if hasattr(obj, "site"):
delattr(obj, "site")
url = get_object_live_url(obj, language="en", site=self.site2)
self.assertEqual(url, "/en/dummy/")
def test_grouper_site_is_respected(self):
page = create_page("test page", "nav_playground.html", "de", site=self.site1)
obj = page.get_content_obj("de")
url = get_object_live_url(obj, language="de", site=self.site2)
self.assertEqual(url, f"//{self.site1.domain}/de/test-page/")
url = get_object_live_url(obj, language="de", site=self.site1)
self.assertEqual(url, "/de/test-page/")
def test_get_object_live_url_works_with_grouper(self):
page = create_page("test page", "nav_playground.html", "de", site=self.site1)
url = get_object_live_url(page, language="de", site=self.site2)
self.assertEqual(url, f"//{self.site1.domain}/de/test-page/")
url = get_object_live_url(page, language="de", site=self.site1)
self.assertEqual(url, "/de/test-page/")
def test_get_object_live_url_includes_querydict_params(self):
"""
Verifies that get_object_live_url includes QueryDict parameters
but excludes the ENDPOINT_LIVE_URL_QUERYSTRING_PARAM
"""
from django.http import QueryDict
page = create_page("test page", "nav_playground.html", "de", site=self.site1)
# Create a QueryDict with test parameters
params = QueryDict(mutable=True)
params["param1"] = "value1"
params["param2"] = "value2"
params[get_cms_setting("ENDPOINT_LIVE_URL_QUERYSTRING_PARAM")] = "should_be_excluded"
# Test without site argument (should include params)
url = get_object_live_url(page, language="de", site=None, params=params)
self.assertIn("param1=value1", url)
self.assertIn("param2=value2", url)
self.assertNotIn("should_be_excluded", url)
self.assertIn("?", url)
# Test with same site (should include params, no domain prefix)
url = get_object_live_url(page, language="de", site=self.site1, params=params)
self.assertIn("param1=value1", url)
self.assertIn("param2=value2", url)
self.assertNotIn("should_be_excluded", url)
self.assertIn("?", url)
self.assertFalse(url.startswith("//"))
# Test with different site (should include params, with domain prefix)
url = get_object_live_url(page, language="de", site=self.site2, params=params)
self.assertIn("param1=value1", url)
self.assertIn("param2=value2", url)
self.assertNotIn("should_be_excluded", url)
self.assertTrue(url.startswith(f"//{self.site1.domain}"))
self.assertIn("?", url)
class CharPkFrontendPlaceholderAdminTest(ToolbarTestBase):
def get_admin(self):
admin.autodiscover()
return admin.site._registry[CharPksExample]
def test_url_char_pk(self):
"""
Tests whether the frontend admin matches the edit_fields url with alphanumeric pks
"""
ex = CharPksExample(
char_1="one",
slug="some-Special_slug_123",
)
ex.save()
superuser = self.get_superuser()
with UserLoginContext(self, superuser):
response = self.client.get(
admin_reverse("placeholderapp_charpksexample_edit_field", args=(ex.pk, "en")),
data={"edit_fields": "char_1"},
)
# if we get a response pattern matches
self.assertEqual(response.status_code, 200)
def test_url_numeric_pk(self):
"""
Tests whether the frontend admin matches the edit_fields url with numeric pks
"""
ex = self._get_example_obj()
superuser = self.get_superuser()
with UserLoginContext(self, superuser):
response = self.client.get(
admin_reverse("placeholderapp_example1_edit_field", args=(ex.pk, "en")), data={"edit_fields": "char_1"}
)
# if we get a response pattern matches
self.assertEqual(response.status_code, 200)
def test_view_numeric_pk(self):
"""
Tests whether the admin urls triggered when the toolbar is active works
(i.e.: no NoReverseMatch is raised) with numeric pks
"""
page = create_page("Test", "col_two.html", "en")
page_content = self.get_pagecontent_obj(page)
edit_url = get_object_edit_url(page_content)
ex = self._get_example_obj()
superuser = self.get_superuser()
request = self.get_page_request(page, superuser, edit_url)
response = detail_view(request, ex.pk)
# if we get a response pattern matches
self.assertEqual(response.status_code, 200)
class ToolbarAPITests(TestCase):
def test_find_item(self):
api = ToolbarAPIMixin()
first = api.add_link_item("First", "http://www.example.org")
second = api.add_link_item("Second", "http://www.example.org")
all_links = api.find_items(LinkItem)
self.assertEqual(len(all_links), 2)
result = api.find_first(LinkItem, name="First")
self.assertNotEqual(result, None)
self.assertEqual(result.index, 0)
self.assertEqual(result.item, first)
result = api.find_first(LinkItem, name="Second")
self.assertNotEqual(result, None)
self.assertEqual(result.index, 1)
self.assertEqual(result.item, second)
no_result = api.find_first(LinkItem, name="Third")
self.assertEqual(no_result, None)
def test_find_item_lazy(self):
lazy_attribute = lazy(lambda x: x, str)("Test")
api = ToolbarAPIMixin()
api.add_link_item(lazy_attribute, None)
result = api.find_first(LinkItem, name="Test")
self.assertNotEqual(result, None)
self.assertEqual(result.index, 0)
def test_find_item_lazy_matches_source_not_translation(self):
# Lazy (gettext_lazy) names are matched by their *source* string, independent of the
# active language. Third-party apps must therefore search by the untranslated string.
api = ToolbarAPIMixin()
with override("de"):
label = _("Save")
# Sanity check: the German catalog is active, so the label actually translates.
self.assertNotEqual(str(label), "Save")
api.add_link_item(label, None)
# Found by the untranslated source string...
self.assertIsNotNone(api.find_first(LinkItem, name="Save"))
# ...but not by its translation.
self.assertIsNone(api.find_first(LinkItem, name=str(label)))
def test_not_is_staff(self):
request = RequestFactory().get("/en/")
request.session = {}
request.LANGUAGE_CODE = "en"
request.user = AnonymousUser()
toolbar = CMSToolbar(request)
self.assertEqual(len(toolbar.get_left_items()), 0)
self.assertEqual(len(toolbar.get_right_items()), 0)
def test_item_search_result(self):
item = object()
result = ItemSearchResult(item, 2)
self.assertEqual(result.item, item)
self.assertEqual(int(result), 2)
result += 2
self.assertEqual(result.item, item)
self.assertEqual(result.index, 4)
class TestLanguageMenu(CMSTestCase):
@override_settings(
LANGUAGE_CODE="en",
LANGUAGES=(("en", "English"),),
CMS_LANGUAGES={
1: [
{"code": "en", "name": "English", "public": True},
],
},
)
def test_no_language_menu(self):
"""No language menu appears if only one language is available"""
request = self.get_page_request(None, self.get_superuser(), "/")
toolbar = CMSToolbar(request)
toolbar.populate()
self.assertNotIn(LANGUAGE_MENU_IDENTIFIER, toolbar.menus)
def test_language_menu(self):
"""A language menu appears if more than one language is available"""
request = self.get_page_request(None, self.get_superuser(), "/")
toolbar = CMSToolbar(request)
toolbar.populate()
self.assertIn(LANGUAGE_MENU_IDENTIFIER, toolbar.menus)