plone / plone/plone.restapi

@controlpanels/content-rules/<rule>/action/<idx> omits values for fields in non-default fieldsets

Open
#2,016 0 comments 0 reactions 1 assignee View on GitHub

@ericof is already working on this.

Since Apr 24, 2026.

Dominant language
Python
Stars
109
Forks
107
Avg merge
2d 3h
Merged PRs (30d)
4

Description

Summary

GET @controlpanels/content-rules/<rule-id>/action/<idx> (and .../condition/<idx>) returns values for only the fields in the default fieldset of the action/condition schema. Fields declared inside non-default fieldset(...) directives appear in @schema but have no corresponding key/value in the response payload, which makes the endpoint unusable for any rule element that groups its fields into tabs.

The same bug affects the update path for the same reason: view.form_instance.applyChanges(data) only applies fields that z3c.form's default-fieldset Fields knows about.

Environment

  • plone.restapi 9.15.4
  • Plone 6.x, Python 3.13
  • Reproduces against any rule action/condition whose schema uses plone.supermodel.directives.fieldset — e.g. collective/sc.contentrules.groupbydate ≥ 3.0.

Root cause

plone/restapi/controlpanels/rules.py, in ContentRulesControlpanel.get():

if view:
    view.form_instance.update()
    for field in view.form_instance.fields:
        fields[field] = getattr(extra_ob, field)
    schema = view.form.schema
    fields["@schema"] = rule_schema_as_json(schema, self.request)
    return fields

view.form_instance.fields is the z3c.form Fields object populated by plone.autoform.form.AutoExtensibleForm.updateFieldsFromSchemata(). That method puts only default-fieldset fields on form_instance.fields; fields declared inside fieldset("...", fields=[...]) directives end up on form_instance.groups[i].fields and are never visited by this loop.

rule_schema_as_json(schema, ...) on the next line walks zope.schema directly, so @schema correctly lists all fields — which is exactly why the mismatch between @schema and the emitted values is so visible to API consumers.

ContentRulesControlpanel.update() suffers the same issue: view.form_instance.applyChanges(data) is bounded by the same default-fieldset Fields, so PATCHing values into non-default-fieldset fields is silently dropped.

Reproduction

  1. Install any rule action whose schema uses fieldsets. Minimal example:

    from plone.autoform import directives
    from plone.supermodel.directives import fieldset
    from zope import schema
    from zope.interface import Interface
    
    
    class IMyAction(Interface):
    
        base_container = schema.TextLine(title="Base", required=True)
    
        fieldset(
            "advanced",
            label="Advanced",
            fields=["extra_title", "extra_description"],
        )
    
        extra_title = schema.TextLine(title="Extra title", default="hello")
        extra_description = schema.TextLine(title="Extra description", default="world")
    
  2. Create a rule that uses this action, populate it.

  3. GET /@controlpanels/content-rules/<rule-id>/action/0

Observed: response contains @id, @schema (with all three fields), base_container, but no extra_title / extra_description.

Expected: response should include a value for every field listed under @schema.properties.

Proposed fix

Walk the schema directly rather than iterating form_instance.fields. Two equivalent shapes:

Option A — reuse rule_schema_as_json output
if view:
    view.form_instance.update()
    schema_json = rule_schema_as_json(view.form.schema, self.request)
    fields["@schema"] = schema_json
    for name in schema_json.get("properties", {}):
        fields[name] = getattr(extra_ob, name, None)
    return fields
Option B — iterate the zope.schema interface directly
from zope.schema import getFieldsInOrder

if view:
    view.form_instance.update()
    for name, _field in getFieldsInOrder(view.form.schema):
        fields[name] = getattr(extra_ob, name, None)
    fields["@schema"] = rule_schema_as_json(view.form.schema, self.request)
    return fields

getattr(..., None) is deliberate: rule action/condition classes commonly use class-level defaults and never write unchanged attributes to __dict__, so getattr with a fallback preserves current behavior while staying safe for classes that simply don't declare a given attribute.

update() should be similarly reworked to set attributes via schema iteration (or apply changes per-group via form.applyChanges on each of form_instance.groups).

Impact

Any third-party rule action/condition that organizes its UI with fieldset() (the idiomatic Plone way) is effectively invisible on the REST control panel. Fixing this is backward-compatible: existing default-fieldset-only schemas keep working unchanged, and consumers gain the values that @schema has been advertising all along.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.