Moving behavior fields to different fieldset?

I have a custom DX content-types and collective.geolocationbehavior added to the CT through its FTI.

Now the geolocation field shows up default fieldset in edit mode. I need to move it to a different fieldset or its own fieldset.

Using this

fieldset('MyField', fields=['geolocation'])

inside my schema does work:

zope.configuration.config.ConfigurationExecutionError: <type 'exceptions.ValueError'>: The directive plone.supermodel.fieldsets applied to interface dynamore.policy.content.dynalocation.IDynalocation refers to unknown field name geolocation
  in:
  File "/home/ajung/sandboxes/plone-server-buildout-plone5/eggs/plone.supermodel-1.3.4-py2.7.egg/plone/supermodel/configure.zcml", line 9.4-12.10
      <zcml:customAction
          handler=".model.finalizeSchemas"
          order="9999999"
          />

You can do this globally by moving the fields for all CTs using it (example showing the changeNote field, but can be applied to all other behaviors as well):

from plone.app.dexterity import _ as _DX
from plone.app.versioningbehavior.behaviors import IVersionable
from plone.supermodel.interfaces import FIELDSETS_KEY
from plone.supermodel.model import Fieldset


settings = Fieldset(
    'settings',
    label=_DX(u'Settings'),
    fields=['changeNote'],
)
fieldsets = IVersionable.getTaggedValue(FIELDSETS_KEY)
fieldsets.append(settings)
1 Like

Your (magic) example code works great! I initially thought it belonged in the class definition for the content type's interface but, in fact, it belongs outside of any class definition.

But I could not get it to work with lead image behaviour. It would result in this error. I don't know why ILeadImage would be different.

KeyError: 'Plone.supermodel.fieldsets'

when reaching the line

fieldsets = ILeadImage.getTaggedValue(FIELDSETS_KEY)

The way I found that worked was to override the two lead image classes with my own behaviours.py file:

# -*- coding: utf-8 -*-
from plone.app.contenttypes import _
from plone.autoform.interfaces import IFormFieldProvider
from plone.app.contenttypes.behaviors.leadimage import ILeadImage
from plone.dexterity.interfaces import IDexterityContent
from plone.supermodel import model
from zope.component import adapter
from zope.interface import implementer
from zope.interface import provider


@provider(IFormFieldProvider)
class IOIELeadImage(ILeadImage):
    model.fieldset(
        'leadimage',
        label=_(u'Lead Image'),
        fields=['image', 'image_caption'],
    )


@implementer(IOIELeadImage)
@adapter(IDexterityContent)
class LeadImage(object):

    def __init__(self, context):
        self.context = context

and then using that behaviour in the content type XML definition instead.

...ok maybe I spoke too soon...it behaves correctly in the add form but isn't working when saving

[a moment passes]
To go down this route and have it work, I had to register my new customized behaviour.

But @tmassman's (much simpler and more elegant) original suggestion works for lead images if you do something like this:

leadimage_fieldset = Fieldset(
    'leadimage',
    label=_(u'Lead Image'),
    fields=['image', 'image_caption'],
)
try:
    leadimage_fieldsets = ILeadImage.getTaggedValue(FIELDSETS_KEY)
    leadimage_fieldsets.append(leadimage_fieldset)
except KeyError:
    ILeadImage.setTaggedValue(FIELDSETS_KEY, [leadimage_fieldset])

I know ILeadImage defines no fieldsets because calling ILeadImage.getTaggedValue(FIELDSETS_KEY) gives a KeyError. I used a try block to be more general here.

This should be added to the docs...https://github.com/plone/plone.app.dexterity/issues/282 and https://github.com/plone/training/issues/379

Warming up an old thread to dump my working code snippet here:

from plone.supermodel.interfaces import FIELDSETS_KEY
from plone.supermodel.model import Fieldset


def move_field(
    schema, fieldname, to_fieldset_name, label=None, description=None, order=None
):
    """Moves a field named "fieldname" on a Zope "schema" to a new fieldset "to_field_name".

    - creates a new fieldset on demand (then label, description and order are passed to the new one).
    - if value of "to_fieldset_name" is "default", then the field sticks on the main form.
    """
    # find schema with field in inheritance tree
    schema_with_field = None
    for pschema in reversed(schema.__iro__):
        if pschema.direct(fieldname):
            schema_with_field = pschema
            break
    if schema_with_field is None:
        raise KeyError(f"field '{fieldname}' does not exist on {schema}.")

    # remove field from fieldset (if in any)
    fieldsets_direct = schema_with_field.queryDirectTaggedValue(FIELDSETS_KEY)
    if fieldsets_direct is not None:
        for fieldset in fieldsets_direct:
            if fieldname in fieldset.fields:
                fieldset.fields.remove(fieldname)
                break

    if to_fieldset_name == "default":
        # default means to fieldset, but on main form
        return

    if fieldsets_direct is None:
        # no tagged value set so far!
        fieldsets_direct = list()
        schema.setTaggedValue(FIELDSETS_KEY, fieldsets_direct)

    # try to find the fieldset, append and exit
    for fieldset in fieldsets_direct:
        if fieldset.__name__ == to_fieldset_name:
            fieldset.fields.append(fieldname)
            return

    # not found, need to create new fieldset
    new_fieldset = Fieldset(
        to_fieldset_name,
        fields=[fieldname],
    )
    if label is not None:
        new_fieldset.label = label
    if description is not None:
        new_fieldset.description = description
    if order is not None:
        new_fieldset.order = order
    fieldsets_direct.append(new_fieldset)
    # Done!



# example
from plone.app.contenttypes.behaviors.leadimage import ILeadImageBehavior
from plone.app.dexterity.behaviors.metadata import ICategorization
from plone.app.dexterity.behaviors.metadata import IDublinCore


# 1) move direct field language on ICategorization from fieldset
#    categorization to settings
move_field(ICategorization, "language", "settings")

# 2) move inherited field subject on IDublinCore from fieldset
#    categorization to settings
move_field(IDublinCore, "subjects", "settings")

# 3) move inherited field image on ILeadImage from main form
#    to new fiedset images
move_field(ILeadImageBehavior, "image", "images", label="Images", order=10)

We may want to have this in the utils.py of plone.supermodel or plone.autoform I suppose. Any volunteers?

8 Likes