z3c.form.Datagridfield. Widgets and setting

Two questions:

  1. Is there any other widget than CheckBoxFieldWidget and InOutWidget that could work with a (MultiSelect) List field inside a DictRow

  2. Is it possible to disable disable 'auto append' for all fields or is the only option to

    def updateWidgets(self):
    super(EditForm, self).updateWidgets()
    self.widgets['interactions'].auto_append = False

for every 'Datagrid-field'

@espenmn did you ever work out how to disable auto_append (and friends) in either the schema or the interface?

No, I think I 'cheated' by forking some add on and change the defaults (settings)

Update: It might be that those settings was saved in / with a javascript

1 Like

I was just able to customize the other properties by doing this:

@implementer(z3c.form.interfaces.IFieldWidget)
def VOCInfoGridFactory(field, request):
    """
    A special widget constructor setting up widget parameters for DGF.
    """
    widget = DataGridFieldFactory(field, request)
    widget.allow_insert = False
    widget.allow_delete = False
    widget.allow_reorder = False
    #widget.auto_append = False
    return widget

    
@provider(IFormFieldProvider)
class IUrprodukt(model.Schema):
    voc_info = schema.List(
        title       = _(u'VOC EU Grenzwert Kennzeichnung'),
        description = _(u'Art und Grenzwert für Flüchtige organische Verbindungen gemäß Richtlinie 2004/42/EG'),
        required    = False,
        value_type  = DictRow(
            schema = IMaxVolatileCompoundsSchema
        ),
    )
    directives.widget('voc_info', VOCInfoGridFactory)


class IMaxVolatileCompoundsSchema(Interface):
    voc_max_schedule = schema.TextLine(
        title    = _(u'Anwendungsbereich'),
        required = True,
    )
    directives.widget('voc_max_schedule',   DGFTreeSelectFieldWidget)

    voc_max_category = schema.TextLine(
        title    = _(u'Kategorie'),
        required = True,
    )
    directives.widget('voc_max_category', DGFTreeSelectFieldWidget)

    voc_max_class = schema.TextLine(
        title    = _(u'Klasse'),
        required = True,
    )
    directives.widget('voc_max_class', DGFTreeSelectFieldWidget)

    voc_max_content = schema.Int(
        title    = _(u'Produkt Enthält Max.'),
        required = True,
        min = 0,
        max = 999,
    )    
IMaxVolatileCompoundsSchema.setTaggedValue('vocabularyName', 'mdb_theme.MaxVolatileCompounds')

This produces:

Code adapted from https://github.com/collective/collective.z3cform.dgftreeselect/blob/master/src/collective/z3cform/dgftreeselect/testform.py#L80

However, when I attempt to set auto_append to False my edit form's fieldsets stop rendering in separate tabs and the datagrid cannot be edited any longer...

After a bit more testing:

If the datagrid already contains a value, the page is rendered correctly. Looks like all that I need to do is to update my content by adding a default value to this new field.

A vague memory: does it help to set the fields of IMaxVolatileCompoundsSchema to required=False ?

PS: I used this for theme fragments. Do you think your approach would work without having registering a custom widget ( in zcml )?

I ended up setting the default values in my dexterity schema and made the field required instead of the grid cells. This last step was mostly for cosmetic effect.

With "custom widget" you mean the tree widget? I haven't tried with other widgets, but I guess you could add a custom factory that implements a standard widget in similar fashion. The only zcml registration I have for the tree widget is a data converter adapter, nothing out of the ordinary.

Hi!
Context: add DGF (List with Multiwidget with DictRows from DGF)

if someone arrives here to try to get some info on DGF and widget settings, my 2 cents:

  • there's some differences if you're working on xml model and a file system schema
    If you're just using DGF row schema without a form wrapper, widget settings does not work.

With this plain schema for the DGF row:


from collective.z3cform.datagridfield.datagridfield import DataGridFieldWidgetFactory

class ITableRowSchema(model.Schema): # can work with interface.Interface?
    one = schema.TextLine(title=u"One")
    two = schema.TextLine(title=u"Two")
    three = schema.TextLine(title=u"Three")

class IFormSchema(interface.Interface):
    table = schema.List(
        title=u"Table",
        value_type=DictRow(
            title=u"tablerow",
            schema=ITableRowSchema,
        ),
    )

    widget('table', DataGridFieldFactory, allow_reorder=True)

For example, if you add this field xml in the model editor:

      <field name="mygrid" type="zope.schema.List">
        <description></description>
        <required>False</required>
        <title>My Grid</title>
        <form:widget type="myproduct.interfaces.DataGridFieldFactory"/>
      </field>

widget settings like allow_reorder=True are ignored. The works if you use your own DataGridFieldFactory:

