diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 73447527bc..72d7ada3ff 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -3,7 +3,7 @@ on: [push, pull_request] jobs: build: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: diff --git a/news/1848.bugfix b/news/1848.bugfix new file mode 100644 index 0000000000..c9816615af --- /dev/null +++ b/news/1848.bugfix @@ -0,0 +1 @@ +Fix resolving paths in deserializer if the target was moved in the same request. @cekk diff --git a/news/1850.internal b/news/1850.internal new file mode 100644 index 0000000000..d4a67450ab --- /dev/null +++ b/news/1850.internal @@ -0,0 +1 @@ +Fix time-dependence of tests. @davisagli diff --git a/news/1851.bugfix b/news/1851.bugfix new file mode 100644 index 0000000000..dc29516054 --- /dev/null +++ b/news/1851.bugfix @@ -0,0 +1 @@ +Optimized performance of DexterityObjectPrimaryFieldTarget adapter. @maurits diff --git a/src/plone/restapi/deserializer/utils.py b/src/plone/restapi/deserializer/utils.py index 67d67d5556..c6b687fa2f 100644 --- a/src/plone/restapi/deserializer/utils.py +++ b/src/plone/restapi/deserializer/utils.py @@ -2,6 +2,9 @@ from plone.uuid.interfaces import IUUID from plone.uuid.interfaces import IUUIDAware from zope.component import getMultiAdapter +from plone.app.redirector.interfaces import IRedirectionStorage +from zope.component import getUtility + import re PATH_RE = re.compile(r"^(.*?)((?=/@@|#).*)?$") @@ -35,6 +38,14 @@ def path2uid(context, link): suffix = match.group(2) or "" obj = portal.unrestrictedTraverse(path, None) + if obj is None: + # last try: maybe the object or some parent has been renamed. + # if yes, there should be a reference into redirection storage + storage = getUtility(IRedirectionStorage) + alias_path = storage.get(path) + if alias_path: + path = alias_path + obj = portal.unrestrictedTraverse(path, None) if obj is None or obj == portal: return link segments = path.split("/") diff --git a/src/plone/restapi/serializer/dxcontent.py b/src/plone/restapi/serializer/dxcontent.py index e497bb6b68..eb0fc44c6e 100644 --- a/src/plone/restapi/serializer/dxcontent.py +++ b/src/plone/restapi/serializer/dxcontent.py @@ -220,23 +220,26 @@ def __init__(self, context, request): def __call__(self): primary_field_name = self.get_primary_field_name() + if not primary_field_name: + return for schema in iterSchemata(self.context): read_permissions = mergedTaggedValueDict(schema, READ_PERMISSIONS_KEY) - for name, field in getFields(schema).items(): - if not self.check_permission(read_permissions.get(name), self.context): - continue - - if name != primary_field_name: - continue - - target_adapter = queryMultiAdapter( - (field, self.context, self.request), IPrimaryFieldTarget - ) - if target_adapter: - target = target_adapter() - if target: - return target + field = getFields(schema).get(primary_field_name) + if field is None: + continue + if not self.check_permission( + read_permissions.get(primary_field_name), + self.context, + ): + return + + target_adapter = queryMultiAdapter( + (field, self.context, self.request), IPrimaryFieldTarget + ) + if not target_adapter: + return + return target_adapter() def get_primary_field_name(self): fieldname = None diff --git a/src/plone/restapi/tests/test_blocks_deserializer.py b/src/plone/restapi/tests/test_blocks_deserializer.py index 874e5446e8..dcaf3bf2c8 100644 --- a/src/plone/restapi/tests/test_blocks_deserializer.py +++ b/src/plone/restapi/tests/test_blocks_deserializer.py @@ -1,3 +1,4 @@ +from plone import api from plone.dexterity.interfaces import IDexterityFTI from plone.dexterity.interfaces import IDexterityItem from plone.restapi.behaviors import IBlocks @@ -724,3 +725,16 @@ def test_deserialize_url_with_image_scales(self): res = self.deserialize(blocks=blocks) self.assertTrue(res.blocks["123"]["url"].startswith("../resolveuid/")) self.assertNotIn("image_scales", res.blocks["123"]) + + def test_deserializer_resolve_path_also_if_it_is_an_alias(self): + + self.portal.invokeFactory( + "Document", + id="doc", + ) + api.content.move(source=self.portal.doc, id="renamed-doc") + blocks = {"abc": {"href": "%s/doc" % self.portal.absolute_url()}} + + res = self.deserialize(blocks=blocks) + link = res.blocks["abc"]["href"] + self.assertEqual(link, f"../resolveuid/{self.portal['renamed-doc'].UID()}") diff --git a/src/plone/restapi/tests/test_serializer_summary.py b/src/plone/restapi/tests/test_serializer_summary.py index 9a7b7db455..e186c2f31c 100644 --- a/src/plone/restapi/tests/test_serializer_summary.py +++ b/src/plone/restapi/tests/test_serializer_summary.py @@ -17,6 +17,7 @@ from zope.interface import alsoProvides import Missing +import pytz import unittest @@ -234,14 +235,15 @@ def setUp(self): ) self.portal.portal_types.DXTestDocument.behaviors = behaviors + self.start = datetime(1995, 7, 31, 13, 45, tzinfo=pytz.timezone("UTC")) self.event = createContentInContainer( self.portal, "DXTestDocument", id="doc1", title="Lorem Ipsum event", description="Description event", - start=datetime.now(), - end=datetime.now() + timedelta(hours=1), + start=self.start, + end=self.start + timedelta(hours=1), recurrence="RRULE:FREQ=DAILY;COUNT=3", # see https://github.com/plone/plone.app.event/blob/master/plone/app/event/tests/base_setup.py ) @@ -256,7 +258,7 @@ def tearDown(self): "this test needs a plone.app.event version that does not include a IContentListingObject adapter", ) def test_dx_event_with_recurrence_old_version(self): - tomorrow = datetime.now() + timedelta(days=1) + tomorrow = self.start + timedelta(days=1) tomorrow_str = tomorrow.strftime("%Y-%m-%d") ot = OccurrenceTraverser(self.event, self.request) ocurrence = ot.publishTraverse(self.request, tomorrow_str) @@ -269,7 +271,7 @@ def test_dx_event_with_recurrence_old_version(self): "this test needs a plone.app.event version that includes a IContentListingObject adapter", ) def test_dx_event_with_recurrence_new_version(self): - tomorrow = datetime.now() + timedelta(days=1) + tomorrow = self.start + timedelta(days=1) tomorrow_str = tomorrow.strftime("%Y-%m-%d") ot = OccurrenceTraverser(self.event, self.request) ocurrence = ot.publishTraverse(self.request, tomorrow_str)