Datagridfield and subforms - tricky z3c.form question

We need some z3c.form-fu.

In collective.z3cform.datagridfield, we have an AutoExtensibleSubformAdapter, added by @miohtama in 2012: Added support for plone.autoform and grok'ed row forms · collective/collective.z3cform.datagridfield@4198925 · GitHub

In it's current state it is causing problems when adding some fields in the dexterity or easyform schema editor: AutoExtensibleSubformAdapter breaks the schema editor settings popup for certain fields · Issue #114 · collective/collective.z3cform.datagridfield · GitHub

It looks like everything is working fine when removing the adapter registration (/cc @batlock666 ): Remove the ZCML registration for AutoExtensibleSubformAdapter. by batlock666 · Pull Request #115 · collective/collective.z3cform.datagridfield · GitHub

But there is an alternative fix by @tmassman which is a proper fix for the adapter: Fix implementation for sub form adapter by thomasmassmann · Pull Request #116 · collective/collective.z3cform.datagridfield · GitHub

The adapter seems to add some subform support for plone.autoform based forms. But isn't that already the case in core Plone?

It's hard to tell what this prototypical z3c.form code example does from just reading it without reading the whole set of z3c.form, plone.z3cform, plone.app.z3cform, plone.autoform, plone.supermodel and and so forth.

Maybe one of the community knows out of her/his head what this is all about.

Help needed!

A related question is: Do we need subforms at all?
If all what it does is to group submissions within a form - e.g. uploading an image separately instead within the main form submission - then we can also use JavaScript for that.
In Patternslib we have pat-subform for that and it's a magnitude simpler to use that subforms in z3c.form: See: Subform — Patterns

Hi, the last days I've been investigating how to update Plone 6.0 coredev to the latest z3c.form==4.2 and it turned out, that the zope.schema.Object implementation of plone.app.z3cform was the major breaking bit to get the whole build running on z3c.form==4.2 ... so after a few changes and some backward compatible test fixes, the build is green... see Update to latest z3c.form by petschki · Pull Request #782 · plone/buildout.coredev · GitHub.

But the subform implementation of datagridfield has to be rewritten to work with z3c.form>=4.x I'm not the most advanced subform integrator, but maybe the docs here have some pointers how to fix that: 3. Sub-Forms — z3c.form 4.3.dev0 documentation

1 Like

@petschki great news! Thanks for the effort on updating z3c.form.

I still have the suspicion that we can get away without any z3c.form based subform at all, just by using pat-subform from Patternslib.

@tmassman @thet I've been digging a bit deeper, and it turned out that plone.autoform provides a mixin class for "schema hints" on subforms. So I've backported the removed ObjectSubForm and ISubformFactory to plone.z3cform in order to make this work again. See the updated subform.txt doctest here: Reimplementation of backported ObjectSubForm and ISubformFactory adapter by petschki · Pull Request #41 · plone/plone.autoform · GitHub ...

But as said, this should not be reimplemented in collective.z3cform.datagridfield anymore.

