=====
Terms
=====

Terms are used to provide choices for sequence widgets or any other construct
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
  >>> ratings = vocabulary.SimpleVocabulary([
  ...     vocabulary.SimpleVocabulary.createTerm(0, '0', u'bad'),
  ...     vocabulary.SimpleVocabulary.createTerm(1, '1', u'okay'),
  ...     vocabulary.SimpleVocabulary.createTerm(2, '2', u'good')
  ...     ])

Terms
~~~~~

Now we can create the terms object:

  >>> from z3c.form import term
  >>> terms = term.Terms()
  >>> terms.terms = ratings

Getting a term from a given value is simple:

  >>> terms.getTerm(0).title
  u'bad'
  >>> terms.getTerm(3)
  Traceback (most recent call last):
  ...
  LookupError: 3

When converting values from their Web representation back to the internal
representation, we have to be able to look up a term by its token:

  >>> terms.getTermByToken('0').title
  u'bad'
  >>> terms.getTerm('3')
  Traceback (most recent call last):
  ...
  LookupError: 3

However, often we just want the value so asking for the value that is
represented by a token saves usually one line of code:

  >>> terms.getValue('0')
  0
  >>> terms.getValue('3')
  Traceback (most recent call last):
  ...
  LookupError: 3

You can also iterate through all terms:

  >>> [entry.title for entry in terms]
  [u'bad', u'okay', u'good']

Or ask how many terms you have in the first place:

  >>> len(terms)
  3

Finally the API allows you to check whether a particular value is available in
the terms:

  >>> 0 in terms
  True
  >>> 3 in terms
  False

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
~~~~~~~~~~~~

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:

  >>> import zope.component
  >>> zope.component.provideAdapter(term.ChoiceTermsVocabulary)
  >>> import z3c.form.testing
  >>> request = z3c.form.testing.TestRequest()
  >>> import z3c.form.widget
  >>> widget = z3c.form.widget.Widget(request)

  >>> import zope.schema

  >>> ratingField = zope.schema.Choice(
  ...     title=u'Rating',
  ...     vocabulary=ratings)

  >>> terms = term.ChoiceTerms(
  ...     None, request, None, ratingField, widget)
  >>> [entry.title for entry in terms]
  [u'bad', u'okay', u'good']

Sometimes choice fields only specify a vocabulary name and the actual
vocabulary is looked up at run time.

  >>> ratingField2 = zope.schema.Choice(
  ...     title=u'Rating',
  ...     vocabulary='Ratings')

Initially we get an error because the "Ratings" vocabulary is not defined:

  >>> terms = term.ChoiceTerms(
  ...     None, request, None, ratingField2, widget)
  Traceback (most recent call last):
  ...
  VocabularyRegistryError: unknown vocabulary: 'Ratings'

Let's now register the vocabulary under this name:

  >>> def RatingsVocabulary(obj):
  ...     return ratings

  >>> from zope.schema import vocabulary
  >>> vr = vocabulary.getVocabularyRegistry()
  >>> vr.register('Ratings', RatingsVocabulary)

