diff --git a/src/plone/restapi/deserializer/utils.py b/src/plone/restapi/deserializer/utils.py index 04438be768..b2a45489cd 100644 --- a/src/plone/restapi/deserializer/utils.py +++ b/src/plone/restapi/deserializer/utils.py @@ -25,11 +25,13 @@ def path2uid(context, link): ) # handle edge-case when we have non traversable path like /@@download/file - if "/@@" in path: - path, suffix = path.split("/@@", 1) - suffix = "/@@" + suffix - else: - suffix = "" + SUFFIXES = ["/@@", "#"] + suffix = "" + for suffix_separator in SUFFIXES: + if suffix_separator in path: + path, suffix = path.split(suffix_separator, 1) + suffix = suffix_separator + suffix + obj = portal.unrestrictedTraverse(path, None) if obj is None or obj == portal: return link diff --git a/src/plone/restapi/serializer/utils.py b/src/plone/restapi/serializer/utils.py index 4ac4e4ee7b..29765d7248 100644 --- a/src/plone/restapi/serializer/utils.py +++ b/src/plone/restapi/serializer/utils.py @@ -8,7 +8,7 @@ import re -RESOLVEUID_RE = re.compile("^(?:|.*/)resolve[Uu]id/([^/]*)/?(.*)$") +RESOLVEUID_RE = re.compile("^(?:|.*/)resolve[Uu]id/([^/#]*)/?(.*?)(#.*)?$") def resolve_uid(path): @@ -23,13 +23,15 @@ def resolve_uid(path): if match is None: return path, None - uid, suffix = match.groups() + uid, suffix, anchor = match.groups() brain = uuidToCatalogBrain(uid) if brain is None: return path, None href = brain.getURL() if suffix: return href + "/" + suffix, brain + if anchor: + return href + anchor, brain target_object = brain._unrestrictedGetObject() adapter = queryMultiAdapter( (target_object, target_object.REQUEST), diff --git a/src/plone/restapi/tests/test_blocks_deserializer.py b/src/plone/restapi/tests/test_blocks_deserializer.py index 37a4e6ecd5..4a1b19209a 100644 --- a/src/plone/restapi/tests/test_blocks_deserializer.py +++ b/src/plone/restapi/tests/test_blocks_deserializer.py @@ -18,7 +18,6 @@ class TestBlocksDeserializer(unittest.TestCase): - layer = PLONE_RESTAPI_DX_INTEGRATION_TESTING def setUp(self): @@ -444,6 +443,66 @@ def test_slate_simple_link_deserializer(self): link = value[0]["children"][1]["data"]["url"] self.assertTrue(link.startswith("../resolveuid/")) + def test_slate_simple_link_deserializer_with_anchor(self): + blocks = { + "abc": { + "@type": "slate", + "plaintext": "Frontpage content here", + "value": [ + { + "children": [ + {"text": "Frontpage "}, + { + "children": [{"text": "content "}], + "data": { + "url": "%s/image-1#anchor-id" + % self.portal.absolute_url() + }, + "type": "link", + }, + {"text": "here"}, + ], + "type": "h2", + } + ], + } + } + + res = self.deserialize(blocks=blocks) + value = res.blocks["abc"]["value"] + link = value[0]["children"][1]["data"]["url"] + self.assertEqual(link, f"../resolveuid/{self.image.UID()}#anchor-id") + + def test_slate_simple_link_deserializer_with_suffix(self): + blocks = { + "abc": { + "@type": "slate", + "plaintext": "Frontpage content here", + "value": [ + { + "children": [ + {"text": "Frontpage "}, + { + "children": [{"text": "content "}], + "data": { + "url": "%s/image-1/@@download/file" + % self.portal.absolute_url() + }, + "type": "link", + }, + {"text": "here"}, + ], + "type": "h2", + } + ], + } + } + + res = self.deserialize(blocks=blocks) + value = res.blocks["abc"]["value"] + link = value[0]["children"][1]["data"]["url"] + self.assertEqual(link, f"../resolveuid/{self.image.UID()}/@@download/file") + def test_aquisition_messing_with_link_deserializer(self): self.portal.invokeFactory( "Folder", diff --git a/src/plone/restapi/tests/test_blocks_serializer.py b/src/plone/restapi/tests/test_blocks_serializer.py index a72abb09d4..5b0d3735d1 100644 --- a/src/plone/restapi/tests/test_blocks_serializer.py +++ b/src/plone/restapi/tests/test_blocks_serializer.py @@ -29,7 +29,6 @@ class TestBlocksSerializer(unittest.TestCase): - layer = PLONE_RESTAPI_DX_INTEGRATION_TESTING def setUp(self): @@ -285,7 +284,75 @@ def test_simple_link_serializer(self): ) value = res["abc"]["value"] link = value[0]["children"][1]["data"]["url"] - self.assertTrue(link, self.portal.absolute_url() + "/doc1") + self.assertEqual(link, self.portal.absolute_url() + "/doc1") + + def test_simple_link_serializer_with_anchor(self): + doc_uid = IUUID(self.portal["doc1"]) + resolve_uid_link = f"../resolveuid/{doc_uid}#anchor-id" + + blocks = { + "abc": { + "@type": "slate", + "plaintext": "Frontpage content here", + "value": [ + { + "children": [ + {"text": "Frontpage "}, + { + "children": [{"text": "content "}], + "data": { + "url": resolve_uid_link, + }, + "type": "link", + }, + {"text": "here"}, + ], + "type": "h2", + } + ], + } + } + res = self.serialize( + context=self.portal["doc1"], + blocks=blocks, + ) + value = res["abc"]["value"] + link = value[0]["children"][1]["data"]["url"] + self.assertEqual(link, f"{self.portal['doc1'].absolute_url()}#anchor-id") + + def test_simple_link_serializer_with_suffix(self): + doc_uid = IUUID(self.portal["doc1"]) + resolve_uid_link = f"../resolveuid/{doc_uid}/@@download/file" + + blocks = { + "abc": { + "@type": "slate", + "plaintext": "Frontpage content here", + "value": [ + { + "children": [ + {"text": "Frontpage "}, + { + "children": [{"text": "content "}], + "data": { + "url": resolve_uid_link, + }, + "type": "link", + }, + {"text": "here"}, + ], + "type": "h2", + } + ], + } + } + res = self.serialize( + context=self.portal["doc1"], + blocks=blocks, + ) + value = res["abc"]["value"] + link = value[0]["children"][1]["data"]["url"] + self.assertEqual(link, f"{self.portal['doc1'].absolute_url()}/@@download/file") def test_slate_table_block_link_serializer(self): doc_uid = IUUID(self.portal["doc1"]) @@ -388,7 +455,7 @@ def test_slate_table_block_link_serializer(self): rows = res["abc"]["table"]["rows"] cell = rows[1]["cells"][0] link = cell["value"][0]["children"][1]["data"]["url"] - self.assertTrue(link, self.portal.absolute_url() + "/doc1") + self.assertEqual(link, self.portal.absolute_url() + "/doc1") @unittest.skipUnless( HAS_PLONE_6,