[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