=============================
Source Widget Query Framework
=============================

Sources are objects that represent sets of values from which one might
choose and are used with Choice schema fields.  An important aspect of
sources is that they have too many values to enumerate.  Rather than
listing all of the values, we, instead, provide interfaces for
querying values and selecting values from query results.  Matters are
further complicated by the fact that different sources may have very
different interfaces for querying them.

To make matters more interesting, a source may be an aggregation of
several collections, each with their own querying facilities.
An example of such a source is a principal source, where principals
might come from a number of places, such as an LDAP database and
ZCML-based principal definitions.

The default widgets for selecting values from sources use the
following approach:

- One or more query objects are obtained from the source by adapting
  the source to `zope.schema.ISourceQueriables`.  If no adapter is
  obtained, then the source itself is assumed to be queriable.

- For each queriable found, a
  `zope.app.form.browser.interfaces.ISourceQueryView` view is looked
  up.  This view is used to obtain the HTML for displaying a query
  form.  The view is also used to obtain search results.

In addition to providing queriables and query views, the widgets need
views that can be used to get tokens to represent source values in
forms, as well as textual representations of values.  We use
`zope.app.form.browser.interfaces.ITerms` views for that.

Let's start with a simple example.  We have a very trivial source,
which is basically a list:

  >>> import zope.interface
  >>> import zope.schema
  >>> class SourceList(list):
  ...     zope.interface.implements(zope.schema.interfaces.ISource)

We provide an `ITerms` view for the source:

  >>> class Term:
  ...
  ...     def __init__(self, **kw):
  ...         self.__dict__.update(kw)

  >>> class ListTerms:
  ...
  ...     def __init__(self, source, request):
  ...         pass # We don't actually need the source or the request :)
  ...
  ...     def getTerm(self, value):
  ...         title = unicode(value)
  ...         token = title.encode('base64').strip()
  ...         return Term(title=title, token=token)
  ...
  ...     def getValue(self, token):
  ...         return token.decode('base64')

  >>> from zope.app.testing import ztapi
  >>> from zope.publisher.interfaces.browser import IBrowserRequest
  >>> import zope.app.form.browser.interfaces
  >>> ztapi.provideAdapter((SourceList, IBrowserRequest),
  ...                      zope.app.form.browser.interfaces.ITerms,
  ...                      ListTerms)

This view just uses the unicode representations of values as titles
and the base-64 encoding of the titles as tokens.  This is a very
simple strategy that's only approriate when the values have short and
unique unicode representations.

We aren't going to provide an adapter to `ISourceQueriables`, so the
source itself will be used as it's own queriable.  We need to provide a
query view for the source:

  >>> class ListQueryView:
  ...
  ...     def __init__(self, source, request):
  ...         self.source = source
  ...         self.request = request
  ...
  ...     def render(self, name):
  ...         return (
  ...             '<input name="%s.string">\n'
  ...             '<input type="submit" name="%s" value="Search">'
  ...             % (name, name)
  ...             )
  ...
  ...     def results(self, name):
  ...         if name in self.request:
  ...             search_string = self.request.get(name+'.string')
  ...             if search_string is not None:
  ...                 return [value
  ...                         for value in self.source
  ...                         if search_string in value
  ...                         ]
  ...         return None

  >>> ztapi.provideAdapter((SourceList, IBrowserRequest),
  ...                      zope.app.form.browser.interfaces.ISourceQueryView,
  ...                      ListQueryView)

Now, we can define a choice field:

  >>> dog = zope.schema.Choice(
  ...    __name__ = 'dog',
  ...    title=u"Dogs",
  ...    source=SourceList(['spot', 'bowser', 'prince', 'duchess', 'lassie']),
  ...    )

When we get a choice input widget for a choice field, the default
widget factory gets a view on the field and the field's source.  We'll
just create the view directly:

  >>> import zope.app.form.browser.source
  >>> from zope.publisher.browser import TestRequest
  >>> request = TestRequest()
  >>> widget = zope.app.form.browser.source.SourceInputWidget(
  ...     dog, dog.source, request)

Now if we render the widget, we'll see the input value (initially
nothing) and a form elements for seaching for values:

  >>> print widget()
  <div class="value">
    <div class="row">
      <div class="label">
       Selected
      </div>
      <div class="field">
       Nothing
      </div>
    </div>
    <input type="hidden" name="field.dog.displayed" value="y">
    <div class="queries">
      <div class="query">
        <div class="queryinput">
  <input name="field.dog.query.string">
  <input type="submit" name="field.dog.query" value="Search">
        </div> <!-- queryinput -->
      </div> <!-- query -->
    </div> <!-- queries -->
  </div> <!-- value -->

