collective / collective.exportimport

Export and import content and other data from and to Plone
GNU General Public License v2.0
15 stars 17 forks source link

Exporting portlet with relation field fails #47

Open mauritsvanrees opened 3 years ago

mauritsvanrees commented 3 years ago

I have several relation fields in a portlet. Exporting portlets then fails because a RelationValue is not json serialisable:

http://localhost:9152/plone/@@export_portlets
Traceback (innermost last):
  Module ZPublisher.Publish, line 138, in publish
  Module ZPublisher.mapply, line 77, in mapply
  Module ZPublisher.Publish, line 48, in call_object
  Module collective.exportimport.export_other, line 477, in __call__
  Module json, line 251, in dumps
  Module json.encoder, line 209, in encode
  Module json.encoder, line 431, in _iterencode
  Module json.encoder, line 332, in _iterencode_list
  Module json.encoder, line 408, in _iterencode_dict
  Module json.encoder, line 408, in _iterencode_dict
  Module json.encoder, line 332, in _iterencode_list
  Module json.encoder, line 408, in _iterencode_dict
  Module json.encoder, line 408, in _iterencode_dict
  Module json.encoder, line 442, in _iterencode
  Module json.encoder, line 184, in default
TypeError: <z3c.relationfield.relation.RelationValue object at 0x114132050> is not JSON serializable

A bit related Is this comment from Philip where he removes some relations code, although I guess this was only active when exporting content, and not portlets.

The following diff in the portlet export code fixes it for me:

$ git diff
diff --git a/src/collective/exportimport/export_other.py b/src/collective/exportimport/export_other.py
index f358a1c..383635a 100644
--- a/src/collective/exportimport/export_other.py
+++ b/src/collective/exportimport/export_other.py
@@ -535,13 +535,18 @@ def export_local_portlets(obj):
             settings = IPortletAssignmentSettings(assignment)
             if manager_name not in items:
                 items[manager_name] = []
+            from z3c.relationfield.relation import RelationValue
+            assignment_data = {}
+            for name in schema.names():
+                value = getattr(assignment, name, None)
+                if value and isinstance(value, RelationValue):
+                    value = value.to_object.UID()
+                assignment_data[name] = value
+
             items[manager_name].append({
                 'type': portlet_type,
                 'visible': settings.get('visible', True),
-                'assignment': {
-                    name: getattr(assignment, name, None)
-                    for name in schema.names()
-                },
+                'assignment': assignment_data,
             })
     return items

The code needs to be more robust, but those are details. I am not sure if this is a reasonable place for this fix or if there is a more general place.

Ah, wait, using this works too:

json_compatible(getattr(assignment, name, None))

At least then you get an export without errors, although my earlier code that returns uuids could be preferable in some cases.

mauritsvanrees commented 3 years ago

With the json_compatible call, the export will have a field value in the assignment like this:

"linkitem1": {
    "@type": "Document",
    "review_state": "published",
    "@id": "http://localhost:9152/plone/water",
    "description": "",
    "title": "Water"
}

The current portlet importer stores this dictionary directly in the field, instead of turning it into a RelationValue. So the portlet view gives an error and the edit form does not show any current value.

Turning it into a UID during export, makes it easier on the import side. For best results, I do have to change my portlet template, and change my portlet field definition from RelationChoice to schema.Choice, and use the RelatedItemsFieldWidget. But this particular portlet has 6 relation fields, linkitem1 though linkitem6, so I may want to refactor it to a single schema.List anyway. BTW, I am very happy that I can look at example.contenttype for the various field/widget combinations.

So: shall I make a PR for the portlet export that changes relation values into uuids?

if value and isinstance(value, RelationValue):
    value = value.to_object.UID()