Skip to content
3 changes: 2 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ Changelog
5.9.5 (unreleased)
------------------

- Nothing changed yet.
- Fixed empty spaces in shortname when adding a content.
[daniele]


5.9.4 (2025-12-05)
Expand Down
1 change: 0 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import os
import sys


# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
Expand Down
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from setuptools import find_packages
from setuptools import setup


long_description = "\n\n".join(
[
open("README.rst").read(),
Expand Down
55 changes: 54 additions & 1 deletion src/redturtle/volto/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,64 @@
# -*- coding: utf-8 -*-

from Acquisition import aq_base
from plone.app.content.interfaces import INameFromTitle
from plone.app.dexterity.behaviors.metadata import ICategorization
from plone.app.dexterity.textindexer import utils
from plone.app.uuid.utils import uuidToObject
from plone.restapi.bbb import base_hasattr
from plone.restapi.services.content import utils as rest_utils
from plone.uuid.interfaces import IUUID
from redturtle.volto import patches # noqa
from zope.container.contained import notifyContainerModified
from zope.container.contained import ObjectAddedEvent
from zope.container.interfaces import INameChooser
from zope.event import notify
from zope.i18nmessageid import MessageFactory


_ = MessageFactory("redturtle.volto")

# Index also subjects in SearchableText.
utils.searchable(ICategorization, "subjects")


def add_patched(container, obj, rename=True):
"""Add an object to a container."""

id_ = getattr(aq_base(obj), "id", None)

# Archetypes objects are already created in a container thus we just fire
# the notification events and rename the object if necessary.
if base_hasattr(obj, "_at_rename_after_creation"):
notify(ObjectAddedEvent(obj, container, id_))
notifyContainerModified(container)
if obj._at_rename_after_creation and rename:
obj._renameAfterCreation(check_auto_id=True)
return obj
else:
if rename:
chooser = INameChooser(container)
# INameFromTitle adaptable objects should not get a name
# suggestion. NameChooser would prefer the given name instead of
# the one provided by the INameFromTitle adapter.
suggestion = None
name_from_title = INameFromTitle(obj, None)
if name_from_title is None:
suggestion = obj.Title()
id_ = chooser.chooseName(suggestion, obj)
obj.id = id_

chooser = INameChooser(container)
id_ = chooser.chooseName(obj.id, obj)
obj.id = id_
new_id = container._setObject(id_, obj)
# _setObject triggers ObjectAddedEvent which can end up triggering a
# content rule to move the item to a different container. In this case
# look up the object by UUID.
try:
return container._getOb(new_id)
except AttributeError:
uuid = IUUID(obj)
return uuidToObject(uuid)


rest_utils.add = add_patched
2 changes: 2 additions & 0 deletions src/redturtle/volto/adapters/namechooser.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ def chooseName(self, name, obj):
"""
Additional check: the id should not be in redirection tool.
"""

id = super().chooseName(name=name, obj=obj)

# this raise BadRequest if there is an override with aliases
check_alias(context=self.context, id=id)

return id
1 change: 0 additions & 1 deletion src/redturtle/volto/adapters/rss.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from plone.volto.behaviors.preview import IPreview
from redturtle.volto.interfaces import ICustomFeedItem


try:
from plone.base.interfaces.syndication import IFeed
except ModuleNotFoundError:
Expand Down
1 change: 0 additions & 1 deletion src/redturtle/volto/adapters/stringinterp.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from redturtle.volto import _
from zope.component import adapter


try:
from plone.stringinterp import _ as stringinterp_mf
except ImportError:
Expand Down
1 change: 0 additions & 1 deletion src/redturtle/volto/browser/find_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import json
import logging


logger = logging.getLogger(__name__)

BLOCKS = [
Expand Down
2 changes: 0 additions & 2 deletions src/redturtle/volto/browser/find_broken_links.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from six import StringIO
from zope.schema import getFieldsInOrder


try:
from collective.volto.blocksfield.field import BlocksField

Expand All @@ -17,7 +16,6 @@
import csv
import logging


logger = logging.getLogger(__name__)


Expand Down
1 change: 0 additions & 1 deletion src/redturtle/volto/browser/fix_links.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import logging
import re


logger = logging.getLogger(__name__)


Expand Down
1 change: 0 additions & 1 deletion src/redturtle/volto/config/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import os


MAX_LIMIT = int(os.environ.get("REDTURTLE_VOLTO_MAX_LIMIT_SEARCH") or 500)
1 change: 0 additions & 1 deletion src/redturtle/volto/indexers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from zope.interface import implementer
from zope.publisher.interfaces.browser import IBrowserRequest


try:
from plone.base.utils import safe_text
except ImportError:
Expand Down
1 change: 0 additions & 1 deletion src/redturtle/volto/locales/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import pkg_resources
import subprocess


domain = "redturtle.volto"
os.chdir(pkg_resources.resource_filename(domain, ""))
os.chdir("../../../")
Expand Down
1 change: 0 additions & 1 deletion src/redturtle/volto/monkey.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import logging
import os


logger = logging.getLogger(__name__)


Expand Down
1 change: 0 additions & 1 deletion src/redturtle/volto/patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

import logging


logger = logging.getLogger(__name__)

_ = MessageFactory("plone")
Expand Down
1 change: 0 additions & 1 deletion src/redturtle/volto/restapi/deserializer/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from zope.component import adapter
from zope.interface import implementer


EXCLUDE_KEYS = ["@type", "type", "token", "value", "@id", "query", "bg_color"]
EXCLUDE_TYPES = [
"title",
Expand Down
1 change: 0 additions & 1 deletion src/redturtle/volto/restapi/serializer/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from zope.globalrequest import getRequest
from zope.interface import implementer


EXCLUDE_KEYS = ["@type", "type", "token", "value", "@id", "query", "bg_color"]
EXCLUDE_TYPES = [
"title",
Expand Down
1 change: 0 additions & 1 deletion src/redturtle/volto/restapi/serializer/dxfields.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

import re


RESOLVEUID_RE = re.compile(".*?/resolve[Uu]id/([^/]*)/?(.*)$")


Expand Down
1 change: 0 additions & 1 deletion src/redturtle/volto/restapi/serializer/summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from zope.interface import implementer
from zope.interface import Interface


EMPTY_STRINGS = ["None"]


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

import logging


logger = logging.getLogger(__name__)


Expand Down
1 change: 0 additions & 1 deletion src/redturtle/volto/restapi/services/search/get.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

import logging


logger = logging.getLogger(__name__)

# search for 'ranking' in 'SearchableText' and rank very high
Expand Down
1 change: 0 additions & 1 deletion src/redturtle/volto/setuphandlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import logging


logger = logging.getLogger(__name__)


Expand Down
1 change: 0 additions & 1 deletion src/redturtle/volto/tests/test_blocks_linkintegrity.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

import unittest


HAS_PLONE_6 = getattr(
import_module("Products.CMFPlone.factory"), "PLONE60MARKER", False
)
Expand Down
1 change: 1 addition & 0 deletions src/redturtle/volto/tests/test_content_types.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
"""Setup tests for this package."""

from plone import api
from plone.app.testing import setRoles
from plone.app.testing import SITE_OWNER_NAME
Expand Down
1 change: 1 addition & 0 deletions src/redturtle/volto/tests/test_ct_link.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
"""Setup tests for this package."""

from plone import api
from plone.app.testing import setRoles
from plone.app.testing import SITE_OWNER_NAME
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
"""Setup tests for this package."""

from plone import api
from plone.app.testing import setRoles
from plone.app.testing import TEST_USER_ID
Expand Down
1 change: 1 addition & 0 deletions src/redturtle/volto/tests/test_patch_absolutize_path.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
"""Setup tests for this package."""

from plone import api
from plone.app.testing import setRoles
from plone.app.testing import TEST_USER_ID
Expand Down
2 changes: 1 addition & 1 deletion src/redturtle/volto/tests/test_rss.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*-
"""Setup tests for this package."""

from plone import api
from plone.app.testing import setRoles
from plone.app.testing import TEST_USER_ID
from redturtle.volto.adapters.rss import CustomFeedItem
from redturtle.volto.interfaces import ICustomFeedItem


try:
from plone.base.interfaces.syndication import IFeed
except ModuleNotFoundError:
Expand Down
2 changes: 1 addition & 1 deletion src/redturtle/volto/tests/test_setup.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# -*- coding: utf-8 -*-
"""Setup tests for this package."""

from plone import api
from plone.app.testing import setRoles
from plone.app.testing import TEST_USER_ID
from redturtle.volto.testing import REDTURTLE_VOLTO_INTEGRATION_TESTING # noqa: E501

import unittest


try:
from Products.CMFPlone.utils import get_installer
except ImportError:
Expand Down
67 changes: 67 additions & 0 deletions src/redturtle/volto/tests/test_spaces_in_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
"""Setup tests for this package."""

from plone.app.testing import setRoles
from plone.app.testing import SITE_OWNER_NAME
from plone.app.testing import SITE_OWNER_PASSWORD
from plone.app.testing import TEST_USER_ID
from plone.restapi.testing import RelativeSession
from redturtle.volto.testing import REDTURTLE_VOLTO_API_FUNCTIONAL_TESTING

import unittest


class TestCreation(unittest.TestCase):
layer = REDTURTLE_VOLTO_API_FUNCTIONAL_TESTING

def setUp(self):
self.app = self.layer["app"]
self.portal = self.layer["portal"]
self.portal_url = self.portal.absolute_url()
setRoles(self.portal, TEST_USER_ID, ["Manager"])

self.api_session = RelativeSession(self.portal_url)
self.api_session.headers.update({"Accept": "application/json"})
self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD)

def test_fix_id_if_contains_spaces(self):
"""Test that id with spaces is fixed"""

response = self.api_session.post(
self.portal_url,
json={
"@type": "Document",
"title": "My doc",
"id": "aa bb",
},
)

self.assertEqual(response.json()["id"], "aa-bb")

obj_url = response.json()["@id"]
response = self.api_session.patch(
obj_url,
json={"id": "aa bb"},
)

self.assertEqual(response.status_code, 204)

response = self.api_session.get(
obj_url,
)

self.assertEqual(response.json()["id"], "aa-bb-1")

def test_fix_id_if_contains_invalid_chars(self):
"""Test that id with spaces is fixed"""

response = self.api_session.post(
self.portal_url,
json={
"@type": "Document",
"title": "My doc",
"id": "aàbbù",
},
)

self.assertEqual(response.status_code, 400)
1 change: 1 addition & 0 deletions src/redturtle/volto/types/adapters.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
"""JsonSchema providers."""

from plone.restapi.types.adapters import TextLineJsonSchemaProvider as Base
from plone.restapi.types.interfaces import IJsonSchemaProvider
from redturtle.volto import _
Expand Down
2 changes: 0 additions & 2 deletions src/redturtle/volto/upgrades.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from uuid import uuid4
from zope.schema import getFields


try:
from plone.base.utils import get_installer
except Exception:
Expand All @@ -23,7 +22,6 @@
import logging
import transaction


try:
from collective.volto.blocksfield.field import BlocksField

Expand Down
Loading