import json import re import warnings from unittest.mock import patch from django.conf import settings from django.contrib.auth import get_user_model from django.contrib.sites.models import Site from django.core.exceptions import ImproperlyConfigured from django.template import Template, TemplateSyntaxError, engines from django.template.loader import get_template from django.test import TestCase from django.test.utils import override_settings from django.utils.encoding import force_str from django.utils.numberformat import format from sekizai.context import SekizaiContext from cms import constants from cms.api import add_plugin, create_page, create_page_content from cms.exceptions import DuplicatePlaceholderWarning, PlaceholderNotFound from cms.models.placeholdermodel import Placeholder from cms.models.pluginmodel import CMSPlugin from cms.models.settingmodels import UserSettings from cms.plugin_pool import plugin_pool from cms.test_utils.project.placeholder_relation_field_app.models import ( FancyPoll, ) from cms.test_utils.project.placeholderapp.models import ( DynamicPlaceholderSlotExample, Example1, TwoPlaceholderExample, ) from cms.test_utils.project.sampleapp.models import Category from cms.test_utils.testcases import CMSTestCase, TransactionCMSTestCase from cms.test_utils.util.context_managers import override_placeholder_conf from cms.test_utils.util.mock import AttributeObject from cms.tests.test_toolbar import ToolbarTestBase from cms.toolbar.utils import ( get_object_edit_url, get_object_structure_url, get_toolbar_from_request, ) from cms.utils.placeholder import ( _get_nodelist, _scan_placeholders, get_placeholder_conf, get_placeholders, ) from cms.utils.urlutils import admin_reverse def _get_placeholder_slots(template): return [pl.slot for pl in get_placeholders(template)] def _render_placeholder(placeholder, context, **kwargs): request = context["request"] toolbar = get_toolbar_from_request(request) content_renderer = toolbar.content_renderer return content_renderer.render_placeholder(placeholder, context, **kwargs) class PlaceholderTestCase(TransactionCMSTestCase): def setUp(self): u = self._create_user("test", True, True) self._login_context = self.login_user_context(u) self._login_context.__enter__() def tearDown(self): self._login_context.__exit__(None, None, None) def test_placeholder_scanning_extend(self): placeholders = _get_placeholder_slots("placeholder_tests/test_one.html") self.assertEqual(sorted(placeholders), sorted(["new_one", "two", "three"])) def test_placeholder_scanning_variable_extend(self): placeholders = _get_placeholder_slots("placeholder_tests/test_variable_extends.html") self.assertEqual(placeholders, ["one", "two", "three", "four"]) def test_placeholder_scanning_inherit_from_variable_extend(self): placeholders = _get_placeholder_slots("placeholder_tests/test_inherit_from_variable_extends.html") self.assertEqual(placeholders, ["one", "two", "three", "four"]) def test_placeholder_scanning_sekizai_extend(self): placeholders = _get_placeholder_slots("placeholder_tests/test_one_sekizai.html") self.assertEqual(sorted(placeholders), sorted(["new_one", "two", "three"])) def test_placeholder_scanning_include(self): placeholders = _get_placeholder_slots("placeholder_tests/test_two.html") self.assertEqual(sorted(placeholders), sorted(["child", "three"])) def test_placeholder_scanning_double_extend(self): placeholders = _get_placeholder_slots("placeholder_tests/test_three.html") self.assertEqual(sorted(placeholders), sorted(["new_one", "two", "new_three"])) def test_placeholder_scanning_sekizai_double_extend(self): placeholders = _get_placeholder_slots("placeholder_tests/test_three_sekizai.html") self.assertEqual(sorted(placeholders), sorted(["new_one", "two", "new_three"])) def test_placeholder_scanning_complex(self): placeholders = _get_placeholder_slots("placeholder_tests/test_four.html") self.assertEqual(sorted(placeholders), sorted(["new_one", "child", "four"])) def test_placeholder_scanning_super(self): placeholders = _get_placeholder_slots("placeholder_tests/test_five.html") self.assertEqual(sorted(placeholders), sorted(["one", "extra_one", "two", "three"])) def test_placeholder_scanning_nested(self): placeholders = _get_placeholder_slots("placeholder_tests/test_six.html") self.assertEqual(sorted(placeholders), sorted(["new_one", "new_two", "new_three"])) def test_placeholder_scanning_duplicate(self): placeholders = self.failUnlessWarns( DuplicatePlaceholderWarning, 'Duplicate {% placeholder "one" %} in template placeholder_tests/test_seven.html.', _get_placeholder_slots, "placeholder_tests/test_seven.html", ) self.assertEqual(sorted(placeholders), sorted(["one"])) def test_placeholder_scanning_extend_outside_block(self): placeholders = _get_placeholder_slots("placeholder_tests/outside.html") self.assertEqual(sorted(placeholders), sorted(["new_one", "two", "base_outside"])) def test_placeholder_scanning_sekizai_extend_outside_block(self): placeholders = _get_placeholder_slots("placeholder_tests/outside_sekizai.html") self.assertEqual(sorted(placeholders), sorted(["new_one", "two", "base_outside"])) def test_placeholder_scanning_extend_outside_block_nested(self): placeholders = _get_placeholder_slots("placeholder_tests/outside_nested.html") self.assertEqual(sorted(placeholders), sorted(["new_one", "two", "base_outside"])) def test_placeholder_scanning_sekizai_extend_outside_block_nested(self): placeholders = _get_placeholder_slots("placeholder_tests/outside_nested_sekizai.html") self.assertEqual(sorted(placeholders), sorted(["new_one", "two", "base_outside"])) def test_placeholder_scanning_var(self): t = Template('{%load cms_tags %}{% include name %}{% placeholder "a_placeholder" %}') phs = sorted(node.get_declaration().slot for node in _scan_placeholders(t.nodelist)) self.assertListEqual(phs, sorted(["a_placeholder"])) t = Template('{% include "placeholder_tests/outside_nested_sekizai.html" %}') phs = sorted(node.get_declaration().slot for node in _scan_placeholders(t.nodelist)) self.assertListEqual(phs, sorted(["two", "new_one", "base_outside"])) def test_placeholder_scanning_unresolvable_extends_variable(self): """ Test that _scan_placeholders gracefully handles extends tags with unresolvable variables (like CMS_TEMPLATE without a default). Issue #8334. """ # Template with an extends tag using a variable that won't be in the context template_str = ( '{% extends CMS_TEMPLATE %}{% load cms_tags %}{% block content %}{% placeholder "slot" %}{% endblock %}' ) tpl = Template(template_str) # Should not raise TemplateSyntaxError, just return empty list nodes = _scan_placeholders(tpl.nodelist) slots = [node.get_declaration().slot for node in nodes] self.assertEqual(slots, []) def test_placeholder_scanning_nonexistent_template_extends(self): """ Test that _scan_placeholders gracefully handles extends tags pointing to nonexistent templates. Regression test for issue #8334. """ # Template extending a nonexistent template t = Template( '{% extends "nonexistent_template.html" %}{% load cms_tags %}' '{% block content %}{% placeholder "test_slot" %}{% endblock %}' ) # Should not raise TemplateDoesNotExist, just return empty list nodes = _scan_placeholders(t.nodelist) slots = [node.get_declaration().slot for node in nodes] self.assertEqual(slots, []) def test_placeholder_scanning_no_object(self): """Placeholder scanning for a template without a toolbar object raises PlaceholderNotFound""" context = SekizaiContext() request = self.get_request(language="en", page=None) toolbar = get_toolbar_from_request(request) renderer = toolbar.get_content_renderer() with self.assertRaises(PlaceholderNotFound): renderer.render_obj_placeholder("someslot", context, False) def test_fieldsets_requests(self): response = self.client.get(admin_reverse("placeholderapp_example1_add")) self.assertEqual(response.status_code, 200) response = self.client.get(admin_reverse("placeholderapp_twoplaceholderexample_add")) self.assertEqual(response.status_code, 200) def test_page_only_plugins(self): ex = Example1(char_1="one", char_2="two", char_3="tree", char_4="four") ex.save() response = self.client.get(admin_reverse("placeholderapp_example1_change", args=(ex.pk,))) self.assertEqual(response.status_code, 200) self.assertNotContains(response, "InheritPagePlaceholderPlugin") def test_inter_placeholder_plugin_move(self): ex = TwoPlaceholderExample(char_1="one", char_2="two", char_3="tree", char_4="four") ex.save() ph1 = ex.placeholder_1 ph2 = ex.placeholder_2 ph1_pl1 = add_plugin(ph1, "TextPlugin", "en", body="ph1 plugin1").cmsplugin_ptr ph1_pl2 = add_plugin(ph1, "TextPlugin", "en", body="ph1 plugin2").cmsplugin_ptr ph1_pl3 = add_plugin(ph1, "TextPlugin", "en", body="ph1 plugin3").cmsplugin_ptr ph2_pl1 = add_plugin(ph2, "TextPlugin", "en", body="ph2 plugin1").cmsplugin_ptr ph2_pl2 = add_plugin(ph2, "TextPlugin", "en", body="ph2 plugin2").cmsplugin_ptr ph2_pl3 = add_plugin(ph2, "TextPlugin", "en", body="ph2 plugin3").cmsplugin_ptr endpoint = self.get_move_plugin_uri(ph1_pl2) # Move ph2_pl3 to position 1 on placeholder 2 data = { "plugin_id": str(ph2_pl3.pk), "target_language": "en", "target_position": 1, } response = self.client.post(endpoint, data) self.assertEqual(response.status_code, 200) self.assertEqual([ph2_pl3, ph2_pl1, ph2_pl2], list(ph2.cmsplugin_set.order_by("position"))) # Move ph1_pl2 to last position on placeholder 2 data = { "placeholder_id": str(ph2.pk), "plugin_id": str(ph1_pl2.pk), "target_language": "en", "target_position": ph2.get_next_plugin_position("en", insert_order="last"), } response = self.client.post(endpoint, data) self.assertEqual(response.status_code, 200) self.assertEqual([ph1_pl1, ph1_pl3], list(ph1.cmsplugin_set.order_by("position"))) self.assertEqual([ph2_pl3, ph2_pl1, ph2_pl2, ph1_pl2], list(ph2.cmsplugin_set.order_by("position"))) def test_inter_placeholder_nested_plugin_move(self): # Specific test to move a large number of plugins from one placeholder to a placeholder with a # small number of plugins, between similarly size placeholders and a small number of plugins to # a placeholder with a large number of plugins (For this test large is 10 times small.) # These cases ensure the move operation is flexible with the relation of plugins, e.g. creates enough space # # To ensure the test works with different languages, plugins are created for a number of language with # interlacing pk, the move operation only happens in the first language - others should stay unchanged. # language_fun = ("en", "de", "it", "de-formal") # symmetric and asymmetric plugin numbers in target placeholder, source placeholder # (1, 10) One plugin in the target placeholder, 10 plugins to be moved # (5, 5) Same size situation # (10, 1) Ten plugins in the target placeholder, 1 plugin to be moved for n1, n2 in ((1, 10), (10, 1), (5, 5)): ex = TwoPlaceholderExample() ex.save() ph1 = ex.placeholder_1 ph2 = ex.placeholder_2 ph1parent = {} # parents ph2parent = {} ph1children = {} # Children ph2children = {} for lang in language_fun: # first create parent in target placeholder ph1parent[lang] = add_plugin(ph1, "TextPlugin", lang, body="ph1 parent").cmsplugin_ptr ph1children[lang] = [] for i in range(n1): # create children in target placeholder ph1children[lang].append( add_plugin(ph1, "TextPlugin", lang, target=ph1parent[lang], body=f"ph1 child{i}").cmsplugin_ptr ) # now create parent in source placeholder ph2parent[lang] = add_plugin(ph2, "TextPlugin", lang, body="ph2 parent").cmsplugin_ptr ph2children[lang] = [] for i in range(n2): # followed by children in source placeholder ph2children[lang].append( add_plugin(ph2, "TextPlugin", lang, target=ph2parent[lang], body=f"ph2 child{i}").cmsplugin_ptr ) # Plugins have been created in all languages. Now move the first language plugins from the source # placeholder to the target placeholder ph2.move_plugin( ph2parent[language_fun[0]], # Move parent (first language only) **and** its children target_plugin=ph1parent[language_fun[0]], # as child of parent in target placeholder target_position=2, # First child, that is target_placeholder=ph1, ) # The result in theory: # Parent Target Placeholder # Parent Source Placeholder <-- Moved plugin parent # Children Source Placeholder <-- Moved plugin children # Children Target Placeholder left = ( [ph1parent[language_fun[0]], ph2parent[language_fun[0]]] + ph2children[language_fun[0]] + ph1children[language_fun[0]] ) # The result in practice right = list(ph1.cmsplugin_set.filter(language=language_fun[0]).order_by("position")) self.assertEqual(left, right) ph1.delete() ph2.delete() def test_get_last_plugin_position(self): # get_last_plugin_position is supposed to either get the position of the last plugin of the placeholder # or the position of the last grand(!) child if a given plugin, independent of language! # This test creates a tree of the form # 1. Plugin 1 # 2. Plugin 2 # 3. Plugin 3 # 4. Plugin 4 # 5. Final Plugin language_fun = ("en", "de", "it", "de-formal") ex = Example1() ex.save() ph = ex.placeholder parent = None n = 4 # This will be the position of the last plugin plugins = {lang: [] for lang in language_fun} parent = dict.fromkeys(language_fun) for i in range(n): for lang in language_fun: parent[lang] = add_plugin(ph, "TextPlugin", lang, target=parent[lang]).cmsplugin_ptr plugins[lang].append(parent[lang]) for lang in language_fun: self.assertEqual(ph.get_last_plugin_position(lang), n) # should be n for parent in plugins[lang]: # needs to be n also if we look at the parents # the last plugin does not have a child hence position should be none self.assertEqual( ph.get_last_plugin_position(lang, parent=parent), n if parent != plugins[lang][-1] else None, ) add_plugin(ph, "TextPlugin", lang, target=None) # Add a top level final plugin self.assertEqual(ph.get_last_plugin_position(lang), n + 1) # plus the last plugin for parent in plugins[lang]: # still needs to be n also if we look at the parents # the last plugin does not have a child hence position should be none self.assertEqual( ph.get_last_plugin_position(lang, parent=parent), n if parent != plugins[lang][-1] else None, ) def test_get_last_plugin_position_order(self): # This test creates a tree like this # 1. Plugin n # 2. Plugin n-1 # 3. Plugin n-2 # ... # n. Plugin 1 # The children are created with the 'first-child' option, i.e. in reverse order: new children # are prepended to the existing ones. Still get_last_plugin_position needs to return the position # n if the last plugin (though it was created first). # To make this more fun we interlace plugins with other languages to ensure they do not influence # the result. language_fun = ("en", "de", "it", "en-US") ex = Example1() ex.save() ph = ex.placeholder n = 15 # This will be the position of the last plugin for i in range(n): # Use 'first-child' order causes pk and position run inversely for language in language_fun: add_plugin(ph, "TextPlugin", language, "first-child").cmsplugin_ptr self.assertEqual(ph.get_last_plugin_position("en"), n) # should be n def test_copy_plugin(self): superuser = self.get_superuser() page_en = create_page("CopyPluginTestPage (EN)", "nav_playground.html", "en") ph_source = page_en.get_placeholders("en").get(slot="body") plugin = add_plugin(ph_source, "LinkPlugin", "en", name="A Link", external_link="https://www.django-cms.org") endpoint = self.get_copy_plugin_uri(plugin) user_settings = UserSettings.objects.create( language="en", user=superuser, clipboard=Placeholder.objects.create(), ) data = { "source_plugin_id": plugin.pk, "source_placeholder_id": ph_source.pk, "source_language": plugin.language, "target_language": "en", "target_placeholder_id": user_settings.clipboard.pk, } with self.login_user_context(superuser): # Copy plugins into the clipboard response = self.client.post(endpoint, data) self.assertEqual(response.status_code, 200) clipboard_plugins = user_settings.clipboard.get_plugins() self.assertTrue(clipboard_plugins.filter(plugin_type="LinkPlugin").exists()) self.assertEqual(len(clipboard_plugins), 1) def test_copy_plugin_without_custom_model(self): superuser = self.get_superuser() page_en = create_page("CopyPluginTestPage (EN)", "nav_playground.html", "en") ph_source = page_en.get_placeholders("en").get(slot="body") plugin = add_plugin(ph_source, "NoCustomModel", "en") endpoint = self.get_copy_plugin_uri(plugin) user_settings = UserSettings.objects.create( language="en", user=superuser, clipboard=Placeholder.objects.create(), ) data = { "source_plugin_id": plugin.pk, "source_placeholder_id": ph_source.pk, "source_language": plugin.language, "target_language": "en", "target_placeholder_id": user_settings.clipboard.pk, } with self.login_user_context(superuser): # Copy plugins into the clipboard response = self.client.post(endpoint, data) self.assertEqual(response.status_code, 200) clipboard_plugins = user_settings.clipboard.get_plugins() self.assertTrue(clipboard_plugins.filter(plugin_type="NoCustomModel").exists()) self.assertEqual(len(clipboard_plugins), 1) def test_paste_plugin_from_clipboard(self): superuser = self.get_superuser() page_en = create_page("CopyPluginTestPage (EN)", "nav_playground.html", "en") ph_target = page_en.get_placeholders("en").get(slot="body") add_plugin(ph_target, "LinkPlugin", "en", name="A Link", external_link="https://www.django-cms.org") user_settings = UserSettings.objects.create( language="en", user=superuser, clipboard=Placeholder.objects.create(), ) plugin = add_plugin( user_settings.clipboard, "LinkPlugin", language="de", # Test x-language name="A Link", external_link="https://www.django-cms.org", ) endpoint = self.get_move_plugin_uri(plugin) # Paste LinkPlugin to first position data = { "plugin_id": plugin.pk, "move_a_copy": True, "target_language": "en", "target_position": 1, "placeholder_id": ph_target.pk, } with self.login_user_context(superuser): # Paste plugin from the clipboard into target placeholder response = self.client.post(endpoint, data) self.assertEqual(response.status_code, 200) plugins = ph_target.get_plugins("en") self.assertTrue(plugins.filter(plugin_type="LinkPlugin").exists()) self.assertEqual(len(plugins), 2) # Paste LinkPlugin as child of first LinkPlugin target = ph_target.get_plugins("en").get(position=1) data = { "plugin_id": plugin.pk, "move_a_copy": True, "target_language": "en", "target_position": 2, "plugin_parent": target.pk, "placeholder_id": ph_target.pk, } with self.login_user_context(superuser): # Paste plugin from the clipboard into target placeholder response = self.client.post(endpoint, data) self.assertEqual(response.status_code, 200) plugins = ph_target.get_plugins("en") self.assertTrue(plugins.filter(plugin_type="LinkPlugin").exists()) self.assertEqual(len(plugins), 3) def test_placeholder_render_ghost_plugin(self): """ Tests a placeholder won't render a ghost plugin. """ page_en = create_page("page_en", "col_two.html", "en") placeholder_en = page_en.get_placeholders("en").get(slot="col_left") CMSPlugin.objects.create( language="en", plugin_type="LinkPlugin", position=1, placeholder=placeholder_en, parent=None, ) add_plugin( placeholder_en, "LinkPlugin", "en", name="name", external_link="http://example.com/", ) context_en = SekizaiContext() context_en["request"] = self.get_request(language="en", page=page_en) content_en = _render_placeholder(placeholder_en, context_en) self.assertEqual(content_en.strip(), 'name') def test_placeholder_render_ghost_plugin_with_child(self): """ Tests a placeholder won't render a ghost plugin or any of it's children. """ page_en = create_page("page_en", "col_two.html", "en") placeholder_en = page_en.get_placeholders("en").get(slot="col_left") plugin = CMSPlugin.objects.create( language="en", plugin_type="LinkPlugin", position=1, placeholder=placeholder_en, parent=None, ) add_plugin( placeholder_en, "LinkPlugin", "en", target=plugin, name="invalid", external_link="http://example.com/", ) add_plugin( placeholder_en, "LinkPlugin", "en", name="valid", external_link="http://example.com/", ) context_en = SekizaiContext() context_en["request"] = self.get_request(language="en", page=page_en) content_en = _render_placeholder(placeholder_en, context_en) self.assertEqual(content_en.strip(), 'valid') @override_settings(CMS_PERMISSION=False) def test_databridge_contains_changes(self): """ Checks #1366 error condition. When adding/editing a plugin whose icon_src() method returns a URL containing a hyphen, the hyphen is escaped by django escapejs resulting in a incorrect URL """ ex = Example1(char_1="one", char_2="two", char_3="tree", char_4="four") ex.save() ph1 = ex.placeholder ### # add the test plugin ### test_plugin = add_plugin(ph1, "EmptyPlugin", "en") test_plugin.save() endpoint = self.get_change_plugin_uri(test_plugin) response = self.client.post(endpoint, {}) self.assertContains(response, '', response.content.decode(), re.S, ).group(1) ) message = bridge["messages"][0]["message"] self.assertNotIn(payload, message) self.assertIn("<img src=x onerror="alert(1)">", message) def test_placeholder_scanning_fail(self): self.assertRaises(TemplateSyntaxError, _get_placeholder_slots, "placeholder_tests/test_eleven.html") def test_placeholder_tag(self): request = self.get_request("/", language=settings.LANGUAGES[0][0]) template = "{% load cms_tags %}{% render_placeholder placeholder %}" output = self.render_template_obj(template, {}, request) self.assertEqual(output, "") placeholder = Placeholder.objects.create(slot="test") output = self.render_template_obj(template, {"placeholder": placeholder}, request) self.assertEqual(output, "") self.assertEqual(placeholder.get_plugins().count(), 0) add_plugin(placeholder, "TextPlugin", settings.LANGUAGES[0][0], body="test") self.assertEqual(placeholder.get_plugins().count(), 1) placeholder = self.reload(placeholder) output = self.render_template_obj(template, {"placeholder": placeholder}, request) self.assertEqual(output, "test") def test_placeholder_tag_language(self): template = "{% load cms_tags %}{% render_placeholder placeholder language language %}" placeholder = Placeholder.objects.create(slot="test") add_plugin(placeholder, "TextPlugin", "en", body="English") add_plugin(placeholder, "TextPlugin", "de", body="Deutsch") request = self.get_request("/") output = self.render_template_obj(template, {"placeholder": placeholder, "language": "en"}, request) self.assertEqual(output.strip(), "English") del placeholder._plugins_cache output = self.render_template_obj(template, {"placeholder": placeholder, "language": "de"}, request) self.assertEqual(output.strip(), "Deutsch") def test_get_placeholder_conf(self): TEST_CONF = { "main": { "name": "main content", "plugins": ["TextPlugin", "LinkPlugin"], "default_plugins": [ { "plugin_type": "TextPlugin", "values": {"body": "

