diff --git a/news/1903.feature b/news/1903.feature new file mode 100644 index 000000000..32dbac472 --- /dev/null +++ b/news/1903.feature @@ -0,0 +1 @@ +Added the option to enable file target URLs to be transformed to information about the target. @JeffersonBledsoe diff --git a/src/plone/restapi/serializer/dxfields.py b/src/plone/restapi/serializer/dxfields.py index de74f966e..ac0cf5667 100644 --- a/src/plone/restapi/serializer/dxfields.py +++ b/src/plone/restapi/serializer/dxfields.py @@ -25,6 +25,7 @@ from zope.schema.interfaces import IVocabularyTokenized import logging +import os log = logging.getLogger(__name__) @@ -205,6 +206,24 @@ def __call__(self): if namedfile is None: return + enable_transform = os.environ.get( + "enable_link_target_transform", "" + ).lower() in ("true", "1", "TRUE") + + if enable_transform: + download_url = "/".join( + (self.context.absolute_url(), "@@download", self.field.__name__) + ) + result = { + "@id": self.context.absolute_url(), + "@type": self.context.portal_type, + "download": download_url, + "filename": namedfile.filename, + "content-type": namedfile.contentType, + "size": namedfile.getSize(), + } + return json_compatible(result) + return "/".join( (self.context.absolute_url(), "@@download", self.field.__name__) ) diff --git a/src/plone/restapi/serializer/utils.py b/src/plone/restapi/serializer/utils.py index 89ce0b005..b0fb81e03 100644 --- a/src/plone/restapi/serializer/utils.py +++ b/src/plone/restapi/serializer/utils.py @@ -43,6 +43,8 @@ def resolve_uid(path): def uid_to_url(path): + if isinstance(path, dict) and path.get("@id"): + path = path["@id"] path, _brain = resolve_uid(path) return path diff --git a/src/plone/restapi/tests/test_blocks_serializer.py b/src/plone/restapi/tests/test_blocks_serializer.py index 34ca3c9d0..2a765df20 100644 --- a/src/plone/restapi/tests/test_blocks_serializer.py +++ b/src/plone/restapi/tests/test_blocks_serializer.py @@ -4,6 +4,7 @@ from plone.dexterity.interfaces import IDexterityItem from plone.dexterity.utils import iterSchemata from plone.namedfile.file import NamedBlobImage +from plone.namedfile.file import NamedFile from plone.restapi.behaviors import IBlocks from plone.restapi.interfaces import IBlockFieldSerializationTransformer from plone.restapi.interfaces import IFieldSerializer @@ -19,6 +20,7 @@ from zope.interface import implementer from zope.publisher.interfaces.browser import IBrowserRequest +import os import pathlib import unittest @@ -638,3 +640,45 @@ def test_teaser_block_serializer_legacy(self): self.assertEqual(block["description"], "Custom description") href = block["href"][0] self.assertEqual(href["@id"], doc.absolute_url()) + + def test_primary_field_target_link_expanded(self): + self.portal.invokeFactory( + "DXTestDocument", + id="docWithFile", + test_primary_namedfile_field=NamedFile( + data="Spam and eggs", + contentType="text/plain", + filename="test.txt", + ), + ) + os.environ["enable_link_target_transform"] = "1" + logout() + + resolve_uid_link = f"../resolveuid/{self.portal.docWithFile.UID()}" + value = self.serialize( + context=self.portal.doc1, + blocks={ + "1": { + "@type": "custom", + "url": resolve_uid_link + }, + "2": { + "@id": "custom", + "url": [{"@id": resolve_uid_link}], + } + }, + ) + + content_url = self.portal.docWithFile.absolute_url() + download_url = f"{content_url}/@@download/test_primary_namedfile_field" + + # Links to it that aren't IDs should be expanded + self.assertEqual(value['1']["url"]["@id"], self.portal.docWithFile.absolute_url()) + self.assertEqual(value['1']["url"]["@type"], "DXTestDocument") + self.assertEqual(value['1']["url"]["download"], download_url) + self.assertEqual(value['1']["url"]["filename"], "test.txt") + self.assertEqual(value['1']["url"]["content-type"], "text/plain") + self.assertIn("size", value['1']["url"]) + + # @id field should still just be a string + self.assertEqual(value["2"]["url"][0]["@id"], self.portal.docWithFile.absolute_url()) diff --git a/src/plone/restapi/tests/test_dxcontent_serializer.py b/src/plone/restapi/tests/test_dxcontent_serializer.py index ebbdeda1c..2ccf5a2a8 100644 --- a/src/plone/restapi/tests/test_dxcontent_serializer.py +++ b/src/plone/restapi/tests/test_dxcontent_serializer.py @@ -37,6 +37,7 @@ from zope.publisher.interfaces.browser import IBrowserRequest import json +import os import unittest @@ -807,6 +808,8 @@ def test_get_layout_for_siteroot(self): class TestDXContentPrimaryFieldTargetUrl(unittest.TestCase): layer = PLONE_RESTAPI_DX_INTEGRATION_TESTING + test_filename = "test.txt" + def setUp(self): self.portal = self.layer["portal"] self.request = self.layer["request"] @@ -815,7 +818,9 @@ def setUp(self): "DXTestDocument", id="doc1", test_primary_namedfile_field=NamedFile( - data="Spam and eggs", contentType="text/plain", filename="test.txt" + data="Spam and eggs", + contentType="text/plain", + filename=self.test_filename, ), ) @@ -900,3 +905,33 @@ def test_primary_field_target_for_link_objects_for_anonymous(self): (self.portal.link, self.request), IObjectPrimaryFieldTarget ) self.assertEqual(adapter(), self.portal.linked.absolute_url()) + + def test_primary_field_target_expanded(self): + os.environ["enable_link_target_transform"] = "1" + logout() + serializer = getMultiAdapter((self.portal.doc1, self.request), ISerializeToJson) + data = serializer() + self.assertIn("targetUrl", data) + content_url = self.portal.doc1.absolute_url() + download_url = "/".join( + [ + self.portal.doc1.absolute_url(), + "@@download/test_primary_namedfile_field", + ] + ) + + url_data = data["targetUrl"] + + self.assertEqual(url_data["@id"], content_url) + self.assertEqual(url_data["@type"], "DXTestDocument") + self.assertEqual(url_data["download"], download_url) + self.assertEqual(url_data["filename"], self.test_filename) + self.assertEqual(url_data["content-type"], "text/plain") + self.assertIn("size", url_data) + + # Check disabling the env variable still gives us the `@@download` string representation + os.environ["enable_link_target_transform"] = "0" + data = serializer() + url_data = data["targetUrl"] + + self.assertEqual(url_data, download_url)