Error Views

Error views are looked up every time a validation error occurs during data extraction and/or validation. Unfortunately, it was often hard to adjust error messages based on specific situations. The error view implementation in this package is designed to provide several high-level features that make customizing error messages easier.

>>> from z3c.form import error

Creating and Displaying the Default Error View

Let’s create an error view message for the TooSmall validation error:

>>> from zope.schema.interfaces import TooSmall, TooBig
>>> from z3c.form.testing import TestRequest
>>> view = error.ErrorViewSnippet(
...     TooSmall(), TestRequest(), None, None, None, None)
>>> view
<ErrorViewSnippet for TooSmall>

The discriminators for an error view are as follows: error, request, widget, field, form, and content. After updating the view, a test message is available:

>>> view.update()
>>> view.message
u'Value is too small'

And after registering a template for the error view, we can also render the view:

>>> import os
>>> filename = os.path.join(os.path.dirname(error.__file__), 'error.pt')
>>> import zope.component
>>> from zope.pagetemplate.interfaces import IPageTemplate
>>> from z3c.form import interfaces
>>> zope.component.provideAdapter(
...     error.ErrorViewTemplateFactory(filename, 'text/html'),
...     (interfaces.IErrorViewSnippet, None), IPageTemplate)
>>> print view.render()
<div class="error">Value is too small</div>

Customizing Error Messages

As you can imagine, writing new error views for every scenario can be very tedious, especially if you only want to provide a specific error message. So let’s create somewhat more interesting setup:

>>> import zope.interface
>>> import zope.schema
>>> class IPerson(zope.interface.Interface):
...     name = zope.schema.TextLine(title=u'Name')
...     age = zope.schema.Int(
...         title=u'Age',
...         min=0)

You must agree, that the follwoing message is pretty dull when entering a negative age:

>>> print view.render()
<div class="error">Value is too small</div>

So let’s register a better message for this particular situation:

>>> NegativeAgeMessage = error.ErrorViewMessage(
...     u'A negative age is not sensible.',
...     error=TooSmall, field=IPerson['age'])
>>> zope.component.provideAdapter(NegativeAgeMessage, name='message')

The created object is a common attribute value for the error view message. The discriminators are the same as for the error view itself. Now we create an error view message for TooSmall of the age field:

>>> view = error.ErrorViewSnippet(
...     TooSmall(), TestRequest(), None, IPerson['age'], None, None)
>>> view.update()
>>> print view.render()
<div class="error">A negative age is not sensible.</div>

Much better, isn’t it?

We can also provide dynamic error view messages that are computed each time we get an error. For example, we have an IAdult interface that have minimal age of 18:

>>> class IAdult(zope.interface.Interface):
...     age = zope.schema.Int(title=u'Age', min=18)

Now, let’s create a function that will be called by a message value adapter, it receives one argument, a special z3c.form.value.ComputedValue object that will have all needed attributes: error, request, widget, field, form and content. Let’s use one of them:

>>> def getAgeTooSmallErrorMessage(value):
...     return u'Given age is smaller than %d, you are not adult.' % (
...         value.field.min)

Now, register the computed view message:

>>> ComputedAgeMessage = error.ComputedErrorViewMessage(
...     getAgeTooSmallErrorMessage, error=TooSmall, field=IAdult['age'])
>>> zope.component.provideAdapter(ComputedAgeMessage, name='message')

Now, the error view snippet will show dynamically generated message:

>>> view = error.ErrorViewSnippet(
...     TooSmall(), TestRequest(), None, IAdult['age'], None, None)
>>> view.update()
>>> print view.render()
<div class="error">Given age is smaller than 18, you are not adult.</div>

Registering Custom Error Views

Even though message attribute values will solve most of our customization needs, sometimes one wishes to register a custom view to have more complex views. In this example we wish to register a custom error message:

>>> from z3c.form import ptcompat as viewpagetemplatefile
>>> from z3c.form import tests
>>> class NegativeAgeView(error.ErrorViewSnippet):
...     template = viewpagetemplatefile.ViewPageTemplateFile(
...         'custom_error.pt', os.path.dirname(tests.__file__))

We now need to assert the special discriminators specific to this view:

>>> error.ErrorViewDiscriminators(
...     NegativeAgeView, error=TooSmall, field=IPerson['age'])

After registering the new and default error view, ...

>>> zope.component.provideAdapter(NegativeAgeView)
>>> zope.component.provideAdapter(error.ErrorViewSnippet)

we can now make use of it, but only for this particular field and error:

>>> zope.component.getMultiAdapter(
...     (TooSmall(), TestRequest(), None, IPerson['age'], None, None),
...     interfaces.IErrorViewSnippet)
<NegativeAgeView for TooSmall>

Other combinations will return the default screen instead:

>>> zope.component.getMultiAdapter(
...     (TooBig(), TestRequest(), None, IPerson['age'], None, None),
...     interfaces.IErrorViewSnippet)
<ErrorViewSnippet for TooBig>
>>> zope.component.getMultiAdapter(
...     (TooSmall(), TestRequest(), None, IPerson['name'], None, None),
...     interfaces.IErrorViewSnippet)
<ErrorViewSnippet for TooSmall>

Value Error View Snippets

In the previous examples we have always worked with the view of the validation error. Since data managers can also return value errors, there is also an error view for them:

>>> valueError = ValueError(2)
>>> errorView = error.ValueErrorViewSnippet(
...     valueError, TestRequest(), None, None, None, None)

It uses the same template:

>>> errorView.update()
>>> print errorView.render()
<div class="error">The system could not process the given value.</div>

Unfortunately, we cannot make use of the original string representation of the value error, since it cannot be localized well enough. Thus we provide our own message. Of course, the message can be overridden:

>>> CustomMessage = error.ErrorViewMessage(
...     u'The entered value is not valid.', error=ValueError)
>>> zope.component.provideAdapter(CustomMessage, name='message')

Let’s now render the snippet again:

>>> errorView.update()
>>> print errorView.render()
<div class="error">The entered value is not valid.</div>

Invalid Error View Snippets

When invariants are used, commonly the Invalid exception (from the zope.interface package) is raised from within the invariant, if the invariant finds a problem. We need a special error view snippet for this class of errors:

>>> invalidError = zope.interface.Invalid(u'The data was invalid.')
>>> errorView = error.InvalidErrorViewSnippet(
...     invalidError, TestRequest(), None, None, None, None)

Since the same template as before is used, the error simply renders:

>>> errorView.update()
>>> print errorView.render()
<div class="error">The data was invalid.</div>

As you can see, the first argument to the exception is used as the explanatory message of the error.