diff --git a/CHANGES.rst b/CHANGES.rst index a89de65e..a6c973b3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -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) diff --git a/docs/conf.py b/docs/conf.py index 2deb08ed..0c999488 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -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. diff --git a/setup.py b/setup.py index 999772b2..c0ea9e6e 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,6 @@ from setuptools import find_packages from setuptools import setup - long_description = "\n\n".join( [ open("README.rst").read(), diff --git a/src/redturtle/volto/__init__.py b/src/redturtle/volto/__init__.py index ca68948b..198bda69 100644 --- a/src/redturtle/volto/__init__.py +++ b/src/redturtle/volto/__init__.py @@ -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 diff --git a/src/redturtle/volto/adapters/namechooser.py b/src/redturtle/volto/adapters/namechooser.py index fdf3f965..62712d28 100644 --- a/src/redturtle/volto/adapters/namechooser.py +++ b/src/redturtle/volto/adapters/namechooser.py @@ -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 diff --git a/src/redturtle/volto/adapters/rss.py b/src/redturtle/volto/adapters/rss.py index 2ed8ea70..fa3c404d 100644 --- a/src/redturtle/volto/adapters/rss.py +++ b/src/redturtle/volto/adapters/rss.py @@ -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: diff --git a/src/redturtle/volto/adapters/stringinterp.py b/src/redturtle/volto/adapters/stringinterp.py index a4a4e215..7eaaf67f 100644 --- a/src/redturtle/volto/adapters/stringinterp.py +++ b/src/redturtle/volto/adapters/stringinterp.py @@ -4,7 +4,6 @@ from redturtle.volto import _ from zope.component import adapter - try: from plone.stringinterp import _ as stringinterp_mf except ImportError: diff --git a/src/redturtle/volto/browser/find_blocks.py b/src/redturtle/volto/browser/find_blocks.py index dec9a452..6d970b89 100644 --- a/src/redturtle/volto/browser/find_blocks.py +++ b/src/redturtle/volto/browser/find_blocks.py @@ -9,7 +9,6 @@ import json import logging - logger = logging.getLogger(__name__) BLOCKS = [ diff --git a/src/redturtle/volto/browser/find_broken_links.py b/src/redturtle/volto/browser/find_broken_links.py index b9034716..83cf265d 100644 --- a/src/redturtle/volto/browser/find_broken_links.py +++ b/src/redturtle/volto/browser/find_broken_links.py @@ -6,7 +6,6 @@ from six import StringIO from zope.schema import getFieldsInOrder - try: from collective.volto.blocksfield.field import BlocksField @@ -17,7 +16,6 @@ import csv import logging - logger = logging.getLogger(__name__) diff --git a/src/redturtle/volto/browser/fix_links.py b/src/redturtle/volto/browser/fix_links.py index 686a18bd..61ad60fc 100644 --- a/src/redturtle/volto/browser/fix_links.py +++ b/src/redturtle/volto/browser/fix_links.py @@ -13,7 +13,6 @@ import logging import re - logger = logging.getLogger(__name__) diff --git a/src/redturtle/volto/config/__init__.py b/src/redturtle/volto/config/__init__.py index caf3ecbe..34c0cdb6 100644 --- a/src/redturtle/volto/config/__init__.py +++ b/src/redturtle/volto/config/__init__.py @@ -1,4 +1,3 @@ import os - MAX_LIMIT = int(os.environ.get("REDTURTLE_VOLTO_MAX_LIMIT_SEARCH") or 500) diff --git a/src/redturtle/volto/indexers.py b/src/redturtle/volto/indexers.py index ae7b392c..f0704889 100644 --- a/src/redturtle/volto/indexers.py +++ b/src/redturtle/volto/indexers.py @@ -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: diff --git a/src/redturtle/volto/locales/update.py b/src/redturtle/volto/locales/update.py index a694c489..156f460f 100644 --- a/src/redturtle/volto/locales/update.py +++ b/src/redturtle/volto/locales/update.py @@ -4,7 +4,6 @@ import pkg_resources import subprocess - domain = "redturtle.volto" os.chdir(pkg_resources.resource_filename(domain, "")) os.chdir("../../../") diff --git a/src/redturtle/volto/monkey.py b/src/redturtle/volto/monkey.py index 963da2f6..295e9ace 100644 --- a/src/redturtle/volto/monkey.py +++ b/src/redturtle/volto/monkey.py @@ -20,7 +20,6 @@ import logging import os - logger = logging.getLogger(__name__) diff --git a/src/redturtle/volto/patches.py b/src/redturtle/volto/patches.py index 6d916180..5c1f3b0f 100644 --- a/src/redturtle/volto/patches.py +++ b/src/redturtle/volto/patches.py @@ -16,7 +16,6 @@ import logging - logger = logging.getLogger(__name__) _ = MessageFactory("plone") diff --git a/src/redturtle/volto/restapi/deserializer/blocks.py b/src/redturtle/volto/restapi/deserializer/blocks.py index ee7809aa..d3935e3e 100644 --- a/src/redturtle/volto/restapi/deserializer/blocks.py +++ b/src/redturtle/volto/restapi/deserializer/blocks.py @@ -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", diff --git a/src/redturtle/volto/restapi/serializer/blocks.py b/src/redturtle/volto/restapi/serializer/blocks.py index dd246bca..94850f01 100644 --- a/src/redturtle/volto/restapi/serializer/blocks.py +++ b/src/redturtle/volto/restapi/serializer/blocks.py @@ -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", diff --git a/src/redturtle/volto/restapi/serializer/dxfields.py b/src/redturtle/volto/restapi/serializer/dxfields.py index d5f81fe2..dda44fc9 100644 --- a/src/redturtle/volto/restapi/serializer/dxfields.py +++ b/src/redturtle/volto/restapi/serializer/dxfields.py @@ -15,7 +15,6 @@ import re - RESOLVEUID_RE = re.compile(".*?/resolve[Uu]id/([^/]*)/?(.*)$") diff --git a/src/redturtle/volto/restapi/serializer/summary.py b/src/redturtle/volto/restapi/serializer/summary.py index 5cc82224..71dc93a5 100644 --- a/src/redturtle/volto/restapi/serializer/summary.py +++ b/src/redturtle/volto/restapi/serializer/summary.py @@ -14,7 +14,6 @@ from zope.interface import implementer from zope.interface import Interface - EMPTY_STRINGS = ["None"] diff --git a/src/redturtle/volto/restapi/services/querystringsearch/get.py b/src/redturtle/volto/restapi/services/querystringsearch/get.py index 299d3d2d..9374445c 100644 --- a/src/redturtle/volto/restapi/services/querystringsearch/get.py +++ b/src/redturtle/volto/restapi/services/querystringsearch/get.py @@ -22,7 +22,6 @@ import logging - logger = logging.getLogger(__name__) diff --git a/src/redturtle/volto/restapi/services/search/get.py b/src/redturtle/volto/restapi/services/search/get.py index e4d44258..220629be 100644 --- a/src/redturtle/volto/restapi/services/search/get.py +++ b/src/redturtle/volto/restapi/services/search/get.py @@ -10,7 +10,6 @@ import logging - logger = logging.getLogger(__name__) # search for 'ranking' in 'SearchableText' and rank very high diff --git a/src/redturtle/volto/setuphandlers.py b/src/redturtle/volto/setuphandlers.py index 95ea06dc..f80638f8 100644 --- a/src/redturtle/volto/setuphandlers.py +++ b/src/redturtle/volto/setuphandlers.py @@ -5,7 +5,6 @@ import logging - logger = logging.getLogger(__name__) diff --git a/src/redturtle/volto/tests/test_blocks_linkintegrity.py b/src/redturtle/volto/tests/test_blocks_linkintegrity.py index 39a3f761..579e85f3 100644 --- a/src/redturtle/volto/tests/test_blocks_linkintegrity.py +++ b/src/redturtle/volto/tests/test_blocks_linkintegrity.py @@ -10,7 +10,6 @@ import unittest - HAS_PLONE_6 = getattr( import_module("Products.CMFPlone.factory"), "PLONE60MARKER", False ) diff --git a/src/redturtle/volto/tests/test_content_types.py b/src/redturtle/volto/tests/test_content_types.py index 9d1f7ed1..c7171623 100644 --- a/src/redturtle/volto/tests/test_content_types.py +++ b/src/redturtle/volto/tests/test_content_types.py @@ -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 diff --git a/src/redturtle/volto/tests/test_ct_link.py b/src/redturtle/volto/tests/test_ct_link.py index 5fb981c1..6b645a76 100644 --- a/src/redturtle/volto/tests/test_ct_link.py +++ b/src/redturtle/volto/tests/test_ct_link.py @@ -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 diff --git a/src/redturtle/volto/tests/test_namechooser_customization.py b/src/redturtle/volto/tests/test_namechooser_customization.py index 1f5a628a..2b736278 100644 --- a/src/redturtle/volto/tests/test_namechooser_customization.py +++ b/src/redturtle/volto/tests/test_namechooser_customization.py @@ -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 diff --git a/src/redturtle/volto/tests/test_patch_absolutize_path.py b/src/redturtle/volto/tests/test_patch_absolutize_path.py index ecfefaf7..8ad225b5 100644 --- a/src/redturtle/volto/tests/test_patch_absolutize_path.py +++ b/src/redturtle/volto/tests/test_patch_absolutize_path.py @@ -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 diff --git a/src/redturtle/volto/tests/test_rss.py b/src/redturtle/volto/tests/test_rss.py index 6095e477..a59d42bc 100644 --- a/src/redturtle/volto/tests/test_rss.py +++ b/src/redturtle/volto/tests/test_rss.py @@ -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: diff --git a/src/redturtle/volto/tests/test_setup.py b/src/redturtle/volto/tests/test_setup.py index 3db707e9..c7e98fc6 100644 --- a/src/redturtle/volto/tests/test_setup.py +++ b/src/redturtle/volto/tests/test_setup.py @@ -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 @@ -7,7 +8,6 @@ import unittest - try: from Products.CMFPlone.utils import get_installer except ImportError: diff --git a/src/redturtle/volto/tests/test_spaces_in_id.py b/src/redturtle/volto/tests/test_spaces_in_id.py new file mode 100644 index 00000000..3e91db86 --- /dev/null +++ b/src/redturtle/volto/tests/test_spaces_in_id.py @@ -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) diff --git a/src/redturtle/volto/types/adapters.py b/src/redturtle/volto/types/adapters.py index 286b6a53..8ea3f284 100644 --- a/src/redturtle/volto/types/adapters.py +++ b/src/redturtle/volto/types/adapters.py @@ -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 _ diff --git a/src/redturtle/volto/upgrades.py b/src/redturtle/volto/upgrades.py index d466f259..5d8f592f 100644 --- a/src/redturtle/volto/upgrades.py +++ b/src/redturtle/volto/upgrades.py @@ -13,7 +13,6 @@ from uuid import uuid4 from zope.schema import getFields - try: from plone.base.utils import get_installer except Exception: @@ -23,7 +22,6 @@ import logging import transaction - try: from collective.volto.blocksfield.field import BlocksField