[Zope3-checkins] SVN: Zope3/trunk/src/zope/app/form/browser/ add a simple IIterableSource input widget

Fred L. Drake, Jr. fdrake at gmail.com
Thu Oct 27 20:39:16 EDT 2005


Log message for revision 39682:
  add a simple IIterableSource input widget

Changed:
  U   Zope3/trunk/src/zope/app/form/browser/configure.zcml
  U   Zope3/trunk/src/zope/app/form/browser/source.py
  U   Zope3/trunk/src/zope/app/form/browser/source.txt

-=-
Modified: Zope3/trunk/src/zope/app/form/browser/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/configure.zcml	2005-10-27 21:17:40 UTC (rev 39681)
+++ Zope3/trunk/src/zope/app/form/browser/configure.zcml	2005-10-28 00:39:15 UTC (rev 39682)
@@ -382,6 +382,17 @@
       permission="zope.Public"
       />
 
+  <!-- Default Multi-Views for fields and iterable sources -->
+
+  <view
+      type="zope.publisher.interfaces.browser.IBrowserRequest"
+      for="zope.schema.interfaces.IChoice
+           zope.schema.interfaces.IIterableSource"
+      provides="zope.app.form.interfaces.IInputWidget"
+      factory=".source.SourceDropdownWidget"
+      permission="zope.Public"
+      />
+
   <!-- These widgets are minimal and only support lists with unique members,
        without ordering capabilities -->
 

Modified: Zope3/trunk/src/zope/app/form/browser/source.py
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/source.py	2005-10-27 21:17:40 UTC (rev 39681)
+++ Zope3/trunk/src/zope/app/form/browser/source.py	2005-10-28 00:39:15 UTC (rev 39682)
@@ -492,3 +492,77 @@
 
     def hasInput(self):
         return self.name+'.displayed' in self.request.form
+
+
+# Input widgets for IIterableSource:
+
+class SourceSelectWidget(zope.app.form.browser.SelectWidget):
+    """Select-box widget for iterable vocabularies."""
+
+    # This is a very thin veneer over the vocabulary widget, but deals
+    # with the only differences in retrieving information about values
+    # that existing between sources and vocabularies.
+
+    def __init__(self, field, source, request):
+        super(SourceSelectWidget, self).__init__(field, source, request)
+        self.terms = zapi.getMultiAdapter(
+            (self.vocabulary, self.request),
+            zope.app.form.browser.interfaces.ITerms,
+            )
+
+    def convertTokensToValues(self, tokens):
+        """Convert term tokens to the terms themselves.
+
+        Tokens are used in the HTML form to represent terms. This method takes
+        the form tokens and converts them back to terms.
+        """
+        values = []
+        for token in tokens:
+            try:
+                value = self.terms.getValue(token)
+            except LookupError, error:
+                pass
+            else:
+                values.append(value)
+        return values
+
+    def renderItemsWithValues(self, values):
+        """Render the list of possible values, with those found in
+        `values` being marked as selected."""
+
+        cssClass = self.cssClass
+
+        # multiple items with the same value are not allowed from a
+        # vocabulary, so that need not be considered here
+        rendered_items = []
+        count = 0
+        for value in self.vocabulary:
+            term = self.terms.getTerm(value)
+            item_text = self.textForValue(term)
+
+            if value in values:
+                rendered_item = self.renderSelectedItem(count,
+                                                        item_text,
+                                                        term.token,
+                                                        self.name,
+                                                        cssClass)
+            else:
+                rendered_item = self.renderItem(count,
+                                                item_text,
+                                                term.token,
+                                                self.name,
+                                                cssClass)
+
+            rendered_items.append(rendered_item)
+            count += 1
+
+        return rendered_items
+
+    def textForValue(self, term):
+        return term.title
+
+
+class SourceDropdownWidget(SourceSelectWidget):
+    """Variant of the SourceSelectWidget that uses a drop-down list."""
+
+    size = 1

Modified: Zope3/trunk/src/zope/app/form/browser/source.txt
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/source.txt	2005-10-27 21:17:40 UTC (rev 39681)
+++ Zope3/trunk/src/zope/app/form/browser/source.txt	2005-10-28 00:39:15 UTC (rev 39682)
@@ -1,48 +1,35 @@
-=============================
-Source Widget Query Framework
-=============================
+==============
+Source Widgets
+==============
 
 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.
+choose and are used with Choice schema fields.  Source widgets
+currently fall into two categories:
 
-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.
+- widgets for iterable sources
 
-The default widgets for selecting values from sources use the
-following approach:
+- widgets for queryable sources
 
-- 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.
+Sources (combined with the available adapters) may support both
+approaches, but no widgets currently support both.
 
