Skip to content

Commit b3a2b81

Browse files
committed
WIP Enhance relation field serialization for image content types
- 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
1 parent 2dbafe5 commit b3a2b81

File tree

6 files changed

+121
-1
lines changed

6 files changed

+121
-1
lines changed

base.cfg

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ parts =
1818
develop = .
1919
sources-dir = extras
2020
auto-checkout =
21+
plone.volto
2122
# plone.rest
2223

2324
allow-hosts =
@@ -45,6 +46,7 @@ eggs =
4546
Pillow
4647
plone.app.debugtoolbar
4748
plone.restapi [test]
49+
plone.volto
4850
environment-vars =
4951
zope_i18n_compile_mo_files true
5052

@@ -190,6 +192,7 @@ output = ${buildout:directory}/bin/zpretty-run
190192
mode = 755
191193

192194
[sources]
195+
plone.volto = git [email protected]:plone/plone.volto.git pushurl[email protected]:plone/plone.volto.git branch=preview-image-link
193196
plone.rest = git git://github.com/plone/plone.rest.git pushurl[email protected]:plone/plone.rest.git branch=master
194197
plone.schema = git git://github.com/plone/plone.schema.git pushurl[email protected]:plone/plone.schema.git branch=master
195198
Products.ZCatalog = git git://github.com/zopefoundation/Products.ZCatalog.git pushurl[email protected]:zopefoundation/Products.ZCatalog.git

news/xxxx.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Enhance relation field serialization for image content types [@reebalazs]

src/plone/restapi/interfaces.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,3 +224,15 @@ def non_metadata_attributes():
224224

225225
def blocklisted_attributes():
226226
"""Returns a set with attributes blocked during serialization."""
227+
228+
229+
class IRelationObjectSerializer(Interface):
230+
"""The relation object serializer multi adapter serializes the relation object into
231+
JSON compatible python data.
232+
"""
233+
234+
def __init__(rel_obj, field, context, request):
235+
"""Adapts relation object, field, context and request."""
236+
237+
def __call__():
238+
"""Returns JSON compatible python data."""

src/plone/restapi/serializer/configure.zcml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,9 @@
113113
name="plone.restapi.summary_serializer_metadata"
114114
/>
115115

116+
<!-- Relation object serializer -->
117+
<adapter factory=".relationobject.DefaultRelationObjectSerializer" />
118+
<adapter factory=".relationobject.ImageRelationObjectSerializer" />
119+
120+
116121
</configure>

src/plone/restapi/serializer/relationfield.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,25 @@ def relationvalue_converter(value):
2727
@adapter(IRelationChoice, IDexterityContent, Interface)
2828
@implementer(IFieldSerializer)
2929
class RelationChoiceFieldSerializer(DefaultFieldSerializer):
30-
pass
30+
def __call__(self):
31+
result = json_compatible(self.get_value())
32+
# Enhance information based on the content type in relation
33+
if result is None:
34+
return None
35+
portal = getMultiAdapter(
36+
(self.context, self.request), name="plone_portal_state"
37+
).portal()
38+
portal_url = portal.absolute_url()
39+
rel_url = result["@id"]
40+
if not rel_url.startswith(portal_url):
41+
raise RuntimeError(
42+
f"Url must start with portal url. [{portal_url} <> {rel_url}]"
43+
)
44+
rel_path = rel_url[len(portal_url) + 1 :]
45+
rel_obj = portal.unrestrictedTraverse(rel_path, None)
46+
serializer = getMultiAdapter((rel_obj, self.field, self.context, self.request))
47+
result = serializer()
48+
return result
3149

3250

3351
@adapter(IRelationList, IDexterityContent, Interface)
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from plone.dexterity.interfaces import IDexterityContent
2+
from plone.restapi.interfaces import IRelationObjectSerializer
3+
from plone.restapi.serializer.converters import json_compatible
4+
from zope.component import adapter
5+
from zope.component import getMultiAdapter
6+
from zope.interface import implementer
7+
from zope.interface import Interface
8+
from zope.interface import alsoProvides
9+
from plone.app.contenttypes.interfaces import IImage
10+
from plone.namedfile.interfaces import INamedImageField
11+
from z3c.relationfield.interfaces import IRelationChoice
12+
13+
import logging
14+
15+
16+
log = logging.getLogger(__name__)
17+
18+
19+
@adapter(IDexterityContent, IRelationChoice, IDexterityContent, Interface)
20+
@implementer(IRelationObjectSerializer)
21+
class DefaultRelationObjectSerializer:
22+
def __init__(self, rel_obj, field, context, request):
23+
self.context = context
24+
self.request = request
25+
self.field = field
26+
self.rel_obj = rel_obj
27+
28+
def __call__(self):
29+
obj = self.rel_obj
30+
# Start from the values of the default field serializer
31+
result = json_compatible(self.get_value())
32+
# Add some more values from the object in relation
33+
additional = {
34+
"id": obj.id,
35+
"created": obj.created(),
36+
"modified": obj.modified(),
37+
"UID": obj.UID(),
38+
}
39+
result.update(additional)
40+
return json_compatible(result)
41+
42+
def get_value(self, default=None):
43+
return getattr(self.field.interface(self.context), self.field.__name__, default)
44+
45+
46+
class FieldSim:
47+
def __init__(self, name, provides):
48+
self.__name__ = name
49+
alsoProvides(self, provides)
50+
51+
def get(self, context):
52+
return getattr(context, self.__name__)
53+
54+
55+
class FieldRelationObjectSerializer(DefaultRelationObjectSerializer):
56+
"""The relationship object is treatable like a field
57+
58+
So we can reuse the serialization for that specific field.
59+
"""
60+
61+
field_name = None
62+
field_interface = None
63+
64+
def __call__(self):
65+
field = FieldSim(self.field_name, self.field_interface)
66+
result = super().__call__()
67+
# Reuse a serializer from dxfields
68+
serializer = getMultiAdapter((field, self.rel_obj, self.request))
69+
# Extend the default values with the content specific ones
70+
additional = serializer()
71+
if additional is not None:
72+
result.update(additional)
73+
return result
74+
75+
76+
@adapter(IImage, IRelationChoice, IDexterityContent, Interface)
77+
class ImageRelationObjectSerializer(FieldRelationObjectSerializer):
78+
# The name of the attribute that contains the data object within the relation object
79+
field_name = "image"
80+
# The field adapter that we will emulate to get the serialized data for this content type
81+
field_interface = INamedImageField

0 commit comments

Comments
 (0)