This shows that we haven't selected a dog. We get a search box that we
can type seach strings into.  Let's supply a search string.  We do
this by providing data in the form and by "selecting" the submit
button:

  >>> request.form['field.dog.displayed'] = u'y'
  >>> request.form['field.dog.query.string'] = u'o'
  >>> request.form['field.dog.query'] = u'Search'

Because the field is required, a non-selection is not valid. Thus, the widget
does not have an input value.

  >>> widget.hasInput()
  False

If the field is not required:

  >>> dog.required = False

then as long as the field is displayed, the widget says it has input.

  >>> widget.hasInput()
  True

  >>> dog.required = True

If we actually provide some input:

  >>> request.form['field.dog'] = 'foo'

then we have input:

  >>> widget.hasInput()
  True

  >>> del request.form['field.dog']

Now if we render the widget, we'll see the search results:

  >>> print widget()
  <div class="value">
    <div class="row">
      <div class="label">
       Selected
      </div>
      <div class="field">
       Nothing
      </div>
    </div>
    <input type="hidden" name="field.dog.displayed" value="y">
    <div class="queries">
      <div class="query">
        <div class="queryinput">
  <input name="field.dog.query.string">
  <input type="submit" name="field.dog.query" value="Search">
        </div> <!-- queryinput -->
        <div class="queryresults">
  <select name="field.dog.selection">
  <option value="Ym93c2Vy">bowser</option>
  <option value="c3BvdA==">spot</option>
  </select>
  <input type="submit" name="field.dog.apply" value="Apply">
        </div> <!-- queryresults -->
      </div> <!-- query -->
    </div> <!-- queries -->
  </div> <!-- value -->

If we select an item:

  >>> request.form['field.dog.displayed'] = u'y'
  >>> del request.form['field.dog.query.string']
  >>> del request.form['field.dog.query']
  >>> request.form['field.dog.selection'] = u'c3BvdA=='
  >>> request.form['field.dog.apply'] = u'Apply'

Then we'll show the newly selected value:

  >>> print widget()
  <div class="value">
    <div class="row">
      <div class="label">
       Selected
      </div>
      <div class="field">
       spot
      </div>
    </div>
    <input type="hidden" name="field.dog" value="c3BvdA==">
    <input type="hidden" name="field.dog.displayed" value="y">
    <div class="queries">
      <div class="query">
        <div class="queryinput">
  <input name="field.dog.query.string">
  <input type="submit" name="field.dog.query" value="Search">
        </div> <!-- queryinput -->
      </div> <!-- query -->
    </div> <!-- queries -->
  </div> <!-- value -->

Note that we should have an input value now, since pressing the 'Apply' button
provides us with input.

  >>> widget.hasInput()
  True

We should also be able to get the input value:

  >>> widget.getInputValue()
  'spot'

Now, let's look at a more complicated example.  We'll define a source
that combines multiple sources:

  >>> class MultiSource:
  ...
  ...     zope.interface.implements(
  ...        zope.schema.interfaces.ISource,
  ...        zope.schema.interfaces.ISourceQueriables,
  ...        )
  ...
  ...     def __init__(self, *sources):
  ...         self.sources = [(unicode(i), s) for (i, s) in enumerate(sources)]
  ...
  ...     def __contains__(self, value):
  ...         for i, s in self.sources:
  ...             if value in s:
  ...                 return True
  ...         return False
  ...
  ...     def getQueriables(self):
  ...         return self.sources

This multi-source implements `ISourceQueriables`. It assumes that the
sources it's given are queriable and just returns the sources as the
queryable objects.

We can reuse our terms view:

  >>> ztapi.provideAdapter((MultiSource, IBrowserRequest),
  ...                      zope.app.form.browser.interfaces.ITerms,
  ...                      ListTerms)

Now, we'll create a pet choice that combines dogs and cats:

  >>> pet = zope.schema.Choice(
  ...    __name__ = 'pet',
  ...    title=u"Dogs and Cats",
  ...    source=MultiSource(
  ...      dog.source,
  ...      SourceList(['boots', 'puss', 'tabby', 'tom', 'tiger']),
  ...      ),
  ...    )

and a widget:

  >>> widget = zope.app.form.browser.source.SourceInputWidget(
  ...     pet, pet.source, request)

Now if we display the widget, we'll see search inputs for both dogs
and cats:

  >>> print widget()
  <div class="value">
    <div class="row">
      <div class="label">
       Selected
      </div>
      <div class="field">
       Nothing
      </div>
    </div>
    <input type="hidden" name="field.pet.displayed" value="y">
    <div class="queries">
      <div class="query">
        <div class="queryinput">
  <input name="field.pet.MA__.query.string">
  <input type="submit" name="field.pet.MA__.query" value="Search">
        </div> <!-- queryinput -->
      </div> <!-- query -->
      <div class="query">
        <div class="queryinput">
  <input name="field.pet.MQ__.query.string">
  <input type="submit" name="field.pet.MQ__.query" value="Search">
        </div> <!-- queryinput -->
      </div> <!-- query -->
    </div> <!-- queries -->
  </div> <!-- value -->

