How to get the context from a collective.z3cform.datagridfield

In Plone 5.1, I am attempting to write a validator for a datagrid field. When editing the content, I want to skip validation for values that are not being changed. This requires knowing the context in which the field is being used.

Considering this: 1. Validators โ€” z3c.form 5.2.dev0 documentation

Finally, the context is available as attribute directly:

>>> data.__context__ is context
True

It is used by the validators (especially invariant validators) to provide a context of validation, for example to look up a vocabulary or access the parent of an object. Note that the context will be different between add and edit forms.

and finding an example that is very close to my use case here: Dexterity: Unique field validation - #2 by tmassman I look forward to a fairly simple task...

But instead, I discover that the z3c.form.validator.Data object in a DGF does not have a __context__ attribute. The known DGF workaround to get the context via getRequest() returns a Products.Five.metaclass.MyFormWrapper object.

I don't know how to get the context from it, or its acquisition parents.

Any hints?

Edit: or should I just forget about this approach and write an adapter, following Validation โ€” Plone Documentation v5.2

Wild guess but something like that (which is also using the global request):

    request = getRequest()
    ctx = request.PUBLISHED.context

Thanks, just tried this:

req.PUBLISHED is a <Products.Five.metaclass.InlineValidationView object at 0x7f328c5fa550>

its context is again the same formwrapper: <Products.Five.metaclass.MyFormWrapper object at 0x7f32a2775fd0> that I also get using req.PARENTS[0]

Even ChatGPT gave up on me :sob: - after investigating possible use of IDataManager, and repeatedly suggesting that I'd pass the context to my function, it told me to FO...

Review Plone Documentation: Consult the Plone documentation or community forums to see if there are any recommended patterns or best practices for accessing the context in similar scenarios.

If ChatGPT does not know, only Dieter knows.

4 Likes

Seems like I'm making some progress:

wrote a validator class that I registered as an adapter (hat tip to @fredvd and @petschki ) and I see the value in my logs, sometimes alongside the correct context.

I am able to get the context, e.g. <Container at pnz-hartwachs-oel-evolution> with:
getattr(aq_parent(self.view),'context', None) - which seems odd.

Leaves me to figure out where the other types of 'context' come from and what to do with those.

Not sure if you have tried this construct already:

from z3c.form import validator


class MyFieldValidator(validator.SimpleFieldValidator):

    def validate(self, value):
        # here you have self.context, self.request, self.view, self.field and self.widget
        # make sure you raise zope.interface.Invalid(msg) if the validation is wrong.
        
        if(<not valid>):
            raise Invalid("my error message shown at the field")

        return None


validator.WidgetValidatorDiscriminators(
    MyFieldValidator,
    field=IMySchema["my_field"],
)

and register the adapter in zcml with:

<adapter factory=".module.MyFieldValidator" />

1 Like

Yes, thank you!

I stole your example code from How to raise a WidgetActionExecutionError for a non-default fieldset field?

In my case, I needed a parameter force in the function as well. When this is value is True, I am able to get the correct context.

I now see a Traceback of the error message, but it does not show up for the user.

Traceback (innermost last):
  Module ZPublisher.Publish, line 138, in publish
  Module ZPublisher.mapply, line 77, in mapply
  Module ZPublisher.Publish, line 48, in call_object
  Module plone.app.z3cform.inline_validation, line 28, in __call__
  Module plone.dexterity.browser.add, line 134, in update
  Module plone.z3cform.fieldsets.extensible, line 65, in update
  Module plone.z3cform.patch, line 30, in GroupForm_update
  Module z3c.form.group, line 141, in update
  Module z3c.form.group, line 52, in update
  Module z3c.form.group, line 48, in updateWidgets
  Module z3c.form.field, line 277, in update
  Module z3c.form.browser.multi, line 63, in update
  Module z3c.form.browser.widget, line 171, in update
  Module z3c.form.widget, line 496, in update
  Module Products.CMFPlone.patches.z3c_form, line 47, in _wrapped
  Module z3c.form.widget, line 92, in update
  Module z3c.form.widget, line 491, in value
  Module collective.z3cform.datagridfield.datagridfield, line 171, in updateWidgets
  Module z3c.form.widget, line 433, in updateWidgets
  Module z3c.form.widget, line 410, in applyValue
  Module collective.z3cform.datagridfield.datagridfield, line 262, in datagrid_field_set
  Module collective.z3cform.datagridfield.datagridfield, line 311, in updateWidgets
  Module z3c.form.object, line 210, in updateWidgets
  Module collective.z3cform.datagridfield.datagridfield, line 423, in _validate
  Module mdb_theme.utilities.validators, line 103, in validate
Invalid: 02094 exists in catalog index article_number for /mdb/de/produkte/karibu-arbeitsplatten-oel-evolution

I think thats because of the DGF version for Plone 5 ... my example works only in Plone 6. Check the try/except block in collective.z3cform.datagridfield.datagridfield, line 423 which exceptions are catched and try to raise this one.

As it turns out, this mutes my Invalid error:

Eureka! Thanks for the hint. I added this to the existing try / except near line 423:

            except Invalid as error:
                widget.error = error.args[0]

and tweaked the DataGridValidator ever so sligthly, to take advantage of InvalidErrorViewSnippet: z3c.form/src/z3c/form/error.py at master ยท zopefoundation/z3c.form ยท GitHub

Resulting in:

    def validate(self, value, force=False):
        """
            Don't validate the table - however, if there is a cell
            error, make sure that the table widget shows it.
        """
        for subform in [widget.subform for widget in self.widget.widgets]:
            for widget in subform.widgets.values():
                if hasattr(widget, 'error') and widget.error:  
                    raise Invalid(u'%s %s' % (widget.label, widget.error))
        return None

This might be brittle, but for now it gives me something to work with...