[Checkins] SVN: z3c.form/branches/zagy-sources/s - added tests for zagy's support of sources
Michael Howitz
mh at gocept.com
Thu Aug 28 05:07:37 EDT 2008
Log message for revision 90534:
- added tests for zagy's support of sources
- support sources for collections, too
Changed:
U z3c.form/branches/zagy-sources/setup.py
U z3c.form/branches/zagy-sources/src/z3c/form/browser/checkbox.txt
U z3c.form/branches/zagy-sources/src/z3c/form/browser/radio.txt
U z3c.form/branches/zagy-sources/src/z3c/form/configure.zcml
U z3c.form/branches/zagy-sources/src/z3c/form/converter.txt
U z3c.form/branches/zagy-sources/src/z3c/form/interfaces.py
U z3c.form/branches/zagy-sources/src/z3c/form/term.py
U z3c.form/branches/zagy-sources/src/z3c/form/term.txt
U z3c.form/branches/zagy-sources/src/z3c/form/testing.py
-=-
Modified: z3c.form/branches/zagy-sources/setup.py
===================================================================
--- z3c.form/branches/zagy-sources/setup.py 2008-08-28 09:07:30 UTC (rev 90533)
+++ z3c.form/branches/zagy-sources/setup.py 2008-08-28 09:07:37 UTC (rev 90534)
@@ -75,7 +75,7 @@
namespace_packages = ['z3c'],
extras_require = dict(
test = ['zope.app.container', 'zope.testing',
- 'z3c.coverage', 'z3c.template'],
+ 'z3c.coverage', 'z3c.template', 'zc.sourcefactory'],
adding = ['zope.app.container'],
),
install_requires = [
Modified: z3c.form/branches/zagy-sources/src/z3c/form/browser/checkbox.txt
===================================================================
--- z3c.form/branches/zagy-sources/src/z3c/form/browser/checkbox.txt 2008-08-28 09:07:30 UTC (rev 90533)
+++ z3c.form/branches/zagy-sources/src/z3c/form/browser/checkbox.txt 2008-08-28 09:07:37 UTC (rev 90534)
@@ -55,9 +55,9 @@
>>> print widget.render()
<input name="widget.name-empty-marker" type="hidden" value="1" />
-Let's provide some values for this widget. We can do this by defining a source
-providing ``ITerms``. This source uses descriminators wich will fit for our
-setup.
+Let's provide some values for this widget. We can do this by defining
+a vocabulary providing ``ITerms``. This vocabulary uses descriminators
+wich will fit for our setup.
>>> import zope.schema.interfaces
>>> from zope.schema.vocabulary import SimpleVocabulary
Modified: z3c.form/branches/zagy-sources/src/z3c/form/browser/radio.txt
===================================================================
--- z3c.form/branches/zagy-sources/src/z3c/form/browser/radio.txt 2008-08-28 09:07:30 UTC (rev 90533)
+++ z3c.form/branches/zagy-sources/src/z3c/form/browser/radio.txt 2008-08-28 09:07:37 UTC (rev 90534)
@@ -9,7 +9,7 @@
>>> from z3c.form import interfaces
>>> from z3c.form.browser import radio
-The TextWidget is a widget:
+The RadioWidget is a widget:
>>> verifyClass(interfaces.IWidget, radio.RadioWidget)
True
@@ -54,11 +54,14 @@
providing ITerms. This source uses descriminators wich will fit for our setup.
>>> import zope.schema.interfaces
- >>> from zope.schema.vocabulary import SimpleVocabulary
>>> import z3c.form.term
- >>> class MyTerms(z3c.form.term.ChoiceTermsVocabulary):
+ >>> from zc.sourcefactory.basic import BasicSourceFactory
+ >>> class YesNoSourceFactory(BasicSourceFactory):
+ ... def getValues(self):
+ ... return ['yes', 'no']
+ >>> class MyTerms(z3c.form.term.ChoiceTermsSource):
... def __init__(self, context, request, form, field, widget):
- ... self.terms = SimpleVocabulary.fromValues(['yes', 'no'])
+ ... self.terms = YesNoSourceFactory()
>>> zope.component.provideAdapter(z3c.form.term.BoolTerms,
... adapts=(zope.interface.Interface,
... interfaces.IFormLayer, zope.interface.Interface,
Modified: z3c.form/branches/zagy-sources/src/z3c/form/configure.zcml
===================================================================
--- z3c.form/branches/zagy-sources/src/z3c/form/configure.zcml 2008-08-28 09:07:30 UTC (rev 90533)
+++ z3c.form/branches/zagy-sources/src/z3c/form/configure.zcml 2008-08-28 09:07:37 UTC (rev 90534)
@@ -81,9 +81,15 @@
factory=".term.ChoiceTermsSource"
/>
<adapter
- factory=".term.CollectionTerms"
+ factory=".term.collection_terms_multiplexer"
/>
<adapter
+ factory=".term.CollectionTermsVocabulary"
+ />
+ <adapter
+ factory=".term.CollectionTermsSource"
+ />
+ <adapter
factory=".term.BoolTerms"
/>
Modified: z3c.form/branches/zagy-sources/src/z3c/form/converter.txt
===================================================================
--- z3c.form/branches/zagy-sources/src/z3c/form/converter.txt 2008-08-28 09:07:30 UTC (rev 90533)
+++ z3c.form/branches/zagy-sources/src/z3c/form/converter.txt 2008-08-28 09:07:37 UTC (rev 90534)
@@ -463,14 +463,26 @@
For widgets and fields that work with choices of a sequence, a special data
converter is required that works with terms. A prime example is a choice
-field. Before we can use the converter, we have to register the terms adapter:
+field. Before we can use the converter, we have to register some adapters:
>>> from z3c.form import term
+ >>> import zc.sourcefactory.browser.source
+ >>> import zc.sourcefactory.browser.token
>>> zope.component.provideAdapter(term.ChoiceTermsVocabulary)
+ >>> zope.component.provideAdapter(term.ChoiceTermsSource)
>>> zope.component.provideAdapter(term.choice_terms_multiplexer)
+ >>> zope.component.provideAdapter(
+ ... zc.sourcefactory.browser.source.FactoredTerms)
+ >>> zope.component.provideAdapter(
+ ... zc.sourcefactory.browser.token.fromInteger)
-Let's now create a choice field and a widget:
+The choice fields can be used together with vocabularies and sources.
+Using vocabulary
+~~~~~~~~~~~~~~~~
+
+Let's now create a choice field (using a vocabulary) and a widget:
+
>>> from zope.schema.vocabulary import SimpleVocabulary
>>> gender = zope.schema.Choice(
@@ -520,19 +532,82 @@
>>> sdv.toFieldValue([])
'missing'
+Using source
+~~~~~~~~~~~~
+Let's now create a choice field (using a source) and a widget:
+
+ >>> from zc.sourcefactory.basic import BasicSourceFactory
+ >>> class GenderSourceFactory(BasicSourceFactory):
+ ... _mapping = {0: u'male', 1: u'female'}
+ ... def getValues(self):
+ ... return self._mapping.keys()
+ ... def getTitle(self, value):
+ ... return self._mapping[value]
+ >>> gender_source = zope.schema.Choice(
+ ... source = GenderSourceFactory())
+
+ >>> seqWidget = widget.SequenceWidget(TestRequest())
+ >>> seqWidget.field = gender_source
+
+We now use the field and widget to instantiate the converter:
+
+ >>> sdv = converter.SequenceDataConverter(gender, seqWidget)
+
+We can now convert a real value to a widget value, which will be the term's
+token:
+
+ >>> sdv.toWidgetValue(0)
+ ['0']
+
+The result is always a sequence, since sequence widgets only deal collections
+of values. Of course, we can convert the widget value back to an internal
+value:
+
+ >>> sdv.toFieldValue(['0'])
+ 0
+
+Sometimes a field is not required. In those cases, the internalvalue is the
+missing value of the field. The converter interprets that as no value being
+selected:
+
+ >>> gender.missing_value = 'missing'
+
+ >>> sdv.toWidgetValue(gender.missing_value)
+ []
+
+If "no value" has been specified in the widget, the missing value
+of the field is returned:
+
+ >>> sdv.toFieldValue([u'--NOVALUE--'])
+ 'missing'
+
+An empty list will also cause the missing value to be returned:
+
+ >>> sdv.toFieldValue([])
+ 'missing'
+
+
Collection Sequence Data Converter
----------------------------------
For widgets and fields that work with a sequence of choices, another data
converter is required that works with terms. A prime example is a list
-field. Before we can use the converter, we have to register the terms adapter:
+field. Before we can use the converter, we have to register the terms adapters:
>>> from z3c.form import term
- >>> zope.component.provideAdapter(term.CollectionTerms)
+ >>> zope.component.provideAdapter(term.collection_terms_multiplexer)
+ >>> zope.component.provideAdapter(term.CollectionTermsVocabulary)
+ >>> zope.component.provideAdapter(term.CollectionTermsSource)
-Let's now create a set field and a widget:
+Collections can also use either vocabularies or sources.
+Using vocabulary
+~~~~~~~~~~~~~~~~
+
+Let's now create a list field (using the previously defined field using
+a vocabulary) and a widget:
+
>>> genders = zope.schema.List(value_type=gender)
>>> seqWidget = widget.SequenceWidget(TestRequest())
>>> seqWidget.field = genders
@@ -570,7 +645,7 @@
set([0])
Getting Terms
-~~~~~~~~~~~~~
++++++++++++++
As an optimization of this converter, the converter actually does not look up
the terms itself but uses the widget's ``terms`` attribute. If the terms are
@@ -588,7 +663,7 @@
['m']
>>> seqWidget.terms
- <z3c.form.term.CollectionTerms object ...>
+ <z3c.form.term.CollectionTermsVocabulary object ...>
The same is true when getting the field value:
@@ -602,9 +677,90 @@
set([0])
>>> seqWidget.terms
- <z3c.form.term.CollectionTerms object ...>
+ <z3c.form.term.CollectionTermsVocabulary object ...>
+Using source
+~~~~~~~~~~~~
+Let's now create a list field (using the previously defined field using
+a source) and a widget:
+
+ >>> genders_source = zope.schema.List(value_type=gender_source)
+ >>> seqWidget = widget.SequenceWidget(TestRequest())
+ >>> seqWidget.field = genders_source
+
+We now use the field and widget to instantiate the converter:
+
+ >>> csdv = converter.CollectionSequenceDataConverter(
+ ... genders_source, seqWidget)
+
+We can now convert a real value to a widget value, which will be the term's
+token:
+
+ >>> csdv.toWidgetValue([0])
+ ['0']
+
+The result is always a sequence, since sequence widgets only deal collections
+of values. Of course, we can convert the widget value back to an internal
+value:
+
+ >>> csdv.toFieldValue(['0'])
+ [0]
+
+For some field, like the ``Set``, the collection type is a tuple. Sigh. In
+these cases we use the last entry in the tuple as the type to use:
+
+ >>> genders_source = zope.schema.Set(value_type=gender_source)
+ >>> seqWidget = widget.SequenceWidget(TestRequest())
+ >>> seqWidget.field = genders_source
+
+ >>> csdv = converter.CollectionSequenceDataConverter(
+ ... genders_source, seqWidget)
+
+ >>> csdv.toWidgetValue(set([0]))
+ ['0']
+
+ >>> csdv.toFieldValue(['0'])
+ set([0])
+
+Getting Terms
++++++++++++++
+
+As an optimization of this converter, the converter actually does not look up
+the terms itself but uses the widget's ``terms`` attribute. If the terms are
+not yet retrieved, the converter will ask the widget to do so when in need.
+
+So let's see how this works when getting the widget value:
+
+ >>> seqWidget = widget.SequenceWidget(TestRequest())
+ >>> seqWidget.field = genders_source
+
+ >>> seqWidget.terms
+
+ >>> csdv = converter.CollectionSequenceDataConverter(
+ ... genders_source, seqWidget)
+ >>> csdv.toWidgetValue([0])
+ ['0']
+
+ >>> seqWidget.terms
+ <z3c.form.term.CollectionTermsSource object ...>
+
+The same is true when getting the field value:
+
+ >>> seqWidget = widget.SequenceWidget(TestRequest())
+ >>> seqWidget.field = genders_source
+
+ >>> seqWidget.terms
+
+ >>> csdv = converter.CollectionSequenceDataConverter(
+ ... genders_source, seqWidget)
+ >>> csdv.toFieldValue(['0'])
+ set([0])
+
+ >>> seqWidget.terms
+ <z3c.form.term.CollectionTermsSource object ...>
+
+
Boolean to Single Checkbox Data Converter
-----------------------------------------
Modified: z3c.form/branches/zagy-sources/src/z3c/form/interfaces.py
===================================================================
--- z3c.form/branches/zagy-sources/src/z3c/form/interfaces.py 2008-08-28 09:07:30 UTC (rev 90533)
+++ z3c.form/branches/zagy-sources/src/z3c/form/interfaces.py 2008-08-28 09:07:37 UTC (rev 90534)
@@ -273,7 +273,7 @@
# term interfaces
class ITerms(zope.interface.Interface):
- """"""
+ """ """
context = zope.schema.Field()
request = zope.schema.Field()
@@ -300,6 +300,16 @@
LookupError is raised if there isn't a value in the source.
"""
+ def __iter__():
+ """Iterate over terms."""
+
+ def __len__():
+ """Return number of terms."""
+
+ def __contains__(value):
+ """Check wether terms containes the ``value``."""
+
+
class IBoolTerms(ITerms):
"""A specialization that handles boolean choices."""
Modified: z3c.form/branches/zagy-sources/src/z3c/form/term.py
===================================================================
--- z3c.form/branches/zagy-sources/src/z3c/form/term.py 2008-08-28 09:07:30 UTC (rev 90533)
+++ z3c.form/branches/zagy-sources/src/z3c/form/term.py 2008-08-28 09:07:37 UTC (rev 90534)
@@ -27,7 +27,7 @@
class Terms(object):
- """Base implementationf or custom ITerms."""
+ """Base implementation for custom ITerms."""
zope.interface.implements(interfaces.ITerms)
@@ -49,7 +49,43 @@
def __contains__(self, value):
return self.terms.__contains__(value)
+class SourceTerms(Terms):
+ """Base implementation for ITerms using a source instead of a vocabulary."""
+ zope.interface.implements(interfaces.ITerms)
+
+ def __init__(self, context, request, form, field, source, widget):
+ self.context = context
+ self.request = request
+ self.form = form
+ self.field = field
+ self.widget = widget
+ self.source = source
+ self.terms = zope.component.getMultiAdapter(
+ (self.source, self.request),
+ zope.app.form.browser.interfaces.ITerms)
+
+ def getTermByToken(self, token):
+ # This is rather expensive
+ for value in self.source:
+ term = self.getTerm(value)
+ if term.token == token:
+ return term
+
+ def getValue(self, token):
+ return self.terms.getValue(token)
+
+ def __iter__(self):
+ for value in self.source:
+ yield self.terms.getTerm(value)
+
+ def __len__(self):
+ return len(self.source)
+
+ def __contains__(self, value):
+ return value in self.source
+
+
@zope.interface.implementer(interfaces.ITerms)
@zope.component.adapter(
zope.interface.Interface,
@@ -67,7 +103,8 @@
class ChoiceTermsVocabulary(Terms):
- """ITerms adapter for zope.schema.IChoice based implementations."""
+ """ITerms adapter for zope.schema.IChoice based implementations using
+ vocabulary."""
zope.component.adapts(
zope.interface.Interface,
@@ -88,7 +125,8 @@
self.terms = vocabulary
-class ChoiceTermsSource(object):
+class ChoiceTermsSource(SourceTerms):
+ "ITerms adapter for zope.schema.IChoice based implementations using source."
zope.component.adapts(
zope.interface.Interface,
@@ -100,45 +138,7 @@
zope.interface.implements(interfaces.ITerms)
- def __init__(self, context, request, form, field, source, widget):
- self.context = context
- self.request = request
- self.form = form
- self.field = field
- self.widget = widget
- self.source = source
- self.terms = zope.component.getMultiAdapter(
- (self.source, self.request),
- zope.app.form.browser.interfaces.ITerms)
- def getTerm(self, value):
- return self.terms.getTerm(value)
-
- def getTermByToken(self, token):
- # This is rather expensive
- for value in self.source:
- term = self.getTerm(value)
- if term.token == token:
- return term
-
- def getValue(self, token):
- return self.terms.getValue(token)
-
- def __iter__(self):
- # XXX Not defined in interface
- for value in self.source:
- yield self.terms.getTerm(value)
-
- def __len__(self):
- # XXX Not defined in interface
- return len(self.source)
-
- def __contains__(self, value):
- # XXX Not defined in interface
- # XXX value or term?
- return value in self.source
-
-
class BoolTerms(Terms):
"""Default yes and no terms are used by default for IBool fields."""
@@ -165,21 +165,49 @@
(False, 'false', self.falseLabel)]]
self.terms = vocabulary.SimpleVocabulary(terms)
+ at zope.interface.implementer(interfaces.ITerms)
+ at zope.component.adapter(
+ zope.interface.Interface,
+ interfaces.IFormLayer,
+ zope.interface.Interface,
+ zope.schema.interfaces.ICollection,
+ interfaces.IWidget)
+def collection_terms_multiplexer(context, request, form, field, widget):
+ terms = field.value_type.bind(context).vocabulary
+ return zope.component.queryMultiAdapter(
+ (context, request, form, field, terms, widget),
+ interfaces.ITerms)
-class CollectionTerms(Terms):
- """ITerms adapter for zope.schema.ICollection based implementations."""
+class CollectionTermsVocabulary(Terms):
+ """ITerms adapter for zope.schema.ICollection based implementations using
+ vocabulary."""
zope.component.adapts(
zope.interface.Interface,
interfaces.IFormLayer,
zope.interface.Interface,
zope.schema.interfaces.ICollection,
+ zope.schema.interfaces.IBaseVocabulary,
interfaces.IWidget)
- def __init__(self, context, request, form, field, widget):
+ def __init__(self, context, request, form, field, vocabulary, widget):
self.context = context
self.request = request
self.form = form
self.field = field
self.widget = widget
- self.terms = field.value_type.bind(self.context).vocabulary
+ self.terms = vocabulary
+
+class CollectionTermsSource(SourceTerms):
+ """ITerms adapter for zope.schema.ICollection based implementations using
+ source."""
+
+ zope.component.adapts(
+ zope.interface.Interface,
+ interfaces.IFormLayer,
+ zope.interface.Interface,
+ zope.schema.interfaces.ICollection,
+ zope.schema.interfaces.IIterableSource,
+ interfaces.IWidget)
+
+ zope.interface.implements(interfaces.ITerms)
Modified: z3c.form/branches/zagy-sources/src/z3c/form/term.txt
===================================================================
--- z3c.form/branches/zagy-sources/src/z3c/form/term.txt 2008-08-28 09:07:30 UTC (rev 90533)
+++ z3c.form/branches/zagy-sources/src/z3c/form/term.txt 2008-08-28 09:07:37 UTC (rev 90534)
@@ -6,6 +6,9 @@
needing them. Since Zope 3 already has sources and vocabularies, the base
terms class simply builds on them.
+Vocabularies
+============
+
Thus, let's create a vocabulary first:
>>> from zope.schema import vocabulary
@@ -15,6 +18,9 @@
... vocabulary.SimpleVocabulary.createTerm(2, '2', u'good')
... ])
+Terms
+-----
+
Now we can create the terms object:
>>> from z3c.form import term
@@ -70,8 +76,11 @@
Now, there are several terms implementations that were designed for particular
fields. Within the framework, terms are used as adapters with the follwoing
-discriminators: context, request, form, field, and widget.
+discriminators: context, request, form, field, vocabulary/source and widget.
+Choice field
+============
+
The first terms implementation is for ``Choice`` fields. Choice fields
unfortunately can have a vocabulary and a source which behave differently.
Let's have a look a the vocabulary first:
@@ -125,6 +134,9 @@
>>> [entry.title for entry in terms]
[u'bad', u'okay', u'good']
+Bool fields
+-----------
+
A similar terms implementation exists for a ``Bool`` field:
>>> truthField = zope.schema.Bool()
@@ -144,12 +156,106 @@
>>> [entry.title for entry in terms]
[u'True', u'False']
-Finally, there is a terms adapter for all collections:
+Collections
+-----------
+Finally, there are a terms adapters for all collections. But we have to
+register some adapters before using it:
+
+ >>> from z3c.form import term
+ >>> zope.component.provideAdapter(term.collection_terms_multiplexer)
+ >>> zope.component.provideAdapter(term.CollectionTermsVocabulary)
+ >>> zope.component.provideAdapter(term.CollectionTermsSource)
+
>>> ratingsField = zope.schema.List(
... title=u'Ratings',
... value_type=ratingField)
- >>> terms = term.CollectionTerms(None, None, None, ratingsField, None)
+ >>> terms = term.collection_terms_multiplexer(
+ ... None, request, None, ratingsField, widget)
>>> [entry.title for entry in terms]
[u'bad', u'okay', u'good']
+
+
+Sources
+=======
+
+Let's create a source first:
+
+ >>> from zc.sourcefactory.basic import BasicSourceFactory
+ >>> class RatingSourceFactory(BasicSourceFactory):
+ ... _mapping = {10: u'ugly', 20: u'nice', 30: u'great'}
+ ... def getValues(self):
+ ... return self._mapping.keys()
+ ... def getTitle(self, value):
+ ... return self._mapping[value]
+
+As we did not include the configure.zcml of zc.sourcefactory we have
+to register some required adapters manually. We also need the
+ChoiceTermsSource adapter:
+
+ >>> import zope.component
+ >>> import zc.sourcefactory.browser.source
+ >>> import zc.sourcefactory.browser.token
+ >>> zope.component.provideAdapter(
+ ... zc.sourcefactory.browser.source.FactoredTerms)
+ >>> zope.component.provideAdapter(
+ ... zc.sourcefactory.browser.token.fromInteger)
+ >>> zope.component.provideAdapter(term.ChoiceTermsSource)
+
+Choice fields
+-------------
+
+Sources can be used with ``Choice`` fields like vocabularies. First
+we create a field based on the source:
+
+ >>> sourceRatingField = zope.schema.Choice(
+ ... title=u'Sourced Rating',
+ ... source=RatingSourceFactory())
+
+We connect the field to a widget to see the ITerms adapter for sources
+at work:
+
+ >>> terms = term.choice_terms_multiplexer(
+ ... None, request, None, sourceRatingField, widget)
+
+Iterating over the terms adapter returnes the term objects:
+
+ >>> [entry for entry in terms]
+ [<zc.sourcefactory.browser.source.FactoredTerm object at 0x...>,
+ <zc.sourcefactory.browser.source.FactoredTerm object at 0x...>,
+ <zc.sourcefactory.browser.source.FactoredTerm object at 0x...>]
+ >>> len(terms)
+ 3
+ >>> [entry.token for entry in terms]
+ ['10', '20', '30']
+ >>> [entry.title for entry in terms]
+ [u'ugly', u'nice', u'great']
+
+Using a token it is possible to look up the term and the value:
+
+ >>> terms.getTermByToken('20').title
+ u'nice'
+ >>> terms.getValue('30')
+ 30
+
+With can test if a value is in the source:
+
+ >>> 30 in terms
+ True
+ >>> 25 in terms
+ False
+
+Collections
+-----------
+
+Finally, there are terms adapters for all collections:
+
+ >>> sourceRatingsField = zope.schema.List(
+ ... title=u'Sourced Ratings',
+ ... value_type=sourceRatingField)
+
+ >>> terms = term.collection_terms_multiplexer(
+ ... None, request, None, sourceRatingsField, widget)
+ >>> [entry.title for entry in terms]
+ [u'ugly', u'nice', u'great']
Modified: z3c.form/branches/zagy-sources/src/z3c/form/testing.py
===================================================================
--- z3c.form/branches/zagy-sources/src/z3c/form/testing.py 2008-08-28 09:07:30 UTC (rev 90533)
+++ z3c.form/branches/zagy-sources/src/z3c/form/testing.py 2008-08-28 09:07:37 UTC (rev 90534)
@@ -136,6 +136,10 @@
# Adapter for providing terms to radio list and other widgets
zope.component.provideAdapter(term.choice_terms_multiplexer)
zope.component.provideAdapter(term.ChoiceTermsVocabulary)
+ zope.component.provideAdapter(term.ChoiceTermsSource)
+ zope.component.provideAdapter(term.collection_terms_multiplexer)
+ zope.component.provideAdapter(term.CollectionTermsVocabulary)
+ zope.component.provideAdapter(term.CollectionTermsSource)
zope.component.provideAdapter(term.BoolTerms)
# Adapter to create an action from a button
zope.component.provideAdapter(
More information about the Checkins
mailing list