1. Attribute Value Adapters

In advanced, highly customized projects it is often the case that a property wants to be overridden for a particular customer in a particular case. A prime example is the label of a widget. Until this implementation of a form framework was written, widgets only could get their label from the field they were representing. Thus, wanting to change the label of a widget meant implementing a custom schema and re-registering the form in question for the custom schema. It is needless to say that this was very annoying.

For this form framework, we are providing multiple levels of customization. The user has the choice to change the value of an attribute through attribute assignment or adapter lookup. The chronological order of an attribute value assignment is as follows:

  1. During initialization or right thereafter, the attribute value can be set by direct attribute assignment, i.e. obj.attr = value
  2. While updating the object, an adapter is looked up for the attribute. If an adapter is found, the attribute value will be overridden. Of course, if the object does not have an update() method, one can choose another location to do the adapter lookup.
  3. After updating, the developer again has the choice to override the attribute allowing granularity above and beyond the adapter.

The purpose of this module is to implement the availability of an attribute value using an adapter.

>>> from z3c.form import value

The module provides helper functions and classes, to create those adapters with as little code as possible.

1.1. Static Value Adapter

To demonstrate the static value adapter, let’s go back to our widget label example. Let’s create a couple of simple widgets and forms first:

>>> class TextWidget(object):
...    label = u'Text'
>>> tw = TextWidget()
>>> class CheckboxWidget(object):
...    label = u'Checkbox'
>>> cbw = CheckboxWidget()
>>> class Form1(object):
...    pass
>>> form1 = Form1()
>>> class Form2(object):
...    pass
>>> form2 = Form2()

We can now create a generic widget property adapter:

>>> WidgetAttribute = value.StaticValueCreator(
...     discriminators = ('widget', 'view')
...     )

Creating the widget attribute object, using the helper function above, allows us to define the discriminators (or the granulatrity) that can be used to control a widget attribute by an adapter. In our case this is the widget itself and the form/view in which the widget is displayed. In other words, it will be possible to register a widget attribute value specifically for a particular widget, a particular form, or a combination thereof.

Let’s now create a label attribute adapter for the text widget, since our customer does not like the default label:

>>> TextLabel = WidgetAttribute(u'My Text', widget=TextWidget)

The first argument of any static attribute value is the value itself, in our case the string “My Text”. The following keyword arguments are the discriminators specified in the property factory. Since we only specify the widget, the label will be available to all widgets. But first we have to register the adapter:

>>> import zope.component
>>> zope.component.provideAdapter(TextLabel, name='label')

The name of the adapter is the attribute name of the widget. Let’s now see how we can get the label:

>>> from z3c.form import interfaces
>>> staticValue = zope.component.getMultiAdapter(
...     (tw, form1), interfaces.IValue, name='label')
>>> staticValue
<StaticValue u'My Text'>

The resulting value object has one public method get(), which returns the actual value:

>>> staticValue.get()
u'My Text'

As we said before, the value should be available to all forms, …

>>> zope.component.getMultiAdapter(
...     (tw, form2), interfaces.IValue, name='label')
<StaticValue u'My Text'>

… but only to the TextWidget:

>>> zope.component.getMultiAdapter(
...     (cbw, form2), interfaces.IValue, name='label')
Traceback (most recent call last):
...
ComponentLookupError: ((<CheckboxWidget...>, <Form2...>),
                        <InterfaceClass ...IValue>, 'label')

By the way, the attribute adapter factory notices, if you specify a discriminator that was not specified:

>>> WidgetAttribute(u'My Text', form=Form2)
Traceback (most recent call last):
...
ValueError: One or more keyword arguments did not match the discriminators.
>>> WidgetAttribute.discriminators
('widget', 'view')

1.2. Computed Value Adapter

A second implementation of the value adapter in the evaluated value, where one can specify a function that computes the value to be returned. The only argument to the function is the value adapter instance itself, which then contains all the discriminators as specified when creating the generic widget attribute factory. Let’s take the same use case as before, but generating the value as follows:

>>> def getLabelValue(adapter):
...     return adapter.widget.label + ' (1)'

Now we create the value adapter for it:

>>> WidgetAttribute = value.ComputedValueCreator(
...     discriminators = ('widget', 'view')
...     )
>>> TextLabel = WidgetAttribute(getLabelValue, widget=TextWidget)

After registering the adapter, …

>>> zope.component.provideAdapter(TextLabel, name='label')

we now get the answers:

>>> from z3c.form import interfaces
>>> zope.component.getMultiAdapter(
...     (tw, form1), interfaces.IValue, name='label')
<ComputedValue u'Text (1)'>
__Note__: The two implementations of the attribute value adapters are not
meant to be canonical features that must always be used. The API is kept simple to allow you to quickly implement your own value adapter.

1.3. Automatic Interface Assignment

Oftentimes it is desirable to register an attribute value adapter for an instance. A good example is a field, so let’s create a small schema:

>>> import zope.interface
>>> import zope.schema
>>> class IPerson(zope.interface.Interface):
...     firstName = zope.schema.TextLine(title=u'First Name')
...     lastName = zope.schema.TextLine(title=u'Last Name')

The customer now requires that the title – which is the basis of the widget label for field widgets – of the last name should be “Surname”. Until now the option was to write a new schema changing the title. With this attribute value module, as introduced thus far, we would need to provide a special interface for the last name field, since registering a label adapter for all text fields would also change the first name.

Before demonstrating the solution to this problem, let’s first create a field attribute value:

>>> FieldAttribute = value.StaticValueCreator(
...     discriminators = ('field',)
...     )

We can now create the last name title, changing only the title of the lastName field. Instead of passing in an interface of class as the field discriminator, we pass in the field instance:

>>> LastNameTitle = FieldAttribute(u'Surname', field=IPerson['lastName'])

The attribute value factory will automatically detect instances, create an interface on the fly, directly provide it on the field and makes it the discriminator interface for the adapter registratioon.

So after registering the adapter, …

>>> zope.component.provideAdapter(LastNameTitle, name='title')

the adapter is only available to the last name field and not the first name:

>>> zope.component.queryMultiAdapter(
...     (IPerson['lastName'],), interfaces.IValue, name='title')
<StaticValue u'Surname'>
>>> zope.component.queryMultiAdapter(
...     (IPerson['firstName'],), interfaces.IValue, name='title')