Field Inheritance

Content can inherit field values from content it references. The REST API response of the referencing object is filled up with the values of the referenced object, without copying any data into the database.

The classic example is the MemberBlock: it references a Contact and shows that contact’s name, address and opening hours, while still being able to override single fields locally.

Which fields inherit is configured declaratively — per field, with the inherit: supermodel namespace.

Rules

  • A field only inherits while the referencing object has no own value for it. Any value entered locally wins.

  • The referenced object is resolved through a relation field on the referencing object. If the reference is empty or broken, nothing is inherited.

  • Values are only inherited if the current user may view the referenced object, and only for fields the user may read.

  • The source object must have a field of the same name. Fields without a counterpart are silently skipped.

  • With the Inherit field data behavior enabled, editors get a checkbox to switch inheritance off per object.

Configuring inheritable fields

Single fields

Add inherit:from="<relation field name>" to a field in the supermodel XML. The value names the relation field that points to the source object:

<model xmlns="http://namespaces.plone.org/supermodel/schema"
       xmlns:inherit="http://namespaces.webcloud7.ch/supermodel/inherit">
  <schema>
    <field name="badge" type="zope.schema.TextLine" inherit:from="contact">
      <title>Badge</title>
      <required>False</required>
    </field>
  </schema>
</model>

badge now inherits from the object referenced by the contact relation field — provided the referenced type has a badge field too.

A whole schema

Put the attribute on the <schema> node to make every field of that schema inherit:

<model xmlns="http://namespaces.plone.org/supermodel/schema"
       xmlns:inherit="http://namespaces.webcloud7.ch/supermodel/inherit">
  <schema inherit:from="contact">
    <field name="slogan" type="zope.schema.TextLine">
      <title>Slogan</title>
    </field>
    <field name="motto" type="zope.schema.TextLine">
      <title>Motto</title>
    </field>
  </schema>
</model>

Single fields can still be configured explicitly; an inherit:from on a field overrules the one on its schema.

In Python schemas

Behaviors written in Python use the inherit_from directive, which does the same thing:

from wcs.backend.inheritance.directives import inherit_from


class ITeamCard(model.Schema):

    inherit_from('contact')                       # all fields of this schema
    inherit_from('contact', 'slogan', 'motto')    # or only the named ones

    slogan = schema.TextLine(title=_('label_slogan', default='Slogan'))

Enabling it for a content type

Inheritance is applied by the REST API serializer of the content type. For a new type, register a serializer that mixes FieldInheritanceMixin into the serializer the type would use anyway:

@implementer(ISerializeToJson)
@adapter(ITeamCard, Interface)
class TeamCardSerializer(FieldInheritanceMixin, DefaultBlockSerializer):
    """"""
<adapter factory=".restapi.TeamCardSerializer" />

Add the Inherit field data behavior to the type if editors should be able to switch inheritance off.

REST API

Inherited values appear as ordinary field values — consumers cannot tell them apart from values stored on the object itself:

const response = await fetch('https://example.org/++api++/a-page/my-member-block', {
  headers: { Accept: 'application/json' },
});
const block = await response.json();

block.lastname;     // from the block, or inherited from the contact
block.badge;        // XML configured field, same behaviour
block.inherit_data; // false if inheritance was switched off for this block

Setting inherit_data to false returns only the values stored on the object:

await fetch('https://example.org/++api++/a-page/my-member-block', {
  method: 'PATCH',
  headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
  body: JSON.stringify({ inherit_data: false }),
});