I've tested datagridfield with the updated z3c.form and plone.(app).z3cform packages here and I think there needs to be some refactorings with the new ObjectWidget implementation. This is the traceback I get when running tests (or accessing the @@demo-collective.z3cform.datagrid view) ... maybe somebody can pick this up?

    Traceback (most recent call last):
      File "/Users/peterm/.pyenv/versions/3.9.7/lib/python3.9/doctest.py", line 1336, in __run
        exec(compile(example.source, filename, "single",
      File "<doctest browser.rst[6]>", line 1, in <module>
        browser.open(portal_url+'/@@demo-collective.z3cform.datagrid')
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/eggs/zope.testbrowser-5.6.1-py3.9.egg/zope/testbrowser/browser.py", line 256, in open
        self._processRequest(url, make_request)
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/eggs/zope.testbrowser-5.6.1-py3.9.egg/zope/testbrowser/browser.py", line 282, in _processRequest
        resp = make_request(reqargs)
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/eggs/zope.testbrowser-5.6.1-py3.9.egg/zope/testbrowser/browser.py", line 253, in make_request
        return self.testapp.get(url, **args)
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/eggs/WebTest-3.0.0-py3.9.egg/webtest/app.py", line 324, in get
        return self.do_request(req, status=status,
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/eggs/zope.testbrowser-5.6.1-py3.9.egg/zope/testbrowser/browser.py", line 93, in do_request
        response = super(TestbrowserApp, self).do_request(req, status,
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/eggs/WebTest-3.0.0-py3.9.egg/webtest/app.py", line 620, in do_request
        res = req.get_response(app, catch_exc_info=True)
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/eggs/WebOb-1.8.7-py3.9.egg/webob/request.py", line 1309, in send
        status, headers, app_iter, exc_info = self.call_application(
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/eggs/WebOb-1.8.7-py3.9.egg/webob/request.py", line 1278, in call_application
        app_iter = application(self.environ, start_response)
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/eggs/WebTest-3.0.0-py3.9.egg/webtest/lint.py", line 196, in lint_app
        iterator = application(environ, start_response_wrapper)
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/eggs/plone.testing-8.0.3-py3.9.egg/plone/testing/_z2_testbrowser.py", line 39, in wrapped_func
        return func(*args, **kw)
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/eggs/plone.testing-8.0.3-py3.9.egg/plone/testing/_z2_testbrowser.py", line 66, in __call__
        wsgi_result = publish(environ, start_response)
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/src/Zope/src/ZPublisher/WSGIPublisher.py", line 376, in publish_module
        response = _publish(request, new_mod_info)
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/src/Zope/src/ZPublisher/WSGIPublisher.py", line 271, in publish
        result = mapply(obj,
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/src/Zope/src/ZPublisher/mapply.py", line 85, in mapply
        return debug(object, args, context)
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/src/Zope/src/ZPublisher/WSGIPublisher.py", line 68, in call_object
        return obj(*args)
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/src/z3c.form/src/z3c/form/form.py", line 233, in __call__
        self.update()
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/src/z3c.form/src/z3c/form/form.py", line 226, in update
        super(Form, self).update()
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/src/plone.z3cform/src/plone/z3cform/patch.py", line 20, in BaseForm_update
        _original_BaseForm_update(self)
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/src/z3c.form/src/z3c/form/form.py", line 154, in update
        self.updateWidgets()
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/src/collective.z3cform.datagridfield/src/collective/z3cform/datagridfield/demo/editform_simple.py", line 161, in updateWidgets
        super(EditForm, self).updateWidgets()
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/src/z3c.form/src/z3c/form/form.py", line 136, in updateWidgets
        self.widgets.update()
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/src/z3c.form/src/z3c/form/field.py", line 274, in update
        widget.update()
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/src/z3c.form/src/z3c/form/browser/multi.py", line 63, in update
        super(MultiWidget, self).update()
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/src/z3c.form/src/z3c/form/browser/widget.py", line 171, in update
        super(HTMLFormElement, self).update()
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/src/z3c.form/src/z3c/form/widget.py", line 509, in update
        super(MultiWidget, self).update()
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/src/Products.CMFPlone/Products/CMFPlone/patches/z3c_form.py", line 46, in _wrapped
        return update(self)
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/src/z3c.form/src/z3c/form/widget.py", line 132, in update
        self.value = converter.toWidgetValue(value)
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/src/z3c.form/src/z3c/form/widget.py", line 504, in value
        self.updateWidgets()
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/src/collective.z3cform.datagridfield/src/collective/z3cform/datagridfield/datagridfield.py", line 163, in updateWidgets
        super(DataGridField, self).updateWidgets()
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/src/z3c.form/src/z3c/form/widget.py", line 446, in updateWidgets
        self.applyValue(widget, v)
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/src/z3c.form/src/z3c/form/widget.py", line 396, in applyValue
        fvalue = converter.toFieldValue(value)
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/src/z3c.form/src/z3c/form/object.py", line 101, in toFieldValue
        obj = self.widget.getObject(value)
      File "/Volumes/WORKSPACE2/buildout.coredev-plip-3459/src/z3c.form/src/z3c/form/object.py", line 166, in getObject
        if value.originalValue is ObjectWidget_NO_VALUE:
    AttributeError: 'dict' object has no attribute 'originalValue'
1 Like

new PR with z3c.form updates here: Update to latest z3c.form implementation by petschki · Pull Request #120 · collective/collective.z3cform.datagridfield · GitHub ... though I'm not really sure if this custom DictRowConverter is the way to go. I'd recommend to refactor the zope.schema.Object field implementation to use its schema and the schema hints from plone.autoform and simply write a custom DataGridfieldWidget which displays everything as table ... but that's just a quick guess.

1 Like

Finally the PR above is ready!

I got rid of the whole SubForm implementation and refactored the JS resources to ES6 and the new Plone 6 resource registry. Almost all styles are gone in favor of Bootstrap 5 table styles.

Review welcome :wink:

1 Like