Some default text

"}, }, ], }, "layout/home.html main": { "name": "main content with FilerImagePlugin and limit", "plugins": ["TextPlugin", "FilerImagePlugin", "LinkPlugin"], "inherit": "main", "limits": {"global": 1}, }, "layout/other.html main": { "name": "main content with FilerImagePlugin and no limit", "inherit": "layout/home.html main", "limits": {}, "excluded_plugins": ["LinkPlugin"], }, None: { "name": "All", "plugins": ["FilerImagePlugin", "LinkPlugin"], "limits": {}, }, } with override_placeholder_conf(CMS_PLACEHOLDER_CONF=TEST_CONF): # test no inheritance returned = get_placeholder_conf("plugins", "main") self.assertEqual(returned, TEST_CONF["main"]["plugins"]) # test no inherited value with inheritance enabled returned = get_placeholder_conf("plugins", "main", "layout/home.html") self.assertEqual(returned, TEST_CONF["layout/home.html main"]["plugins"]) # test direct inherited value returned = get_placeholder_conf("plugins", "main", "layout/other.html") self.assertEqual(returned, TEST_CONF["layout/home.html main"]["plugins"]) # test excluded_plugins key returned = get_placeholder_conf("excluded_plugins", "main", "layout/other.html") self.assertEqual(returned, TEST_CONF["layout/other.html main"]["excluded_plugins"]) # test grandparent inherited value returned = get_placeholder_conf("default_plugins", "main", "layout/other.html") self.assertEqual(returned, TEST_CONF["main"]["default_plugins"]) # test generic configuration returned = get_placeholder_conf("plugins", "something") self.assertEqual(returned, TEST_CONF[None]["plugins"]) # test doubly-inherited returned = get_placeholder_conf("default_plugins", "layout/other.html main") self.assertEqual(returned, TEST_CONF["main"]["default_plugins"]) def test_get_placeholder_conf_chained_inheritance(self): """ A two-level ``inherit`` chain resolves transitively without leaking unrelated top-level conf keys into the slot config and without mutating the inherited-from slot's own configuration. Regression test for resolve_inheritance merging the whole top-level CMS_PLACEHOLDER_CONF dict instead of the inherited slot's config, and for the resolved base config being updated in place. """ TEST_CONF = { "base": {"plugins": ["TextPlugin"], "name": "Base"}, "middle": {"inherit": "base", "limits": {"global": 5}}, "leaf": {"inherit": "middle", "name": "Leaf"}, } pristine = { "base": {"plugins": ["TextPlugin"], "name": "Base"}, "middle": {"inherit": "base", "limits": {"global": 5}}, "leaf": {"inherit": "middle", "name": "Leaf"}, } with override_placeholder_conf(CMS_PLACEHOLDER_CONF=TEST_CONF): # leaf inherits base's plugins through middle self.assertEqual(get_placeholder_conf("plugins", "leaf"), ["TextPlugin"]) # leaf inherits middle's own limits self.assertEqual(get_placeholder_conf("limits", "leaf"), {"global": 5}) # leaf keeps its own name self.assertEqual(get_placeholder_conf("name", "leaf"), "Leaf") # middle resolves to base's plugins plus its own limits self.assertEqual(get_placeholder_conf("plugins", "middle"), ["TextPlugin"]) self.assertEqual(get_placeholder_conf("name", "middle"), "Base") # base itself is untouched by the inheritance resolution self.assertEqual(get_placeholder_conf("name", "base"), "Base") self.assertIsNone(get_placeholder_conf("limits", "base")) # resolution must not write into the user's settings dict self.assertEqual(TEST_CONF, pristine) def test_placeholder_name_conf(self): page_en = create_page("page_en", "col_two.html", "en") placeholder_1 = page_en.get_placeholders("en").get(slot="col_left") placeholder_2 = Placeholder.objects.create(slot="col_left") placeholder_3 = Placeholder.objects.create(slot="no_name") TEST_CONF = { "col_left": { "name": "renamed left column", }, "col_two.html col_left": { "name": "left column", }, None: { "name": "fallback", }, } with override_placeholder_conf(CMS_PLACEHOLDER_CONF=TEST_CONF): self.assertEqual(force_str(placeholder_1.get_label()), "left column") self.assertEqual(force_str(placeholder_2.get_label()), "renamed left column") self.assertEqual(force_str(placeholder_3.get_label()), "fallback") del TEST_CONF[None] with override_placeholder_conf(CMS_PLACEHOLDER_CONF=TEST_CONF): self.assertEqual(force_str(placeholder_1.get_label()), "left column") self.assertEqual(force_str(placeholder_2.get_label()), "renamed left column") self.assertEqual(force_str(placeholder_3.get_label()), "No_Name") def test_placeholders_from_blocks_order(self): placeholders = _get_placeholder_slots("placeholder_tests/test_with_block.html") self.assertEqual(placeholders, ["one", "two", "three", "four"]) def test_placeholder_scanning_nested_super(self): placeholders = _get_placeholder_slots("placeholder_tests/nested_super_level1.html") self.assertEqual(sorted(placeholders), sorted(["level1", "level2", "level3", "level4"])) def test_placeholder_field_db_table(self): """ Test for leaking Model._meta.db_table monkeypatching on SQLite (#3891). """ example = Category.objects.create( name="category", parent=None, depth=1, ) self.assertEqual(example.description._get_attached_model(), Category) def test_placeholder_field_dynamic_slot_generation(self): instance = DynamicPlaceholderSlotExample.objects.create(char_1="slot1", char_2="slot2") self.assertEqual(instance.char_1, instance.placeholder_1.slot) self.assertEqual(instance.char_2, instance.placeholder_2.slot) def test_placeholder_field_dynamic_slot_update(self): instance = DynamicPlaceholderSlotExample.objects.create(char_1="slot1", char_2="slot2") # Plugin counts old_placeholder_1_plugin_count = len(instance.placeholder_1.get_plugins()) old_placeholder_2_plugin_count = len(instance.placeholder_2.get_plugins()) # Switch around the slot names instance.char_1, instance.char_2 = instance.char_2, instance.char_1 # Store the ids before save, to test that a new placeholder is NOT created. placeholder_1_id = instance.placeholder_1.pk placeholder_2_id = instance.placeholder_2.pk # Save instance instance.save() current_placeholder_1_plugin_count = len(instance.placeholder_1.get_plugins()) current_placeholder_2_plugin_count = len(instance.placeholder_2.get_plugins()) # Now test that the placeholder slots have changed self.assertEqual(instance.char_2, "slot1") self.assertEqual(instance.char_1, "slot2") # Test that a new placeholder was never created self.assertEqual(instance.placeholder_1.pk, placeholder_1_id) self.assertEqual(instance.placeholder_2.pk, placeholder_2_id) # And test the plugin counts remain the same self.assertEqual(old_placeholder_1_plugin_count, current_placeholder_1_plugin_count) self.assertEqual(old_placeholder_2_plugin_count, current_placeholder_2_plugin_count) def test_plugins_prepopulate(self): """Tests prepopulate placeholder configuration""" conf = { "col_left": { "default_plugins": [ { "plugin_type": "TextPlugin", "values": {"body": "

en default body 1

"}, }, { "plugin_type": "TextPlugin", "values": {"body": "

en default body 2

"}, }, ] }, } with override_placeholder_conf(CMS_PLACEHOLDER_CONF=conf): page = create_page("page_en", "col_two.html", "en") placeholder = page.get_placeholders("en").get(slot="col_left") context = SekizaiContext() context["request"] = self.get_request(language="en", page=page) # Our page should have "en default body 1" AND "en default body 2" content = _render_placeholder(placeholder, context) self.assertRegex(content, r"^

en default body 1

\s*

en default body 2

$") def test_circular_inheritance_in_placeholder_conf_raises(self): circular_conf = { "slot_a": {"inherit": "slot_b"}, "slot_b": {"inherit": "slot_c"}, "slot_c": {"inherit": "slot_a"}, } with self.assertRaises(ImproperlyConfigured): with override_placeholder_conf(CMS_PLACEHOLDER_CONF=circular_conf): # Attempt to access any slot to trigger the config resolution page = create_page("circular_page", "col_two.html", "en") placeholder = page.get_placeholders("en").get(slot="slot_a") # Accessing the label should trigger the circular inheritance detection force_str(placeholder.get_label()) def test_plugins_children_prepopulate(self): """ Validate a default textplugin with a nested default link plugin """ conf = { "col_left": { "default_plugins": [ { "plugin_type": "TextPlugin", "values": {"body": "

body %(_tag_child_1)s and %(_tag_child_2)s

"}, "children": [ { "plugin_type": "LinkPlugin", "values": {"name": "django", "external_link": "https://www.djangoproject.com/"}, }, { "plugin_type": "LinkPlugin", "values": {"name": "django-cms", "external_link": "https://www.django-cms.org"}, }, ], }, ] }, } with override_placeholder_conf(CMS_PLACEHOLDER_CONF=conf): page = create_page("page_en", "col_two.html", "en") placeholder = page.get_placeholders("en").get(slot="col_left") context = SekizaiContext() context["request"] = self.get_request(language="en", page=page) _render_placeholder(placeholder, context) plugins = placeholder.get_plugins_list() self.assertEqual(len(plugins), 3) self.assertEqual(plugins[0].plugin_type, "TextPlugin") self.assertEqual(plugins[1].plugin_type, "LinkPlugin") self.assertEqual(plugins[2].plugin_type, "LinkPlugin") self.assertTrue(plugins[1].parent == plugins[2].parent and plugins[1].parent == plugins[0]) def test_placeholder_pk_thousands_format(self): page = create_page("page", "nav_playground.html", "en") title = page.get_content_obj("en") for placeholder in page.get_placeholders("en"): title.placeholders.remove(placeholder) placeholder.pk += 1000 placeholder.save() title.placeholders.add(placeholder) page.reload() for placeholder in page.get_placeholders("en"): add_plugin(placeholder, "TextPlugin", "en", body="body") with self.settings(USE_THOUSAND_SEPARATOR=True, USE_L10N=True): # Superuser user = self.get_superuser() self.client.login( username=getattr(user, get_user_model().USERNAME_FIELD), password=getattr(user, get_user_model().USERNAME_FIELD), ) endpoint = get_object_edit_url(title) response = self.client.get(endpoint) for placeholder in page.get_placeholders("en"): self.assertContains(response, '"placeholder_id": "%s"' % placeholder.pk) self.assertNotContains( response, '"placeholder_id": "%s"' % format(placeholder.pk, ".", grouping=3, thousand_sep=",") ) self.assertNotContains( response, '"plugin_id": "%s"' % format(placeholder.pk, ".", grouping=3, thousand_sep=",") ) self.assertNotContains( response, '"clipboard": "%s"' % format(response.context["request"].toolbar.clipboard.pk, ".", grouping=3, thousand_sep=","), ) def test_placeholder_languages_model(self): """ Checks the retrieval of filled languages for a placeholder in a django model """ avail_langs = {"en", "de", "fr"} # Setup instance ex = Example1(char_1="one", char_2="two", char_3="tree", char_4="four") ex.save() ### # add the test plugin ### for lang in avail_langs: add_plugin(ex.placeholder, "EmptyPlugin", lang) # reload instance from database ex = Example1.objects.get(pk=ex.pk) # get languages langs = [lang["code"] for lang in ex.placeholder.get_filled_languages(Site.objects.get_current().pk)] self.assertEqual(avail_langs, set(langs)) def test_placeholder_languages_page(self): """ Checks the retrieval of filled languages for a placeholder in a django model """ avail_langs = {"en", "de", "fr"} # Setup instances page = create_page("test page", "col_two.html", "en") for lang in avail_langs: if lang != "en": create_page_content(lang, "test page %s" % lang, page) placeholder = page.get_placeholders("en").get(slot="col_sidebar") ### # add the test plugin ### for lang in avail_langs: add_plugin(placeholder, "EmptyPlugin", lang) # reload placeholder from database placeholder = page.get_placeholders("en").get(slot="col_sidebar") # get languages langs = [lang["code"] for lang in placeholder.get_filled_languages(Site.objects.get_current().pk)] self.assertEqual(avail_langs, set(langs)) @override_settings( TEMPLATE_LOADERS=( ( "django.template.loaders.cached.Loader", ( "django.template.loaders.filesystem.Loader", "django.template.loaders.app_directories.Loader", ), ), ) ) def test_cached_template_not_corrupted_by_placeholder_scan(self): """ This is the test for the low-level code that caused the bug: the placeholder scan corrupts the nodelist of the extends node, which is retained by the cached template loader, and future renders of that template will render the super block twice. """ nodelist = _get_nodelist(get_template("placeholder_tests/test_super_extends_2.html")) self.assertNotIn( "one", nodelist[0].blocks.keys(), "test_super_extends_1.html contains a block called 'one', " "but _2.html does not.", ) _get_placeholder_slots("placeholder_tests/test_super_extends_2.html") nodelist = _get_nodelist(get_template("placeholder_tests/test_super_extends_2.html")) self.assertNotIn( "one", nodelist[0].blocks.keys(), "test_super_extends_1.html still should not contain a block " "called 'one' after rescanning placeholders.", ) @override_settings( TEMPLATE_LOADERS=( ( "django.template.loaders.cached.Loader", ( "django.template.loaders.filesystem.Loader", "django.template.loaders.app_directories.Loader", ), ), ) ) def test_super_extends_not_corrupted_by_placeholder_scan(self): """ This is the test for the symptom of the bug: because the block context now contains two copies of the inherited block, that block will be executed twice, and if it adds content to {{block.super}}, that content will be added twice. """ template = get_template("placeholder_tests/test_super_extends_2.html") output = template.render({}) self.assertEqual(["Whee"], [o for o in output.split("\n") if "Whee" in o]) _get_placeholder_slots("placeholder_tests/test_super_extends_2.html") template = get_template("placeholder_tests/test_super_extends_2.html") output = template.render({}) self.assertEqual(["Whee"], [o for o in output.split("\n") if "Whee" in o]) def test_sets_source_when_title_is_created(self): """ This tests when a title is created, the source field for the created placeholders are set to that title. """ page = create_page("test page en", "col_two.html", "en") # check for en page_content_en = self.get_pagecontent_obj(page) self.assertQuerySetEqual( Placeholder.objects.get_for_obj(page_content_en), page_content_en.get_placeholders(), transform=lambda x: x, ordered=False, ) # check for another language = de page_content_de = create_page_content("de", "test page de", page) self.assertQuerySetEqual( Placeholder.objects.get_for_obj(page_content_de), page_content_de.get_placeholders(), transform=lambda x: x, ordered=False, ) def test_sets_source_when_title_is_copied(self): """ This tests when a title is copied, the source field for the created placeholders are set to the new title. """ page = create_page("source page", "col_two.html", "en") with self.login_user_context(self.get_superuser()): new_page = self.copy_page(page, page, position=1) page_content = self.get_pagecontent_obj(page) new_page_content = self.get_pagecontent_obj(new_page) page_content_plhs = Placeholder.objects.get_for_obj(page_content) new_page_content_plhs = Placeholder.objects.get_for_obj(new_page_content) self.assertEqual(page_content_plhs.count(), new_page_content_plhs.count()) def test_sets_source_when_external_object_is_rendered(self): """ This tests the implementation for external objects to use the {% placeholder %} template tag where placeholders are created with the source field set to the current object. """ from cms.utils.placeholder import get_declared_placeholders_for_obj poll = FancyPoll.objects.create(name="poll 1") # Go to the poll for the first time response = self.client.get(poll.get_absolute_url()) placeholders = Placeholder.objects.get_for_obj(poll) self.assertEqual(placeholders.count(), 2) # Now go to edit mode with self.login_user_context(self.get_superuser()): response = self.client.get(get_object_edit_url(poll)) placeholders = Placeholder.objects.get_for_obj(poll) self.assertEqual(placeholders.count(), 2) # Now go to structure mode with self.login_user_context(self.get_superuser()): response = self.client.get(get_object_structure_url(poll)) placeholders = Placeholder.objects.get_for_obj(poll) self.assertEqual(placeholders.count(), 2) declared_placeholders = get_declared_placeholders_for_obj(poll) self.assertEqual(len(declared_placeholders), 2) for placeholder in declared_placeholders: self.assertContains( response, '