diff --git a/news/1639.bugfix b/news/1639.bugfix
new file mode 100644
index 000000000..4923c63d3
--- /dev/null
+++ b/news/1639.bugfix
@@ -0,0 +1,2 @@
+Remove the hard code dependency by plone.app.multilingual, use it conditionaly instead
+[@folix-01]
diff --git a/news/1642.feature b/news/1642.feature
new file mode 100644
index 000000000..bf5f1a7dc
--- /dev/null
+++ b/news/1642.feature
@@ -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
diff --git a/src/plone/restapi/__init__.py b/src/plone/restapi/__init__.py
index 303836933..ba8cb1604 100644
--- a/src/plone/restapi/__init__.py
+++ b/src/plone/restapi/__init__.py
@@ -4,6 +4,13 @@
from Products.PluggableAuthService.PluggableAuthService import registerMultiPlugin
from zope.i18nmessageid import MessageFactory
+import pkg_resources
+
+try:
+ pkg_resources.get_distribution("plone.app.multilingual")
+ HAS_MULTILINGUAL = True
+except pkg_resources.DistributionNotFound:
+ HAS_MULTILINGUAL = False
_ = MessageFactory("plone.restapi")
PROJECT_NAME = "plone.restapi"
diff --git a/src/plone/restapi/deserializer/blocks.py b/src/plone/restapi/deserializer/blocks.py
index 99199f78a..a4746eb4b 100644
--- a/src/plone/restapi/deserializer/blocks.py
+++ b/src/plone/restapi/deserializer/blocks.py
@@ -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()
diff --git a/src/plone/restapi/serializer/blocks.py b/src/plone/restapi/serializer/blocks.py
index e42e6baf3..5a8733580 100644
--- a/src/plone/restapi/serializer/blocks.py
+++ b/src/plone/restapi/serializer/blocks.py
@@ -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
@@ -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
@@ -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
diff --git a/src/plone/restapi/serializer/utils.py b/src/plone/restapi/serializer/utils.py
index f47541663..c2f89e4e2 100644
--- a/src/plone/restapi/serializer/utils.py
+++ b/src/plone/restapi/serializer/utils.py
@@ -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),
@@ -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):
diff --git a/src/plone/restapi/services/configure.zcml b/src/plone/restapi/services/configure.zcml
index f22f4a207..2096fd818 100644
--- a/src/plone/restapi/services/configure.zcml
+++ b/src/plone/restapi/services/configure.zcml
@@ -48,10 +48,9 @@
package=".workingcopy"
zcml:condition="installed plone.app.iterate"
/>
-
+
+
+