Search code examples
pythonvalidationplonedexterityz3c.form

Move invariant validation error message on a field in Plone


I'm using Plone with dexterity and I'm validating 2 related fields using the invariant decorator. Everything works but... I'd like to move the generic error message on one specific field.

How can I do this? I've found a three years old suggestion by Martin Aspeli about how it would be cool doing it:

http://plone.293351.n2.nabble.com/plone-app-form-does-not-display-invariant-errors-td348710.html

but they dont came out with a solution.

I've also found a way to do it but it's ugly: placing on the update method of the form this code:

for widget in widgets:
    name = widget.context.getName()
    if errors:
        for error in errors:
            if isinstance(error, Invalid) and name in error.args[1:]:
                if widget._error is None:
                    widget._error = error

Isn't there a lower level implementation that allows to pass the names of the fields to the raised Invalid and does not need to loop trough all the fields and all the errors for each field?!?


Solution

  • You can do this by doing your extra validation in the form's action handler, and raise WidgetActionExecutionError specifying the widget for which the error should be shown.

    This looks as follows (taken from http://plone.org/products/dexterity/documentation/manual/schema-driven-forms/customising-form-behaviour/validation):

    from five import grok
    from plone.directives import form
    
    from zope.interface import invariant, Invalid
    from zope import schema
    
    from z3c.form import button
    from z3c.form.interfaces import ActionExecutionError, WidgetActionExecutionError
    
    from Products.CMFCore.interfaces import ISiteRoot
    from Products.statusmessages.interfaces import IStatusMessage
    
    from example.dexterityforms.interfaces import MessageFactory as _
    
    
    ...
    
    
    class OrderForm(form.SchemaForm):
    
        ...
    
        @button.buttonAndHandler(_(u'Order'))
        def handleApply(self, action):
            data, errors = self.extractData()
    
            # Some additional validation
            if 'address1' in data and 'address2' in data:
    
                if len(data['address1']) < 2 and len(data['address2']) < 2:
                    raise ActionExecutionError(Invalid(_(u"Please provide a valid address")))
                elif len(data['address1']) < 2 and len(data['address2']) > 10:
                    raise WidgetActionExecutionError('address2', Invalid(u"Please put the main part of the address in the first field"))
    
            if errors:
                self.status = self.formErrorsMessage
                return
    

    I think it may also be possible to raise WidgetActionExecutionError from your invariant, but then it might not do what you want if the invariant is getting checked at some other time than while processing a z3c.form form.