From 7db9aff4d63922de52d3e4b27ced53f546c853e5 Mon Sep 17 00:00:00 2001 From: Daniele Andreotti Date: Tue, 31 Mar 2026 12:18:38 +0200 Subject: [PATCH 01/13] override service to fix short name with empty spaces --- .../volto/restapi/services/configure.zcml | 2 ++ .../volto/restapi/services/content/add.py | 28 +++++++++++++++++++ .../restapi/services/content/configure.zcml | 17 +++++++++++ 3 files changed, 47 insertions(+) create mode 100644 src/redturtle/volto/restapi/services/content/add.py create mode 100644 src/redturtle/volto/restapi/services/content/configure.zcml diff --git a/src/redturtle/volto/restapi/services/configure.zcml b/src/redturtle/volto/restapi/services/configure.zcml index 25e3acbf..c46bd846 100644 --- a/src/redturtle/volto/restapi/services/configure.zcml +++ b/src/redturtle/volto/restapi/services/configure.zcml @@ -10,6 +10,8 @@ + + + + + + + \ No newline at end of file From 9d25fac2fe20548706d080497b809e9b3cbaab52 Mon Sep 17 00:00:00 2001 From: Daniele Andreotti Date: Tue, 31 Mar 2026 12:28:11 +0200 Subject: [PATCH 02/13] formatted --- src/redturtle/volto/restapi/services/content/add.py | 9 --------- .../volto/restapi/services/content/configure.zcml | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/redturtle/volto/restapi/services/content/add.py b/src/redturtle/volto/restapi/services/content/add.py index bc79b378..f9a89082 100644 --- a/src/redturtle/volto/restapi/services/content/add.py +++ b/src/redturtle/volto/restapi/services/content/add.py @@ -2,8 +2,6 @@ from plone.restapi.services.content.add import FolderPost as BaseFolderPost from zExceptions import BadRequest -import plone.protect.interfaces - class FolderPost(BaseFolderPost): """Creates a new content object.""" @@ -18,11 +16,4 @@ def reply(self): msg = "Il nome breve non deve contenere spazi" raise BadRequest(msg) - return dict( - error=dict( - type="Bad Request", - message=str("Il nome breve non deve contenere spazi"), - ) - ) - return super().reply() diff --git a/src/redturtle/volto/restapi/services/content/configure.zcml b/src/redturtle/volto/restapi/services/content/configure.zcml index 6a62d6bd..4d52efaa 100644 --- a/src/redturtle/volto/restapi/services/content/configure.zcml +++ b/src/redturtle/volto/restapi/services/content/configure.zcml @@ -14,4 +14,4 @@ layer="redturtle.volto.interfaces.IRedturtleVoltoLayer" /> - \ No newline at end of file + From fc4463f59e3b346a0f531527268eb48cd0624446 Mon Sep 17 00:00:00 2001 From: Daniele Andreotti Date: Sun, 5 Apr 2026 11:52:49 +0200 Subject: [PATCH 03/13] added event to fix id with spaces --- CHANGES.rst | 3 +- src/redturtle/volto/configure.zcml | 6 + src/redturtle/volto/events.py | 9 ++ .../locales/it/LC_MESSAGES/redturtle.volto.po | 85 ++++++++--- .../volto/locales/redturtle.volto.pot | 85 ++++++++--- .../volto/restapi/services/configure.zcml | 2 - .../volto/restapi/services/content/add.py | 19 --- .../restapi/services/content/configure.zcml | 17 --- .../volto/tests/test_spaces_in_id.py | 140 ++++++++++++++++++ 9 files changed, 291 insertions(+), 75 deletions(-) delete mode 100644 src/redturtle/volto/restapi/services/content/add.py delete mode 100644 src/redturtle/volto/restapi/services/content/configure.zcml create mode 100644 src/redturtle/volto/tests/test_spaces_in_id.py diff --git a/CHANGES.rst b/CHANGES.rst index a89de65e..9bb306e2 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,7 +4,8 @@ Changelog 5.9.5 (unreleased) ------------------ -- Nothing changed yet. +- Check spaces in shortname when adding a content. + [daniele] 5.9.4 (2025-12-05) diff --git a/src/redturtle/volto/configure.zcml b/src/redturtle/volto/configure.zcml index 00c6d078..daa65bdd 100644 --- a/src/redturtle/volto/configure.zcml +++ b/src/redturtle/volto/configure.zcml @@ -55,6 +55,12 @@ handler=".events.manage_auth_token" /> + + diff --git a/src/redturtle/volto/events.py b/src/redturtle/volto/events.py index 0b00b88a..a06f0ed1 100644 --- a/src/redturtle/volto/events.py +++ b/src/redturtle/volto/events.py @@ -1,3 +1,7 @@ +from plone import api +from redturtle.volto.adapters.namechooser import NormalizingNameChooser + + import os @@ -16,3 +20,8 @@ def manage_auth_token(event): auth_token = request.cookies.get("auth_token") if auth_token: request._auth = "Bearer " + auth_token + + +def avoid_spaces_in_shortname(obj, event): + fixed_id = NormalizingNameChooser(obj).chooseName(obj.id, obj) + api.content.rename(obj, new_id=fixed_id) diff --git a/src/redturtle/volto/locales/it/LC_MESSAGES/redturtle.volto.po b/src/redturtle/volto/locales/it/LC_MESSAGES/redturtle.volto.po index f01e3dd0..d1ec7f2c 100644 --- a/src/redturtle/volto/locales/it/LC_MESSAGES/redturtle.volto.po +++ b/src/redturtle/volto/locales/it/LC_MESSAGES/redturtle.volto.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2024-03-28 10:20+0000\n" +"POT-Creation-Date: 2026-04-02 13:24+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -14,75 +14,124 @@ msgstr "" "Preferred-Encodings: utf-8 latin1\n" "Domain: DOMAIN\n" -#: redturtle/volto/configure.zcml:29 +#: ../patches.py:62 +msgid "Alternative url path must not be a view." +msgstr "" + +#: ../patches.py:51 +msgid "Alternative url path must start with a slash." +msgstr "" + +#: ../patches.py:92 +msgid "Cannot use a working path as alternative url." +msgstr "" + +#: ../configure.zcml:29 msgid "Installs the redturtle.volto add-on." msgstr "" -#: redturtle/volto/profiles/default/registry/criteria.xml +#: ../profiles/default/registry/criteria.xml msgid "Metadata" msgstr "" -#: redturtle/volto/browser/controlpanel.py:11 -#: redturtle/volto/profiles/default/controlpanel.xml +#: ../browser/controlpanel.py:11 +#: ../profiles/default/controlpanel.xml msgid "RedTurtle Volto Settings" msgstr "RedTurtle Volto" -#: redturtle/volto/configure.zcml:29 +#: ../configure.zcml:29 msgid "RedTurtle: Volto" msgstr "" -#: redturtle/volto/configure.zcml:38 +#: ../configure.zcml:46 +msgid "RedTurtle: Volto (to 4307)" +msgstr "" + +#: ../configure.zcml:38 msgid "RedTurtle: Volto (uninstall)" msgstr "" -#: redturtle/volto/profiles/default/registry/criteria.xml +#: ../profiles/default/registry/criteria.xml msgid "Select False to show only elements not excluded from navigation." msgstr "Seleziona False per mostrare solo gli elementi non omessi dalla navigazione." -#: redturtle/volto/profiles/default/registry/criteria.xml +#: ../profiles/default/registry/criteria.xml msgid "Show elements excluded from navigation" msgstr "Elementi omessi dalla navigazione" -#: redturtle/volto/configure.zcml:38 +#: ../patches.py:64 +msgid "Target path must not be a view." +msgstr "" + +#: ../patches.py:59 +msgid "Target path must start with a slash." +msgstr "" + +#: ../patches.py:74 +msgid "The provided alternative url already exists!" +msgstr "" + +#: ../patches.py:98 +msgid "The provided target object does not exist." +msgstr "" + +#: ../configure.zcml:38 msgid "Uninstalls the redturtle.volto add-on." msgstr "" +#: ../browser/configure.zcml:82 +msgid "View Document" +msgstr "" + +#: ../patches.py:48 +msgid "You have to enter a target." +msgstr "" + +#: ../patches.py:46 +msgid "You have to enter an alternative url." +msgstr "" + #. Default: "If enabled, users can't create contents with ids that are already used as aliases." -#: redturtle/volto/interfaces.py:36 +#: ../interfaces.py:41 msgid "check_aliases_in_namechooser_help" msgstr "Se attivato, alla creazione o rinomina di un contenuto, verrà eseguito anche un controllo su eventuali alias presenti (quelli visibili in Gestione URL), ed eventualmente viene impedita la creazione con quell'id." #. Default: "Disallow ids used in aliases" -#: redturtle/volto/interfaces.py:32 +#: ../interfaces.py:37 msgid "check_aliases_in_namechooser_label" msgstr "Controllo degli id anche sugli alias" +#. Default: "The id \"${id}\" is invalid because contains spaces." +#: ../adapters/namechooser.py:18 +msgid "check_spaces_in_shortname" +msgstr "Il nome breve (id) non è valido perché contiene degli spazi." + #. Default: "If enabled, a custom ranking for SearchableText searches will be used." -#: redturtle/volto/interfaces.py:23 +#: ../interfaces.py:28 msgid "enable_advanced_query_ranking_help" msgstr "Se abilitato, verrà utilizzato un ranking custom per la ricerca testuale." #. Default: "Enable AdvancedQuery ranking" -#: redturtle/volto/interfaces.py:19 +#: ../interfaces.py:24 msgid "enable_advanced_query_ranking_label" msgstr "Abilita ranking custom con AdvancedQuery" #. Default: "The id \"${id}\" is invalid because there is already an alias for that path. Change its id or ask site administrators to remove \"${fixed_path}\" in aliases management." -#: redturtle/volto/adapters/namechooser.py:29 +#: ../adapters/namechooser.py:39 msgid "name_chooser_alias_error" msgstr "L'id \"${id}\" non è valido perché già utilizzato per un alias. Modifca l'id oppure rivolgiti agli amministratori per cancellare l'alias esistente: \"${fixed_path}\"." #. Default: "Insert an external link directly into the field,or select an internal link clicking on the icon." -#: redturtle/volto/types/adapters.py:25 +#: ../types/adapters.py:25 msgid "remoteUrl_restapi_label" msgstr "Inserisci un link esterno direttamente nel campo, oppure seleziona un collegamento ad un contenuto del sito cliccando sull'icona accanto." #. Default: "Volto Parent URL: Content url without \"/api\"." -#: redturtle/volto/adapters/stringinterp.py:35 +#: ../adapters/stringinterp.py:35 msgid "stringinterp_volto_parent_url" msgstr "" #. Default: "Volto URL: Content url without \"/api\"." -#: redturtle/volto/adapters/stringinterp.py:18 +#: ../adapters/stringinterp.py:18 msgid "stringinterp_volto_url" msgstr "Volto URL: URL del contenuto Plone senza \"/api\"." diff --git a/src/redturtle/volto/locales/redturtle.volto.pot b/src/redturtle/volto/locales/redturtle.volto.pot index a33cfec1..0e7245e0 100644 --- a/src/redturtle/volto/locales/redturtle.volto.pot +++ b/src/redturtle/volto/locales/redturtle.volto.pot @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2024-03-28 12:42+0000\n" +"POT-Creation-Date: 2026-04-02 13:24+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,75 +17,124 @@ msgstr "" "Preferred-Encodings: utf-8 latin1\n" "Domain: redturtle.volto\n" -#: redturtle/volto/configure.zcml:29 +#: ../patches.py:62 +msgid "Alternative url path must not be a view." +msgstr "" + +#: ../patches.py:51 +msgid "Alternative url path must start with a slash." +msgstr "" + +#: ../patches.py:92 +msgid "Cannot use a working path as alternative url." +msgstr "" + +#: ../configure.zcml:29 msgid "Installs the redturtle.volto add-on." msgstr "" -#: redturtle/volto/profiles/default/registry/criteria.xml +#: ../profiles/default/registry/criteria.xml msgid "Metadata" msgstr "" -#: redturtle/volto/browser/controlpanel.py:11 -#: redturtle/volto/profiles/default/controlpanel.xml +#: ../browser/controlpanel.py:11 +#: ../profiles/default/controlpanel.xml msgid "RedTurtle Volto Settings" msgstr "" -#: redturtle/volto/configure.zcml:29 +#: ../configure.zcml:29 msgid "RedTurtle: Volto" msgstr "" -#: redturtle/volto/configure.zcml:38 +#: ../configure.zcml:46 +msgid "RedTurtle: Volto (to 4307)" +msgstr "" + +#: ../configure.zcml:38 msgid "RedTurtle: Volto (uninstall)" msgstr "" -#: redturtle/volto/profiles/default/registry/criteria.xml +#: ../profiles/default/registry/criteria.xml msgid "Select False to show only elements not excluded from navigation." msgstr "" -#: redturtle/volto/profiles/default/registry/criteria.xml +#: ../profiles/default/registry/criteria.xml msgid "Show elements excluded from navigation" msgstr "" -#: redturtle/volto/configure.zcml:38 +#: ../patches.py:64 +msgid "Target path must not be a view." +msgstr "" + +#: ../patches.py:59 +msgid "Target path must start with a slash." +msgstr "" + +#: ../patches.py:74 +msgid "The provided alternative url already exists!" +msgstr "" + +#: ../patches.py:98 +msgid "The provided target object does not exist." +msgstr "" + +#: ../configure.zcml:38 msgid "Uninstalls the redturtle.volto add-on." msgstr "" +#: ../browser/configure.zcml:82 +msgid "View Document" +msgstr "" + +#: ../patches.py:48 +msgid "You have to enter a target." +msgstr "" + +#: ../patches.py:46 +msgid "You have to enter an alternative url." +msgstr "" + #. Default: "If enabled, users can't create contents with ids that are already used as aliases." -#: redturtle/volto/interfaces.py:36 +#: ../interfaces.py:41 msgid "check_aliases_in_namechooser_help" msgstr "" #. Default: "Disallow ids used in aliases" -#: redturtle/volto/interfaces.py:32 +#: ../interfaces.py:37 msgid "check_aliases_in_namechooser_label" msgstr "" +#. Default: "The id \"${id}\" is invalid because contains spaces." +#: ../adapters/namechooser.py:18 +msgid "check_spaces_in_shortname" +msgstr "msgstr "Il nome breve (id) non è valido perché contiene degli spazi." + #. Default: "If enabled, a custom ranking for SearchableText searches will be used." -#: redturtle/volto/interfaces.py:23 +#: ../interfaces.py:28 msgid "enable_advanced_query_ranking_help" msgstr "" #. Default: "Enable AdvancedQuery ranking" -#: redturtle/volto/interfaces.py:19 +#: ../interfaces.py:24 msgid "enable_advanced_query_ranking_label" msgstr "" #. Default: "The id \"${id}\" is invalid because there is already an alias for that path. Change its id or ask site administrators to remove \"${fixed_path}\" in aliases management." -#: redturtle/volto/adapters/namechooser.py:29 +#: ../adapters/namechooser.py:39 msgid "name_chooser_alias_error" msgstr "" #. Default: "Insert an external link directly into the field,or select an internal link clicking on the icon." -#: redturtle/volto/types/adapters.py:25 +#: ../types/adapters.py:25 msgid "remoteUrl_restapi_label" msgstr "" #. Default: "Volto Parent URL: Content url without \"/api\"." -#: redturtle/volto/adapters/stringinterp.py:35 +#: ../adapters/stringinterp.py:35 msgid "stringinterp_volto_parent_url" msgstr "" #. Default: "Volto URL: Content url without \"/api\"." -#: redturtle/volto/adapters/stringinterp.py:18 +#: ../adapters/stringinterp.py:18 msgid "stringinterp_volto_url" msgstr "" diff --git a/src/redturtle/volto/restapi/services/configure.zcml b/src/redturtle/volto/restapi/services/configure.zcml index c46bd846..25e3acbf 100644 --- a/src/redturtle/volto/restapi/services/configure.zcml +++ b/src/redturtle/volto/restapi/services/configure.zcml @@ -10,8 +10,6 @@ - - - - - - - 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..18b4bca9 --- /dev/null +++ b/src/redturtle/volto/tests/test_spaces_in_id.py @@ -0,0 +1,140 @@ +# -*- 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.interfaces import IRedTurtleVoltoSettings +from redturtle.volto.testing import REDTURTLE_VOLTO_INTEGRATION_TESTING +from zExceptions import BadRequest +from zope.container.interfaces import INameChooser + +import unittest + + +class FakeObject: + """""" + + def __of__(self, xxx): + pass + + +class TestNameChooserEnabled(unittest.TestCase): + """ """ + + layer = REDTURTLE_VOLTO_INTEGRATION_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"]) + + foo = api.content.create( + container=self.portal, + type="Document", + title="Foo", + ) + + self.bar = api.content.create( + container=self.portal, + type="Document", + title="Bar", + ) + + api.content.rename(obj=foo, new_id="xxx") + + # enable it + api.portal.set_registry_record( + "check_aliases_in_namechooser", True, interface=IRedTurtleVoltoSettings + ) + + def test_name_chooser_raise_badrequest_on_site_root(self): + """Test that we cannot choose an already created alias""" + fake_obj = FakeObject() + chooser = INameChooser(self.portal) + + # can set an unused id + self.assertEqual("unused-id", chooser.chooseName("unused id", fake_obj)) + + # default behavior when trying to use an already-created id + self.assertEqual("bar-1", chooser.chooseName("bar", fake_obj)) + + # raise exception if the name is an alias + with self.assertRaises(BadRequest) as cm: + chooser.chooseName("foo", fake_obj) + + self.assertEqual( + 'The id "foo" is invalid because there is already an alias for that path. Change its id or ask site administrators to remove "/foo" in aliases management.', + str(cm.exception), + ) + + def test_if_enabled_name_chooser_raise_badrequest_on_folderish_container(self): + """Test that we cannot choose an already created alias""" + + container = api.content.create( + container=self.portal, + type="Document", + title="container", + ) + + child = api.content.create( + container=container, + type="Document", + title="aaa", + ) + api.content.create( + container=container, + type="Document", + title="bbb", + ) + + api.content.rename(obj=child, new_id="xxx") + + fake_obj = FakeObject() + chooser = INameChooser(container) + + # can set an unused id + self.assertEqual("unused-id", chooser.chooseName("unused id", fake_obj)) + + # can set an alias is used in another path + self.assertEqual("foo", chooser.chooseName("foo", fake_obj)) + + # default behavior when trying to use an already-created id in this context + self.assertEqual("bbb-1", chooser.chooseName("bbb", fake_obj)) + + # raise exception if the name is an alias + with self.assertRaises(BadRequest) as cm: + chooser.chooseName("aaa", fake_obj) + + self.assertEqual( + 'The id "aaa" is invalid because there is already an alias for that path. Change its id or ask site administrators to remove "/container/aaa" in aliases management.', + str(cm.exception), + ) + + def test_api_rename_raise_exception_if_name_is_alias(self): + """Test that we cannot choose an already created alias""" + + item = api.content.create( + container=self.portal, + type="Document", + title="item", + ) + with self.assertRaises(BadRequest) as cm: + api.content.rename(obj=item, new_id="foo", safe_id=True) + + self.assertEqual( + 'The id "foo" is invalid because there is already an alias for that path. Change its id or ask site administrators to remove "/foo" in aliases management.', + str(cm.exception), + ) + + # without safe_id=True, InameChooser will not be called + res = api.content.rename(obj=item, new_id="foo") + self.assertEqual(res.getId(), "foo") + + def test_fix_id_if_contains_spaces(self): + """Test that id with spaces is fixed""" + obj = api.content.create( + container=self.portal, type="Document", title="test", id="aa bb" + ) + + self.assertEqual("aa-bb", obj.id) From 5f5090b2c6a98f51833fd5680c2073e8958b26c1 Mon Sep 17 00:00:00 2001 From: Daniele Andreotti Date: Sun, 5 Apr 2026 22:24:18 +0200 Subject: [PATCH 04/13] updated tests --- .../volto/tests/test_spaces_in_id.py | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/redturtle/volto/tests/test_spaces_in_id.py b/src/redturtle/volto/tests/test_spaces_in_id.py index 18b4bca9..153a94bd 100644 --- a/src/redturtle/volto/tests/test_spaces_in_id.py +++ b/src/redturtle/volto/tests/test_spaces_in_id.py @@ -5,9 +5,16 @@ from plone.app.testing import TEST_USER_ID from redturtle.volto.interfaces import IRedTurtleVoltoSettings from redturtle.volto.testing import REDTURTLE_VOLTO_INTEGRATION_TESTING +from redturtle.volto.testing import REDTURTLE_VOLTO_API_FUNCTIONAL_TESTING + from zExceptions import BadRequest from zope.container.interfaces import INameChooser +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 + import unittest @@ -131,10 +138,25 @@ def test_api_rename_raise_exception_if_name_is_alias(self): res = api.content.rename(obj=item, new_id="foo") self.assertEqual(res.getId(), "foo") + +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""" - obj = api.content.create( - container=self.portal, type="Document", title="test", id="aa bb" - ) + import pdb + + pdb.set_trace() + response = self.api_session.post("/@add", json={"source": ["/aa bb"]}) - self.assertEqual("aa-bb", obj.id) + self.assertEqual(response.json()["message"], "aa-bb") From 4954da9e9629373cfebf5d60993478ff43553c8b Mon Sep 17 00:00:00 2001 From: Daniele Andreotti Date: Sun, 5 Apr 2026 23:09:12 +0200 Subject: [PATCH 05/13] fixed test --- src/redturtle/volto/tests/test_spaces_in_id.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/redturtle/volto/tests/test_spaces_in_id.py b/src/redturtle/volto/tests/test_spaces_in_id.py index 153a94bd..3b3bd0c9 100644 --- a/src/redturtle/volto/tests/test_spaces_in_id.py +++ b/src/redturtle/volto/tests/test_spaces_in_id.py @@ -12,7 +12,6 @@ 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 import unittest @@ -154,9 +153,13 @@ def setUp(self): def test_fix_id_if_contains_spaces(self): """Test that id with spaces is fixed""" - import pdb - - pdb.set_trace() - response = self.api_session.post("/@add", json={"source": ["/aa bb"]}) + response = self.api_session.post( + self.portal_url, + json={ + "@type": "Document", + "title": "My doc", + "id": "aa bb", + }, + ) - self.assertEqual(response.json()["message"], "aa-bb") + self.assertEqual(response.json()["id"], "aa-bb") From ba6c51304ccd76ef3443240bacac90e1114f841f Mon Sep 17 00:00:00 2001 From: Daniele Andreotti Date: Tue, 7 Apr 2026 09:51:15 +0200 Subject: [PATCH 06/13] clean up --- .../volto/tests/test_spaces_in_id.py | 128 ------------------ 1 file changed, 128 deletions(-) diff --git a/src/redturtle/volto/tests/test_spaces_in_id.py b/src/redturtle/volto/tests/test_spaces_in_id.py index 3b3bd0c9..0669439a 100644 --- a/src/redturtle/volto/tests/test_spaces_in_id.py +++ b/src/redturtle/volto/tests/test_spaces_in_id.py @@ -1,15 +1,8 @@ # -*- 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.interfaces import IRedTurtleVoltoSettings -from redturtle.volto.testing import REDTURTLE_VOLTO_INTEGRATION_TESTING from redturtle.volto.testing import REDTURTLE_VOLTO_API_FUNCTIONAL_TESTING - -from zExceptions import BadRequest -from zope.container.interfaces import INameChooser - from plone.app.testing import SITE_OWNER_NAME from plone.app.testing import SITE_OWNER_PASSWORD from plone.restapi.testing import RelativeSession @@ -17,127 +10,6 @@ import unittest -class FakeObject: - """""" - - def __of__(self, xxx): - pass - - -class TestNameChooserEnabled(unittest.TestCase): - """ """ - - layer = REDTURTLE_VOLTO_INTEGRATION_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"]) - - foo = api.content.create( - container=self.portal, - type="Document", - title="Foo", - ) - - self.bar = api.content.create( - container=self.portal, - type="Document", - title="Bar", - ) - - api.content.rename(obj=foo, new_id="xxx") - - # enable it - api.portal.set_registry_record( - "check_aliases_in_namechooser", True, interface=IRedTurtleVoltoSettings - ) - - def test_name_chooser_raise_badrequest_on_site_root(self): - """Test that we cannot choose an already created alias""" - fake_obj = FakeObject() - chooser = INameChooser(self.portal) - - # can set an unused id - self.assertEqual("unused-id", chooser.chooseName("unused id", fake_obj)) - - # default behavior when trying to use an already-created id - self.assertEqual("bar-1", chooser.chooseName("bar", fake_obj)) - - # raise exception if the name is an alias - with self.assertRaises(BadRequest) as cm: - chooser.chooseName("foo", fake_obj) - - self.assertEqual( - 'The id "foo" is invalid because there is already an alias for that path. Change its id or ask site administrators to remove "/foo" in aliases management.', - str(cm.exception), - ) - - def test_if_enabled_name_chooser_raise_badrequest_on_folderish_container(self): - """Test that we cannot choose an already created alias""" - - container = api.content.create( - container=self.portal, - type="Document", - title="container", - ) - - child = api.content.create( - container=container, - type="Document", - title="aaa", - ) - api.content.create( - container=container, - type="Document", - title="bbb", - ) - - api.content.rename(obj=child, new_id="xxx") - - fake_obj = FakeObject() - chooser = INameChooser(container) - - # can set an unused id - self.assertEqual("unused-id", chooser.chooseName("unused id", fake_obj)) - - # can set an alias is used in another path - self.assertEqual("foo", chooser.chooseName("foo", fake_obj)) - - # default behavior when trying to use an already-created id in this context - self.assertEqual("bbb-1", chooser.chooseName("bbb", fake_obj)) - - # raise exception if the name is an alias - with self.assertRaises(BadRequest) as cm: - chooser.chooseName("aaa", fake_obj) - - self.assertEqual( - 'The id "aaa" is invalid because there is already an alias for that path. Change its id or ask site administrators to remove "/container/aaa" in aliases management.', - str(cm.exception), - ) - - def test_api_rename_raise_exception_if_name_is_alias(self): - """Test that we cannot choose an already created alias""" - - item = api.content.create( - container=self.portal, - type="Document", - title="item", - ) - with self.assertRaises(BadRequest) as cm: - api.content.rename(obj=item, new_id="foo", safe_id=True) - - self.assertEqual( - 'The id "foo" is invalid because there is already an alias for that path. Change its id or ask site administrators to remove "/foo" in aliases management.', - str(cm.exception), - ) - - # without safe_id=True, InameChooser will not be called - res = api.content.rename(obj=item, new_id="foo") - self.assertEqual(res.getId(), "foo") - - class TestCreation(unittest.TestCase): layer = REDTURTLE_VOLTO_API_FUNCTIONAL_TESTING From ac83b61bc8c22f51f490b8ed19412c949b6fee5b Mon Sep 17 00:00:00 2001 From: Daniele Andreotti Date: Tue, 7 Apr 2026 14:47:46 +0200 Subject: [PATCH 07/13] restored original file for translation --- .../locales/it/LC_MESSAGES/redturtle.volto.po | 85 ++++--------------- .../volto/locales/redturtle.volto.pot | 85 ++++--------------- 2 files changed, 36 insertions(+), 134 deletions(-) diff --git a/src/redturtle/volto/locales/it/LC_MESSAGES/redturtle.volto.po b/src/redturtle/volto/locales/it/LC_MESSAGES/redturtle.volto.po index d1ec7f2c..f01e3dd0 100644 --- a/src/redturtle/volto/locales/it/LC_MESSAGES/redturtle.volto.po +++ b/src/redturtle/volto/locales/it/LC_MESSAGES/redturtle.volto.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2026-04-02 13:24+0000\n" +"POT-Creation-Date: 2024-03-28 10:20+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -14,124 +14,75 @@ msgstr "" "Preferred-Encodings: utf-8 latin1\n" "Domain: DOMAIN\n" -#: ../patches.py:62 -msgid "Alternative url path must not be a view." -msgstr "" - -#: ../patches.py:51 -msgid "Alternative url path must start with a slash." -msgstr "" - -#: ../patches.py:92 -msgid "Cannot use a working path as alternative url." -msgstr "" - -#: ../configure.zcml:29 +#: redturtle/volto/configure.zcml:29 msgid "Installs the redturtle.volto add-on." msgstr "" -#: ../profiles/default/registry/criteria.xml +#: redturtle/volto/profiles/default/registry/criteria.xml msgid "Metadata" msgstr "" -#: ../browser/controlpanel.py:11 -#: ../profiles/default/controlpanel.xml +#: redturtle/volto/browser/controlpanel.py:11 +#: redturtle/volto/profiles/default/controlpanel.xml msgid "RedTurtle Volto Settings" msgstr "RedTurtle Volto" -#: ../configure.zcml:29 +#: redturtle/volto/configure.zcml:29 msgid "RedTurtle: Volto" msgstr "" -#: ../configure.zcml:46 -msgid "RedTurtle: Volto (to 4307)" -msgstr "" - -#: ../configure.zcml:38 +#: redturtle/volto/configure.zcml:38 msgid "RedTurtle: Volto (uninstall)" msgstr "" -#: ../profiles/default/registry/criteria.xml +#: redturtle/volto/profiles/default/registry/criteria.xml msgid "Select False to show only elements not excluded from navigation." msgstr "Seleziona False per mostrare solo gli elementi non omessi dalla navigazione." -#: ../profiles/default/registry/criteria.xml +#: redturtle/volto/profiles/default/registry/criteria.xml msgid "Show elements excluded from navigation" msgstr "Elementi omessi dalla navigazione" -#: ../patches.py:64 -msgid "Target path must not be a view." -msgstr "" - -#: ../patches.py:59 -msgid "Target path must start with a slash." -msgstr "" - -#: ../patches.py:74 -msgid "The provided alternative url already exists!" -msgstr "" - -#: ../patches.py:98 -msgid "The provided target object does not exist." -msgstr "" - -#: ../configure.zcml:38 +#: redturtle/volto/configure.zcml:38 msgid "Uninstalls the redturtle.volto add-on." msgstr "" -#: ../browser/configure.zcml:82 -msgid "View Document" -msgstr "" - -#: ../patches.py:48 -msgid "You have to enter a target." -msgstr "" - -#: ../patches.py:46 -msgid "You have to enter an alternative url." -msgstr "" - #. Default: "If enabled, users can't create contents with ids that are already used as aliases." -#: ../interfaces.py:41 +#: redturtle/volto/interfaces.py:36 msgid "check_aliases_in_namechooser_help" msgstr "Se attivato, alla creazione o rinomina di un contenuto, verrà eseguito anche un controllo su eventuali alias presenti (quelli visibili in Gestione URL), ed eventualmente viene impedita la creazione con quell'id." #. Default: "Disallow ids used in aliases" -#: ../interfaces.py:37 +#: redturtle/volto/interfaces.py:32 msgid "check_aliases_in_namechooser_label" msgstr "Controllo degli id anche sugli alias" -#. Default: "The id \"${id}\" is invalid because contains spaces." -#: ../adapters/namechooser.py:18 -msgid "check_spaces_in_shortname" -msgstr "Il nome breve (id) non è valido perché contiene degli spazi." - #. Default: "If enabled, a custom ranking for SearchableText searches will be used." -#: ../interfaces.py:28 +#: redturtle/volto/interfaces.py:23 msgid "enable_advanced_query_ranking_help" msgstr "Se abilitato, verrà utilizzato un ranking custom per la ricerca testuale." #. Default: "Enable AdvancedQuery ranking" -#: ../interfaces.py:24 +#: redturtle/volto/interfaces.py:19 msgid "enable_advanced_query_ranking_label" msgstr "Abilita ranking custom con AdvancedQuery" #. Default: "The id \"${id}\" is invalid because there is already an alias for that path. Change its id or ask site administrators to remove \"${fixed_path}\" in aliases management." -#: ../adapters/namechooser.py:39 +#: redturtle/volto/adapters/namechooser.py:29 msgid "name_chooser_alias_error" msgstr "L'id \"${id}\" non è valido perché già utilizzato per un alias. Modifca l'id oppure rivolgiti agli amministratori per cancellare l'alias esistente: \"${fixed_path}\"." #. Default: "Insert an external link directly into the field,or select an internal link clicking on the icon." -#: ../types/adapters.py:25 +#: redturtle/volto/types/adapters.py:25 msgid "remoteUrl_restapi_label" msgstr "Inserisci un link esterno direttamente nel campo, oppure seleziona un collegamento ad un contenuto del sito cliccando sull'icona accanto." #. Default: "Volto Parent URL: Content url without \"/api\"." -#: ../adapters/stringinterp.py:35 +#: redturtle/volto/adapters/stringinterp.py:35 msgid "stringinterp_volto_parent_url" msgstr "" #. Default: "Volto URL: Content url without \"/api\"." -#: ../adapters/stringinterp.py:18 +#: redturtle/volto/adapters/stringinterp.py:18 msgid "stringinterp_volto_url" msgstr "Volto URL: URL del contenuto Plone senza \"/api\"." diff --git a/src/redturtle/volto/locales/redturtle.volto.pot b/src/redturtle/volto/locales/redturtle.volto.pot index 0e7245e0..a33cfec1 100644 --- a/src/redturtle/volto/locales/redturtle.volto.pot +++ b/src/redturtle/volto/locales/redturtle.volto.pot @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2026-04-02 13:24+0000\n" +"POT-Creation-Date: 2024-03-28 12:42+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,124 +17,75 @@ msgstr "" "Preferred-Encodings: utf-8 latin1\n" "Domain: redturtle.volto\n" -#: ../patches.py:62 -msgid "Alternative url path must not be a view." -msgstr "" - -#: ../patches.py:51 -msgid "Alternative url path must start with a slash." -msgstr "" - -#: ../patches.py:92 -msgid "Cannot use a working path as alternative url." -msgstr "" - -#: ../configure.zcml:29 +#: redturtle/volto/configure.zcml:29 msgid "Installs the redturtle.volto add-on." msgstr "" -#: ../profiles/default/registry/criteria.xml +#: redturtle/volto/profiles/default/registry/criteria.xml msgid "Metadata" msgstr "" -#: ../browser/controlpanel.py:11 -#: ../profiles/default/controlpanel.xml +#: redturtle/volto/browser/controlpanel.py:11 +#: redturtle/volto/profiles/default/controlpanel.xml msgid "RedTurtle Volto Settings" msgstr "" -#: ../configure.zcml:29 +#: redturtle/volto/configure.zcml:29 msgid "RedTurtle: Volto" msgstr "" -#: ../configure.zcml:46 -msgid "RedTurtle: Volto (to 4307)" -msgstr "" - -#: ../configure.zcml:38 +#: redturtle/volto/configure.zcml:38 msgid "RedTurtle: Volto (uninstall)" msgstr "" -#: ../profiles/default/registry/criteria.xml +#: redturtle/volto/profiles/default/registry/criteria.xml msgid "Select False to show only elements not excluded from navigation." msgstr "" -#: ../profiles/default/registry/criteria.xml +#: redturtle/volto/profiles/default/registry/criteria.xml msgid "Show elements excluded from navigation" msgstr "" -#: ../patches.py:64 -msgid "Target path must not be a view." -msgstr "" - -#: ../patches.py:59 -msgid "Target path must start with a slash." -msgstr "" - -#: ../patches.py:74 -msgid "The provided alternative url already exists!" -msgstr "" - -#: ../patches.py:98 -msgid "The provided target object does not exist." -msgstr "" - -#: ../configure.zcml:38 +#: redturtle/volto/configure.zcml:38 msgid "Uninstalls the redturtle.volto add-on." msgstr "" -#: ../browser/configure.zcml:82 -msgid "View Document" -msgstr "" - -#: ../patches.py:48 -msgid "You have to enter a target." -msgstr "" - -#: ../patches.py:46 -msgid "You have to enter an alternative url." -msgstr "" - #. Default: "If enabled, users can't create contents with ids that are already used as aliases." -#: ../interfaces.py:41 +#: redturtle/volto/interfaces.py:36 msgid "check_aliases_in_namechooser_help" msgstr "" #. Default: "Disallow ids used in aliases" -#: ../interfaces.py:37 +#: redturtle/volto/interfaces.py:32 msgid "check_aliases_in_namechooser_label" msgstr "" -#. Default: "The id \"${id}\" is invalid because contains spaces." -#: ../adapters/namechooser.py:18 -msgid "check_spaces_in_shortname" -msgstr "msgstr "Il nome breve (id) non è valido perché contiene degli spazi." - #. Default: "If enabled, a custom ranking for SearchableText searches will be used." -#: ../interfaces.py:28 +#: redturtle/volto/interfaces.py:23 msgid "enable_advanced_query_ranking_help" msgstr "" #. Default: "Enable AdvancedQuery ranking" -#: ../interfaces.py:24 +#: redturtle/volto/interfaces.py:19 msgid "enable_advanced_query_ranking_label" msgstr "" #. Default: "The id \"${id}\" is invalid because there is already an alias for that path. Change its id or ask site administrators to remove \"${fixed_path}\" in aliases management." -#: ../adapters/namechooser.py:39 +#: redturtle/volto/adapters/namechooser.py:29 msgid "name_chooser_alias_error" msgstr "" #. Default: "Insert an external link directly into the field,or select an internal link clicking on the icon." -#: ../types/adapters.py:25 +#: redturtle/volto/types/adapters.py:25 msgid "remoteUrl_restapi_label" msgstr "" #. Default: "Volto Parent URL: Content url without \"/api\"." -#: ../adapters/stringinterp.py:35 +#: redturtle/volto/adapters/stringinterp.py:35 msgid "stringinterp_volto_parent_url" msgstr "" #. Default: "Volto URL: Content url without \"/api\"." -#: ../adapters/stringinterp.py:18 +#: redturtle/volto/adapters/stringinterp.py:18 msgid "stringinterp_volto_url" msgstr "" From 42e84f6513f4c37e4f041b0f2ed892329e5aa4d1 Mon Sep 17 00:00:00 2001 From: Daniele Andreotti Date: Tue, 7 Apr 2026 14:49:32 +0200 Subject: [PATCH 08/13] updated CHANGES.rst --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 9bb306e2..a6c973b3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,7 +4,7 @@ Changelog 5.9.5 (unreleased) ------------------ -- Check spaces in shortname when adding a content. +- Fixed empty spaces in shortname when adding a content. [daniele] From dcdaa7461cd5a12c9dc0fb89d85324afff7cfc2e Mon Sep 17 00:00:00 2001 From: Daniele Andreotti Date: Wed, 8 Apr 2026 14:44:13 +0200 Subject: [PATCH 09/13] isort --- src/redturtle/volto/events.py | 1 - src/redturtle/volto/tests/test_spaces_in_id.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/redturtle/volto/events.py b/src/redturtle/volto/events.py index a06f0ed1..f4e83043 100644 --- a/src/redturtle/volto/events.py +++ b/src/redturtle/volto/events.py @@ -1,7 +1,6 @@ from plone import api from redturtle.volto.adapters.namechooser import NormalizingNameChooser - import os diff --git a/src/redturtle/volto/tests/test_spaces_in_id.py b/src/redturtle/volto/tests/test_spaces_in_id.py index 0669439a..783e6a7a 100644 --- a/src/redturtle/volto/tests/test_spaces_in_id.py +++ b/src/redturtle/volto/tests/test_spaces_in_id.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- """Setup tests for this package.""" from plone.app.testing import setRoles -from plone.app.testing import TEST_USER_ID -from redturtle.volto.testing import REDTURTLE_VOLTO_API_FUNCTIONAL_TESTING 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 From 6cb48aa9d5dbe94d7f192c3c0a52d63e57cbfe22 Mon Sep 17 00:00:00 2001 From: Daniele Andreotti Date: Wed, 8 Apr 2026 14:51:50 +0200 Subject: [PATCH 10/13] blacked --- src/redturtle/volto/__init__.py | 1 - src/redturtle/volto/adapters/rss.py | 1 - src/redturtle/volto/adapters/stringinterp.py | 1 - src/redturtle/volto/browser/find_blocks.py | 1 - src/redturtle/volto/browser/find_broken_links.py | 2 -- src/redturtle/volto/browser/fix_links.py | 1 - src/redturtle/volto/config/__init__.py | 1 - src/redturtle/volto/indexers.py | 1 - src/redturtle/volto/locales/update.py | 1 - src/redturtle/volto/monkey.py | 1 - src/redturtle/volto/patches.py | 1 - src/redturtle/volto/restapi/deserializer/blocks.py | 1 - src/redturtle/volto/restapi/serializer/blocks.py | 1 - src/redturtle/volto/restapi/serializer/dxfields.py | 1 - src/redturtle/volto/restapi/serializer/summary.py | 1 - src/redturtle/volto/restapi/services/querystringsearch/get.py | 1 - src/redturtle/volto/restapi/services/search/get.py | 1 - src/redturtle/volto/setuphandlers.py | 1 - src/redturtle/volto/tests/test_blocks_linkintegrity.py | 1 - src/redturtle/volto/tests/test_content_types.py | 1 + src/redturtle/volto/tests/test_ct_link.py | 1 + src/redturtle/volto/tests/test_namechooser_customization.py | 1 + src/redturtle/volto/tests/test_patch_absolutize_path.py | 1 + src/redturtle/volto/tests/test_rss.py | 2 +- src/redturtle/volto/tests/test_setup.py | 2 +- src/redturtle/volto/tests/test_spaces_in_id.py | 1 + src/redturtle/volto/types/adapters.py | 1 + src/redturtle/volto/upgrades.py | 2 -- 28 files changed, 8 insertions(+), 24 deletions(-) diff --git a/src/redturtle/volto/__init__.py b/src/redturtle/volto/__init__.py index ca68948b..3ccac0ed 100644 --- a/src/redturtle/volto/__init__.py +++ b/src/redturtle/volto/__init__.py @@ -4,7 +4,6 @@ from redturtle.volto import patches # noqa from zope.i18nmessageid import MessageFactory - _ = MessageFactory("redturtle.volto") # Index also subjects in SearchableText. 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 index 783e6a7a..ea5aa4f7 100644 --- a/src/redturtle/volto/tests/test_spaces_in_id.py +++ b/src/redturtle/volto/tests/test_spaces_in_id.py @@ -1,5 +1,6 @@ # -*- 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 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 From e5f175a7f48f1f5469280fee844b79525baad3c3 Mon Sep 17 00:00:00 2001 From: Daniele Andreotti Date: Wed, 8 Apr 2026 14:57:47 +0200 Subject: [PATCH 11/13] sorted --- docs/conf.py | 1 - setup.py | 1 - 2 files changed, 2 deletions(-) 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(), From bde78f55db538f92922092a1c8b0b3010c1fc752 Mon Sep 17 00:00:00 2001 From: Daniele Andreotti Date: Tue, 19 May 2026 15:08:57 +0200 Subject: [PATCH 12/13] fixed tests, improved check on id --- src/redturtle/volto/__init__.py | 54 +++++++++++++++++++ src/redturtle/volto/adapters/namechooser.py | 2 + src/redturtle/volto/configure.zcml | 6 --- src/redturtle/volto/events.py | 8 --- .../volto/tests/test_spaces_in_id.py | 29 ++++++++++ 5 files changed, 85 insertions(+), 14 deletions(-) diff --git a/src/redturtle/volto/__init__.py b/src/redturtle/volto/__init__.py index 3ccac0ed..2bdc1917 100644 --- a/src/redturtle/volto/__init__.py +++ b/src/redturtle/volto/__init__.py @@ -3,8 +3,62 @@ from plone.app.dexterity.textindexer import utils from redturtle.volto import patches # noqa from zope.i18nmessageid import MessageFactory +from plone.restapi.services.content import utils as rest_utils + +from Acquisition import aq_base +from plone.app.content.interfaces import INameFromTitle +from plone.app.uuid.utils import uuidToObject +from plone.restapi.bbb import base_hasattr +from plone.uuid.interfaces import IUUID +from zope.container.contained import notifyContainerModified +from zope.container.contained import ObjectAddedEvent +from zope.container.interfaces import INameChooser +from zope.event import notify _ = 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/configure.zcml b/src/redturtle/volto/configure.zcml index daa65bdd..00c6d078 100644 --- a/src/redturtle/volto/configure.zcml +++ b/src/redturtle/volto/configure.zcml @@ -55,12 +55,6 @@ handler=".events.manage_auth_token" /> - - diff --git a/src/redturtle/volto/events.py b/src/redturtle/volto/events.py index f4e83043..0b00b88a 100644 --- a/src/redturtle/volto/events.py +++ b/src/redturtle/volto/events.py @@ -1,6 +1,3 @@ -from plone import api -from redturtle.volto.adapters.namechooser import NormalizingNameChooser - import os @@ -19,8 +16,3 @@ def manage_auth_token(event): auth_token = request.cookies.get("auth_token") if auth_token: request._auth = "Bearer " + auth_token - - -def avoid_spaces_in_shortname(obj, event): - fixed_id = NormalizingNameChooser(obj).chooseName(obj.id, obj) - api.content.rename(obj, new_id=fixed_id) diff --git a/src/redturtle/volto/tests/test_spaces_in_id.py b/src/redturtle/volto/tests/test_spaces_in_id.py index ea5aa4f7..3e91db86 100644 --- a/src/redturtle/volto/tests/test_spaces_in_id.py +++ b/src/redturtle/volto/tests/test_spaces_in_id.py @@ -26,6 +26,7 @@ def setUp(self): def test_fix_id_if_contains_spaces(self): """Test that id with spaces is fixed""" + response = self.api_session.post( self.portal_url, json={ @@ -36,3 +37,31 @@ def test_fix_id_if_contains_spaces(self): ) 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) From c8d3f6412aa06ce5aa1365e88e3bc25af6a7f559 Mon Sep 17 00:00:00 2001 From: Daniele Andreotti Date: Tue, 19 May 2026 15:18:33 +0200 Subject: [PATCH 13/13] sorted import --- src/redturtle/volto/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/redturtle/volto/__init__.py b/src/redturtle/volto/__init__.py index 2bdc1917..198bda69 100644 --- a/src/redturtle/volto/__init__.py +++ b/src/redturtle/volto/__init__.py @@ -1,19 +1,19 @@ # -*- coding: utf-8 -*- -from plone.app.dexterity.behaviors.metadata import ICategorization -from plone.app.dexterity.textindexer import utils -from redturtle.volto import patches # noqa -from zope.i18nmessageid import MessageFactory -from plone.restapi.services.content import utils as rest_utils 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")