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!