diff --git a/base.cfg b/base.cfg index 33f786f8ec..27278b4bc8 100644 --- a/base.cfg +++ b/base.cfg @@ -18,6 +18,7 @@ parts = develop = . sources-dir = extras auto-checkout = + plone.volto # plone.rest allow-hosts = @@ -45,6 +46,7 @@ eggs = Pillow plone.app.debugtoolbar plone.restapi [test] + plone.volto environment-vars = zope_i18n_compile_mo_files true @@ -190,6 +192,7 @@ output = ${buildout:directory}/bin/zpretty-run mode = 755 [sources] +plone.volto = git git@github.com:plone/plone.volto.git pushurl=git@github.com:plone/plone.volto.git branch=preview-image-link plone.rest = git git://github.com/plone/plone.rest.git pushurl=git@github.com:plone/plone.rest.git branch=master plone.schema = git git://github.com/plone/plone.schema.git pushurl=git@github.com:plone/plone.schema.git branch=master Products.ZCatalog = git git://github.com/zopefoundation/Products.ZCatalog.git pushurl=git@github.com:zopefoundation/Products.ZCatalog.git \ No newline at end of file diff --git a/news/xxxx.feature b/news/xxxx.feature new file mode 100644 index 0000000000..637f61bba1 --- /dev/null +++ b/news/xxxx.feature @@ -0,0 +1 @@ +- Enhance relation field serialization for image content types [@reebalazs] \ No newline at end of file diff --git a/src/plone/restapi/interfaces.py b/src/plone/restapi/interfaces.py index 9137914b80..e20c2d9cbd 100644 --- a/src/plone/restapi/interfaces.py +++ b/src/plone/restapi/interfaces.py @@ -224,3 +224,15 @@ def non_metadata_attributes(): def blocklisted_attributes(): """Returns a set with attributes blocked during serialization.""" + + +class IRelationObjectSerializer(Interface): + """The relation object serializer multi adapter serializes the relation object into + JSON compatible python data. + """ + + def __init__(rel_obj, field, context, request): + """Adapts relation object, field, context and request.""" + + def __call__(): + """Returns JSON compatible python data.""" diff --git a/src/plone/restapi/serializer/configure.zcml b/src/plone/restapi/serializer/configure.zcml index f827730c19..ceb1915e91 100644 --- a/src/plone/restapi/serializer/configure.zcml +++ b/src/plone/restapi/serializer/configure.zcml @@ -113,4 +113,9 @@ name="plone.restapi.summary_serializer_metadata" /> + + + + + diff --git a/src/plone/restapi/serializer/relationfield.py b/src/plone/restapi/serializer/relationfield.py index 931453fbea..12e253a5fd 100644 --- a/src/plone/restapi/serializer/relationfield.py +++ b/src/plone/restapi/serializer/relationfield.py @@ -27,7 +27,25 @@ def relationvalue_converter(value): @adapter(IRelationChoice, IDexterityContent, Interface) @implementer(IFieldSerializer) class RelationChoiceFieldSerializer(DefaultFieldSerializer): - pass + def __call__(self): + result = json_compatible(self.get_value()) + # Enhance information based on the content type in relation + if result is None: + return None + portal = getMultiAdapter( + (self.context, self.request), name="plone_portal_state" + ).portal() + portal_url = portal.absolute_url() + rel_url = result["@id"] + if not rel_url.startswith(portal_url): + raise RuntimeError( + f"Url must start with portal url. [{portal_url} <> {rel_url}]" + ) + rel_path = rel_url[len(portal_url) + 1 :] + rel_obj = portal.unrestrictedTraverse(rel_path, None) + serializer = getMultiAdapter((rel_obj, self.field, self.context, self.request)) + result = serializer() + return result @adapter(IRelationList, IDexterityContent, Interface) diff --git a/src/plone/restapi/serializer/relationobject.py b/src/plone/restapi/serializer/relationobject.py new file mode 100644 index 0000000000..a7d22d5f1a --- /dev/null +++ b/src/plone/restapi/serializer/relationobject.py @@ -0,0 +1,81 @@ +from plone.dexterity.interfaces import IDexterityContent +from plone.restapi.interfaces import IRelationObjectSerializer +from plone.restapi.serializer.converters import json_compatible +from zope.component import adapter +from zope.component import getMultiAdapter +from zope.interface import implementer +from zope.interface import Interface +from zope.interface import alsoProvides +from plone.app.contenttypes.interfaces import IImage +from plone.namedfile.interfaces import INamedImageField +from z3c.relationfield.interfaces import IRelationChoice + +import logging + + +log = logging.getLogger(__name__) + + +@adapter(IDexterityContent, IRelationChoice, IDexterityContent, Interface) +@implementer(IRelationObjectSerializer) +class DefaultRelationObjectSerializer: + def __init__(self, rel_obj, field, context, request): + self.context = context + self.request = request + self.field = field + self.rel_obj = rel_obj + + def __call__(self): + obj = self.rel_obj + # Start from the values of the default field serializer + result = json_compatible(self.get_value()) + # Add some more values from the object in relation + additional = { + "id": obj.id, + "created": obj.created(), + "modified": obj.modified(), + "UID": obj.UID(), + } + result.update(additional) + return json_compatible(result) + + def get_value(self, default=None): + return getattr(self.field.interface(self.context), self.field.__name__, default) + + +class FieldSim: + def __init__(self, name, provides): + self.__name__ = name + alsoProvides(self, provides) + + def get(self, context): + return getattr(context, self.__name__) + + +class FieldRelationObjectSerializer(DefaultRelationObjectSerializer): + """The relationship object is treatable like a field + + So we can reuse the serialization for that specific field. + """ + + field_name = None + field_interface = None + + def __call__(self): + field = FieldSim(self.field_name, self.field_interface) + result = super().__call__() + # Reuse a serializer from dxfields + serializer = getMultiAdapter((field, self.rel_obj, self.request)) + # Extend the default values with the content specific ones + additional = serializer() + if additional is not None: + result.update(additional) + return result + + +@adapter(IImage, IRelationChoice, IDexterityContent, Interface) +class ImageRelationObjectSerializer(FieldRelationObjectSerializer): + # The name of the attribute that contains the data object within the relation object + field_name = "image" + # The field adapter that we will emulate to get the serialized data for this content type + field_interface = INamedImageField