Skip to content

Commit

Permalink
Update image scales in blocks data when serializing internal links (#…
Browse files Browse the repository at this point in the history
…1642)

* Add image scales to blocks data when serializing internal links

* put image_scales adjacent to the url (better backwards compatibility)

* Remove unintended changes, try to fix tests in Plone 5

* fix test

---------

Co-authored-by: Piero Nicolli <pnicolli@users.noreply.github.com>
  • Loading branch information
davisagli and pnicolli committed Jul 11, 2023
1 parent b09b0d0 commit cff29fd
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 32 deletions.
2 changes: 2 additions & 0 deletions news/1642.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
When serializing blocks, `image_scales` is now added to blocks that contain a resolveuid-based `url`.
When deserializing blocks, `image_scales` is removed. @davisagli
11 changes: 3 additions & 8 deletions src/plone/restapi/deserializer/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,9 @@ def _process_data(self, data, field=None):
if data.get("@type", None) == "URL" and data.get("value", None):
data["value"] = path2uid(context=self.context, link=data["value"])
elif data.get("@id", None):
item_clone = deepcopy(data)
item_clone["@id"] = path2uid(
context=self.context, link=item_clone["@id"]
)
return {
field: self._process_data(data=value, field=field)
for field, value in item_clone.items()
}
data = deepcopy(data)
data["@id"] = path2uid(context=self.context, link=data["@id"])
data.pop("image_scales", None)
return {
field: self._process_data(data=value, field=field)
for field, value in data.items()
Expand Down
33 changes: 20 additions & 13 deletions src/plone/restapi/serializer/blocks.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from copy import deepcopy
from plone.restapi.bbb import IPloneSiteRoot
from plone.restapi.behaviors import IBlocks
from plone.restapi.blocks import visit_blocks, iter_block_transform_handlers
Expand All @@ -9,7 +8,7 @@
from plone.restapi.interfaces import IFieldSerializer
from plone.restapi.serializer.converters import json_compatible
from plone.restapi.serializer.dxfields import DefaultFieldSerializer
from plone.restapi.serializer.utils import uid_to_url
from plone.restapi.serializer.utils import resolve_uid, uid_to_url
from plone.schema import IJSONField
from zope.component import adapter
from zope.interface import implementer
Expand Down Expand Up @@ -57,19 +56,27 @@ def _process_data(self, data, field=None):
if isinstance(data, list):
return [self._process_data(data=value, field=field) for value in data]
if isinstance(data, dict):
if data.get("@type", None) == "URL" and data.get("value", None):
data["value"] = uid_to_url(data["value"])
elif data.get("@id", None):
item_clone = deepcopy(data)
item_clone["@id"] = uid_to_url(item_clone["@id"])
return {
field: self._process_data(data=value, field=field)
for field, value in item_clone.items()
}
return {
field: self._process_data(data=value, field=field)
fields = ["value"] if data.get("@type") == "URL" else []
fields.append("@id")
fields.extend(self.fields)
newdata = {}
for field in fields:
if field not in data or not isinstance(data[field], str):
continue
newdata[field], brain = resolve_uid(data[field])
if brain is not None and "image_scales" not in newdata:
newdata["image_scales"] = getattr(brain, "image_scales", None)
result = {
field: (
newdata[field]
if field in newdata
else self._process_data(data=newdata.get(field, value), field=field)
)
for field, value in data.items()
}
if newdata.get("image_scales"):
result["image_scales"] = newdata["image_scales"]
return result
return data


Expand Down
26 changes: 15 additions & 11 deletions src/plone/restapi/serializer/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,25 @@
RESOLVEUID_RE = re.compile("^[./]*resolve[Uu]id/([^/]*)/?(.*)$")


def uid_to_url(path):
"""turns a resolveuid url into a real url.
def resolve_uid(path):
"""Resolves a resolveuid URL into a tuple of absolute URL and catalog brain.
This uses the catalog first, but wake up the object to check if there is
an IObjectPrimaryFieldTarget on this object. If so, it will return the
target url instead of the object url.
If the original path is not found (including external URLs),
it will be returned unchanged and the brain will be None.
"""
if not path:
return ""
return "", None
match = RESOLVEUID_RE.match(path)
if match is None:
return path
return path, None

uid, suffix = match.groups()
brain = uuidToCatalogBrain(uid)
if brain is None:
return path
return path, None
href = brain.getURL()
if suffix:
return href + "/" + suffix
return href + "/" + suffix, brain
target_object = brain._unrestrictedGetObject()
adapter = queryMultiAdapter(
(target_object, target_object.REQUEST),
Expand All @@ -39,8 +38,13 @@ def uid_to_url(path):
if adapter:
a_href = adapter()
if a_href:
return a_href
return href
return a_href, None
return href, brain


def uid_to_url(path):
path, brain = resolve_uid(path)
return path


def get_portal_type_title(portal_type):
Expand Down
6 changes: 6 additions & 0 deletions src/plone/restapi/tests/test_blocks_deserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -597,3 +597,9 @@ def test_slate_table_block_deserializer(self):
cell = rows[1]["cells"][0]
link = cell["value"][0]["children"][1]["data"]["url"]
self.assertTrue(link.startswith("../resolveuid/"))

def test_deserialize_url_with_image_scales(self):
blocks = {"123": {"url": self.image.absolute_url(), "image_scales": {}}}
res = self.deserialize(blocks=blocks)
self.assertTrue(res.blocks["123"]["url"].startswith("../resolveuid/"))
self.assertNotIn("image_scales", res.blocks["123"])
25 changes: 25 additions & 0 deletions src/plone/restapi/tests/test_blocks_serializer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from importlib import import_module
from plone.dexterity.interfaces import IDexterityFTI
from plone.dexterity.interfaces import IDexterityItem
from plone.dexterity.utils import iterSchemata
from plone.namedfile.file import NamedBlobImage
from plone.restapi.behaviors import IBlocks
from plone.restapi.interfaces import IBlockFieldSerializationTransformer
from plone.restapi.interfaces import IFieldSerializer
Expand All @@ -15,9 +17,17 @@
from zope.interface import implementer
from zope.publisher.interfaces.browser import IBrowserRequest

import pathlib
import unittest


HAS_PLONE_6 = getattr(
import_module("Products.CMFPlone.factory"), "PLONE60MARKER", False
)
IMAGE_PATH = (pathlib.Path(__file__).parent / "image.png").resolve()
IMAGE_DATA = IMAGE_PATH.read_bytes()


class TestBlocksSerializer(unittest.TestCase):

layer = PLONE_RESTAPI_DX_INTEGRATION_TESTING
Expand All @@ -36,6 +46,8 @@ def setUp(self):
self.image = self.portal[
self.portal.invokeFactory("Image", id="image-1", title="Target image")
]
self.image.image = NamedBlobImage(data=IMAGE_DATA, filename="test.jpg")
self.image.reindexObject()

def serialize(self, context, blocks):
fieldname = "blocks"
Expand Down Expand Up @@ -377,3 +389,16 @@ def test_slate_table_block_link_serializer(self):
cell = rows[1]["cells"][0]
link = cell["value"][0]["children"][1]["data"]["url"]
self.assertTrue(link, self.portal.absolute_url() + "/doc1")

@unittest.skipUnless(
HAS_PLONE_6,
"image_scales were added to the catalog in Plone 6",
)
def test_image_scales_serializer(self):
image_uid = self.image.UID()
res = self.serialize(
context=self.portal["doc1"],
blocks={"123": {"@type": "image", "url": f"../resolveuid/{image_uid}"}},
)
self.assertEqual(res["123"]["url"], self.image.absolute_url())
self.assertIn("image_scales", res["123"])

0 comments on commit cff29fd

Please sign in to comment.