[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