<form:widget type="myproduct.interfaces.MyDataGridFieldFactory"/>
in the xml above, and you create your factory (see below). At this point, the above IFormSchema is useless because the xml has already all the info to manage the field and the widget.

  • if you are using DGF and using the editor in the dexterity control panel to add fields using xml, in the example above you've also to add this:
@adapter(schema.interfaces.IField, form.interfaces)
@implementer(form.interfaces.IFieldWidget)
def MyDataGridFieldFactory(field, request):
    """
    A special widget constructor setting up widget parameters for DGF.
    """
    if field.value_type is None:
       # render the widget in the model editor if the schema is missing
       field.value_type=DictRow(schema=ITableRowSchema)
    widget = DataGridFieldWidgetFactory(field, request)
    widget.allow_reorder = True
    widget.auto_append = False
    return widget

the lines if field.value_type is None: are meant to avoid an error when rendering the dexterity content type editor the next time you add another field TTW. The error happen only when you add a new field TTW because the field attribute value_type is None. But you can change the xml and load it from filesystem using the profile. The problem here is that there's no schema for the field other than the one in the schema model editor. So, next time you will add a field, the DictRow serializer here:
https://github.com/collective/collective.z3cform.datagridfield/blob/b8f83531aad8f46678f1db715ded5d890ce68393/src/collective/z3cform/datagridfield/supermodel.py#L1-L5 cannot handle the value_type DictRow export (it is 2011 old code). It also misses the IFieldExportImportHandler interface to be picked up by the serializer, the call handler = queryUtility(IFieldExportImportHandler, name=value_fieldType) return none instead of return the ObjectHandler serializer.

With the code above, you can still define your content type TTW without using a file system schema and then add/remove/change fields in your content type, while using DGF.

  • if you're working in a control panel configlet, you need something like this:
    profiles/registry.xml:
  <record name="my_grid_field" field="my_grid_field">
    <field type="plone.registry.field.List">
      <title></title>
      <required>False</required>
    </field>
    <value_type type="collective.z3cform.datagridfield.registry.DictRow">
      <required>False</required>
    </value_type>
    <value/>
  </record>

and this in the configlet schema:

    lista_servizi = schema.List(
        title=u"",
        value_type=DictRow(
            title=u"tablerow",
            schema=ITableRowMySchema,
        ),
    )
    directives.widget("my_grid_field", DataGridFieldFactory,
                      allow_reorder=True,
                      auto_append = False)

here the directives on widgets works because you've a fs schema that is loaded and then it is easier, z3c.form/plone.autoform and friends will do the boring and complex part.

Why this post? The point is that usually you've your schema in the product and if you add a field using the editor, Dexterity will merge your filesystem schema with the TTW schema and this should the way to go. The method I've shown above can be tricky BUT can be something that someone can/will fall into. So this post is to give you a direction on what is happening if errors like AttributeError: 'NoneType' object has no attribute 'schema' or widget settings not working or zope.schema._bootstrapinterfaces.WrongType: ([<some dicts here>], <class 'str'>, 'value') or TypeError: There is no persistent field equivalent for the field myfield of type 'List'.

Schema are powerful but can be really tricky. My suggestion is to follow the best practices and use plonecli to create boilerplates (interfaces, schema, etc) and search in the github collective for products using DataGridField.

2 Likes

Nice writeup @yurj ! Be aware that my comments are about DGF on Plone 5.0 / 5.1 and specifically collective.z3cform.dfgtreeselect

1 Like

Maybe also interesting for the DGF implementers:

I've started to enhance the demo module to share some examples with behaviors and contenttypes. See Vocabulary lookup fix in `++add++` forms by petschki · Pull Request #187 · collective/collective.z3cform.datagridfield · GitHub (which originally only wanted to fix the vocabulary lookup on ++add++ forms)

Starting to create an example with a XML file as schema I notized, that the ExportImportWidgetHandler for plone.autoform is missing completely, so that's been added here: Fix missing `plone.autoform` ExportImportHandler by petschki · Pull Request #188 · collective/collective.z3cform.datagridfield · GitHub

Now you can customize the DGF widget attributes like this:

<?xml version="1.0" encoding="utf-8"?>
<model xmlns="http://namespaces.plone.org/supermodel/schema"
       xmlns:form="http://namespaces.plone.org/supermodel/form"
       xmlns:i18n="http://xml.zope.org/namespaces/i18n"
       xmlns:security="http://namespaces.plone.org/supermodel/security"
       i18n:domain="collective.z3cform.datagridfield"
>
  <schema>
    <field name="table"
           type="zope.schema.List"
    >
      <title>Tabular Field</title>
      <description />
      <value_type type="collective.z3cform.datagridfield.row.DictRow">
        <schema>collective.z3cform.datagridfield.demo.interfaces.ITableRow</schema>
      </value_type>
      <form:widget type="collective.z3cform.datagridfield.datagridfield.DataGridFieldWidgetFactory">
        <allow_reorder>True</allow_reorder>
        <auto_append>False</auto_append>
      </form:widget>
    </field>
  </schema>
