Fix multi-value field writes (ParallelListField, MultiListField, MultiRelationStringField)
How this was found
Klaus was uploading a batch of PDF documents into document_module via the erp5-mcp-hateoas tools — erp5_create (with the binary), then erp5_write to set metadata (my_group_list, my_publication_section_list, my_follow_up_title_list, etc.), then release_alive_action. The transition kept refusing with "Follow Up or Group must be defined" even though every write reported {status: success, updated_fields: [...]}. A follow-up erp5_read(all_editable=True) revealed the multi-value fields were still empty lists — the writes had been silent no-ops. Same root cause had previously surfaced as a missing recipient on a Mail Message event (field_my_destination_title_list), which Klaus had to set via the UI by hand.
To narrow down the wire format, the actual XHR the renderjs UI sends was sniffed via Playwright (DOM hook on XMLHttpRequest.prototype.send). The captured POST body revealed the real key/companion shapes — none of them matched what the MCP was sending.
Summary
erp5_write was reporting {status: success} for ParallelListField, MultiListField, and MultiRelationStringField updates while silently dropping the value at the ERP5 form layer. The serializer was posting the bare form key (e.g. field_my_group_list = nexedi/de), but the UI submits a wrapped subfield shape that Base_edit actually consumes.
Wire shapes used by the renderjs UI (captured from a live form)
-
ParallelListField/MultiListField— wrapped subfield,:listsuffix on key, and a:list:intsentinel:subfield_field_my_group_list_default:list = "nexedi/de" default_subfield_field_my_group_list_default:list:int = "0"The bare
field_my_group_listMUST NOT be present — Base_edit drops the value if it is. -
MultiRelationStringField— bare title key (repeated keys become a list) plus indexed UID companion:field_my_follow_up_title_list = "<title>" subfield_field_my_follow_up_title_list_relation_0 = "<uid>"For multiple rows, the title is repeated under the same key and the UID companion increments
_0→_1→ ...
These were verified by direct POST to a live ERP5 instance: writing subfield_field_my_group_list_default:list = "nexedi/de" flips the field from ['fdl'] to ['nexedi/de'] in one Base_edit cycle.
Changes
-
_serialize_field:-
RelationStringField(scalar) split fromMultiRelationStringField. -
MultiRelationStringFieldnow emits the bare title key (list value → repeated keys) and an indexed UID companion (<relation_field_id>_0,_1, ...). -
ListField/RadioField(scalar) split fromParallelListField/MultiListField. -
ParallelListField/MultiListFieldnow emitsubfield_<form_key>_default:listfor the value (as a list) anddefault_subfield_<form_key>_default:list:int=0for the sentinel. Barefield_<key>is no longer emitted.
-
-
_validate_field_values: accepts a list value forParallelListField/MultiListFieldand validates each element againstvalid_choices. -
erp5_writemultipart branch: rebuilt as a list of(key, tuple)pairs so the same key can repeat across parts. The previousdictform collapsed list-valued entries intostr(["nexedi/de"]), which ERP5 couldn't parse. Required for PDF / Web Page writes (they have aGadgetFieldand so go through the multipart path).
Tests
- 4 new test cases lock in the captured wire shapes for
ParallelListField(string + list input),MultiListField, andMultiRelationStringField(with indexed relation UID companion). - 2 existing multipart tests adjusted because the container is now a list of tuples instead of a dict.
- All 409 tests pass.
Test plan
-
Run pytest test_erp5_mcp.py -q— expect 409 passed. -
Manual: erp5_createa PDF indocument_module,erp5_writefield_my_group_list=nexedi/de,field_my_publication_section_list=administration/tax,field_my_follow_up_title_list=<ticket title>, thenerp5_read(all_editable=True)— each field should now show a non-empty list value. -
Manual: from Draft, run submit_actionthenrelease_alive_action— should succeed (previously blocked by "Follow Up or Group must be defined" because the group never persisted). -
Manual: on a Mail Message event, erp5_writefield_my_destination_title_list=<Person title>and verify it persists viaerp5_read(all_editable=True).