==========
Validators
==========

Validators are components that validate submitted data. This is certainly not
a new concept, but in the previous form frameworks validation was hidden in
many places:

* Field/Widget Validation

  The schema field consists of a ``validate()`` method. Validation is
  automatically invoked when converting a unicode string to a field value
  using ``fromUnicode()``. This makes it very hard to customize the field
  validation. No hooks were provided to exert dditional restriction at the
  presentation level.

* Schema/Form Validation

  This type of validation was not supported at all initially. ``zope.formlib``
  fixed this problem by validating against schema invariants. While this was a
  first good step, it still made it hard to customize validators, since it
  required touching the base implementations of the forms.

* Action Validation

  ``zope.formlib`` supports the notion of action validatos. Actions have a
  success and failure handler. If the validation succeeds, the success handler
  is called, otherwise the failure handler is chosen. We believe that this
  design was ill-conceived, especially the default, which required the data to
  completely validate in order for the action to successful. There are many
  actions that do not even care about the data in the form, such as "Help",
  "Cancel" and "Reset" buttons. Thus validation should be part of the data
  retrieval process and not the action.

For me, the primary goals of the validator framework are as follows:

* Assert additional restrictions on the data at the presentation
  level.

  There are several use cases for this. Sometimes clients desire additional
  restrictions on data for their particular version of the software. It is not
  always desireable to adjust the model for this client, since the framework
  knows how to handle the less restrictive case anyways. In another case,
  additional restrictions might be applied to a particular form due to limited
  restrictions.

* Make validation pluggable.

  Like most other components of this package, it should be possible to control
  the validation adapters at a fine grained level.

  * Widgets: context, request, view, field[1], widget

  * Widget Managers: context, request, view, schema[2], manager

  [1].. This is optional, since widgets must not necessarily have fields.
  [2].. This is optional, since widget managers must not necessarily have
  manage field widgets and thus know about schemas.

* Provide good defaults that behave sensibly.

  Good defaults are, like in anywhere in this pacakge, very important. We have
  chosen to implement the ``zope.formlib`` behavior as the default, since it
  worked very well -- with exception of action validation, of course.

For this package, we have decided to support validators at the widget and
widget manager level. By default the framework only supports field widgets,
since the validation of field-absent widgets is generally not
well-defined. Thus, we first need to create a schema.

  >>> import zope.interface
  >>> import zope.schema
  >>> class IPerson(zope.interface.Interface):
  ...     login = zope.schema.TextLine(
  ...         title=u'Login',
  ...         min_length=1,
  ...         max_length=10)
  ...
  ...     email = zope.schema.TextLine(
  ...         title=u'E-mail')
  ...
  ...     @zope.interface.invariant
  ...     def isLoginPartOfEmail(person):
  ...         if not person.email.startswith(person.login):
  ...             raise zope.interface.Invalid("The login not part of email.")


Widget Validators
-----------------

Widget validators only validate the data of one particular widget. The
validated value is always assumed to be an internal value and not a widget
value.

By default, the system uses the simple field validator, which simply uses the
``validate()`` method of the field. For instantiation, all validators have the
following signature for its discriminators: context, request, view, field, and
widget

  >>> from z3c.form import validator
  >>> simple = validator.SimpleFieldValidator(
  ...     None, None, None, IPerson['login'], None)

A validator has a single method ``validate()``. When the validation is
successful, ``None`` is returned:

  >>> simple.validate(u'srichter')

A validation error is raised, when the validation fails:

  >>> simple.validate(u'StephanCaveman3')
  Traceback (most recent call last):
  ...
  TooLong: (u'StephanCaveman3', 10)

Let's now create a validator that also requires at least 1 numerical character
in the login name:

  >>> import re

  >>> class LoginValidator(validator.SimpleFieldValidator):
  ...
  ...     def validate(self, value):
  ...         super(LoginValidator, self).validate(value)
  ...         if re.search('[0-9]', value) is None:
  ...             raise zope.interface.Invalid('No numerical character found.')

Let's now try our new validator:

  >>> login = LoginValidator(None, None, None, IPerson['login'], None)

  >>> login.validate(u'srichter1')

  >>> login.validate(u'srichter')
  Traceback (most recent call last):
  ...
  Invalid: No numerical character found.

We can now register the validator with the component architecture, ...

  >>> import zope.component
  >>> zope.component.provideAdapter(LoginValidator)

and look up the adapter using the usual way:

  >>> from z3c.form import interfaces

  >>> zope.component.queryMultiAdapter(
  ...     (None, None, None, IPerson['login'], None),
  ...     interfaces.IValidator)
  <LoginValidator for IPerson['login']>

Unfortunately, the adapter is now registered for all fields, so that the
E-mail field also has this restriction (which is okay in this case, but not
generally):

  >>> zope.component.queryMultiAdapter(
  ...     (None, None, None, IPerson['email'], None),
  ...     interfaces.IValidator)
  <LoginValidator for IPerson['email']>

The validator module provides a helper function to set the discriminators for
a validator, which can include instances:

  >>> validator.WidgetValidatorDiscriminators(
  ...     LoginValidator, field=IPerson['login'])