As before, we can perform a search:

  >>> request.form['field.pet.displayed'] = u'y'
  >>> request.form['field.pet.MQ__.query.string'] = u't'
  >>> request.form['field.pet.MQ__.query'] = u'Search'

In which case, we'll get some results:

  >>> print widget() # doctest:
  <div class="value">
    <div class="row">
      <div class="label">
       Selected
      </div>
      <div class="field">
       Nothing
      </div>
    </div>
    <input type="hidden" name="field.pet.displayed" value="y">
    <div class="queries">
      <div class="query">
        <div class="queryinput">
  <input name="field.pet.MA__.query.string">
  <input type="submit" name="field.pet.MA__.query" value="Search">
        </div> <!-- queryinput -->
      </div> <!-- query -->
      <div class="query">
        <div class="queryinput">
  <input name="field.pet.MQ__.query.string">
  <input type="submit" name="field.pet.MQ__.query" value="Search">
        </div> <!-- queryinput -->
        <div class="queryresults">
  <select name="field.pet.MQ__.selection">
  <option value="Ym9vdHM=">boots</option>
  <option value="dGFiYnk=">tabby</option>
  <option value="dGlnZXI=">tiger</option>
  <option value="dG9t">tom</option>
  </select>
  <input type="submit" name="field.pet.MQ__.apply" value="Apply">
        </div> <!-- queryresults -->
      </div> <!-- query -->
    </div> <!-- queries -->
  </div> <!-- value -->

from which we can choose:

  >>> request.form['field.pet.displayed'] = u'y'
  >>> del request.form['field.pet.MQ__.query.string']
  >>> del request.form['field.pet.MQ__.query']
  >>> request.form['field.pet.MQ__.selection'] = u'dGFiYnk='
  >>> request.form['field.pet.MQ__.apply'] = u'Apply'

and get a selection:

  >>> print widget()
  <div class="value">
    <div class="row">
      <div class="label">
       Selected
      </div>
      <div class="field">
       tabby
      </div>
    </div>
    <input type="hidden" name="field.pet" value="dGFiYnk=">
    <input type="hidden" name="field.pet.displayed" value="y">
    <div class="queries">
      <div class="query">
        <div class="queryinput">
  <input name="field.pet.MA__.query.string">
  <input type="submit" name="field.pet.MA__.query" value="Search">
        </div> <!-- queryinput -->
      </div> <!-- query -->
      <div class="query">
        <div class="queryinput">
  <input name="field.pet.MQ__.query.string">
  <input type="submit" name="field.pet.MQ__.query" value="Search">
        </div> <!-- queryinput -->
      </div> <!-- query -->
    </div> <!-- queries -->
  </div> <!-- value -->

Note that we should have an input value now, since pressing the 'Apply' button
provides us with input.

  >>> widget.hasInput()
  True

and we can get the input value:

  >>> widget.getInputValue()
  'tabby'

There's a display widget, which doesn't use queriables, since it
doesn't assign values:

  >>> request = TestRequest()
  >>> widget = zope.app.form.browser.source.SourceDisplayWidget(
  ...     pet, pet.source, request)
  >>> print widget()
  Nothing

  >>> widget.setRenderedValue('tabby')
  >>> print widget()
  tabby

If we specify a list of choices:

  >>> pets = zope.schema.List(__name__ = 'pets', title=u"Pets",
  ...                         value_type=pet)

when a widget is computed for the field, a view will be looked up
for the field and the source, where, in this case, the field is a
list field.   We'll just call the widget factory directly:

  >>> widget = zope.app.form.browser.source.SourceListInputWidget(
  ...     pets, pets.value_type.source, request)

If we render the widget:

  >>> print widget()
  <div class="value">
    <input type="hidden" name="field.pets.displayed" value="y">
    <div class="queries">
      <div class="query">
        <div class="queryinput">
  <input name="field.pets.MA__.query.string">
  <input type="submit" name="field.pets.MA__.query" value="Search">
        </div> <!-- queryinput -->
      </div> <!-- query -->
      <div class="query">
        <div class="queryinput">
  <input name="field.pets.MQ__.query.string">
  <input type="submit" name="field.pets.MQ__.query" value="Search">
        </div> <!-- queryinput -->
      </div> <!-- query -->
    </div> <!-- queries -->
  </div> <!-- value -->

