dynamicweb/cms/tests/test_nested_plugins.py

1233 lines
58 KiB
Python

from djangocms_text.models import Text
from cms.api import add_plugin, create_page
from cms.models import Page
from cms.models.placeholdermodel import Placeholder
from cms.models.pluginmodel import CMSPlugin
from cms.tests.test_plugins import PluginsTestBaseCase
from cms.utils.plugins import copy_plugins_to_placeholder
class NestedPluginsTestCase(PluginsTestBaseCase):
def compare_plugin_tree(self, tree, placeholder):
counter = 1
plugins = placeholder.get_plugins()
for plugin, data in zip(plugins, tree, strict=False):
msg = 'Expected %s %s. Got %s instead.'
self.assertEqual(plugin.pk, data[0], msg % ('id', data[0], plugin.pk))
self.assertEqual(plugin.position, counter, msg % ('position', counter, plugin.position))
self.assertEqual(plugin.parent_id, data[1], msg % ('parent', data[1], plugin.parent_id))
counter += 1
def copy_placeholders_and_check_results(self, placeholders):
"""
This function is not itself a test; rather, it can be used by any test
that has created placeholders. It will check that whatever the plugin
structure in the placeholder, it will be copied accurately when they are
copied.
placeholders is a list of placeholders
"""
for original_placeholder in placeholders:
# get the plugins
original_plugins = original_placeholder.get_plugins()
# copy them to a new placeholder
copied_placeholder = Placeholder.objects.create(slot=original_placeholder.slot)
copy_plugins_to_placeholder(
original_placeholder.get_plugins(),
placeholder=copied_placeholder,
)
copied_plugins = copied_placeholder.get_plugins()
# we should find the same number of plugins in both placeholders
self.assertEqual(
original_plugins.count(),
copied_plugins.count()
)
# quick check: make sure the two querysets match:
for original, copy in zip(original_plugins, copied_plugins, strict=False):
self.assertEqual(
Text.objects.get(id=original.id).body,
Text.objects.get(id=copy.id).body
)
self.assertEqual(original.position, copy.position)
self.assertEqual(
original._get_descendants_count(),
copy._get_descendants_count()
)
# just in case the test method that called us wants it:
return copied_placeholder
def test_plugin_deep_nesting_and_copying(self):
"""
Create a deeply-nested plugin structure, tests its properties, and tests
that it is copied accurately when the placeholder containing them is
copied.
The structure below isn't arbitrary, but has been designed to test
various conditions, including:
* nodes four levels deep
* multiple successive level increases
* multiple successive level decreases
* successive nodes on the same level followed by level changes
* multiple level decreases between successive nodes
* siblings with and without children
* nodes and branches added to the tree out of sequence
First we create the structure:
11
1
2
12
4
10
8
3
9
5
6
7
13
14
and then we move it all around.
"""
placeholder = Placeholder(slot="some_slot")
placeholder.save() # a good idea, if not strictly necessary
# plugin in placeholder
plugin_1 = add_plugin(
placeholder, "TextPlugin", "en",
body="01"
)
# child of plugin_1
plugin_2 = add_plugin(
placeholder, "TextPlugin", "en",
body="02",
target=plugin_1,
)
# plugin_2 should be plugin_1's only child
# for a single item we use assertSequenceEqual
self.assertSequenceEqual(
CMSPlugin.objects.get(id=plugin_1.pk).get_children(),
[CMSPlugin.objects.get(id=plugin_2.pk)]
)
# create a second child of plugin_1
plugin_3 = add_plugin(
placeholder, "TextPlugin", "en",
body="03",
target=plugin_1
)
# plugin_2 & plugin_3 should be plugin_1's children
# for multiple items we use assertSequenceEqual, because
# assertSequenceEqual may re-order the list without warning
self.assertSequenceEqual(
CMSPlugin.objects.get(id=plugin_1.pk).get_children(),
[
CMSPlugin.objects.get(id=plugin_2.pk),
CMSPlugin.objects.get(id=plugin_3.pk),
])
# child of plugin_2
plugin_4 = add_plugin(
placeholder, "TextPlugin", "en",
body="04",
target=plugin_2
)
# plugin_4 should be plugin_2's child
self.assertSequenceEqual(
CMSPlugin.objects.get(id=plugin_2.pk).get_children(),
[CMSPlugin.objects.get(id=plugin_4.pk)])
# 2,3 & 4 should be descendants of 1
self.assertSequenceEqual(
CMSPlugin.objects.get(id=plugin_1.pk).get_descendants(),
[
# note path ordering of MP reflected here:
CMSPlugin.objects.get(id=plugin_2.pk),
CMSPlugin.objects.get(id=plugin_4.pk),
CMSPlugin.objects.get(id=plugin_3.pk),
],
)
# create a second root plugin
plugin_5 = add_plugin(placeholder, "TextPlugin", "en", body="05")
# child of plugin_5
plugin_6 = add_plugin(
placeholder, "TextPlugin", "en",
body="06",
target=plugin_5
)
# plugin_6 should be plugin_5's child
self.assertSequenceEqual(
CMSPlugin.objects.get(id=plugin_5.pk).get_children(),
[CMSPlugin.objects.get(id=plugin_6.pk)])
# child of plugin_5
plugin_7 = add_plugin(
placeholder, "TextPlugin", "en",
body="07",
target=plugin_5
)
# plugin_7 should be plugin_5's child
self.assertSequenceEqual(
CMSPlugin.objects.get(id=plugin_5.pk).get_children(),
[
CMSPlugin.objects.get(id=plugin_6.pk),
CMSPlugin.objects.get(id=plugin_7.pk)
])
# 6 & 7 should be descendants of 5
self.assertSequenceEqual(
CMSPlugin.objects.get(id=plugin_5.pk).get_descendants(),
[
CMSPlugin.objects.get(id=plugin_6.pk),
CMSPlugin.objects.get(id=plugin_7.pk),
])
# another child of plugin_2
plugin_2 = self.reload(plugin_2)
plugin_8 = add_plugin(
placeholder, "TextPlugin", "en",
body="08",
target=plugin_2
)
# plugin_4 should be plugin_2's child
self.assertSequenceEqual(
CMSPlugin.objects.get(id=plugin_2.pk).get_children(),
[
CMSPlugin.objects.get(id=plugin_4.pk),
CMSPlugin.objects.get(id=plugin_8.pk),
])
# child of plugin_3
plugin_9 = add_plugin(
placeholder, "TextPlugin", "en",
body="09",
target=self.reload(plugin_3),
)
# plugin_9 should be plugin_3's child
self.assertSequenceEqual(
CMSPlugin.objects.get(id=plugin_3.pk).get_children(),
[CMSPlugin.objects.get(id=plugin_9.pk)])
# child of plugin_4
plugin_10 = add_plugin(
placeholder, "TextPlugin", "en",
body="10",
target=self.reload(plugin_4)
)
# plugin_10 should be plugin_4's child
self.assertSequenceEqual(
CMSPlugin.objects.get(id=plugin_4.pk).get_children(),
[CMSPlugin.objects.get(id=plugin_10.pk)])
original_plugins = placeholder.get_plugins()
self.assertEqual(original_plugins.count(), 10)
# elder sibling of plugin_1
plugin_11 = add_plugin(
placeholder, "TextPlugin", "en",
body="11",
target=plugin_1,
position="left"
)
self.assertSequenceEqual(
CMSPlugin.objects.get(id=plugin_1.pk).get_children(),
[
CMSPlugin.objects.get(id=plugin_2.pk),
CMSPlugin.objects.get(id=plugin_3.pk)
])
# elder sibling of plugin_4
plugin_12 = add_plugin(
placeholder, "TextPlugin", "en",
body="12",
target=self.reload(plugin_4),
position="left"
)
self.assertSequenceEqual(
CMSPlugin.objects.get(id=plugin_2.pk).get_children(),
[
CMSPlugin.objects.get(id=plugin_12.pk),
CMSPlugin.objects.get(id=plugin_4.pk),
CMSPlugin.objects.get(id=plugin_8.pk)
])
# younger sibling of plugin_7
plugin_13 = add_plugin(
placeholder, "TextPlugin", "en",
body="13",
target=self.reload(plugin_7),
position="right"
)
self.assertSequenceEqual(
CMSPlugin.objects.get(id=plugin_5.pk).get_children(),
[
CMSPlugin.objects.get(id=plugin_6.pk),
CMSPlugin.objects.get(id=plugin_7.pk),
CMSPlugin.objects.get(id=plugin_13.pk)
])
# new sibling of plugin_5
plugin_14 = add_plugin(
placeholder, "TextPlugin", "en", body="14"
)
self.assertSequenceEqual(
CMSPlugin.objects.filter(parent__isnull=True),
[
CMSPlugin.objects.get(id=plugin_11.pk),
CMSPlugin.objects.get(id=plugin_1.pk),
CMSPlugin.objects.get(id=plugin_5.pk),
CMSPlugin.objects.get(id=plugin_14.pk)
])
tree = [
(plugin_11.pk, None),
(plugin_1.pk, None),
(plugin_2.pk, plugin_1.pk),
(plugin_12.pk, plugin_2.pk),
(plugin_4.pk, plugin_2.pk),
(plugin_10.pk, plugin_4.pk),
(plugin_8.pk, plugin_2.pk),
(plugin_3.pk, plugin_1.pk),
(plugin_9.pk, plugin_3.pk),
(plugin_5.pk, None),
(plugin_6.pk, plugin_5.pk),
(plugin_7.pk, plugin_5.pk),
(plugin_13.pk, plugin_5.pk),
(plugin_14.pk, None),
]
self.copy_placeholders_and_check_results([placeholder])
self.compare_plugin_tree(tree, placeholder)
# now let's move plugins around in the tree
# move plugin_2 before plugin_11
tree = [
(plugin_2.pk, None),
(plugin_12.pk, plugin_2.pk),
(plugin_4.pk, plugin_2.pk),
(plugin_10.pk, plugin_4.pk),
(plugin_8.pk, plugin_2.pk),
(plugin_11.pk, None),
(plugin_1.pk, None),
(plugin_3.pk, plugin_1.pk),
(plugin_9.pk, plugin_3.pk),
(plugin_5.pk, None),
(plugin_6.pk, plugin_5.pk),
(plugin_7.pk, plugin_5.pk),
(plugin_13.pk, plugin_5.pk),
(plugin_14.pk, None),
]
placeholder.move_plugin(plugin=self.reload(plugin_2), target_position=1)
self.compare_plugin_tree(tree, placeholder)
self.copy_placeholders_and_check_results([placeholder])
# move plugin_6 after plugin_7
tree = [
(plugin_2.pk, None),
(plugin_12.pk, plugin_2.pk),
(plugin_4.pk, plugin_2.pk),
(plugin_10.pk, plugin_4.pk),
(plugin_8.pk, plugin_2.pk),
(plugin_11.pk, None),
(plugin_1.pk, None),
(plugin_3.pk, plugin_1.pk),
(plugin_9.pk, plugin_3.pk),
(plugin_5.pk, None),
(plugin_7.pk, plugin_5.pk),
(plugin_6.pk, plugin_5.pk),
(plugin_13.pk, plugin_5.pk),
(plugin_14.pk, None),
]
plugin_7 = self.reload(plugin_7)
placeholder.move_plugin(
plugin=self.reload(plugin_6),
target_position=plugin_7.position,
target_plugin=plugin_7.parent,
)
self.compare_plugin_tree(tree, placeholder)
self.copy_placeholders_and_check_results([placeholder])
# move plugin_3 before plugin_2
tree = [
(plugin_3.pk, None),
(plugin_9.pk, plugin_3.pk),
(plugin_2.pk, None),
(plugin_12.pk, plugin_2.pk),
(plugin_4.pk, plugin_2.pk),
(plugin_10.pk, plugin_4.pk),
(plugin_8.pk, plugin_2.pk),
(plugin_11.pk, None),
(plugin_1.pk, None),
(plugin_5.pk, None),
(plugin_7.pk, plugin_5.pk),
(plugin_6.pk, plugin_5.pk),
(plugin_13.pk, plugin_5.pk),
(plugin_14.pk, None),
]
placeholder.move_plugin(
plugin=self.reload(plugin_3),
target_position=1,
target_plugin=None,
)
self.compare_plugin_tree(tree, placeholder)
self.copy_placeholders_and_check_results([placeholder])
# make plugin_3 plugin_2's first-child
tree = [
(plugin_2.pk, None),
(plugin_3.pk, plugin_2.pk),
(plugin_9.pk, plugin_3.pk),
(plugin_12.pk, plugin_2.pk),
(plugin_4.pk, plugin_2.pk),
(plugin_10.pk, plugin_4.pk),
(plugin_8.pk, plugin_2.pk),
(plugin_11.pk, None),
(plugin_1.pk, None),
(plugin_5.pk, None),
(plugin_7.pk, plugin_5.pk),
(plugin_6.pk, plugin_5.pk),
(plugin_13.pk, plugin_5.pk),
(plugin_14.pk, None),
]
placeholder.move_plugin(
plugin=self.reload(plugin_3),
target_position=2,
target_plugin=self.reload(plugin_2),
)
self.compare_plugin_tree(tree, placeholder)
self.copy_placeholders_and_check_results([placeholder])
# make plugin_7 plugin_2's first-child
tree = [
(plugin_2.pk, None),
(plugin_7.pk, plugin_2.pk),
(plugin_3.pk, plugin_2.pk),
(plugin_9.pk, plugin_3.pk),
(plugin_12.pk, plugin_2.pk),
(plugin_4.pk, plugin_2.pk),
(plugin_10.pk, plugin_4.pk),
(plugin_8.pk, plugin_2.pk),
(plugin_11.pk, None),
(plugin_1.pk, None),
(plugin_5.pk, None),
(plugin_6.pk, plugin_5.pk),
(plugin_13.pk, plugin_5.pk),
(plugin_14.pk, None),
]
plugin_2 = self.reload(plugin_2)
placeholder.move_plugin(
plugin=self.reload(plugin_7),
target_position=plugin_2.position + 1,
target_plugin=plugin_2,
)
self.compare_plugin_tree(tree, placeholder)
self.copy_placeholders_and_check_results([placeholder])
def test_nested_plugin_on_page(self):
"""
Validate a textplugin with a nested link plugin
mptt values are correctly showing a parent child relationship
of a nested plugin
"""
with self.settings(CMS_PERMISSION=False):
# setup page 1
page_one = create_page("Three Placeholder", "col_three.html", "en",
position="last-child", in_navigation=True)
page_one_ph_two = page_one.get_placeholders("en").get(slot="col_left")
# add a plugin
pre_nesting_body = "<p>the nested text plugin with a link inside</p>"
text_plugin = add_plugin(page_one_ph_two, "TextPlugin", "en", body=pre_nesting_body)
# prepare nesting plugin
page_one_ph_two = self.reload(page_one_ph_two)
text_plugin = self.reload(text_plugin)
link_plugin = add_plugin(page_one_ph_two, "LinkPlugin", "en", target=text_plugin)
link_plugin.name = "django-cms Link"
link_plugin.external_link = "https://www.django-cms.org"
# reloading needs to be done after every save
link_plugin = self.reload(link_plugin)
text_plugin = self.reload(text_plugin)
# mptt related insertion correct?
msg = "parent plugin right is not updated, child not inserted correctly"
self.assertTrue(link_plugin.position == text_plugin.position + 1, msg=msg)
msg = "link has no parent"
self.assertFalse(link_plugin.parent is None, msg=msg)
# add the link plugin to the body
# emulate the editor in admin that adds some txt for the nested plugin
in_txt = '<img id="plugin_obj_%s" title="Link" alt="Link" src="/static/cms/img/icons/plugins/link.png">'
nesting_body = f"{text_plugin.body}<p>{in_txt % (link_plugin.id)}</p>"
text_plugin.body = nesting_body
text_plugin.save()
text_plugin = self.reload(text_plugin)
# none of the descendants should have a placeholder other then my own one
self.assertEqual(text_plugin.get_descendants().exclude(placeholder=text_plugin.placeholder).count(), 0)
post_add_plugin_count = CMSPlugin.objects.count()
self.assertEqual(post_add_plugin_count, 2)
def test_copy_page_nested_plugin(self):
"""
Test to verify that page copy with a nested plugin works
page one - 3 placeholder
col_sidebar: 1 text plugin
col_left: 1 text plugin with nested link plugin
col_right: no plugin
page two (copy target)
Verify copied page, placeholders, plugins and body text
"""
with self.settings(CMS_PERMISSION=False):
# setup page 1
page_one = create_page("Three Placeholder", "col_three.html", "en",
position="last-child", in_navigation=True)
page_one_ph_one = page_one.get_placeholders("en").get(slot="col_sidebar")
page_one_ph_two = page_one.get_placeholders("en").get(slot="col_left")
page_one.get_placeholders("en").get(slot="col_right")
# add the text plugin to placeholder one
text_plugin_en = add_plugin(page_one_ph_one, "TextPlugin", "en", body="Hello World")
self.assertEqual(text_plugin_en.id, CMSPlugin.objects.all()[0].id)
self.assertEqual(text_plugin_en.get_children().count(), 0)
pre_add_plugin_count = CMSPlugin.objects.count()
self.assertEqual(pre_add_plugin_count, 1)
###
# add a plugin to placeholder two
###
pre_nesting_body = "<p>the nested text plugin with a link inside</p>"
text_plugin_two = add_plugin(page_one_ph_two, "TextPlugin", "en", body=pre_nesting_body)
text_plugin_two = self.reload(text_plugin_two)
# prepare nesting plugin
page_one_ph_two = self.reload(page_one_ph_two)
text_plugin_two = self.reload(text_plugin_two)
link_plugin = add_plugin(page_one_ph_two, "LinkPlugin", "en", target=text_plugin_two)
link_plugin.name = "django-cms Link"
link_plugin.external_link = "https://www.django-cms.org"
link_plugin.parent = text_plugin_two
link_plugin.save()
link_plugin = self.reload(link_plugin)
text_plugin_two = self.reload(text_plugin_two)
in_txt = """<cms-plugin id="%s" title="Link" alt="Link"></cms-plugin>"""
nesting_body = f"{text_plugin_two.body}<p>{in_txt % (link_plugin.id)}</p>"
# emulate the editor in admin that adds some txt for the nested plugin
text_plugin_two.body = nesting_body
text_plugin_two.save()
text_plugin_two = self.reload(text_plugin_two)
# the link is attached as a child?
self.assertEqual(text_plugin_two.get_children().count(), 1)
post_add_plugin_count = CMSPlugin.objects.count()
self.assertEqual(post_add_plugin_count, 3)
page_one.save()
# get the plugins from the original page
page_one = self.reload(page_one)
page_one_ph_one = page_one.get_placeholders("en").get(slot="col_sidebar")
page_one_ph_two = page_one.get_placeholders("en").get(slot="col_left")
page_one_ph_three = page_one.get_placeholders("en").get(slot="col_right")
# verify that the plugins got created
org_placeholder_one_plugins = page_one_ph_one.get_plugins()
self.assertEqual(len(org_placeholder_one_plugins), 1)
org_placeholder_two_plugins = page_one_ph_two.get_plugins()
self.assertEqual(len(org_placeholder_two_plugins), 2)
org_placeholder_three_plugins = page_one_ph_three.get_plugins()
self.assertEqual(len(org_placeholder_three_plugins), 0)
self.assertEqual(page_one.get_placeholders("en").count(), 3)
placeholder_count = Placeholder.objects.count()
self.assertEqual(placeholder_count, 3)
self.assertEqual(CMSPlugin.objects.count(), 3)
##
# setup page_copy_target page
##
page_copy_target = create_page("Three Placeholder - page copy target", "col_three.html", "en",
position="last-child", in_navigation=True)
all_page_count = Page.objects.count()
pre_copy_placeholder_count = Placeholder.objects.count()
self.assertEqual(pre_copy_placeholder_count, 6)
# copy the page
superuser = self.get_superuser()
with self.login_user_context(superuser):
page_two = self.copy_page(page_one, page_copy_target)
# validate the expected pages,placeholders,plugins,pluginbodies
after_copy_page_plugin_count = CMSPlugin.objects.count()
self.assertEqual(after_copy_page_plugin_count, 6)
# check the amount of copied stuff
after_copy_page_count = Page.objects.count()
after_copy_placeholder_count = Placeholder.objects.count()
self.assertGreater(after_copy_page_count, all_page_count, "no new page after copy")
self.assertGreater(after_copy_page_plugin_count, post_add_plugin_count, "plugin count is not grown")
self.assertGreater(after_copy_placeholder_count, pre_copy_placeholder_count,
"placeholder count is not grown")
self.assertEqual(after_copy_page_count, 3, "no new page after copy")
# original placeholder
page_one = self.reload(page_one)
page_one_ph_one = page_one.get_placeholders("en").get(slot="col_sidebar")
page_one_ph_two = page_one.get_placeholders("en").get(slot="col_left")
page_one_ph_three = page_one.get_placeholders("en").get(slot="col_right")
# check if there are multiple pages assigned to this placeholders
found_page = page_one_ph_one.page if page_one_ph_one else None
self.assertEqual(found_page, page_one)
found_page = page_one_ph_two.page if page_one_ph_two else None
self.assertEqual(found_page, page_one)
found_page = page_one_ph_three.page if page_one_ph_three else None
self.assertEqual(found_page, page_one)
page_two = self.reload(page_two)
page_two_ph_one = page_two.get_placeholders("en").get(slot="col_sidebar")
page_two_ph_two = page_two.get_placeholders("en").get(slot="col_left")
page_two_ph_three = page_two.get_placeholders("en").get(slot="col_right")
# check if there are multiple pages assigned to this placeholders
found_page = page_two_ph_one.page if page_two_ph_one else None
self.assertEqual(found_page, page_two)
found_page = page_two_ph_two.page if page_two_ph_two else None
self.assertEqual(found_page, page_two)
found_page = page_two_ph_three.page if page_two_ph_three else None
self.assertEqual(found_page, page_two)
# check the stored placeholders org vs copy
msg = f'placehoder ids copy:{page_two_ph_one.pk} org:{page_one_ph_one.pk} copied page {page_two.pk} are identical - tree broken'
self.assertNotEqual(page_two_ph_one.pk, page_one_ph_one.pk, msg)
msg = f'placehoder ids copy:{page_two_ph_two.pk} org:{page_one_ph_two.pk} copied page {page_two.pk} are identical - tree broken'
self.assertNotEqual(page_two_ph_two.pk, page_one_ph_two.pk, msg)
msg = f'placehoder ids copy:{page_two_ph_three.pk} org:{page_one_ph_three.pk} copied page {page_two.pk} are identical - tree broken'
self.assertNotEqual(page_two_ph_three.pk, page_one_ph_three.pk, msg)
# get the plugins from the original page
org_placeholder_one_plugins = page_one_ph_one.get_plugins()
self.assertEqual(len(org_placeholder_one_plugins), 1)
org_placeholder_two_plugins = page_one_ph_two.get_plugins()
self.assertEqual(len(org_placeholder_two_plugins), 2)
org_placeholder_three_plugins = page_one_ph_three.get_plugins()
self.assertEqual(len(org_placeholder_three_plugins), 0)
# get the plugins from the copied page
copied_placeholder_one_plugins = page_two_ph_one.get_plugins()
self.assertEqual(len(copied_placeholder_one_plugins), 1)
copied_placeholder_two_plugins = page_two_ph_two.get_plugins()
self.assertEqual(len(copied_placeholder_two_plugins), 2)
copied_placeholder_three_plugins = page_two_ph_three.get_plugins()
self.assertEqual(len(copied_placeholder_three_plugins), 0)
# verify the plugins got copied
# placeholder 1
count_plugins_copied = len(copied_placeholder_one_plugins)
count_plugins_org = len(org_placeholder_one_plugins)
msg = f"plugin count {count_plugins_copied} {count_plugins_org} for placeholder one not equal"
self.assertEqual(count_plugins_copied, count_plugins_org, msg)
# placeholder 2
count_plugins_copied = len(copied_placeholder_two_plugins)
count_plugins_org = len(org_placeholder_two_plugins)
msg = f"plugin count {count_plugins_copied} {count_plugins_org} for placeholder two not equal"
self.assertEqual(count_plugins_copied, count_plugins_org, msg)
# placeholder 3
count_plugins_copied = len(copied_placeholder_three_plugins)
count_plugins_org = len(org_placeholder_three_plugins)
msg = f"plugin count {count_plugins_copied} {count_plugins_org} for placeholder three not equal"
self.assertEqual(count_plugins_copied, count_plugins_org, msg)
# verify the body of text plugin with nested link plugin
# org to copied
org_nested_text_plugin = None
# do this iteration to find the real text plugin with the attached link
# the inheritance mechanism for the cmsplugins works through
# (tuple)get_plugin_instance()
for x in org_placeholder_two_plugins:
if x.plugin_type == "TextPlugin":
instance = x.get_plugin_instance()[0]
if instance.body.startswith(pre_nesting_body):
org_nested_text_plugin = instance
break
copied_nested_text_plugin = None
for x in copied_placeholder_two_plugins:
if x.plugin_type == "TextPlugin":
instance = x.get_plugin_instance()[0]
if instance.body.startswith(pre_nesting_body):
copied_nested_text_plugin = instance
break
msg = "original nested text plugin not found"
self.assertNotEqual(org_nested_text_plugin, None, msg=msg)
msg = "copied nested text plugin not found"
self.assertNotEqual(copied_nested_text_plugin, None, msg=msg)
# get the children ids of the texplugin with a nested link
# to check if the body of the text is generated correctly
org_link_child_plugin = org_nested_text_plugin.get_children()[0]
copied_link_child_plugin = copied_nested_text_plugin.get_children()[0]
# validate the textplugin body texts
msg = "org plugin and copied plugin are the same"
self.assertTrue(org_link_child_plugin.id != copied_link_child_plugin.id, msg)
needle = "%s"
msg = "child plugin id differs to parent in body"
# linked child is in body
self.assertTrue(org_nested_text_plugin.body.find(needle % (org_link_child_plugin.id)) != -1, msg)
msg = "copy: child plugin id differs to parent in body"
self.assertTrue(copied_nested_text_plugin.body.find(needle % (copied_link_child_plugin.id)) != -1, msg)
# really nothing else
msg = "child link plugin id differs to parent body"
self.assertTrue(org_nested_text_plugin.body.find(needle % (copied_link_child_plugin.id)) == -1, msg)
msg = "copy: child link plugin id differs to parent body"
self.assertTrue(copied_nested_text_plugin.body.find(needle % (org_link_child_plugin.id)) == -1, msg)
# now reverse lookup the placeholders from the plugins
org_placeholder = org_link_child_plugin.placeholder
copied_placeholder = copied_link_child_plugin.placeholder
msg = "placeholder of the original plugin and copied plugin are the same"
ok = (org_placeholder.id != copied_placeholder.id)
self.assertTrue(ok, msg)
def test_copy_page_nested_plugin_moved_parent_plugin(self):
"""
Test to verify that page copy with a nested plugin works
when a plugin with child got moved to another placeholder
page one - 3 placeholder
col_sidebar:
1 text plugin
col_left: 1 text plugin with nested link plugin
col_right: no plugin
page two (copy target)
step2: move the col_left text plugin to col_right
col_sidebar:
1 text plugin
col_left: no plugin
col_right: 1 text plugin with nested link plugin
verify the copied page structure
"""
with self.settings(CMS_PERMISSION=False):
# setup page 1
page_one = create_page("Three Placeholder", "col_three.html", "en",
position="last-child", in_navigation=True)
page_one_ph_one = page_one.get_placeholders("en").get(slot="col_sidebar")
page_one_ph_two = page_one.get_placeholders("en").get(slot="col_left")
page_one.get_placeholders("en").get(slot="col_right")
# add the text plugin to placeholder one
text_plugin_en = add_plugin(page_one_ph_one, "TextPlugin", "en", body="Hello World")
self.assertEqual(text_plugin_en.id, CMSPlugin.objects.all()[0].id)
self.assertEqual(text_plugin_en.get_children().count(), 0)
pre_add_plugin_count = CMSPlugin.objects.count()
self.assertEqual(pre_add_plugin_count, 1)
# add a plugin to placeholder twho
pre_nesting_body = "<p>the nested text plugin with a link inside</p>"
text_plugin_two = add_plugin(page_one_ph_two, "TextPlugin", "en", body=pre_nesting_body)
text_plugin_two = self.reload(text_plugin_two)
# prepare nesting plugin
page_one_ph_two = self.reload(page_one_ph_two)
text_plugin_two = self.reload(text_plugin_two)
link_plugin = add_plugin(page_one_ph_two, "LinkPlugin", "en", target=text_plugin_two)
link_plugin.name = "django-cms Link"
link_plugin.external_link = "https://www.django-cms.org"
link_plugin.parent = text_plugin_two
link_plugin.save()
# reload after every save
link_plugin = self.reload(link_plugin)
text_plugin_two = self.reload(text_plugin_two)
in_txt = """<cms-plugin id="%s" title="Link" alt="Link"></cms-plugin>"""
nesting_body = f"{text_plugin_two.body}<p>{in_txt % (link_plugin.id)}</p>"
# emulate the editor in admin that adds some txt for the nested plugin
text_plugin_two.body = nesting_body
text_plugin_two.save()
text_plugin_two = self.reload(text_plugin_two)
# the link is attached as a child?
self.assertEqual(text_plugin_two.get_children().count(), 1)
post_add_plugin_count = CMSPlugin.objects.count()
self.assertEqual(post_add_plugin_count, 3)
page_one.save()
# get the plugins from the original page
page_one = self.reload(page_one)
page_one_ph_one = page_one.get_placeholders("en").get(slot="col_sidebar")
page_one_ph_two = page_one.get_placeholders("en").get(slot="col_left")
page_one_ph_three = page_one.get_placeholders("en").get(slot="col_right")
# verify the plugins got created
org_placeholder_one_plugins = page_one_ph_one.get_plugins()
self.assertEqual(len(org_placeholder_one_plugins), 1)
org_placeholder_two_plugins = page_one_ph_two.get_plugins()
self.assertEqual(len(org_placeholder_two_plugins), 2)
org_placeholder_three_plugins = page_one_ph_three.get_plugins()
self.assertEqual(len(org_placeholder_three_plugins), 0)
self.assertEqual(page_one.get_placeholders("en").count(), 3)
placeholder_count = Placeholder.objects.count()
self.assertEqual(placeholder_count, 3)
self.assertEqual(CMSPlugin.objects.count(), 3)
# setup page_copy_target
page_copy_target = create_page("Three Placeholder - page copy target", "col_three.html", "en",
position="last-child", in_navigation=True)
all_page_count = Page.objects.count()
pre_copy_placeholder_count = Placeholder.objects.count()
self.assertEqual(pre_copy_placeholder_count, 6)
superuser = self.get_superuser()
with self.login_user_context(superuser):
# now move the parent text plugin to another placeholder
post_data = {
'placeholder_id': page_one_ph_three.id,
'plugin_id': text_plugin_two.id,
'target_language': 'en',
'target_position': page_one_ph_three.get_next_plugin_position('en', insert_order='last'),
'plugin_parent': '',
}
edit_url = self.get_move_plugin_uri(text_plugin_two)
response = self.client.post(edit_url, post_data)
self.assertEqual(response.status_code, 200)
# check if the plugin got moved
page_one = self.reload(page_one)
self.reload(text_plugin_two)
page_one_ph_one = page_one.get_placeholders("en").get(slot="col_sidebar")
page_one_ph_two = page_one.get_placeholders("en").get(slot="col_left")
page_one_ph_three = page_one.get_placeholders("en").get(slot="col_right")
org_placeholder_one_plugins = page_one_ph_one.get_plugins()
self.assertEqual(len(org_placeholder_one_plugins), 1)
org_placeholder_two_plugins = page_one_ph_two.get_plugins()
# the plugin got moved and child got moved
self.assertEqual(len(org_placeholder_two_plugins), 0)
org_placeholder_three_plugins = page_one_ph_three.get_plugins()
self.assertEqual(len(org_placeholder_three_plugins), 2)
# copy the page
page_two = self.copy_page(page_one, page_copy_target)
# validate the expected pages,placeholders,plugins,pluginbodies
after_copy_page_plugin_count = CMSPlugin.objects.count()
self.assertEqual(after_copy_page_plugin_count, 6)
after_copy_page_count = Page.objects.count()
after_copy_placeholder_count = Placeholder.objects.count()
self.assertGreater(after_copy_page_count, all_page_count, "no new page after copy")
self.assertGreater(after_copy_page_plugin_count, post_add_plugin_count, "plugin count is not grown")
self.assertGreater(after_copy_placeholder_count, pre_copy_placeholder_count,
"placeholder count is not grown")
self.assertEqual(after_copy_page_count, 3, "no new page after copy")
# validate the structure
# original placeholder
page_one = self.reload(page_one)
page_one_ph_one = page_one.get_placeholders("en").get(slot="col_sidebar")
page_one_ph_two = page_one.get_placeholders("en").get(slot="col_left")
page_one_ph_three = page_one.get_placeholders("en").get(slot="col_right")
# check if there are multiple pages assigned to this placeholders
found_page = page_one_ph_one.page if page_one_ph_one else None
self.assertEqual(found_page, page_one)
found_page = page_one_ph_two.page if page_one_ph_two else None
self.assertEqual(found_page, page_one)
found_page = page_one_ph_three.page if page_one_ph_three else None
self.assertEqual(found_page, page_one)
page_two = self.reload(page_two)
page_two_ph_one = page_two.get_placeholders("en").get(slot="col_sidebar")
page_two_ph_two = page_two.get_placeholders("en").get(slot="col_left")
page_two_ph_three = page_two.get_placeholders("en").get(slot="col_right")
# check if there are multiple pages assigned to this placeholders
found_page = page_two_ph_one.page if page_two_ph_one else None
self.assertEqual(found_page, page_two)
found_page = page_two_ph_two.page if page_two_ph_two else None
self.assertEqual(found_page, page_two)
found_page = page_two_ph_three.page if page_two_ph_three else None
self.assertEqual(found_page, page_two)
# check the stored placeholders org vs copy
msg = f'placehoder ids copy:{page_two_ph_one.pk} org:{page_one_ph_one.pk} copied page {page_two.pk} are identical - tree broken'
self.assertNotEqual(page_two_ph_one.pk, page_one_ph_one.pk, msg)
msg = f'placehoder ids copy:{page_two_ph_two.pk} org:{page_one_ph_two.pk} copied page {page_two.pk} are identical - tree broken'
self.assertNotEqual(page_two_ph_two.pk, page_one_ph_two.pk, msg)
msg = f'placehoder ids copy:{page_two_ph_three.pk} org:{page_one_ph_three.pk} copied page {page_two.pk} are identical - tree broken'
self.assertNotEqual(page_two_ph_three.pk, page_one_ph_three.pk, msg)
# get the plugins from the original page
org_placeholder_one_plugins = page_one_ph_one.get_plugins()
self.assertEqual(len(org_placeholder_one_plugins), 1)
org_placeholder_two_plugins = page_one_ph_two.get_plugins()
self.assertEqual(len(org_placeholder_two_plugins), 0)
org_placeholder_three_plugins = page_one_ph_three.get_plugins()
self.assertEqual(len(org_placeholder_three_plugins), 2)
# get the plugins from the copied page
copied_placeholder_one_plugins = page_two_ph_one.get_plugins()
self.assertEqual(len(copied_placeholder_one_plugins), 1)
copied_placeholder_two_plugins = page_two_ph_two.get_plugins()
self.assertEqual(len(copied_placeholder_two_plugins), 0)
copied_placeholder_three_plugins = page_two_ph_three.get_plugins()
self.assertEqual(len(copied_placeholder_three_plugins), 2)
# verify the plugins got copied
# placeholder 1
count_plugins_copied = len(copied_placeholder_one_plugins)
count_plugins_org = len(org_placeholder_one_plugins)
msg = f"plugin count {count_plugins_copied} {count_plugins_org} for placeholder one not equal"
self.assertEqual(count_plugins_copied, count_plugins_org, msg)
# placeholder 2
count_plugins_copied = len(copied_placeholder_two_plugins)
count_plugins_org = len(org_placeholder_two_plugins)
msg = f"plugin count {count_plugins_copied} {count_plugins_org} for placeholder two not equal"
self.assertEqual(count_plugins_copied, count_plugins_org, msg)
# placeholder 3
count_plugins_copied = len(copied_placeholder_three_plugins)
count_plugins_org = len(org_placeholder_three_plugins)
msg = f"plugin count {count_plugins_copied} {count_plugins_org} for placeholder three not equal"
self.assertEqual(count_plugins_copied, count_plugins_org, msg)
# verify the body of text plugin with nested link plugin
# org to copied
org_nested_text_plugin = None
# do this iteration to find the real text plugin with the attached link
# the inheritance mechanism for the cmsplugins works through
# (tuple)get_plugin_instance()
for x in org_placeholder_three_plugins:
if x.plugin_type == "TextPlugin":
instance = x.get_plugin_instance()[0]
if instance.body.startswith(pre_nesting_body):
org_nested_text_plugin = instance
break
copied_nested_text_plugin = None
for x in copied_placeholder_three_plugins:
if x.plugin_type == "TextPlugin":
instance = x.get_plugin_instance()[0]
if instance.body.startswith(pre_nesting_body):
copied_nested_text_plugin = instance
break
msg = "original nested text plugin not found"
self.assertNotEqual(org_nested_text_plugin, None, msg=msg)
msg = "copied nested text plugin not found"
self.assertNotEqual(copied_nested_text_plugin, None, msg=msg)
# get the children ids of the texplugin with a nested link
# to check if the body of the text is generated correctly
org_link_child_plugin = org_nested_text_plugin.get_children()[0]
copied_link_child_plugin = copied_nested_text_plugin.get_children()[0]
# validate the textplugin body texts
msg = "org plugin and copied plugin are the same"
self.assertNotEqual(org_link_child_plugin.id, copied_link_child_plugin.id, msg)
needle = "%s"
msg = "child plugin id differs to parent in body"
# linked child is in body
self.assertTrue(org_nested_text_plugin.body.find(needle % (org_link_child_plugin.id)) != -1, msg)
msg = "copy: child plugin id differs to parent in body plugin_obj_id"
self.assertTrue(copied_nested_text_plugin.body.find(needle % (copied_link_child_plugin.id)) != -1, msg)
# really nothing else
msg = "child link plugin id differs to parent body"
self.assertTrue(org_nested_text_plugin.body.find(needle % (copied_link_child_plugin.id)) == -1, msg)
msg = "copy: child link plugin id differs to parent body"
self.assertTrue(copied_nested_text_plugin.body.find(needle % (org_link_child_plugin.id)) == -1, msg)
# now reverse lookup the placeholders from the plugins
org_placeholder = org_link_child_plugin.placeholder
copied_placeholder = copied_link_child_plugin.placeholder
msg = "placeholder of the original plugin and copied plugin are the same"
self.assertNotEqual(org_placeholder.id, copied_placeholder.id, msg)
def test_add_child_plugin(self):
page_one = create_page("Three Placeholder", "col_three.html", "en",
position="last-child", in_navigation=True)
page_one_ph_one = page_one.get_placeholders("en").get(slot="col_sidebar")
# add the text plugin to placeholder one
text_plugin_en = add_plugin(page_one_ph_one, "TextPlugin", "en", body="Hello World")
superuser = self.get_superuser()
with self.login_user_context(superuser):
post_data = {
'name': 'test',
'external_link': 'http://www.example.org/'
}
add_url = self.get_add_plugin_uri(page_one_ph_one, 'LinkPlugin', parent=text_plugin_en)
response = self.client.post(add_url, post_data)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(
response,
'admin/cms/page/plugin/confirm_form.html'
)
link_plugin = CMSPlugin.objects.get(parent_id=text_plugin_en.pk)
self.assertEqual(link_plugin.parent_id, text_plugin_en.pk)
self.assertEqual(link_plugin.position, text_plugin_en.position + 1)
def test_plugin_deep_nesting_and_copying_issue_position_parent_child_discrepency(self):
"""
Captures an edge case issue where plugins have been seen to have a higher
position than their parent. When the placeholder is
copied the parent defaults to None because the plugin is not yet created / remapped.
Plugins first created in this order:
Plugin 1 (pk1, position 1)
Plugin 2 (pk2, position 2)
Plugin 3 (pk3, position 3)
Then a top level plugin is made a child of another.
The result is a child with a lower id and higher position that it's parent.
Plugin 1 (pk1, position 1)
Plugin 3 has children (pk3, position 3)
Plugin 2 (pk2, position 2)
"""
placeholder = Placeholder(slot="some_slot")
placeholder.save()
# plugins in placeholder
plugin_1 = add_plugin(placeholder, "TextPlugin", "en", body="01")
plugin_2 = add_plugin(placeholder, "TextPlugin", "en", body="02")
plugin_3 = add_plugin(placeholder, "TextPlugin", "en", body="03")
expected_tree = [
(plugin_1.pk, None),
(plugin_2.pk, None),
(plugin_3.pk, None),
]
self.copy_placeholders_and_check_results([placeholder])
self.compare_plugin_tree(expected_tree, placeholder)
plugin_2.parent = plugin_3
plugin_2.save()
self.reload(plugin_2)
self.reload(plugin_3)
expected_tree = [
(plugin_1.pk, None),
(plugin_2.pk, plugin_3.pk),
(plugin_3.pk, None),
]
placeholder._recalculate_plugin_positions("en")
self.copy_placeholders_and_check_results([placeholder])
self.compare_plugin_tree(expected_tree, placeholder)
def test_recalculate_plugin_positions(self):
"""
Test to verify that the plugin positions are recalculated correctly
when a plugin is moved to another placeholder.
"""
import random
placeholder = Placeholder(slot="some_slot")
placeholder.save()
# plugins in placeholder
n0 = 10 # Number of plugins in the first positions
n = 50 # Number of plugins after the gap (of at least n)
order = list(random.sample(range(1, n0 + 1), n0))
order += list(random.sample(range(n0 + n + 1, n0 + 3 * n + 2), n))
for lang in ("en", "it"):
for i, pos in enumerate(order):
CMSPlugin.objects.create(
plugin_type=str(i),
placeholder=placeholder,
language=lang,
position=pos,
parent=None,
)
placeholder._recalculate_plugin_positions("en")
plugins = placeholder.get_plugins("en").order_by("position")
for i, plugin in enumerate(plugins, start=1):
self.assertEqual(plugin.position, i)
def test_recalculate_plugin_positions_with_gap_and_reversed_order(self):
"""Regression test for GitHub issue #8665.
When the position sequence contains a gap, the old single-statement compaction could
assign a position that another, not-yet-processed row still held, raising an
``IntegrityError`` (duplicate position) mid-statement. The order in which rows are
processed — and therefore whether it failed — depended on the query plan.
This builds the deterministic worst case: a gap at position 1 with positions that
strictly *decrease* as the id increases. A naive row-by-row compaction in id order then
tries to write a position that a higher-id row still occupies (e.g. the first row, at
position n+1, is compacted to n, which the second row still holds). The two-phase
park-then-unpark recalculation must complete without error and produce a dense 1..n.
"""
placeholder = Placeholder(slot="some_slot")
placeholder.save()
n = 6
# Positions n+1, n, ..., 2 assigned in ascending-id order: a gap at position 1 and a
# descending position/id correlation. Keep a second language with its own gap to make
# sure the recalculation is scoped to the requested language only.
created = []
for i in range(n):
created.append(
CMSPlugin.objects.create(
plugin_type=str(i),
placeholder=placeholder,
language="en",
position=n + 1 - i, # n+1, n, ..., 2
parent=None,
)
)
it_plugin = CMSPlugin.objects.create(
plugin_type="it", placeholder=placeholder, language="it", position=5, parent=None
)
# Must not raise IntegrityError, regardless of backend / row-processing order.
placeholder._recalculate_plugin_positions("en")
positions = list(
placeholder.get_plugins("en").order_by("position").values_list("position", flat=True)
)
# Dense 1..n, no negative leftovers from the parking phase, no duplicates.
self.assertEqual(positions, list(range(1, n + 1)))
# Order is preserved: the row originally at the smallest position ends up first.
ordered_ids = list(
placeholder.get_plugins("en").order_by("position").values_list("pk", flat=True)
)
self.assertEqual(ordered_ids, [p.pk for p in reversed(created)])
# The other language was left untouched.
it_plugin.refresh_from_db()
self.assertEqual(it_plugin.position, 5)
def test_move_plugin_heals_preexisting_gap(self):
"""Regression test for GitHub issue #8665 via the move path.
``move_plugin`` shifts plugins (temporarily into negative positions) and then calls
``_recalculate_plugin_positions``. If the placeholder already contains a gap — e.g. because
third-party code deleted a plugin with a plain ``plugin.delete()`` instead of
``placeholder.delete_plugin()`` — the recalculation must still compact to a dense ``1..n``
rather than raising an ``IntegrityError``.
"""
placeholder = Placeholder(slot="some_slot")
placeholder.save()
plugins = [add_plugin(placeholder, "TextPlugin", "en", body=f"plugin {i}") for i in range(5)]
# Simulate a third-party plain delete that leaves a gap (no position recalculation).
CMSPlugin.objects.filter(pk=plugins[1].pk).delete()
gappy = list(
placeholder.get_plugins("en").order_by("position").values_list("position", flat=True)
)
self.assertEqual(gappy, [1, 3, 4, 5]) # gap at position 2
# Move the last remaining plugin to the top. Must not raise and must compact the gap away.
placeholder.move_plugin(plugins[4].cmsplugin_ptr, target_position=1)
positions = list(
placeholder.get_plugins("en").order_by("position").values_list("position", flat=True)
)
self.assertEqual(positions, [1, 2, 3, 4])
def test_move_to_placeholder_heals_preexisting_gap(self):
"""Regression test for GitHub issue #8665 via the cross-placeholder move path.
Moving a plugin to another placeholder reassigns it and recalculates positions in *both*
placeholders. If either one already contains a gap (e.g. from a third-party plain
``plugin.delete()``), the recalculation must still compact both to a dense ``1..n`` instead
of raising an ``IntegrityError``.
"""
source = Placeholder(slot="source")
source.save()
target = Placeholder(slot="target")
target.save()
source_plugins = [add_plugin(source, "TextPlugin", "en", body=f"s{i}") for i in range(5)]
target_plugins = [add_plugin(target, "TextPlugin", "en", body=f"t{i}") for i in range(5)]
# Simulate third-party plain deletes that leave gaps in both placeholders.
CMSPlugin.objects.filter(pk=source_plugins[1].pk).delete()
CMSPlugin.objects.filter(pk=target_plugins[3].pk).delete()
moved = source_plugins[4].cmsplugin_ptr
moved.refresh_from_db()
# Must not raise and must leave both placeholders dense.
source.move_plugin(moved, target_position=2, target_placeholder=target)
source_positions = list(
source.get_plugins("en").order_by("position").values_list("position", flat=True)
)
target_positions = list(
target.get_plugins("en").order_by("position").values_list("position", flat=True)
)
self.assertEqual(source_positions, [1, 2, 3]) # 5 created, 1 deleted, 1 moved out
self.assertEqual(target_positions, [1, 2, 3, 4, 5]) # 5 created, 1 deleted, 1 moved in, healed
# The moved plugin landed at the requested target position.
moved.refresh_from_db()
self.assertEqual(moved.placeholder_id, target.pk)
self.assertEqual(moved.position, 2)
def test_delete_plugins_multiple_blocks(self):
"""``delete_plugins`` removes several non-adjacent plugins and recompacts the placeholder."""
placeholder = Placeholder(slot="slot")
placeholder.save()
plugins = [add_plugin(placeholder, "TextPlugin", "en", body=str(i)) for i in range(6)]
# Delete the 2nd and 4th plugins (positions 2 and 4).
placeholder.delete_plugins([plugins[1].cmsplugin_ptr, plugins[3].cmsplugin_ptr])
remaining = list(placeholder.get_plugins("en").order_by("position").values_list("pk", "position"))
self.assertEqual(
remaining,
[(plugins[0].pk, 1), (plugins[2].pk, 2), (plugins[4].pk, 3), (plugins[5].pk, 4)],
)
def test_delete_plugins_with_descendants_and_overlap(self):
"""Passing a parent and one of its descendants deletes the whole subtree once, no error."""
placeholder = Placeholder(slot="slot")
placeholder.save()
before = add_plugin(placeholder, "TextPlugin", "en", body="before")
parent = add_plugin(placeholder, "TextPlugin", "en", body="parent")
child1 = add_plugin(placeholder, "TextPlugin", "en", target=parent, body="c1")
add_plugin(placeholder, "TextPlugin", "en", target=parent, body="c2")
after = add_plugin(placeholder, "TextPlugin", "en", body="after")
# Overlapping selection: the parent and one of its children. The whole subtree is removed
# exactly once and the remaining plugins are recompacted.
placeholder.delete_plugins([parent.cmsplugin_ptr, child1.cmsplugin_ptr])
remaining = list(placeholder.get_plugins("en").order_by("position").values_list("pk", "position"))
self.assertEqual(remaining, [(before.pk, 1), (after.pk, 2)])
def test_delete_plugins_cross_language(self):
"""``delete_plugins`` recompacts each affected language and leaves untouched ones alone."""
placeholder = Placeholder(slot="slot")
placeholder.save()
en = [add_plugin(placeholder, "TextPlugin", "en", body=f"en{i}") for i in range(4)]
de = [add_plugin(placeholder, "TextPlugin", "de", body=f"de{i}") for i in range(4)]
for _i in range(3):
add_plugin(placeholder, "TextPlugin", "it", body="it")
# One plugin from each of two languages, in a single call.
placeholder.delete_plugins([en[1].cmsplugin_ptr, de[0].cmsplugin_ptr])
for language in ("en", "de"):
self.assertEqual(
list(placeholder.get_plugins(language).order_by("position").values_list("position", flat=True)),
[1, 2, 3],
)
# The untouched language is intact.
self.assertEqual(
list(placeholder.get_plugins("it").order_by("position").values_list("position", flat=True)),
[1, 2, 3],
)
def test_delete_plugins_trailing_and_empty(self):
"""Deleting the trailing plugins leaves a dense placeholder; empty input is a no-op."""
placeholder = Placeholder(slot="slot")
placeholder.save()
plugins = [add_plugin(placeholder, "TextPlugin", "en", body=str(i)) for i in range(4)]
placeholder.delete_plugins([]) # no-op
self.assertEqual(placeholder.get_plugins("en").count(), 4)
# Delete the last two (trailing): no gap is opened, the rest stays dense.
placeholder.delete_plugins([plugins[2].cmsplugin_ptr, plugins[3].cmsplugin_ptr])
self.assertEqual(
list(placeholder.get_plugins("en").order_by("position").values_list("position", flat=True)),
[1, 2],
)
def test_recalculate_plugin_positions_empty_placeholder(self):
"""
Test to verify that recalculating plugin positions on an empty placeholder
doesn't error and leaves the plugin list empty for a given language.
"""
# Create an empty placeholder for language "en"
placeholder = Placeholder(slot="empty_slot")
placeholder.save()
# Invoke the recalculation function for the language "en".
# Adjust the call to _recalculate_plugin_positions as needed.
placeholder._recalculate_plugin_positions("en")
# Assert that the plugin list for the placeholder in language "en" remains empty.
plugins = list(placeholder.get_plugins(language="en"))
self.assertEqual(plugins, [])