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