</model>
3 Likes

I did try it before with no luck, many thanks for this fix!

For me: the example as provided returns the traceback below when attempting to install a controlpanel configlet in Plone 6.0.11

2025-06-29 12:18:48,722 ERROR   [Zope.SiteErrorLog:17][waitress-3] ValueError: http://localhost:8888/Plone/install_products
Traceback (innermost last):
  Module ZPublisher.WSGIPublisher, line 181, in transaction_pubevents
  Module ZPublisher.WSGIPublisher, line 391, in publish_module
  Module ZPublisher.WSGIPublisher, line 285, in publish
  Module ZPublisher.mapply, line 98, in mapply
  Module Products.PDBDebugMode.wsgi_runcall, line 60, in pdb_runcall
  Module Products.CMFPlone.controlpanel.browser.quickinstaller, line 544, in __call__
  Module Products.CMFPlone.controlpanel.browser.quickinstaller, line 310, in install_product
  Module Products.GenericSetup.tool, line 393, in runAllImportStepsFromProfile
   - __traceback_info__: profile-collective.restclient:default
  Module Products.GenericSetup.tool, line 1504, in _runImportStepsFromContext
  Module Products.GenericSetup.tool, line 1316, in _doRunImportStep
   - __traceback_info__: plone.app.registry
  Module plone.app.registry.exportimport.handler, line 79, in importRegistry
   - __traceback_info__: registry/main.xml
  Module plone.app.registry.exportimport.handler, line 123, in importDocument
  Module plone.app.registry.exportimport.handler, line 240, in importRecord
   - __traceback_info__: record name: restclient_connections
  Module plone.supermodel.exportimport, line 132, in read
  Module plone.supermodel.exportimport, line 84, in _constructField
  Module zope.schema._field, line 754, in __init__
ValueError: 'value_type' must be field instance.

To render a DataGrid form in my control panel, I can use <records> in the xml, as follows:

<records interface="collective.restclient.interfaces.IRestConnectionsSchema" />

but the resulting form does not save. There is no action at all after hitting submit.

class IRestConnectionsSchema(Interface):
    """Main DGF REST Connections form"""
    restclient_connections = field.List(
        title=_(
            'restclient_connections_title',
            default = u'REST Client Connection',
        ),
        value_type= DictRow(
            title=_(
                'value_type_title',
                default = u'Table',
            ),
            schema=IRestClientSettings,
        ),
        default=[],
        required=False,
    )
    directives.widget('restclient_connections', DataGridFieldWidgetFactory)

If I replace plone.registry.field with a zope.schema field in the grid, the form posts (obviously no values are saved) but [edit]only becomes a DataGrid after restarting the server[/edit]. Just to be complete, I tried with the regular and the persistent DictRow.

In looking at @yurj 's example once again: value_type is an attribute of the field element. When updating my xml configuration to reflect the correct structure/nesting I get a different (more logical) traceback.

<record name="restclient_connections" field="restclient_connections">
  <field type="plone.registry.List">
    <title />
    <required>False</required>
    <value_type type="collective.z3cform.datagridfield.registry.DictRow">
      <required>False</required>
    </value_type>
  </field>
  <value />
</record>
2025-06-29 14:08:44,946 ERROR   [Zope.SiteErrorLog:17][waitress-0] TypeError: http://localhost:8888/Plone/install_products
Traceback (innermost last):
  Module ZPublisher.WSGIPublisher, line 181, in transaction_pubevents
  Module ZPublisher.WSGIPublisher, line 391, in publish_module
  Module ZPublisher.WSGIPublisher, line 285, in publish
  Module ZPublisher.mapply, line 98, in mapply
  Module Products.PDBDebugMode.wsgi_runcall, line 60, in pdb_runcall
  Module Products.CMFPlone.controlpanel.browser.quickinstaller, line 544, in __call__
  Module Products.CMFPlone.controlpanel.browser.quickinstaller, line 310, in install_product
  Module Products.GenericSetup.tool, line 393, in runAllImportStepsFromProfile
   - __traceback_info__: profile-collective.restclient:default
  Module Products.GenericSetup.tool, line 1504, in _runImportStepsFromContext
  Module Products.GenericSetup.tool, line 1316, in _doRunImportStep
   - __traceback_info__: plone.app.registry
  Module plone.app.registry.exportimport.handler, line 79, in importRegistry
   - __traceback_info__: registry/main.xml
  Module plone.app.registry.exportimport.handler, line 123, in importDocument
  Module plone.app.registry.exportimport.handler, line 235, in importRecord
   - __traceback_info__: record name: restclient_connections
