[Zope3-dev] Field binding considered icky

Jim Fulton jim at zope.com
Wed Mar 31 17:17:38 EST 2004


I'm trying to catch up a bit on documentation.  I'm working on
README.txt files for zope.interface and zope.component and will be
updating the README.txt for zope.schema.

There is a "feature" of schema fields that I really don't want to
document.   This feature is called "binding".  A field can be "bound"
to an object.  This creates a new field instance, which is a clone of
the original, that has a `context` attribute which is some object.
The object *may*, for example, be an object that has an attribute that
is specified by the field.

Why do we need this?

1. Field validation may need to look up (acquire) components.  For
    example, a permission field should allow only defined permission
    ids.  The defined permission ids are obtained by getting all of the
    names of IPermission utilities from the utility service.

2. We may need to consider object identity, or a current object value.

    To illustrate this, consider a bumper car ride.  We have a system
    that let's us assign bumper cars to riders.  Clearly we should only
    assign unoccupied bumper cars.  But suppose that someone is already
    in a bumper car.  In that case, the car they are sitting in should
    be a valid choice for them, even though it is occupied.  To properly
    validate, or to generate a correct UI, we need to take into account
    the identity of the rider we're pickling a bumper car for.

3. Perhaps there are other cases where we want to take context into
    account for some reason.  Perhaps allowable values for a field
    depend on data acquired from a folder.  This is hypothetical, but
    people I trust have told me that this is likely.

Here's a non-reason for needing this: relationships between fields.
Suppose we have two fields, `max` and `min`.  We want to assure that
the `max` field value is not less than the `min` field value.  If we
expressed this as a field constraint, we'd need to have an object that
actually had the values.  It turns out that this is a bad idea.  Field
constraints should only constrain values for that field, without
regard to other fields.  Rather, we express constraints involving
multiple fields using interface invariants.  Why?  Because otherwise,
the validation of fields depends on the order they are processed.
Consider the `max` and `min` case.  Do we put the constraint on `max`,
or `min`, or `both`.  If you put the constraint on both, then you will
be unable to make certain changes.  Suppose we have existing values 3
and 4.   The user changes them to 5 and 6. If we check the constraint
on `min` first, then the constraint will fail because the new `min` is
greater than the old `max`.  If we check the constraint on `max`
first, then we can't change the values to 1 and 2.  The saner approach
is to either check invariants after the values have been assigned, or
to apply the invariants to the full collection of input data.

Reason 1, above, is addressed by:

   http://dev.zope.org/Zope3/FixedDefaultComponentLookup

But we're still stuck with reasons 2 and 3.

Here's a possible approach I wanted to run by y'all: use adapters. :)

We have two cases we need to deal with:

A. Validating values

    We would get rid of the validate method of fields.

    When we want to validate a field, we'll adapt it *with* it's
    context:

      >>> validator = getMultiAdapter((field, context), IValidator)
      >>> validator.validate(value)

    So, in the bumper car example, when editing a rider, we'd:

      >>> validator = getMultiAdapter((field, rider), IValidator)

    But, when adding a rider, we'd use an adding object as the context::

      >>> validator = getMultiAdapter((field, adding), IValidator)

    Now, we might not have an adapter for IAdding, but we might have a
    default adapter (registered for IBumperCarField and Interface) that
    allows only unoccupied bumper cars.

    A disadvantage of this approach is that it will require either
    that:

    - We remove validation logic from zope.schema, or

    - Introduce a dependency of zope.schema on zope.component.  Later,
      if we merge zope.schema into zope.interface, we'd introduce a
      dependency of zope.interface on zope.component.

    I'd rather not have the dependency, so I'd be inclined to lift
    validation up to a higher level.

B. Widgets

    Widgets would become multi-views:

      >>> widget = getMultiView((field, context), '', request, IInputWidget)

    So, in the bumper car example, when editing a rider, we'd use:

      >>> widget = getMultiView((field, rider), '', request, IInputWidget)

    But, when adding a rider, we'd use an adding object as the context::

      >>> widget = getMultiView((field, adding), '', request, IInputWidget)

an alternative to B:

B'. Rather than using multi-views, use views of multi-adapters:

     >>> widget = getViewProviding(
     ...             getMultiAdapter((field, context), someinterface),
     ...             request, IInputWidget,
     ...             )

     The idea is that the widget need not know about context. Any knowledge
     of the context is restricted to the adapter.  I think that this
     has some appeal, but I don't know that I want to require it.

     I don't like this option because:

     - It's not clear what `someinterface` should be.  It was suggested
       that it should be `IField`, but `IField` doesn't really provide
       enough information.

     - It doesn't allow the widget to use the context directly, unless
       `someinterface` exposes it somehow. IMO, it should be possible
       for the widget implementation to depend on the context if it
       wants to. If it doesn't want to, it can always get an adapter
       itself and forget about context.


Thoughts?

Jim

-- 
Jim Fulton           mailto:jim at zope.com       Python Powered!
CTO                  (540) 361-1714            http://www.python.org
Zope Corporation     http://www.zope.com       http://www.zope.org




More information about the Zope3-dev mailing list