-- 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
+In both cases, 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 the
 `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:
+All of our examples will be using the component architecture::
 
   >>> import zope.interface
+  >>> import zope.component
   >>> import zope.schema
-  >>> class SourceList(list):
-  ...     zope.interface.implements(zope.schema.interfaces.ISource)
 
-We provide an `ITerms` view for the source:
+This `ITerms` implementation can be used for the sources involved in
+our tests::
 
+  >>> import zope.publisher.interfaces.browser
+  >>> import zope.app.form.browser.interfaces
+
   >>> class Term:
   ...
   ...     def __init__(self, **kw):
@@ -50,35 +37,180 @@
 
   >>> class ListTerms:
   ...
+  ...     zope.interface.implements(
+  ...         zope.app.form.browser.interfaces.ITerms)
+  ...
   ...     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()
+  ...         try:
+  ...             token = title.encode('base64').strip()
+  ...         except binascii.Error:
+  ...             raise LookupError(token)
   ...         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.
 
+All of the source widgets are in a single module::
+
+  >>> import zope.app.form.browser.source
+
+We'll also need request objects::
+
+  >>> from zope.publisher.browser import TestRequest
+
+
+Iterable Source Widgets
+-----------------------
+
+Iterable sources are expected to be simpler than queriable sources, so
+they represent a good place to start.  The most important aspect of
+iterable sources for widgets is that it's actually possible to
+enumerate all the values from the source.  This allows each possible
+value to be listed in a  <select> form field.
+
+Let's start with a simple example.  We have a very trivial source,
+which is basically a list:
+
+  >>> class SourceList(list):
+  ...     zope.interface.implements(zope.schema.interfaces.IIterableSource)
+
+We need to register our `ITerms` view::
+
+  >>> zope.component.provideAdapter(
+  ...     ListTerms,
+  ...     (SourceList, zope.publisher.interfaces.browser.IBrowserRequest))
+
+Let's define a choice field using our iterable source::
+
+  >>> dog = zope.schema.Choice(
+  ...    __name__ = 'dog',
+  ...    title=u"Dogs",
+  ...    source=SourceList(['spot', 'bowser', 'prince', 'duchess', 'lassie']),
+  ...    )
+
+  >>> dog = dog.bind(object())
+
+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:
+
+  >>> request = TestRequest()
+  >>> widget = zope.app.form.browser.source.SourceSelectWidget(
+  ...     dog, dog.source, request)
+
+  >>> print widget()
+  <div>
+  <div class="value">
+  <select id="field.dog" name="field.dog" size="5" >
+  <option value="c3BvdA==">spot</option>
+  <option value="Ym93c2Vy">bowser</option>
+  <option value="cHJpbmNl">prince</option>
+  <option value="ZHVjaGVzcw==">duchess</option>
+  <option value="bGFzc2ll">lassie</option>
+  </select>
+  </div>
+  <input name="field.dog-empty-marker" type="hidden" value="1" />
+  </div>
+
+If the request contains a value, it is marked as selected::
+
+  >>> request.form["field.dog-empty-marker"] = "1"
+  >>> request.form["field.dog"] = "Ym93c2Vy"
+
+  >>> print widget()
+  <div>
+  <div class="value">
+  <select id="field.dog" name="field.dog" size="5" >
+  <option value="c3BvdA==">spot</option>
+  <option selected="selected" value="Ym93c2Vy">bowser</option>
+  <option value="cHJpbmNl">prince</option>
+  <option value="ZHVjaGVzcw==">duchess</option>
+  <option value="bGFzc2ll">lassie</option>
+  </select>
+  </div>
+  <input name="field.dog-empty-marker" type="hidden" value="1" />
+  </div>
+
+If we set the displayed value for the widget, that value is marked as
+selected::
+
+  >>> widget.setRenderedValue("duchess")
+  >>> print widget()
+  <div>
+  <div class="value">
+  <select id="field.dog" name="field.dog" size="5" >
+  <option value="c3BvdA==">spot</option>
+  <option value="Ym93c2Vy">bowser</option>
+  <option value="cHJpbmNl">prince</option>
+  <option selected="selected" value="ZHVjaGVzcw==">duchess</option>
+  <option value="bGFzc2ll">lassie</option>
+  </select>
+  </div>
+  <input name="field.dog-empty-marker" type="hidden" value="1" />
+  </div>
+
+
+Source Widget Query Framework
+-----------------------------
+
+An important aspect of sources is that they may 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.
+
+Let's start with a simple example.  We have a very trivial source,
+which is basically a list:
+
+  >>> class SourceList(list):
+  ...     zope.interface.implements(zope.schema.interfaces.ISource)
+
+We need to register our `ITerms` view::
+
+  >>> zope.component.provideAdapter(
+  ...     ListTerms,
+  ...     (SourceList, zope.publisher.interfaces.browser.IBrowserRequest))
+
 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:
   ...
+  ...     zope.interface.implements(
+  ...         zope.app.form.browser.interfaces.ISourceQueryView)
+  ...     zope.component.adapts(
+  ...         SourceList,
+  ...         zope.publisher.interfaces.browser.IBrowserRequest,
+  ...         )
+  ...
   ...     def __init__(self, source, request):
   ...         self.source = source
   ...         self.request = request
@@ -100,9 +232,7 @@
   ...                         ]
   ...         return None
 
-  >>> ztapi.provideAdapter((SourceList, IBrowserRequest),
-  ...                      zope.app.form.browser.interfaces.ISourceQueryView,
-  ...                      ListQueryView)
+  >>> zope.component.provideAdapter(ListQueryView)
 
 Now, we can define a choice field:
 
@@ -112,12 +242,8 @@
   ...    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:
+As before, 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)
@@ -278,9 +404,9 @@
 
 We can reuse our terms view:
 
-  >>> ztapi.provideAdapter((MultiSource, IBrowserRequest),
-  ...                      zope.app.form.browser.interfaces.ITerms,
-  ...                      ListTerms)
+  >>> zope.component.provideAdapter(
+  ...     ListTerms,
+  ...     (MultiSource, zope.publisher.interfaces.browser.IBrowserRequest))
 
 Now, we'll create a pet choice that combines dogs and cats:
 



More information about the Zope3-Checkins mailing list