Skip to content

Commit

Permalink
Fix missing terms for sources
Browse files Browse the repository at this point in the history
Version 2.9 introduced a solution for missing terms in vocabularies.
Adapted sources to this solution, too.
  • Loading branch information
Michael Howitz committed Aug 15, 2013
1 parent 8d14d45 commit 5c6e278
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 4 deletions.
3 changes: 3 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ CHANGES
when trying to remove from ignored list. Probably not the 100%
right fix but it catches core dumps and is sane anyways.

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


3.0.0a3 (2013-04-08)
--------------------
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."""
84 changes: 84 additions & 0 deletions src/z3c/form/term.txt
Original file line number Diff line number Diff line change
Expand Up @@ -343,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 5c6e278

Please sign in to comment.