We should now be able to get all terms as before:

  >>> terms = term.ChoiceTerms(
  ...     None, request, None, ratingField, widget)
  >>> [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.

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

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

The same goes with looking up a term by value:

  >>> term = 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 IPerson(zope.interface.Interface):
  ...     gender = zope.schema.Choice(title=u'Gender', vocabulary='Genders')
  >>> @zope.interface.implementer(IPerson)
  ... class Person(object):
  ...     gender = None
  >>> gendersVocabulary = vocabulary.SimpleVocabulary([
  ...     vocabulary.SimpleVocabulary.createTerm(1, 'male', u'Male'),
  ...     vocabulary.SimpleVocabulary.createTerm(2, 'female', u'Female'),
  ...     ])
  >>> def GendersVocabulary(obj):
  ...     return ratings
  >>> vr.register('Genders', GendersVocabulary)

  >>> ctx = Person()
  >>> ctx.gender = 42

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

  >>> terms = term.ChoiceTerms(
  ...     ctx, request, None, IPerson['gender'], genderWidget)

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


Bool fields
~~~~~~~~~~~

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

  >>> truthField = zope.schema.Bool()

  >>> terms = term.BoolTerms(None, None, None, truthField, None)
  >>> [entry.title for entry in terms]
  [u'yes', u'no']

In case you don't like the choice of 'yes' and 'no' for the labels, we
can subclass the ``BoolTerms`` class to control the display labels.

  >>> class MyBoolTerms(term.BoolTerms):
  ...   trueLabel = u'True'
  ...   falseLabel = u'False'

  >>> terms = MyBoolTerms(None, None, None, truthField, None)
  >>> [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:

  >>> from z3c.form import term
  >>> zope.component.provideAdapter(term.CollectionTerms)
  >>> zope.component.provideAdapter(term.CollectionTermsVocabulary)
  >>> zope.component.provideAdapter(term.CollectionTermsSource)

  >>> ratingsField = zope.schema.List(
  ...     title=u'Ratings',
  ...     value_type=ratingField)

  >>> terms = term.CollectionTerms(
  ...     None, request, None, ratingsField, widget)
  >>> [entry.title for entry in terms]
  [u'bad', u'okay', u'good']


Sources
-------

Basic sources
~~~~~~~~~~~~~

Basic sources need no context to compute their value. 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.ChoiceTerms(
  ...     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

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
+++++++++++

Finally, there are terms adapters for all collections:

  >>> sourceRatingsField = zope.schema.List(
  ...     title=u'Sourced Ratings',
  ...     value_type=sourceRatingField)

  >>> terms = term.CollectionTerms(
  ...     None, request, None, sourceRatingsField, widget)
  >>> [entry.title for entry in terms]
  [u'ugly', u'nice', u'great']


Contextual sources
~~~~~~~~~~~~~~~~~~

Contextual sources depend on the context they are called on. Let's
create a context and a contextual source:

  >>> from zc.sourcefactory.contextual import BasicContextualSourceFactory
  >>> class RatingContext(object):
  ...     base_value = 10
  >>> class ContextualRatingSourceFactory(BasicContextualSourceFactory):
  ...     _mapping = {10: u'ugly', 20: u'nice', 30: u'great'}
  ...     def getValues(self, context):
  ...         return [context.base_value + x for x in self._mapping.keys()]
  ...     def getTitle(self, context, value):
  ...         return self._mapping[value - context.base_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.FactoredContextualTerms)
  >>> zope.component.provideAdapter(
  ...     zc.sourcefactory.browser.token.fromInteger)
  >>> zope.component.provideAdapter(term.ChoiceTermsSource)

Choice fields
+++++++++++++

Contextual sources can be used with ``Choice`` fields like
vocabularies.  First we create a field based on the source:

  >>> contextualSourceRatingField = zope.schema.Choice(
  ...     title=u'Context Sourced Rating',
  ...     source=ContextualRatingSourceFactory())

We create an context object and connect the field to a widget to see
the ITerms adapter for sources at work:

  >>> rating_context = RatingContext()
  >>> rating_context.base_value = 100
  >>> terms = term.ChoiceTerms(
  ...     rating_context, request, None, contextualSourceRatingField, 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]
  ['110', '120', '130']
  >>> [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('120').title
  u'nice'
  >>> terms.getValue('130')
  130

With can test if a value is in the source:

  >>> 130 in terms
  True
  >>> 125 in terms
  False

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

Finally, there are terms adapters for all collections:

  >>> contextualSourceRatingsField = zope.schema.List(
  ...     title=u'Contextual Sourced Ratings',
  ...     value_type=contextualSourceRatingField)

  >>> terms = term.CollectionTerms(
  ...     rating_context, request, None, contextualSourceRatingsField, widget)
  >>> [entry.title for entry in terms]
  [u'ugly', u'nice', u'great']