Let's now clean up the component architecture and register the login validator
again:

  >>> from zope.testing import cleanup
  >>> cleanup.cleanUp()

  >>> zope.component.provideAdapter(LoginValidator)

  >>> zope.component.queryMultiAdapter(
  ...     (None, None, None, IPerson['login'], None),
  ...     interfaces.IValidator)
  <LoginValidator for IPerson['login']>

  >>> zope.component.queryMultiAdapter(
  ...     (None, None, None, IPerson['email'], None),
  ...     interfaces.IValidator)


Widget Manager Validators
-------------------------

The widget manager validator, while similar in spirit, works somewhat
different. The discriminators of the widget manager validator are: context,
request, view, schema, and manager.

A simple default implementation is provided that checks the invariants of the
schemas:

  >>> invariants = validator.InvariantsValidator(
  ...     None, None, None, IPerson, None)

Widget manager validators have the option to validate a data dictionary,

  >>> invariants.validate(
  ...     {'login': u'srichter', 'email': u'srichter@foo.com'})
  ()

or an object implementing the schema:

  >>> class Person(object):
  ...     zope.interface.implements(IPerson)
  ...     login = u'srichter'
  ...     email = u'srichter@foo.com'
  >>> stephan = Person()

  >>> invariants.validateObject(stephan)
  ()

Since multiple errors can occur during the validation process, all errors are
collected in a tuple, which is returned. If the tuple is empty, the validation
was successful. Let's now generate a failure:

  >>> errors = invariants.validate(
  ...     {'login': u'srichter', 'email': u'strichter@foo.com'})

  >>> for e in errors:
  ...     print e.__class__.__name__ + ':', e
  Invalid: The login not part of email.

Let's now have a look at writing a custom validator. In this case, we want to
ensure that the E-mail address is at most twice as long as the login:

  >>> class CustomValidator(validator.InvariantsValidator):
  ...     def validateObject(self, obj):
  ...         errors = super(CustomValidator, self).validateObject(obj)
  ...         if len(obj.email) > 2 * len(obj.login):
  ...             errors += (zope.interface.Invalid('Email too long.'),)
  ...         return errors

Since the ``validate()`` method of ``InvatiantsValidator`` simply uses
``validateObject()`` it is enough to only override ``validateObject()``. Now
we can use the validator:

  >>> custom = CustomValidator(
  ...     None, None, None, IPerson, None)

  >>> custom.validate(
  ...     {'login': u'srichter', 'email': u'srichter@foo.com'})
  ()
  >>> errors = custom.validate(
  ...     {'login': u'srichter', 'email': u'srichter@foobar.com'})
  >>> for e in errors:
  ...     print e.__class__.__name__ + ':', e
  Invalid: Email too long.

To register the custom validator only for this schema, we have to use the
discriminator generator again.

  >>> from z3c.form import util
  >>> validator.WidgetsValidatorDiscriminators(
  ...     CustomValidator, schema=util.getSpecification(IPerson, force=True))

Note: Of course we could have used the ``zope.component.adapts()`` function
      from within the class, but I think it is too tedious, since you have to
      specify all discriminators and not only the specific ones you are
      interested in.

After registering the validator,

  >>> zope.component.provideAdapter(CustomValidator)

it becomes the validator for this schema:

  >>> zope.component.queryMultiAdapter(
  ...     (None, None, None, IPerson, None), interfaces.IManagerValidator)
  <CustomValidator for IPerson>

  >>> class ICar(zope.interface.Interface):
  ...     pass
  >>> zope.component.queryMultiAdapter(
  ...     (None, None, None, ICar, None), interfaces.IManagerValidator)


The Data Wrapper
----------------

The ``Data`` class provides a wrapper to present a dictionary as a class
instance. This is used to check for invariants, which always expect an
object. While the common use cases of the data wrapper are well tested in the
code above, there are some corner cases that need to be addressed.

So let's start by creating a data object:

  >>> context = object()
  >>> data = validator.Data(IPerson, {'login': 'srichter', 'other': 1}, context)

When we try to access a name that is not in the schema, we get an attribute
error:

  >>> data.address
  Traceback (most recent call last):
  ...
  AttributeError: address

  >>> data.other
  Traceback (most recent call last):
  ...
  AttributeError: other

If the field found is a method, then a runtime error is raised:

  >>> class IExtendedPerson(IPerson):
  ...     def compute():
  ...         """Compute something."""

  >>> data = validator.Data(IExtendedPerson, {'compute': 1}, context)
  >>> data.compute
  Traceback (most recent call last):
  ...
  RuntimeError: ('Data value is not a schema field', 'compute')

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.

Validation of interface variants when not all fields are displayed in form
--------------------------------------------------------------------------

We need to register the data manager to access the data on the context object:

  >>> from z3c.form import datamanager
  >>> zope.component.provideAdapter(datamanager.AttributeField)

Sometimes you might leave out fields in the form which need to compute the
invariant. An exception should be raised. The data wrapper is used to test
the invariants and looks up values on the context object that are left out in
the form.

  >>> invariants = validator.InvariantsValidator(
  ...     stephan, None, None, IPerson, None)
  >>> errors = invariants.validate({'email': 'foo@bar.com'})
  >>> errors[0].__class__.__name__
  'Invalid'
  >>> errors[0].args[0]
  'The login not part of email.'

