Skip to content

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, :list suffix on key, and a :list:int sentinel:

    subfield_field_my_group_list_default:list = "nexedi/de"
    default_subfield_field_my_group_list_default:list:int = "0"

    The bare field_my_group_list MUST 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 from MultiRelationStringField.
    • MultiRelationStringField now emits the bare title key (list value → repeated keys) and an indexed UID companion (<relation_field_id>_0, _1, ...).
    • ListField/RadioField (scalar) split from ParallelListField/MultiListField.
    • ParallelListField/MultiListField now emit subfield_<form_key>_default:list for the value (as a list) and default_subfield_<form_key>_default:list:int=0 for the sentinel. Bare field_<key> is no longer emitted.
  • _validate_field_values: accepts a list value for ParallelListField/MultiListField and validates each element against valid_choices.
  • erp5_write multipart branch: rebuilt as a list of (key, tuple) pairs so the same key can repeat across parts. The previous dict form collapsed list-valued entries into str(["nexedi/de"]), which ERP5 couldn't parse. Required for PDF / Web Page writes (they have a GadgetField and so go through the multipart path).

Tests

  • 4 new test cases lock in the captured wire shapes for ParallelListField (string + list input), MultiListField, and MultiRelationStringField (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_create a PDF in document_module, erp5_write field_my_group_list=nexedi/de, field_my_publication_section_list=administration/tax, field_my_follow_up_title_list=<ticket title>, then erp5_read(all_editable=True) — each field should now show a non-empty list value.
  • Manual: from Draft, run submit_action then release_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_write field_my_destination_title_list=<Person title> and verify it persists via erp5_read(all_editable=True).
Edited by Klaus Wölfel

Merge request reports