Cannot initialise a Choice field with an IContextSourceBinder source and a default value

Reported by Martin Aspeli on 2009-03-10
2
Affects Status Importance Assigned to Milestone
Zope 3
Undecided
Unassigned

Bug Description

Let's say we have a context source binder:

>>> from zope.schema.interfaces import IContextSourceBinder
>>> from zope.interface import implements
>>> from zope.schema.vocabulary import SimpleVocabulary

>>> class Binder(object):
... implements(IContextSourceBinder)
... def __call__(self, context):
... return SimpleVocabulary.fromValues([u'a', u'b'])
...

If we try to initialise a Choice field with this as a source and no default value, we're OK:

>>> from zope.schema import Choice
>>> field = Choice(title=u"Test", source=Binder())

Note that the field can be validated against an item in the vocabulary only once bound:

>>> field.bind(object()).validate(u'a') is None
True

If the field is not bound, we get an error:

>>> field.validate(u'a') is None
Traceback (most recent call last):
  File "<console>", line 1, in ?
  File "/Users/optilude/.buildout/zope/Zope-2.10.6-final-py2.4/lib/python/zope/schema/_bootstrapfields.py", line 138, in validate
    self._validate(value)
  File "/Users/optilude/.buildout/zope/Zope-2.10.6-final-py2.4/lib/python/zope/schema/_field.py", line 275, in _validate
    if value not in vocabulary:
TypeError: iterable argument required

This is because at this point, self.vocabulary is an IContextSourceBinder instance, not a vocabulary.

Unfortunately, the default value is validated against an unbound field when initialised in the constructor, giving the same error:

>>> field = Choice(title=u"Test", default=u'a', source=Binder())
Traceback (most recent call last):
  File "<console>", line 1, in ?
  File "/Users/optilude/.buildout/zope/Zope-2.10.6-final-py2.4/lib/python/zope/schema/_field.py", line 229, in __init__
    super(Choice, self).__init__(**kw)
  File "/Users/optilude/.buildout/zope/Zope-2.10.6-final-py2.4/lib/python/zope/schema/_bootstrapfields.py", line 114, in __init__
    self.default = default
  File "/Users/optilude/.buildout/zope/Zope-2.10.6-final-py2.4/lib/python/zope/schema/_bootstrapfields.py", line 42, in __set__
    inst.validate(value)
  File "/Users/optilude/.buildout/zope/Zope-2.10.6-final-py2.4/lib/python/zope/schema/_bootstrapfields.py", line 138, in validate
    self._validate(value)
  File "/Users/optilude/.buildout/zope/Zope-2.10.6-final-py2.4/lib/python/zope/schema/_field.py", line 275, in _validate
    if value not in vocabulary:
TypeError: iterable argument required

This means that it is impossible to initialise a choice field with a context source binder and a default. The choice field actually has a flag that it sets in the initialiser that "turns off" validation, but it is only used for named vocabularies:

        # Before a default value is checked, it is validated. However, a
        # named vocabulary is usually not complete when these fields are
        # initialized. Therefore signal the validation method to ignore
        # default value checks during initialization of a Choice tied to a
        # registered vocabulary.
        self._init_field = bool(self.vocabularyName)
        super(Choice, self).__init__(**kw)
        self._init_field = False

I think it should also be set if IContextSourceBinder.providedBy(self.vocabulary), e.g:

        self._init_field = bool(self.vocabularyName) or IContextSourceBinder.providedBy(self.vocabulary)

Of course, this means the default value isn't validated, but that's always going to be impossible anyway with a context-bound source, since the context isn't known at field initialisation time.

Dan Korostelev (nadako) wrote :

Fix committed in 97798.

Changed in zope3:
status: New → Fix Committed
Wolfgang Schnerring (wosc) wrote :

released as zope.schema-3.5.4

Changed in zope3:
status: Fix Committed → Fix Released
To post a comment you must log in.
This report contains Public information  Edit
Everyone can see this information.

Other bug subscribers