Here the output looks very similar to the simple choice case.  We get
a search input for each source.  In this case, we don't show any
inputs (XXX we probably should make it clearer that there are no
selected values.)

As before, we can search one of the sources:

  >>> request.form['field.pets.displayed'] = u'y'
  >>> request.form['field.pets.MQ__.query.string'] = u't'
  >>> request.form['field.pets.MQ__.query'] = u'Search'

In which case, we'll get some results:

  >>> print widget()
  <div class="value">
    <input type="hidden" name="field.pets.displayed" value="y">
    <div class="queries">
      <div class="query">
        <div class="queryinput">
  <input name="field.pets.MA__.query.string">
  <input type="submit" name="field.pets.MA__.query" value="Search">
        </div> <!-- queryinput -->
      </div> <!-- query -->
      <div class="query">
        <div class="queryinput">
  <input name="field.pets.MQ__.query.string">
  <input type="submit" name="field.pets.MQ__.query" value="Search">
        </div> <!-- queryinput -->
        <div class="queryresults">
  <select name="field.pets.MQ__.selection:list" multiple>
  <option value="Ym9vdHM=">boots</option>
  <option value="dGFiYnk=">tabby</option>
  <option value="dGlnZXI=">tiger</option>
  <option value="dG9t">tom</option>
  </select>
  <input type="submit" name="field.pets.MQ__.apply" value="Apply">
        </div> <!-- queryresults -->
      </div> <!-- query -->
    </div> <!-- queries -->
  </div> <!-- value -->

from which we can select some values:

  >>> request.form['field.pets.displayed'] = u'y'
  >>> del request.form['field.pets.MQ__.query.string']
  >>> del request.form['field.pets.MQ__.query']
  >>> request.form['field.pets.MQ__.selection'] = [
  ...     u'dGFiYnk=', u'dGlnZXI=', u'dG9t']
  >>> request.form['field.pets.MQ__.apply'] = u'Apply'

Which then leads to the selections appearing as widget selections:

  >>> print widget()
  <div class="value">
    <input type="checkbox" name="field.pets.checked:list" value="dGFiYnk=">
    tabby
    <input type="hidden" name="field.pets:list" value="dGFiYnk=">
    <br>
    <input type="checkbox" name="field.pets.checked:list" value="dGlnZXI=">
    tiger
    <input type="hidden" name="field.pets:list" value="dGlnZXI=">
    <br>
    <input type="checkbox" name="field.pets.checked:list" value="dG9t">
    tom
    <input type="hidden" name="field.pets:list" value="dG9t">
    <br>
    <input type="submit" name="field.pets.remove" value="Remove">
    <br>
    <input type="hidden" name="field.pets.displayed" value="y">
    <div class="queries">
      <div class="query">
        <div class="queryinput">
  <input name="field.pets.MA__.query.string">
  <input type="submit" name="field.pets.MA__.query" value="Search">
        </div> <!-- queryinput -->
      </div> <!-- query -->
      <div class="query">
        <div class="queryinput">
  <input name="field.pets.MQ__.query.string">
  <input type="submit" name="field.pets.MQ__.query" value="Search">
        </div> <!-- queryinput -->
      </div> <!-- query -->
    </div> <!-- queries -->
  </div> <!-- value -->

We can get the selected values:

  >>> widget.getInputValue()
  ['tabby', 'tiger', 'tom']

We now see the values we selected.  We also have chackboxes and
buttons that allow is to remove selections:

  >>> request.form['field.pets.displayed'] = u'y'
  >>> request.form['field.pets'] = [u'dGFiYnk=', u'dGlnZXI=', u'dG9t']
  >>> del request.form['field.pets.MQ__.selection']
  >>> del request.form['field.pets.MQ__.apply']
  >>> request.form['field.pets.checked'] = [u'dGFiYnk=', u'dG9t']
  >>> request.form['field.pets.remove'] = u'Remove'

  >>> print widget()
  <div class="value">
    <input type="checkbox" name="field.pets.checked:list" value="dGlnZXI=">
    tiger
    <input type="hidden" name="field.pets:list" value="dGlnZXI=">
    <br>
    <input type="submit" name="field.pets.remove" value="Remove">
    <br>
    <input type="hidden" name="field.pets.displayed" value="y">
    <div class="queries">
      <div class="query">
        <div class="queryinput">
  <input name="field.pets.MA__.query.string">
  <input type="submit" name="field.pets.MA__.query" value="Search">
        </div> <!-- queryinput -->
      </div> <!-- query -->
      <div class="query">
        <div class="queryinput">
  <input name="field.pets.MQ__.query.string">
  <input type="submit" name="field.pets.MQ__.query" value="Search">
        </div> <!-- queryinput -->
      </div> <!-- query -->
    </div> <!-- queries -->
  </div> <!-- value -->
