Skip to content

Commit 608b8d3

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 608b8d3

File tree

8 files changed

+424
-2
lines changed

8 files changed

+424
-2
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: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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+
if result is None:
33+
return None
34+
# Add some more values from the object in relation
35+
additional = {
36+
"id": obj.id,
37+
"created": obj.created(),
38+
"modified": obj.modified(),
39+
"UID": obj.UID(),
40+
}
41+
result.update(additional)
42+
return json_compatible(result)
43+
44+
def get_value(self, default=None):
45+
return getattr(self.field.interface(self.context), self.field.__name__, default)
46+
47+
48+
class FieldSim:
49+
def __init__(self, name, provides):
50+
self.__name__ = name
51+
alsoProvides(self, provides)
52+
53+
def get(self, context):
54+
return getattr(context, self.__name__)
55+
56+
57+
class FieldRelationObjectSerializer(DefaultRelationObjectSerializer):
58+
"""The relationship object is treatable like a field
59+
60+
So we can reuse the serialization for that specific field.
61+
"""
62+
63+
field_name = None
64+
field_interface = None
65+
66+
def __call__(self):
67+
field = FieldSim(self.field_name, self.field_interface)
68+
result = super().__call__()
69+
if result is None:
70+
return None
71+
# Reuse a serializer from dxfields
72+
serializer = getMultiAdapter((field, self.rel_obj, self.request))
73+
# Extend the default values with the content specific ones
74+
additional = serializer()
75+
if additional is not None:
76+
result.update(additional)
77+
return result
78+
79+
80+
@adapter(IImage, IRelationChoice, IDexterityContent, Interface)
81+
class ImageRelationObjectSerializer(FieldRelationObjectSerializer):
82+
# The name of the attribute that contains the data object within the relation object
83+
field_name = "image"
84+
# The field adapter that we will emulate to get the serialized data for this content type
85+
field_interface = INamedImageField

src/plone/restapi/tests/test_dxfield_serializer.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from DateTime import DateTime
12
from datetime import date
23
from datetime import datetime
34
from datetime import time
@@ -268,6 +269,9 @@ def test_relationchoice_field_serialization_returns_summary_dict(self):
268269
description="Description 2",
269270
)
270271
]
272+
doc2.creation_date = DateTime("2016-01-21T01:14:48+00:00")
273+
doc2.modification_date = DateTime("2017-01-21T01:14:48+00:00")
274+
doc2_uid = doc2.UID()
271275
value = self.serialize("test_relationchoice_field", doc2)
272276
self.assertEqual(
273277
{
@@ -276,6 +280,13 @@ def test_relationchoice_field_serialization_returns_summary_dict(self):
276280
"title": "Referenceable Document",
277281
"description": "Description 2",
278282
"review_state": "private",
283+
# Additional fields are added by the relationship serializer
284+
# for default content.
285+
"UID": doc2_uid,
286+
"created": "2016-01-21T01:14:48+00:00",
287+
"description": "Description 2",
288+
"id": "doc2",
289+
"modified": "2017-01-21T01:14:48+00:00",
279290
},
280291
value,
281292
)
@@ -634,5 +645,5 @@ def test_namedblobimage_field_serialization_doesnt_choke_on_corrupt_image(self):
634645

635646

636647
class TestDexterityFieldSerializers(TestCase):
637-
def default_field_serializer(self):
648+
def test_default_field_serializer(self):
638649
verifyClass(IFieldSerializer, DefaultFieldSerializer)

0 commit comments

Comments
 (0)