From 98fdc66d1a38892b907fd41178086ab1972eb270 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Thu, 19 Feb 2026 21:38:41 +0100 Subject: [PATCH] Allow to create content with `index_html` id on the site root. The portal root has a `index_html` method which prevented content with the id `index_html` to be created and used as a default page. Fixes: https://github.com/plone/Products.CMFPlone/issues/4278 --- news/4278.bugfix.rst | 8 +++++ src/plone/base/tests/test_utils.py | 55 ++++++++++++++++++++++++++++++ src/plone/base/utils.py | 7 ++-- 3 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 news/4278.bugfix.rst diff --git a/news/4278.bugfix.rst b/news/4278.bugfix.rst new file mode 100644 index 0000000..5869b19 --- /dev/null +++ b/news/4278.bugfix.rst @@ -0,0 +1,8 @@ +Allow to create content with `index_html` id on the site root. + +The portal root has a `index_html` method which prevented content with the id +`index_html` to be created and used as a default page. + +Fixes: https://github.com/plone/Products.CMFPlone/issues/4278 + +@thet diff --git a/src/plone/base/tests/test_utils.py b/src/plone/base/tests/test_utils.py index e9f8bac..6a5e0a5 100644 --- a/src/plone/base/tests/test_utils.py +++ b/src/plone/base/tests/test_utils.py @@ -217,3 +217,58 @@ def test_is_truthy(self): self.assertFalse(is_truthy("NO")) self.assertFalse(is_truthy("no")) self.assertFalse(is_truthy("foo")) + + def test_check_for_collision(self): + from plone.base.utils import _check_for_collision + + class Container(dict): + def __getattribute__(self, name): + if name in self: + return self[name] + return object.__getattribute__(self, name) + + def portal_type(self): + """Necessary to fulfill protocol.""" + + def index_html(self): + """Common attribute - content with id index_html should be + addable.""" + + def some_attr(self): + """Random attribute - content with id some_attr should not be + addable.""" + + container = Container() + container["test"] = Container() + + # "test" is already taken + self.assertIn( + "There is already an item named", + _check_for_collision(container, "test"), + ) + + # "tiptop" is not yet taken + self.assertEqual( + _check_for_collision(container, "tiptop"), + None, + ) + + # "index_html" is not yet taken + self.assertEqual( + _check_for_collision(container, "index_html"), + None, + ) + + container["index_html"] = Container() + + # `False` as "index_html" is now taken + self.assertIn( + "There is already an item named", + _check_for_collision(container, "index_html"), + ) + + # Content ids are not addable, if the id is an container attribute. + self.assertIn( + "is reserved", + _check_for_collision(container, "some_attr"), + ) diff --git a/src/plone/base/utils.py b/src/plone/base/utils.py index 8a79d27..97a4cea 100644 --- a/src/plone/base/utils.py +++ b/src/plone/base/utils.py @@ -506,6 +506,10 @@ def _check_for_collision(contained_by, cid, **kwargs): mapping={"name": cid}, ) + if cid == "index_html": + # always allow index_html + return + # containers may have a field / attribute of the same name if base_hasattr(contained_by, cid): return _("${name} is reserved.", mapping={"name": cid}) @@ -533,9 +537,6 @@ def _check_for_collision(contained_by, cid, **kwargs): # However, we do want to allow overriding of *content* in the object's # parent path, including the portal root. - if cid == "index_html": - # always allow index_html - return portal = getSite() if portal and cid in portal.contentIds(): # Fine to use the same id as a *content* item from the root.