Skip to content

Commit

Permalink
WIP Enhance relation field serialization for image content types
Browse files Browse the repository at this point in the history
- In better support of the preview image link
- Reuses serialization from existing field serializers
- Flexible adapation allows new types to be added in the future

WIP until preview_image_link has been merged into plone.volto
  • Loading branch information
reebalazs committed Jun 6, 2022
1 parent 2dbafe5 commit b3a2b81
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 1 deletion.
3 changes: 3 additions & 0 deletions base.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ parts =
develop = .
sources-dir = extras
auto-checkout =
plone.volto
# plone.rest

allow-hosts =
Expand Down Expand Up @@ -45,6 +46,7 @@ eggs =
Pillow
plone.app.debugtoolbar
plone.restapi [test]
plone.volto
environment-vars =
zope_i18n_compile_mo_files true

Expand Down Expand Up @@ -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
1 change: 1 addition & 0 deletions news/xxxx.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Enhance relation field serialization for image content types [@reebalazs]
12 changes: 12 additions & 0 deletions src/plone/restapi/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
5 changes: 5 additions & 0 deletions src/plone/restapi/serializer/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,9 @@
name="plone.restapi.summary_serializer_metadata"
/>

<!-- Relation object serializer -->
<adapter factory=".relationobject.DefaultRelationObjectSerializer" />
<adapter factory=".relationobject.ImageRelationObjectSerializer" />


</configure>
20 changes: 19 additions & 1 deletion src/plone/restapi/serializer/relationfield.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
81 changes: 81 additions & 0 deletions src/plone/restapi/serializer/relationobject.py
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit b3a2b81

Please sign in to comment.