From bde53558c592ad39adf8c6dce536246c2e1da382 Mon Sep 17 00:00:00 2001 From: Faakhir30 Date: Fri, 27 Sep 2024 16:43:18 +0500 Subject: [PATCH 1/3] Fix invalid values in serializer. --- news/1818.bugfix | 1 + src/plone/restapi/serializer/relationfield.py | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 news/1818.bugfix diff --git a/news/1818.bugfix b/news/1818.bugfix new file mode 100644 index 0000000000..54260238ec --- /dev/null +++ b/news/1818.bugfix @@ -0,0 +1 @@ +Fix response of `RelationListFieldSerializer` by filtering out invalid items. @Faakhir30 diff --git a/src/plone/restapi/serializer/relationfield.py b/src/plone/restapi/serializer/relationfield.py index 28483ce424..82d9879a60 100644 --- a/src/plone/restapi/serializer/relationfield.py +++ b/src/plone/restapi/serializer/relationfield.py @@ -7,6 +7,7 @@ from z3c.relationfield.interfaces import IRelationChoice from z3c.relationfield.interfaces import IRelationList from z3c.relationfield.interfaces import IRelationValue +from z3c.relationfield import RelationValue from zope.component import adapter from zope.component import getMultiAdapter from zope.globalrequest import getRequest @@ -33,4 +34,20 @@ class RelationChoiceFieldSerializer(DefaultFieldSerializer): @adapter(IRelationList, IDexterityContent, Interface) @implementer(IFieldSerializer) class RelationListFieldSerializer(DefaultFieldSerializer): - pass + def get_value(self, default=[]): + """Return field value reduced to list of non-broken Relationvalues. + + Args: + default (list, optional): Default field value. Defaults to empty list. + + Returns: + list: List of RelationValues + """ + value = getattr( + self.field.interface(self.context), self.field.__name__, default + ) + if not value: + return [] + if isinstance(value, RelationValue): + return [value] + return [el for el in value if el.to_object] From 4805c930298a889f70fbe40bfde46f8483322436 Mon Sep 17 00:00:00 2001 From: Faakhir30 Date: Sat, 28 Sep 2024 23:31:17 +0500 Subject: [PATCH 2/3] Add test to check deleted relations --- src/plone/restapi/serializer/relationfield.py | 3 --- .../tests/test_dxcontent_serializer.py | 23 +++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/plone/restapi/serializer/relationfield.py b/src/plone/restapi/serializer/relationfield.py index 82d9879a60..1eb2c69e89 100644 --- a/src/plone/restapi/serializer/relationfield.py +++ b/src/plone/restapi/serializer/relationfield.py @@ -7,7 +7,6 @@ from z3c.relationfield.interfaces import IRelationChoice from z3c.relationfield.interfaces import IRelationList from z3c.relationfield.interfaces import IRelationValue -from z3c.relationfield import RelationValue from zope.component import adapter from zope.component import getMultiAdapter from zope.globalrequest import getRequest @@ -48,6 +47,4 @@ def get_value(self, default=[]): ) if not value: return [] - if isinstance(value, RelationValue): - return [value] return [el for el in value if el.to_object] diff --git a/src/plone/restapi/tests/test_dxcontent_serializer.py b/src/plone/restapi/tests/test_dxcontent_serializer.py index 6fd514956f..200ee127c0 100644 --- a/src/plone/restapi/tests/test_dxcontent_serializer.py +++ b/src/plone/restapi/tests/test_dxcontent_serializer.py @@ -27,6 +27,9 @@ from zope.component import provideAdapter from zope.component import queryUtility from zope.interface import Interface +from z3c.relationfield import RelationValue +from zope.component import getUtility +from zope.intid.interfaces import IIntIds from zope.publisher.interfaces.browser import IBrowserRequest from importlib import import_module @@ -191,6 +194,26 @@ def test_serializer_includes_expansion(self): "foo", ) + def test_serializer_excludes_deleted_relations(self): + + intids = getUtility(IIntIds) + self.portal.invokeFactory( + "DXTestDocument", + id="doc2", + ) + self.portal.doc1.test_relationlist_field = [ + RelationValue(intids.getId(self.portal.doc1)), + RelationValue(intids.getId(self.portal.doc2)), + ] + # delete doc2 to make sure we have a None value in the relation list + self.portal.manage_delObjects(["doc2"]) + + obj = self.serialize() + self.assertEqual(1, len(obj["test_relationlist_field"])) + self.assertEqual( + "http://nohost/plone/doc1", obj["test_relationlist_field"][0]["@id"] + ) + def test_get_is_folderish(self): obj = self.serialize() self.assertIn("is_folderish", obj) From 8c67abee68ee203e8b802cecc69b2942f5eb6c93 Mon Sep 17 00:00:00 2001 From: Faakhir30 Date: Wed, 2 Oct 2024 22:07:15 +0500 Subject: [PATCH 3/3] use to_id attribute to check content's existance. --- src/plone/restapi/serializer/relationfield.py | 6 ++---- src/plone/restapi/tests/test_dxcontent_serializer.py | 9 +++++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/plone/restapi/serializer/relationfield.py b/src/plone/restapi/serializer/relationfield.py index 1eb2c69e89..d54cadb9d1 100644 --- a/src/plone/restapi/serializer/relationfield.py +++ b/src/plone/restapi/serializer/relationfield.py @@ -42,9 +42,7 @@ def get_value(self, default=[]): Returns: list: List of RelationValues """ - value = getattr( - self.field.interface(self.context), self.field.__name__, default - ) + value = super().get_value() if not value: return [] - return [el for el in value if el.to_object] + return [el for el in value if el.to_id] diff --git a/src/plone/restapi/tests/test_dxcontent_serializer.py b/src/plone/restapi/tests/test_dxcontent_serializer.py index 200ee127c0..567aa01e62 100644 --- a/src/plone/restapi/tests/test_dxcontent_serializer.py +++ b/src/plone/restapi/tests/test_dxcontent_serializer.py @@ -28,6 +28,7 @@ from zope.component import queryUtility from zope.interface import Interface from z3c.relationfield import RelationValue +from z3c.relationfield.event import _setRelation from zope.component import getUtility from zope.intid.interfaces import IIntIds from zope.publisher.interfaces.browser import IBrowserRequest @@ -201,10 +202,14 @@ def test_serializer_excludes_deleted_relations(self): "DXTestDocument", id="doc2", ) + rel1 = RelationValue(intids.getId(self.portal.doc1)) + rel2 = RelationValue(intids.getId(self.portal.doc2)) self.portal.doc1.test_relationlist_field = [ - RelationValue(intids.getId(self.portal.doc1)), - RelationValue(intids.getId(self.portal.doc2)), + rel1, + rel2, ] + _setRelation(self.portal.doc1, "test_relationlist_field", rel1) + _setRelation(self.portal.doc1, "test_relationlist_field", rel2) # delete doc2 to make sure we have a None value in the relation list self.portal.manage_delObjects(["doc2"])