Skip to content

Commit

Permalink
Merge pull request #11 from icemac/master
Browse files Browse the repository at this point in the history
Fix missing terms for sources
  • Loading branch information
agroszer committed Aug 15, 2013
2 parents d300602 + 491a2fd commit 36a1aac
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 7 deletions.
3 changes: 2 additions & 1 deletion CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ CHANGES
3.0.3 (unreleased)
------------------

- Nothing changed yet.
- Version 2.9 introduced a solution for missing terms in
vocabularies. Adapted sources to this solution, too.


3.0.2 (2013-08-14)
Expand Down
24 changes: 21 additions & 3 deletions src/z3c/form/term.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
$Id$
"""

import zope.browser
import zope.browser.interfaces
import zope.component
import zope.interface
import zope.schema
from zope.schema import vocabulary

Expand Down Expand Up @@ -64,12 +65,19 @@ def __init__(self, context, request, form, field, source, widget):
(self.source, self.request),
zope.browser.interfaces.ITerms)

def getTerm(self, value):
try:
return super(SourceTerms, self).getTerm(value)
except KeyError:
raise LookupError(value)

def getTermByToken(self, token):
# This is rather expensive
for value in self.source:
term = self.getTerm(value)
if term.token == token:
return term
raise LookupError(token)

def getValue(self, token):
return self.terms.getValue(token)
Expand Down Expand Up @@ -128,7 +136,7 @@ class MissingTermsMixin(object):

def getTerm(self, value):
try:
return self.terms.getTerm(value)
return super(MissingTermsMixin, self).getTerm(value)
except LookupError:
if (interfaces.IContextAware.providedBy(self.widget) and
not self.widget.ignoreContext):
Expand All @@ -153,7 +161,7 @@ def _makeMissingTerm(self, value):

def getTermByToken(self, token):
try:
return self.terms.getTermByToken(token)
return super(MissingTermsMixin, self).getTermByToken(token)
except LookupError:
if (interfaces.IContextAware.providedBy(self.widget) and
not self.widget.ignoreContext):
Expand Down Expand Up @@ -188,6 +196,11 @@ class ChoiceTermsSource(SourceTerms):
interfaces.IWidget)


class MissingChoiceTermsSource(MissingTermsMixin, ChoiceTermsSource):
"""ITerms adapter for zope.schema.IChoice based implementations using source
with missing terms support."""


@zope.interface.implementer_only(interfaces.IBoolTerms)
class BoolTerms(Terms):
"""Default yes and no terms are used by default for IBool fields."""
Expand Down Expand Up @@ -267,3 +280,8 @@ class CollectionTermsSource(SourceTerms):
zope.schema.interfaces.ICollection,
zope.schema.interfaces.IIterableSource,
interfaces.IWidget)


class MissingCollectionTermsSource(MissingTermsMixin, CollectionTermsSource):
"""ITerms adapter for zope.schema.ICollection based implementations using
source with missing terms support."""
92 changes: 90 additions & 2 deletions src/z3c/form/term.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ 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, vocabulary/source and widget.


Choice field
~~~~~~~~~~~~

Expand Down Expand Up @@ -134,7 +135,9 @@ We should now be able to get all terms as before:
>>> [entry.title for entry in terms]
[u'bad', u'okay', u'good']


Missing terms
+++++++++++++

Sometimes it happens that a term goes away from the vocabulary, but our
stored objects still reference that term.
Expand Down Expand Up @@ -224,7 +227,7 @@ And an exception if it does not:


Bool fields
+++++++++++
~~~~~~~~~~~

A similar terms implementation exists for a ``Bool`` field:

Expand All @@ -245,8 +248,9 @@ can subclass the ``BoolTerms`` class to control the display labels.
>>> [entry.title for entry in terms]
[u'True', u'False']


Collections
+++++++++++
~~~~~~~~~~~

Finally, there are a terms adapters for all collections. But we have to
register some adapters before using it:
Expand Down Expand Up @@ -339,6 +343,90 @@ With can test if a value is in the source:
>>> 25 in terms
False

Missing terms
#############

Sometimes it happens that a value goes away from the source, but our
stored objects still has this value.

>>> zope.component.provideAdapter(term.MissingChoiceTermsSource)

>>> terms = term.ChoiceTerms(
... None, request, None, sourceRatingField, widget)
>>> terms.getTermByToken('42')
Traceback (most recent call last):
...
LookupError: 42

The same goes with looking up a term by value:

>>> terms.getTerm('42')
Traceback (most recent call last):
...
LookupError: 42

Ooops, well this works only if the context has the right value for us.
This is because we don't want to accept any crap that's coming from HTML.

>>> class IRating(zope.interface.Interface):
... rating = zope.schema.Choice(title=u'Sourced Rating',
... source=RatingSourceFactory())
>>> @zope.interface.implementer(IRating)
... class Rating(object):
... rating = None

>>> ctx = Rating()
>>> ctx.rating = 42

>>> ratingWidget = z3c.form.widget.Widget(request)
>>> ratingWidget.context = ctx
>>> from z3c.form import interfaces
>>> zope.interface.alsoProvides(ratingWidget, interfaces.IContextAware)
>>> from z3c.form.datamanager import AttributeField
>>> zope.component.provideAdapter(AttributeField)

>>> terms = term.ChoiceTerms(
... ctx, request, None, IRating['rating'], ratingWidget)

Here we go:

>>> missingTerm = terms.getTermByToken('42')

We get the term, we passed the token, the value is coming from the context.

>>> missingTerm.token
'42'
>>> missingTerm.value
42

We cannot figure the title, so we construct one.
Override ``makeMissingTerm`` if you want your own.

>>> missingTerm.title
u'Missing: ${value}'

Still we raise LookupError if the token does not fit the context's value:

>>> missingTerm = terms.getTermByToken('99')
Traceback (most recent call last):
...
LookupError: 99

The same goes with looking up a term by value.
We get the term if the context's value fits:

>>> missingTerm = terms.getTerm(42)
>>> missingTerm.token
'42'

And an exception if it does not:

>>> missingTerm = terms.getTerm(99)
Traceback (most recent call last):
...
LookupError: 99


Collections
+++++++++++

Expand Down
2 changes: 1 addition & 1 deletion src/z3c/form/term.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
factory=".term.MissingChoiceTermsVocabulary"
/>
<adapter
factory=".term.ChoiceTermsSource"
factory=".term.MissingChoiceTermsSource"
/>
<adapter
factory=".term.CollectionTerms"
Expand Down
5 changes: 5 additions & 0 deletions src/z3c/form/tests/test_doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ def test_suite():
setUp=setUp, tearDown=testing.tearDown,
optionflags=flags, checker=testing.outputChecker,
),
doctest.DocFileSuite(
'../term.txt',
setUp=setUp, tearDown=testing.tearDown,
optionflags=flags, checker=testing.outputChecker,
),
doctest.DocFileSuite(
'../util.txt',
setUp=setUp, tearDown=testing.tearDown,
Expand Down

0 comments on commit 36a1aac

Please sign in to comment.