diff --git a/.github/workflows/black.yml b/.github/workflows/black.yml index 9b23c028..9d8d08f4 100644 --- a/.github/workflows/black.yml +++ b/.github/workflows/black.yml @@ -6,30 +6,25 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.8] + python-version: [3.11] steps: - # git checkout - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - # python setup - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - # python cache - - uses: actions/cache@v1 + - uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} restore-keys: | ${{ runner.os }}-pip- - # install black - name: install black run: pip install black - # run black - name: run black run: black src/ --check --diff diff --git a/.github/workflows/flake8.yml b/.github/workflows/flake8.yml index 97c7dfa4..8300d45f 100644 --- a/.github/workflows/flake8.yml +++ b/.github/workflows/flake8.yml @@ -6,30 +6,25 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.8] + python-version: [3.11] steps: - # git checkout - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - # python setup - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - # python cache - - uses: actions/cache@v1 + - uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} restore-keys: | ${{ runner.os }}-pip- - # install flake8 - name: install flake8 run: pip install flake8 - # run black - name: run flake8 run: flake8 src/ setup.py diff --git a/.github/workflows/isort.yaml b/.github/workflows/isort.yaml index 039aeb05..050b2a4c 100644 --- a/.github/workflows/isort.yaml +++ b/.github/workflows/isort.yaml @@ -6,5 +6,5 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: isort/isort-action@v1 diff --git a/.github/workflows/pyroma.yml b/.github/workflows/pyroma.yml index bbad3c28..038fdd83 100644 --- a/.github/workflows/pyroma.yml +++ b/.github/workflows/pyroma.yml @@ -6,30 +6,25 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.8] + python-version: [3.11] steps: - # git checkout - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - # python setup - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - # python cache - - uses: actions/cache@v1 + - uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} restore-keys: | ${{ runner.os }}-pip- - # install pyroma - name: install pyroma run: pip install pyroma - # run pyroma - name: run pyroma run: pyroma -n 10 -d . diff --git a/.gitignore b/.gitignore index e33788dd..1e7140e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +__pycache__ .DS_Store pyvenv.cfg .coverage diff --git a/CHANGES.rst b/CHANGES.rst index 5b013fee..e7221ef7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,11 +1,30 @@ Changelog ========= -5.4.9 (unreleased) ------------------- +5.4.10 (unreleased) +------------------- + +- Fixed limit event occurrences to 100. + [eikichi18] -- Nothing changed yet. +5.4.9 (2024-04-22) +------------------ + +- Limit event occurrences to 100. + [mamico] +- Customize INameChooser adapter to check also alias ids and disallow to create contents that could override aliases. + [cekk] +- Customize also `copy` and `move` endpoints to raise BadRequest if that action will override some aliases. + [cekk] +- Add flag in controlpanel to enable/disable INameChooser customization. + [cekk] +- Exclude bg_color from transformed fields in deserializer. + [cekk] +- Uninstall collective.volto.cookieconsent (deprecated). Will be removed from dependencies in next releases. + [cekk] +- Add dependency to collective.volto.gdprcookie and install it by default. + [cekk] 5.4.8 (2024-03-19) ------------------ diff --git a/buildout.cfg b/buildout.cfg index d7db0388..6bd3272b 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -3,3 +3,6 @@ # use this extend one of the buildout configuration: extends = test_plone60.cfg + +[versions] +plone.restapi = 9.7.0 diff --git a/setup.py b/setup.py index b2c68538..c2648a3f 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name="redturtle.volto", - version="5.4.9.dev0", + version="5.4.10.dev0", description="Helper package to setup a RedTurtle's Plone site ready to work with Volto.", long_description=long_description, # Get more from https://pypi.org/classifiers/ @@ -54,13 +54,15 @@ python_requires=">=3.7", install_requires=[ "setuptools", - "collective.volto.cookieconsent", + "collective.volto.cookieconsent", # this will be uninstalled and removed soon. + "collective.volto.gdprcookie", "collective.monkeypatcher", "collective.purgebyid", "kitconcept.seo>=2.0.0", "plone.volto>=4.0.0", "plone.restapi>=9.6.0", "Products.PortalTransforms>=3.2.0", + "z3c.jbot", ], extras_require={ "advancedquery": [ diff --git a/src/redturtle/volto/adapters/configure.zcml b/src/redturtle/volto/adapters/configure.zcml index ec239441..5b1cd0cf 100644 --- a/src/redturtle/volto/adapters/configure.zcml +++ b/src/redturtle/volto/adapters/configure.zcml @@ -55,4 +55,15 @@ zcml:condition="not-have plone-60" /> + + + diff --git a/src/redturtle/volto/adapters/namechooser.py b/src/redturtle/volto/adapters/namechooser.py new file mode 100644 index 00000000..fdf3f965 --- /dev/null +++ b/src/redturtle/volto/adapters/namechooser.py @@ -0,0 +1,47 @@ +from Acquisition import aq_inner +from plone import api +from plone.app.content.namechooser import ( + NormalizingNameChooser as BaseNormalizingNameChooser, +) +from plone.app.redirector.interfaces import IRedirectionStorage +from redturtle.volto import _ +from redturtle.volto.interfaces import IRedTurtleVoltoSettings +from zExceptions import BadRequest +from zope.component import getUtility + + +def check_alias(context, id): + context = aq_inner(context) + try: + if not api.portal.get_registry_record( + "check_aliases_in_namechooser", + interface=IRedTurtleVoltoSettings, + default=False, + ): + return + except KeyError: + return + storage = getUtility(IRedirectionStorage) + path = "/".join(context.getPhysicalPath()) + "/" + id + if storage.get(path): + portal_path = "/".join(api.portal.get().getPhysicalPath()) + fixed_path = path.replace(portal_path, "") + msg = _( + "name_chooser_alias_error", + 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.', + mapping={"id": id, "fixed_path": fixed_path}, + ) + raise BadRequest(api.portal.translate(msg)) + + +class NormalizingNameChooser(BaseNormalizingNameChooser): + 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/interfaces.py b/src/redturtle/volto/interfaces.py index e495492a..29ec4cf6 100644 --- a/src/redturtle/volto/interfaces.py +++ b/src/redturtle/volto/interfaces.py @@ -27,3 +27,16 @@ class IRedTurtleVoltoSettings(Interface): default=False, required=False, ) + + check_aliases_in_namechooser = Bool( + title=_( + "check_aliases_in_namechooser_label", + default="Disallow ids used in aliases", + ), + description=_( + "check_aliases_in_namechooser_help", + default="If enabled, users can't create contents with ids that are already used as aliases.", + ), + default=False, + required=False, + ) 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 f6683a22..32f7ed60 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: 2023-03-16 14:54+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,7 +14,7 @@ msgstr "" "Preferred-Encodings: utf-8 latin1\n" "Domain: DOMAIN\n" -#: redturtle/volto/configure.zcml:28 +#: redturtle/volto/configure.zcml:29 msgid "Installs the redturtle.volto add-on." msgstr "" @@ -27,11 +27,11 @@ msgstr "" msgid "RedTurtle Volto Settings" msgstr "Impostazioni RedTurtle Volto" -#: redturtle/volto/configure.zcml:28 +#: redturtle/volto/configure.zcml:29 msgid "RedTurtle: Volto" msgstr "" -#: redturtle/volto/configure.zcml:37 +#: redturtle/volto/configure.zcml:38 msgid "RedTurtle: Volto (uninstall)" msgstr "" @@ -43,31 +43,46 @@ msgstr "Seleziona False per mostrare solo gli elementi non omessi dalla navigazi msgid "Show elements excluded from navigation" msgstr "Elementi omessi dalla navigazione" -#: redturtle/volto/configure.zcml:37 +#: redturtle/volto/configure.zcml:38 msgid "Uninstalls the redturtle.volto add-on." msgstr "" +#. Default: "If enabled, users can't create contents with ids that are already used as aliases." +#: 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" +#: redturtle/volto/interfaces.py:32 +msgid "check_aliases_in_namechooser_label" +msgstr "Controllo degli id anche sugli alias" + #. Default: "If enabled, a custom ranking for SearchableText searches will be used." -#: redturtle/volto/interfaces.py:24 +#: 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" -#: redturtle/volto/interfaces.py:20 +#: 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." +#: 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." #: 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\"." -#: redturtle/volto/adapters/stringinterp.py:31 +#: redturtle/volto/adapters/stringinterp.py:35 msgid "stringinterp_volto_parent_url" msgstr "" #. Default: "Volto URL: Content url without \"/api\"." -#: redturtle/volto/adapters/stringinterp.py:13 +#: 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 a940d56c..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: 2023-03-16 14:54+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,7 +17,7 @@ msgstr "" "Preferred-Encodings: utf-8 latin1\n" "Domain: redturtle.volto\n" -#: redturtle/volto/configure.zcml:28 +#: redturtle/volto/configure.zcml:29 msgid "Installs the redturtle.volto add-on." msgstr "" @@ -30,11 +30,11 @@ msgstr "" msgid "RedTurtle Volto Settings" msgstr "" -#: redturtle/volto/configure.zcml:28 +#: redturtle/volto/configure.zcml:29 msgid "RedTurtle: Volto" msgstr "" -#: redturtle/volto/configure.zcml:37 +#: redturtle/volto/configure.zcml:38 msgid "RedTurtle: Volto (uninstall)" msgstr "" @@ -46,31 +46,46 @@ msgstr "" msgid "Show elements excluded from navigation" msgstr "" -#: redturtle/volto/configure.zcml:37 +#: redturtle/volto/configure.zcml:38 msgid "Uninstalls the redturtle.volto add-on." msgstr "" +#. Default: "If enabled, users can't create contents with ids that are already used as aliases." +#: redturtle/volto/interfaces.py:36 +msgid "check_aliases_in_namechooser_help" +msgstr "" + +#. Default: "Disallow ids used in aliases" +#: redturtle/volto/interfaces.py:32 +msgid "check_aliases_in_namechooser_label" +msgstr "" + #. Default: "If enabled, a custom ranking for SearchableText searches will be used." -#: redturtle/volto/interfaces.py:24 +#: redturtle/volto/interfaces.py:23 msgid "enable_advanced_query_ranking_help" msgstr "" #. Default: "Enable AdvancedQuery ranking" -#: redturtle/volto/interfaces.py:20 +#: 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." +#: 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." #: redturtle/volto/types/adapters.py:25 msgid "remoteUrl_restapi_label" msgstr "" #. Default: "Volto Parent URL: Content url without \"/api\"." -#: redturtle/volto/adapters/stringinterp.py:31 +#: redturtle/volto/adapters/stringinterp.py:35 msgid "stringinterp_volto_parent_url" msgstr "" #. Default: "Volto URL: Content url without \"/api\"." -#: redturtle/volto/adapters/stringinterp.py:13 +#: redturtle/volto/adapters/stringinterp.py:18 msgid "stringinterp_volto_url" msgstr "" diff --git a/src/redturtle/volto/monkey.py b/src/redturtle/volto/monkey.py index 609aa18b..180888ed 100644 --- a/src/redturtle/volto/monkey.py +++ b/src/redturtle/volto/monkey.py @@ -14,9 +14,13 @@ from zope.globalrequest import getRequest import datetime +import logging import os +logger = logging.getLogger(__name__) + + def occurrences(self, range_start=None, range_end=None): """Return all occurrences of an event, possibly within a start and end limit. @@ -94,7 +98,15 @@ def get_obj(start): id=str(start.date()), start=start, end=start + duration ).__of__(self.context) + limit = 100 for start in starts: + if limit < 0: + logger.warning( + "Too many occurrences for %s, stopping at 100", + self.context.absolute_url(), + ) + return + limit -= 1 yield get_obj(start) diff --git a/src/redturtle/volto/profiles/default/metadata.xml b/src/redturtle/volto/profiles/default/metadata.xml index f40d93d8..a10e361f 100644 --- a/src/redturtle/volto/profiles/default/metadata.xml +++ b/src/redturtle/volto/profiles/default/metadata.xml @@ -1,10 +1,10 @@ - 4303 + 4305 profile-plone.volto:default profile-plone.app.caching:with-caching-proxy - profile-collective.volto.cookieconsent:default + profile-collective.volto.gdprcookie:default profile-kitconcept.seo:default diff --git a/src/redturtle/volto/restapi/deserializer/blocks.py b/src/redturtle/volto/restapi/deserializer/blocks.py index da3d4a90..78e6610c 100644 --- a/src/redturtle/volto/restapi/deserializer/blocks.py +++ b/src/redturtle/volto/restapi/deserializer/blocks.py @@ -9,8 +9,14 @@ from zope.interface import implementer -EXCLUDE_KEYS = ["@type", "token", "value", "@id", "query"] -EXCLUDE_TYPES = ["title", "listing", "calendar", "searchEvents", "form"] +EXCLUDE_KEYS = ["@type", "token", "value", "@id", "query", "bg_color"] +EXCLUDE_TYPES = [ + "title", + "listing", + "calendar", + "searchEvents", + "form", +] class GenericResolveUIDDeserializer(object): diff --git a/src/redturtle/volto/restapi/serializer/blocks.py b/src/redturtle/volto/restapi/serializer/blocks.py index c026526f..f65eb180 100644 --- a/src/redturtle/volto/restapi/serializer/blocks.py +++ b/src/redturtle/volto/restapi/serializer/blocks.py @@ -13,8 +13,14 @@ from zope.interface import implementer -EXCLUDE_KEYS = ["@type"] -EXCLUDE_TYPES = ["title", "listing", "form"] +EXCLUDE_KEYS = ["@type", "token", "value", "@id", "query", "bg_color"] +EXCLUDE_TYPES = [ + "title", + "listing", + "calendar", + "searchEvents", + "form", +] class GenericResolveUIDSerializer(object): diff --git a/src/redturtle/volto/restapi/services/configure.zcml b/src/redturtle/volto/restapi/services/configure.zcml index ae6e12a8..25e3acbf 100644 --- a/src/redturtle/volto/restapi/services/configure.zcml +++ b/src/redturtle/volto/restapi/services/configure.zcml @@ -3,6 +3,7 @@ xmlns:zcml="http://namespaces.zope.org/zcml" > + diff --git a/src/redturtle/volto/restapi/services/copymove/__init__.py b/src/redturtle/volto/restapi/services/copymove/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/redturtle/volto/restapi/services/copymove/configure.zcml b/src/redturtle/volto/restapi/services/copymove/configure.zcml new file mode 100644 index 00000000..8a44f77d --- /dev/null +++ b/src/redturtle/volto/restapi/services/copymove/configure.zcml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/src/redturtle/volto/restapi/services/copymove/copymove.py b/src/redturtle/volto/restapi/services/copymove/copymove.py new file mode 100644 index 00000000..fa63dcf6 --- /dev/null +++ b/src/redturtle/volto/restapi/services/copymove/copymove.py @@ -0,0 +1,21 @@ +from plone.restapi.services.copymove.copymove import Copy as BaseCopy +from plone.restapi.services.copymove.copymove import Move as BaseMove +from redturtle.volto.adapters.namechooser import check_alias + + +class Copy(BaseCopy): + """Copies existing content objects.""" + + def clipboard(self, parent, ids): + for id in ids: + check_alias(context=self.context, id=id) + return super().clipboard(parent, ids) + + +class Move(BaseMove): + """Moves existing content objects.""" + + def clipboard(self, parent, ids): + for id in ids: + check_alias(context=self.context, id=id) + return super().clipboard(parent, ids) diff --git a/src/redturtle/volto/testing.py b/src/redturtle/volto/testing.py index 81d9eb91..b1a227fb 100644 --- a/src/redturtle/volto/testing.py +++ b/src/redturtle/volto/testing.py @@ -8,7 +8,7 @@ from plone.restapi.testing import PloneRestApiDXLayer from plone.testing import z2 -import collective.volto.cookieconsent +import collective.volto.gdprcookie import kitconcept.seo import plone.app.caching import plone.restapi @@ -23,7 +23,7 @@ def setUpZope(self, app, configurationContext): # Load any other ZCML that is required for your tests. # The z3c.autoinclude feature is disabled in the Plone fixture base # layer. - self.loadZCML(package=collective.volto.cookieconsent) + self.loadZCML(package=collective.volto.gdprcookie) self.loadZCML(package=plone.restapi) self.loadZCML(package=redturtle.volto) self.loadZCML(package=plone.volto) @@ -66,7 +66,7 @@ class RedturtleVoltoRestApiLayer(PloneRestApiDXLayer): def setUpZope(self, app, configurationContext): super(RedturtleVoltoRestApiLayer, self).setUpZope(app, configurationContext) - self.loadZCML(package=collective.volto.cookieconsent) + self.loadZCML(package=collective.volto.gdprcookie) self.loadZCML(package=plone.restapi) self.loadZCML(package=plone.volto) self.loadZCML(package=redturtle.volto) diff --git a/src/redturtle/volto/tests/test_copymove_customization.py b/src/redturtle/volto/tests/test_copymove_customization.py new file mode 100644 index 00000000..faaa5d5d --- /dev/null +++ b/src/redturtle/volto/tests/test_copymove_customization.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +from plone import api +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.interfaces import IRedTurtleVoltoSettings +from redturtle.volto.testing import REDTURTLE_VOLTO_API_FUNCTIONAL_TESTING + +import transaction +import unittest + + +class TestCopyMoveCustomization(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) + + foo = api.content.create( + container=self.portal, + type="Document", + title="Foo", + ) + api.content.rename(obj=foo, new_id="xxx") + + self.bar = api.content.create( + container=self.portal, + type="Document", + title="Bar", + ) + api.content.create( + container=self.bar, + type="Document", + title="foo", + ) + + # enable it + api.portal.set_registry_record( + "check_aliases_in_namechooser", True, interface=IRedTurtleVoltoSettings + ) + + transaction.commit() + + def tearDown(self): + self.api_session.close() + + def test_move_raise_error_if_id_is_a_valid_alias(self): + response = self.api_session.post("/@move", json={"source": ["/bar/foo"]}) + + self.assertEqual(response.status_code, 400) + self.assertEqual( + response.json()["message"], + '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.', + ) + + def test_copy_raise_error_if_id_is_a_valid_alias(self): + response = self.api_session.post("/@copy", json={"source": ["/bar/foo"]}) + + self.assertEqual(response.status_code, 400) + self.assertEqual( + response.json()["message"], + '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.', + ) diff --git a/src/redturtle/volto/tests/test_namechooser_customization.py b/src/redturtle/volto/tests/test_namechooser_customization.py new file mode 100644 index 00000000..1f5a628a --- /dev/null +++ b/src/redturtle/volto/tests/test_namechooser_customization.py @@ -0,0 +1,205 @@ +# -*- 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 TestNameChooserDisabled(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") + + def test_by_default_customization_is_disabled_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)) + + # do not raise exception if the name is an alias + self.assertEqual("foo", chooser.chooseName("foo", fake_obj)) + + def test_by_default_customization_is_disabled_on_site_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)) + + # default behavior when trying to use an already-created id + self.assertEqual("bbb-1", chooser.chooseName("bbb", fake_obj)) + + # do not raise exception if the name is an alias + self.assertEqual("aaa", chooser.chooseName("aaa", fake_obj)) + + +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") diff --git a/src/redturtle/volto/upgrades.py b/src/redturtle/volto/upgrades.py index ff45e9d1..cc0db688 100644 --- a/src/redturtle/volto/upgrades.py +++ b/src/redturtle/volto/upgrades.py @@ -9,6 +9,12 @@ from uuid import uuid4 from zope.schema import getFields + +try: + from plone.base.utils import get_installer +except Exception: + from Products.CMFPlone.utils import get_installer + import json import logging @@ -494,3 +500,11 @@ def to_4303(context): for brain in brains: event = brain.getObject() event.reindexObject(idxs=["start", "end"]) + + +def to_4305(context): + portal = api.portal.get() + installer = get_installer(portal, portal.REQUEST) + installer.uninstall_product(product_id="collective.volto.cookieconsent") + if not installer.is_product_installed("collective.volto.gdprcookie"): + installer.install_product(product_id="collective.volto.gdprcookie") diff --git a/src/redturtle/volto/upgrades.zcml b/src/redturtle/volto/upgrades.zcml index ff5d3b20..8e8b91ec 100644 --- a/src/redturtle/volto/upgrades.zcml +++ b/src/redturtle/volto/upgrades.zcml @@ -216,4 +216,20 @@ handler=".upgrades.to_4303" /> + + diff --git a/test_plone52.cfg b/test_plone52.cfg index 146d479c..0cc701d9 100644 --- a/test_plone52.cfg +++ b/test_plone52.cfg @@ -14,7 +14,8 @@ plone.app.versioningbehavior = 1.4.6 plone.app.vocabularies = 4.3.0 plone.patternslib = 1.1.1 plone.rest = 2.0.0 -plone.restapi = >=9.6.0 +# plone.restapi >= 9.6.1 don't put subjects into SearchableText (to investigate) +plone.restapi = 9.6.0 plone.volto = 4.0.0 pycountry = 19.8.18 collective.purgebyid = 1.1.1 diff --git a/test_plone60.cfg b/test_plone60.cfg index ae6cf76a..1a664bcb 100644 --- a/test_plone60.cfg +++ b/test_plone60.cfg @@ -12,4 +12,5 @@ plone.recipe.codeanalysis = 3.0.1 pycodestyle = 2.10.0 pyflakes = 3.0.1 plone.stringinterp = 2.0.0 -plone.restapi = >=9.6.0 +# plone.restapi >= 9.6.1 don't put subjects into SearchableText (to investigate) +plone.restapi = 9.6.0