Creating custom validators for your type
Many applications require some form of data entry validation. The simplest form of validation you get for free – the z3c.form library ensures that all data entered on Dexterity add and edit forms is valid for the field type.
It is also possible to set certain properties on the fields to add further validation (or even create your own fields with custom validation logic, although that is a lot less common). These properties are set as parameters to the field constructor when the schema interface is created. You should see the zope.schema package for details, but the most common constraints are:
- to make a field required or optional;
- used for
Timedeltafields, specify the minimum and maximum (inclusive) allowed values of the given type;
- used for collection fields (
Dict) and text fields (
TextLine), set the minimum and maximum (inclusive) length of a field.
If this does not suffice, you can pass your own constraint function to a
field. The constraint function should take a single argument: the value
that is to be validated. This will be of the field’s type. The function
should return a boolean
def checkForMagic(value): return 'magic' in value
Hint: The constraint function does not have access to the context, but
if you need to acquire a tool, you can use the
zope.app.component.hooks.getSite() method to obtain the site root.
To use the constraint, pass the function as the
constraint argument to
the field constructor, e.g.:
my_field = schema.TextLine(title=_(u"My field"), constraint=checkForMagic)
Constraints are easy to write, but do not necessarily produce very friendly error messages. It is however possible to customise these error messages using z3c.form error view snippets. See the z3c.form documentation for more details.
You’ll also notice that constraints only check a single field value. If you need to write a validator that compares multiple values, you can use an invariant. Invariants use exceptions to signal errors, which are displayed at the top of the form rather than next to a particular field.
To illustrate an invariant, let’s make sure that the start date of a Program is before the end date. In program.py, we add the following. Code not relevant to this example is snipped with an ellipsis (…):
... from zope.interface import invariant, Invalid class StartBeforeEnd(Invalid): __doc__ = _(u"The start or end date is invalid") class IProgram(model.Schema): ... start = schema.Datetime( title=_(u"Start date"), required=False, ) end = schema.Datetime( title=_(u"End date"), required=False, ) ... @invariant def validateStartEnd(data): if data.start is not None and data.end is not None: if data.start > data.end: raise StartBeforeEnd(_(u"The start date must be before the end date.")) ...