From d012d3f784c5897d5b70164daabdb99b3903ebca Mon Sep 17 00:00:00 2001 From: David Glick Date: Wed, 11 Dec 2024 16:06:59 -0800 Subject: [PATCH 1/3] fix time-dependence of event recurrent tests (#1850) * fix time-dependence of event recurrent tests * changelog --- news/1850.internal | 1 + src/plone/restapi/tests/test_serializer_summary.py | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 news/1850.internal diff --git a/news/1850.internal b/news/1850.internal new file mode 100644 index 000000000..d4a67450a --- /dev/null +++ b/news/1850.internal @@ -0,0 +1 @@ +Fix time-dependence of tests. @davisagli diff --git a/src/plone/restapi/tests/test_serializer_summary.py b/src/plone/restapi/tests/test_serializer_summary.py index 9a7b7db45..e186c2f31 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) From 93ceb885d78b804d4cc0fe8ef636c5bee1b3c20d Mon Sep 17 00:00:00 2001 From: Maurits van Rees Date: Tue, 17 Dec 2024 05:56:01 +0100 Subject: [PATCH 2/3] Make primary field target adapter return early. (#1851) * Make primary field target adapter return early. When there is no primary field, do not go through all fields. First compare the field name, then the permission: comparing two strings is cheaper. If the field name matches, stop iterating over other fields: you have already found the field. * Update news/1851.bugfix * Primary field target: get primary_field_name directly instead of iterating over all fields. * Run tests on ubuntu-22.04. This was ubuntu-latest until last week. With ubuntu-24.04 on Python 3.12 Pillow is not installed correctly. See https://github.com/plone/plone.restapi/pull/1851#issuecomment-2547097591 * Update src/plone/restapi/serializer/dxcontent.py --------- Co-authored-by: David Glick --- .github/workflows/tests.yml | 2 +- news/1851.bugfix | 1 + src/plone/restapi/serializer/dxcontent.py | 31 +++++++++++++---------- 3 files changed, 19 insertions(+), 15 deletions(-) create mode 100644 news/1851.bugfix diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 73447527b..72d7ada3f 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/1851.bugfix b/news/1851.bugfix new file mode 100644 index 000000000..dc2951605 --- /dev/null +++ b/news/1851.bugfix @@ -0,0 +1 @@ +Optimized performance of DexterityObjectPrimaryFieldTarget adapter. @maurits diff --git a/src/plone/restapi/serializer/dxcontent.py b/src/plone/restapi/serializer/dxcontent.py index e497bb6b6..eb0fc44c6 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 From a571899353937b7e3609eaab728116107caf567a Mon Sep 17 00:00:00 2001 From: Andrea Cecchi Date: Tue, 17 Dec 2024 06:12:14 +0100 Subject: [PATCH 3/3] Handle aliases in path2uid (#1848) * Handle aliases in path2uid * add changelog * make sure it found the correct page * adjust changelog --------- Co-authored-by: David Glick --- news/1848.bugfix | 1 + src/plone/restapi/deserializer/utils.py | 11 +++++++++++ .../restapi/tests/test_blocks_deserializer.py | 14 ++++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 news/1848.bugfix diff --git a/news/1848.bugfix b/news/1848.bugfix new file mode 100644 index 000000000..c9816615a --- /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/src/plone/restapi/deserializer/utils.py b/src/plone/restapi/deserializer/utils.py index 67d67d555..c6b687fa2 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/tests/test_blocks_deserializer.py b/src/plone/restapi/tests/test_blocks_deserializer.py index 874e5446e..dcaf3bf2c 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()}")