Hi,
I posted about this earlier, but the message seems to have gotten lost
in the ether.
I am trying and struggling to use ComputedErrorViewMessage. So far, I've
discovered two problems, one which affects IValue adapters in general.
1. The last discriminator of a ComputedErrorViewMessage (and an
ErrorViewMessage) is listed as 'content':
ErrorViewMessage = value.StaticValueCreator(
discriminators = ('error', 'request', 'widget', 'field', 'form',
'content')
)
ComputedErrorViewMessage = value.ComputedValueCreator(
discriminators = ('error', 'request', 'widget', 'field', 'form',
'content')
)
However, the adapter lookup further down in error.py does:
class ErrorViewSnippet(object):
...
def update(self):
value = zope.component.queryMultiAdapter(
(self.context, self.request, self.widget,
self.field, self.form, self),
interfaces.IValue, name='message')
if value is not None:
self.message = value.get()
else:
self.message = self.createMessage()
Notice how the last parameter is 'self', i.e. the ErrorViewSnippet. To
me, at least, 'content' suggests this refers to the value returned by
form.getContent(), most likely form.context.
There's no test for this discriminator that I can see, so I'm not sure
whether the bug is that 'content' is a poor name, or if the last part
of that tuple should be self.form.getContent() or self.form.context?
2. The order in which we use ErrorViewMessage or
ComputedErrorViewMessage seems to make a difference to its ability to
specialise. I really can't figure out what the bug is, though. I've
captured and annotated an interpreter session to illustrate the problem:
First, some imports:
from zope.interface import Interface
from zope.component import provideAdapter
from zope import schema
from zope.schema.interfaces import TooSmall
from z3c.form import form, error, field
from z3c.form.testing import setupFormDefaults, TestRequest
Set up a test environment with two forms.
setupFormDefaults()
class DummyContext(object):
... pass
context = DummyContext()
class ISchema(Interface):
... field1 = schema.Int(min=10)
class Form1(form.Form):
... fields = field.Fields(ISchema)
... ignoreContext = True
class Form2(form.Form):
... fields = field.Fields(ISchema)
... ignoreContext = True
Define two error message value adapters. Note that message2 is a
specialisation of message1, that also takes the form into account. Also
note that message1 (the more general) happens to be created first.
message1 = error.ErrorViewMessage(uMessage 1, error=TooSmall,
field=ISchema['field1'])
message2 = error.ErrorViewMessage(uMessage 2, error=TooSmall,
field=ISchema['field1'], form=Form2)
Now register these adapters (the order doesn't matter)
provideAdapter(message1, name=umessage)
provideAdapter(message2, name=umessage)
Let's now create an instance of Form1 and try to validate it.
form1 = Form1(context, TestRequest(form={'form.widgets.field1': '5'}))
form1.update()
data, errors = form1.extractData()
len(errors)
1
errors[0].message
u'Message 1'
No surprises there. Now let's try Form2. We'd expect to see message2, since
it is registered for the more specific form:
form2 = Form2(context, TestRequest(form={'form.widgets.field1': '5'}))
form2.update()
data, errors = form2.extractData()
len(errors)
1
errors[0].message
u'Message 1'
Ouch. Why is our more specific adapter being ignored?
Recall that the ErrorViewSnippet does the IValue adapter lookup like so:
value = zope.component.queryMultiAdapter(
(self.context, self.request, self.widget,
self.field, self.form, self),
interfaces.IValue, name='message')
The adapted objects are:
(errors[0].context, errors[0].request, errors[0].widget,
errors[0].field, errors[0].form, errors[0])
(TooSmall(5, 10), z3c.form.testing.TestRequest instance
URL=http://127.0.0.1, TextWidget 'form.widgets.field1',
zope.schema._bootstrapfields.Int object at 0x1030a1190,
__main__.Form2 object at 0x1035c0b90, ErrorViewSnippet for TooSmall)
Let's look at the interfaces provided by ISchema['field1']:
from zope.interface import directlyProvidedBy
list(directlyProvidedBy(ISchema['field1']).flattened())
[InterfaceClass z3c.form.util.IGeneratedForObject_4345958800,
InterfaceClass z3c.form.util.IGeneratedForObject_4345958800,
InterfaceClass zope.interface.Interface]
Let's also look at what our adapters are actually adapting:
message1.__component_adapts__
(class 'zope.schema._bootstrapinterfaces.TooSmall', None, None,
InterfaceClass z3c.form.util.IGeneratedForObject_4345958800, None, None)
message2.__component_adapts__
(class 'zope.schema._bootstrapinterfaces.TooSmall', None, None,
InterfaceClass z3c.form.util.IGeneratedForObject_4345958800, class
'__main__.Form2', None)
Our adapters are both in the registry:
from