TypeError: Field of type plone.registry.List used for record restclient_connections is not supported.

I have DataGridField working for control panel in 6.1.1 & 6.1.2.

The code is

widget(vokabularies=DataGridFieldFactory)
    vokabularies = schema.List(
    title = _(u"XXX",
        default=u"XXXX"),
    value_type=DictRow(schema=ISomething),
    required=not_required_in_debug_mode(),
)

and

class ISomething(model.Schema):
vocabulary_entry = schema.TextLine(
    title=_(u'Vocabulary entries', 'TXXXXX'),
    description=u"XXXX.",
    required=False,
)

not sure if its already mentioned, but for controlpanels you have to use this DictRow as value_type: collective.z3cform.datagridfield/src/collective/z3cform/datagridfield/registry.py at master · collective/collective.z3cform.datagridfield · GitHub

did you use:

from collective.z3cform.datagridfield.registry import DictRow

?

Here I do:

from plone.restapi.controlpanels import RegistryConfigletPanel
[...]
@provider(IFormFieldProvider)
class IMySchemaControlPanel(model.Schema):
[...]
class MyThemeControlPanel(RegistryEditForm):
    schema = IMyThemeControlPanel
[...]
@adapter(Interface, IMythemeLayer)
class MyThemeControlPanelConfigletPanel(RegistryConfigletPanel):
    """Control Panel endpoint"""
    schema = IMySchemaControlPanel

the problem is that it can't find an adapter for handler = queryUtility(IFieldExportImportHandler, name=value_fieldType) at plone.app.registry/plone/app/registry/exportimport/handler.py at c96ba05ae7a92d61339f5cff8221096812e97ce1 · plone/plone.app.registry · GitHub

you've to breakpoint before that line and try to guess why it does not find it.

In my example above, I pss the 'schema', ISomething (

value_type=DictRow(schema=ISomething ) 

as value type, that might be what you are missing ?

Thanks for the replies :slight_smile:

@yurj and @petschki - yes I tried both:

@espenmn In my GS profile, I just copied what @yurj wrote. In the schema I use the same as you suggest.

In the meantime, I made changes to my code:

  • use class plone.supermodel.model.Schema instead of zope.interface.Interface for my schema,
  • make all subform fields optional.
  • make all fields be instances of zope.schema instead of plone.registry.field
  • I keep using this in my genericsetup:
    <records interface="collective.restclient.interfaces.IRestConnectionsSchema" prefix="collective.restclient" />

The form now works for me and I confirmed that my values are saved in the registry.

Thanks! I now also added the @provider and @adapter decorators to my form and the configlet.

Edit: now that I have the form working, I wanted to try your GS approach with a single record, and see a typo that explains the traceback I had before:

<record name="restclient_connections" field="restclient_connections">
  <field type="plone.registry.List">
    <title />
    <required>False</required>
    <value_type type="collective.z3cform.datagridfield.registry.DictRow">
      <required>False</required>
    </value_type>
  </field>
  <value />
</record>

Should be: <field type="plone.registry.field.List">

But now, either using plone.registry.field.List or zope.schema.List I get a NotImplementedError from plone.supermodel

2025-06-30 14:48:43,950 ERROR   [Zope.SiteErrorLog:17][waitress-0] NotImplementedError: http://localhost:8888/Plone/install_products
Traceback (innermost last):
  Module ZPublisher.WSGIPublisher, line 181, in transaction_pubevents
  Module ZPublisher.WSGIPublisher, line 391, in publish_module
  Module ZPublisher.WSGIPublisher, line 285, in publish
  Module ZPublisher.mapply, line 98, in mapply
  Module Products.PDBDebugMode.wsgi_runcall, line 60, in pdb_runcall
  Module Products.CMFPlone.controlpanel.browser.quickinstaller, line 544, in __call__
  Module Products.CMFPlone.controlpanel.browser.quickinstaller, line 310, in install_product
  Module Products.GenericSetup.tool, line 393, in runAllImportStepsFromProfile
   - __traceback_info__: profile-collective.restclient:default
  Module Products.GenericSetup.tool, line 1504, in _runImportStepsFromContext
  Module Products.GenericSetup.tool, line 1316, in _doRunImportStep
   - __traceback_info__: plone.app.registry
  Module plone.app.registry.exportimport.handler, line 79, in importRegistry
   - __traceback_info__: registry/main.xml
  Module plone.app.registry.exportimport.handler, line 123, in importDocument
  Module plone.app.registry.exportimport.handler, line 240, in importRecord
   - __traceback_info__: record name: restclient_connections
  Module plone.supermodel.exportimport, line 114, in read
NotImplementedError: Type collective.z3cform.datagridfield.registry.DictRow used for value_type not supported