Collector
=========

SubjectsCollectorBase
---------------------

``SubjectsCollectorBase`` is a template class that you can use to
create a collector based on a vocabulary.  This vocabulary may come
from anywhere, like from ``ATVocabularyManager`` or from the list of
all subjects/tags available in your site.

There's two methods that you need to override when subclassing from
``SubjectsCollectorBase``.

``get_items_for_selection`` must return a list of items that
correspond to the set of choices made by the user.  These choices are
passed as an argument to the method.  The other argument to the method
is the ``cue``.  You must not return data that is older than the
``cue``.  We'll make a simple implementation that looks up items from
a simple dictionary:

  >>> avail_items = {'female': ['Wilma', 'Betty'], 'male': ['Fred', 'Barney']}
  >>> def get_items_for_selection(self, cue, data):
  ...     items = []
  ...     for choice in data:
  ...         items.extend(avail_items[choice])
  ...     return items

The other method that you must provide in your subclass is
``vocabulary``.  Here, you must return a Zope 3 vocabulary object for
use with the field:

  >>> from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
  >>> vocab = SimpleVocabulary(
  ...     [SimpleTerm(k) for k in sorted(avail_items.keys())])
  >>> def vocabulary(self):
  ...     return vocab

We can now put our custom class together and test the ICollector
interface:

  >>> from collective.singing.collector import SubjectsCollectorBase
  >>> class FlintStoneCollector(SubjectsCollectorBase):
  ...     get_items_for_selection = get_items_for_selection
  ...     vocabulary = vocabulary

  >>> collector = FlintStoneCollector('fsc', 'The Flintstones')

The ``schema`` property returns a schema with one field with our
vocabulary:

  >>> collector.schema.names()
  ['subjects']
  >>> collector.schema['subjects'].title
  u'Subjects'
  >>> collector.schema['subjects'].value_type.vocabulary is vocab
  True

We can choose the field's title by setting an attribute on our
collector:

  >>> FlintStoneCollector.field_title = u'Genders'
  >>> collector.schema['subjects'].title
  u'Genders'

The ``get_items`` method expects an optional ``cue`` and an optional
``subscriber`` object.  Let's create a minimal subscription class:

  >>> class Subscription(object):
  ...     def __init__(self, collector_data):
  ...         self.collector_data = collector_data

If we provide neither ``cue`` nor ``subscription``, we expect to get
an empty list back, since our ``get_items_for_selection`` method will
return the empty list:

  >>> collector.get_items() # doctest: +ELLIPSIS
  ([], datetime.datetime(...))

If our subscriber lacks ``subjects``, we'll get the empty list back as
well.  The same goes for a subscription with an empty ``subjects``
list:

  >>> collector.get_items(subscription=Subscription({})) # doctest: +ELLIPSIS
  ([], datetime.datetime(...))
  >>> collector.get_items(subscription=Subscription({'subjects': []})) \
  ... # doctest: +ELLIPSIS
  ([], datetime.datetime(...))

Finally, we'll try a subscriber that has choices:

  >>> collector.get_items(subscription=Subscription({'subjects': ['female']})) \
  ... # doctest: +ELLIPSIS
  (['Wilma', 'Betty'], datetime.datetime(...))

FilteredSubjectsCollectorBase
-----------------------------

FilteredSubjectsCollectorBase is a small specialization of
SubjectsCollectorBase that allows you to select items for users to
choose from from the vocabulary.

  >>> from collective.singing.collector import FilteredSubjectsCollectorBase
  >>> class FlintStoneFilteredCollector(FilteredSubjectsCollectorBase):
  ...     get_items_for_selection = get_items_for_selection
  ...     vocabulary = vocabulary

  >>> collector = FlintStoneFilteredCollector('ffsc', 'The Flintstones')

By default, this will give us back the same vocabulary as the
unfiltered variant:

  >>> sorted(
  ...     [t.token for t in collector.schema['subjects'].value_type.vocabulary])
  ['female', 'male']

We can set the ``filtered_items`` attribute to choose from the
available items:

  >>> collector.filtered_items = ('male', 'neutral')
  >>> sorted(
  ...     [t.token for t in collector.schema['subjects'].value_type.vocabulary])
  ['male']

A form is available that let's use select items from within the
administration interface:

  >>> from zope import component
  >>> from z3c.form import term
  >>> from collective.singing.browser.tests import setup_defaults
  >>> from collective.singing.browser import converters
  >>> component.provideAdapter(term.CollectionTerms)
  >>> component.provideAdapter(converters.DynamicVocabularyCollSeqConverter)
  >>> setup_defaults()

  >>> from z3c.form.testing import TestRequest
  >>> from collective.singing.browser.collector \
  ...     import EditFilteredSubjectsCollector

  >>> request = TestRequest()
  >>> view = EditFilteredSubjectsCollector(collector, request)
  >>> html = view()
  >>> 'female' in html, 'male' in html
  (True, True)

Let's select only 'female':

  >>> [t.token for t in collector.schema['subjects'].value_type.vocabulary]
  ['male']
  >>> request.form['form.widgets.filtered_items'] = [u'female']
  >>> request.form['form.buttons.apply'] = u'Apply'
  >>> html = view()
  >>> [t.token for t in collector.schema['subjects'].value_type.vocabulary]
  ['female']
