[Checkins] SVN: z3c.formwidget.query/trunk/ Implemented multiple selection.

Malthe Borch mborch at gmail.com
Thu May 15 20:43:01 EDT 2008


Log message for revision 86778:
  Implemented multiple selection.

Changed:
  U   z3c.formwidget.query/trunk/README.txt
  U   z3c.formwidget.query/trunk/setup.py
  U   z3c.formwidget.query/trunk/src/z3c/formwidget/query/README.txt
  U   z3c.formwidget.query/trunk/src/z3c/formwidget/query/interfaces.py
  U   z3c.formwidget.query/trunk/src/z3c/formwidget/query/widget.py

-=-
Modified: z3c.formwidget.query/trunk/README.txt
===================================================================
--- z3c.formwidget.query/trunk/README.txt	2008-05-15 15:32:15 UTC (rev 86777)
+++ z3c.formwidget.query/trunk/README.txt	2008-05-16 00:42:59 UTC (rev 86778)
@@ -4,11 +4,16 @@
 This package implements a widget that lets users enter a query and
 select from the results.
 
-The widget currently works with ``zope.schema.Choice``-fields
-supplying a query source [1].
+The widget works with ``zope.schema.Choice``-fields supplying a query
+source [1], optionally in conjunction with a collection field which
+then allows multiple selections.
 
-Results need to implement or adapt to ``ITitledTokenizedTerm``.
+The easiest way to use the widget is to provide one of the following
+as ``widgetFactory``:
 
+* z3c.formwidget.query.widget.QuerySourceFieldRadioWidget
+* z3c.formwidget.query.widget.QuerySourceFieldCheckboxWidget
+
 ------
 
 [1] The source needs to implement ``IQuerySource`` as defined in this

Modified: z3c.formwidget.query/trunk/setup.py
===================================================================
--- z3c.formwidget.query/trunk/setup.py	2008-05-15 15:32:15 UTC (rev 86777)
+++ z3c.formwidget.query/trunk/setup.py	2008-05-16 00:42:59 UTC (rev 86778)
@@ -2,7 +2,7 @@
 from setuptools import setup, find_packages
 
 setup(name='z3c.formwidget.query',
-      version='0.1',
+      version='0.2',
       author = "Zope Community",
       author_email = "zope3-dev at zope.org",
       description = "A source query widget for z3c.form.",
@@ -26,7 +26,7 @@
                           'zope.component',
                           'zope.i18nmessageid',
                           ],
-      classifiers = ['Development Status :: 4 - Alpha',
+      classifiers = ['Development Status :: 4 - Beta',
                      'Environment :: Web Environment',
                      'Framework :: Zope3',
                      'Intended Audience :: Developers',

Modified: z3c.formwidget.query/trunk/src/z3c/formwidget/query/README.txt
===================================================================
--- z3c.formwidget.query/trunk/src/z3c/formwidget/query/README.txt	2008-05-15 15:32:15 UTC (rev 86777)
+++ z3c.formwidget.query/trunk/src/z3c/formwidget/query/README.txt	2008-05-16 00:42:59 UTC (rev 86778)
@@ -24,6 +24,8 @@
   >>> from z3c.formwidget.query.interfaces import IQuerySource
   >>> from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
 
+To make things simple, we'll just use unicode strings as values.
+
   >>> class ItalianCities(object):
   ...     interface.implements(IQuerySource)
   ...
@@ -38,6 +40,9 @@
   ...     __contains__ = vocabulary.__contains__
   ...     __iter__ = vocabulary.__iter__
   ...
+  ...     def getTermByValue(self, value):
+  ...         return self.vocabulary.by_value[value]
+  ...
   ...     def search(self, query_string):
   ...         return [v for v in self if query_string.lower() in v.value.lower()]
 
@@ -65,20 +70,29 @@
   ...     description=u'Select a city.',
   ...     source=ItalianCitiesSourceBinder())
 
-Widget
-------
+Widgets
+-------
 
-  >>> class Content(object):
+There are two widgets available; one that corresponds to a single
+selection or a multi-selection of items from the source.
+
+To enable multiple selections, wrap the ``zope.schema.Choice`` in a field
+derived from ``zope.schema.Collection``.
+
+Let's begin with a single selection.
+
+  >>> class Location(object):
   ...     city = None
   
-  >>> content = Content()
-  >>> field = city.bind(content)
+  >>> location = Location()
+  >>> field = city.bind(location)
 
-  >>> from z3c.formwidget.query.widget import QuerySourceFieldWidget
+  >>> from z3c.formwidget.query.widget import QuerySourceFieldRadioWidget
 
   >>> def setupWidget(field, context, request):
-  ...     widget = QuerySourceFieldWidget(field, request)
+  ...     widget = QuerySourceFieldRadioWidget(field, request)
   ...     widget.name = field.__name__
+  ...     widget.context = context
   ...     return widget
 
   >>> from z3c.form.testing import TestRequest
@@ -86,17 +100,27 @@
 
 An empty query is not carried out.
   
-  >>> widget = setupWidget(field, content, request)
-  
+  >>> widget = setupWidget(field, location, request)
   >>> 'type="radio"' in widget()
   False
 
-Let's make a query for "bologna".
+Let's choose a city:
 
+  >>> location.city = u"Palermo"
+
+  >>> widget = setupWidget(field, location, request)
+
+We now expect a radio button to be present.
+  
+  >>> 'type="radio"' in widget()
+  True
+  
+We can put a query string in the request, and have our source queried.
+
   >>> request = TestRequest(form={
   ...     'city.widgets.query': u'bologna'})
   
-  >>> widget = setupWidget(field, content, request)
+  >>> widget = setupWidget(field, location, request)
 
 Verify results:
   
@@ -106,19 +130,101 @@
   >>> 'Sorrento' in widget()
   False
 
-We'll select 'Bologna' from the list.
+Selecting 'Bologna' from the list should check the corresponding box.
 
   >>> request = TestRequest(form={
   ...     'city.widgets.query': u'bologna',
   ...     'city': ('bologna',)})
 
-  >>> widget = setupWidget(field, content, request)
+  >>> widget = setupWidget(field, location, request)
 
+  >>> 'checked="checked"' in widget()
+  True
+
+Now we want to try out selection of multiple items.
+
+  >>> cities = zope.schema.Set(
+  ...     __name__='cities',
+  ...     title=u'Cities',
+  ...     description=u'Select one or more cities.',
+  ...     value_type=zope.schema.Choice(
+  ...         source=ItalianCitiesSourceBinder()
+  ...     ))
+
+  >>> class Route(object):
+  ...     cities = ()
+  
+  >>> route = Route()
+  >>> field = cities.bind(route)
+
+  >>> from z3c.formwidget.query.widget import QuerySourceFieldCheckboxWidget
+
+  >>> def setupWidget(field, context, request):
+  ...     widget = QuerySourceFieldCheckboxWidget(field, request)
+  ...     widget.name = field.__name__
+  ...     widget.context = context
+  ...     return widget
+
+  >>> request = TestRequest()
+  
+An empty query is not carried out.
+  
+  >>> widget = setupWidget(field, route, request)
+  >>> 'type="checkbox"' in widget()
+  False
+
+Let's set a city on the route:
+
+  >>> route.cities = (u"Palermo",)
+
+  >>> widget = setupWidget(field, route, request)
+  >>> 'type="checkbox"' in widget()
+  True
+  
+Let's make a query for "bologna".
+
+  >>> request = TestRequest(form={
+  ...     'cities.widgets.query': u'bologna'})
+  
+  >>> widget = setupWidget(field, route, request)
+
+Verify results:
+
+  >>> 'Bologna' in widget()
+  True
+
+  >>> 'Sorrento' in widget()
+  False
+
+We'll select 'Bologna' from the list.
+
+  >>> request = TestRequest(form={
+  ...     'cities.widgets.query': u'bologna',
+  ...     'cities': ('bologna',)})
+
+  >>> widget = setupWidget(field, route, request)
+
 Verify that Bologna has been selected.
 
   >>> 'checked="checked"' in widget()
   True
 
+Let's try and simulate removing the item we've set on the
+context. We'll submit an empty tuple.
+
+  >>> request = TestRequest(form={
+  ...     'cities': ()})
+
+  >>> widget = setupWidget(field, route, request)
+
+We expect an unchecked box.
+
+  >>> 'type="checkbox"' in widget()
+  True
+  
+  >>> 'checked="checked"' in widget()
+  False
+
 Todo
 ----
 

Modified: z3c.formwidget.query/trunk/src/z3c/formwidget/query/interfaces.py
===================================================================
--- z3c.formwidget.query/trunk/src/z3c/formwidget/query/interfaces.py	2008-05-15 15:32:15 UTC (rev 86777)
+++ z3c.formwidget.query/trunk/src/z3c/formwidget/query/interfaces.py	2008-05-16 00:42:59 UTC (rev 86778)
@@ -1,5 +1,5 @@
-from zope.schema.interfaces import ISource
+from zope.schema.interfaces import ISource, IVocabularyTokenized
 
-class IQuerySource(ISource):
+class IQuerySource(ISource, IVocabularyTokenized):
     def search(query_string):
         """Return values that match query."""

Modified: z3c.formwidget.query/trunk/src/z3c/formwidget/query/widget.py
===================================================================
--- z3c.formwidget.query/trunk/src/z3c/formwidget/query/widget.py	2008-05-15 15:32:15 UTC (rev 86777)
+++ z3c.formwidget.query/trunk/src/z3c/formwidget/query/widget.py	2008-05-16 00:42:59 UTC (rev 86778)
@@ -14,30 +14,22 @@
 import z3c.form.field
 import z3c.form.widget
 import z3c.form.browser.radio
+import z3c.form.browser.checkbox
 
 from z3c.formwidget.query import MessageFactory as _
 
 class QueryTerms(SimpleVocabulary):
     zope.interface.implements(ITerms)
     
-    def __init__(self, values):
-        terms = [ITitledTokenizedTerm.providedBy(value) and value or \
-                 ITitledTokenizedTerm(value) for value in values]
-
+    def __init__(self, terms):
         super(QueryTerms, self).__init__(terms)
 
     def getTerm(self, value):
-        try:
-            return self.by_value[value]
-        except KeyError:
-            raise LookupError(value)
-
+        return self.by_value[value]
+        
     def getValue(self, token):
-        try:
-            return self.by_token[token]
-        except KeyError:
-            raise LookupError(token)
-
+        return self.by_token[token].value
+        
 class QuerySubForm(z3c.form.form.Form):
     zope.interface.implements(z3c.form.interfaces.ISubForm)
 
@@ -61,10 +53,19 @@
 class QueryContext(object):
     query = None
 
-class QuerySourceWidget(z3c.form.browser.radio.RadioWidget):
+class QuerySourceRadioWidget(z3c.form.browser.radio.RadioWidget):
+    """Query source widget that allows single selection."""
+    
     _queryform = None
     _resultsform = None
 
+    def isChecked(self, term):
+        return term.value in self.selection or term.token in self.value
+
+    @property
+    def source(self):
+        return self.field.source
+
     def update(self):
         # setup query form
         prefix = self.name
@@ -78,7 +79,7 @@
 
         # query source
         query = data['query']
-        source = self.field.source
+        source = self.source
 
         if IContextSourceBinder.providedBy(source):
             source = source(self.context)
@@ -86,28 +87,73 @@
         assert ISource.providedBy(source)
 
         if query is not None:
-            results = source.search(query)
+            terms = set(source.search(query))
         else:
-            results = ()
-            
+            terms = set()
+
+        # add current selection
+        selection = zope.component.getMultiAdapter(
+            (self.context, self.field), z3c.form.interfaces.IDataManager).get()
+
+        if not isinstance(selection, (tuple, set, list)):
+            selection = (selection,)
+
+        values = [term.value for term in terms]
+
+        map(terms.add,
+            map(source.getTermByValue,
+                filter(lambda value: value and value not in values, selection)))
+        
+        self.selection = selection
+
         # set terms
-        self.terms = QueryTerms(results)
+        self.terms = QueryTerms(terms)
 
-                # update widget
-        super(QuerySourceWidget, self).update()
+        # filter on extracted data
+        value = self.extract()
+        if value is not z3c.form.interfaces.NOVALUE:
+            self.selection = map(self.terms.getValue, value)
+
+        # update widget
+        self.updateQueryWidget()
+
+    def updateQueryWidget(self):
+        z3c.form.browser.radio.RadioWidget.update(self)
+
+    def renderQueryWidget(self):
+        return z3c.form.browser.radio.RadioWidget.render(self)
         
     def render(self):
         subform = self.subform
         if self.terms:
-            return "\n".join((subform.render(), super(QuerySourceWidget, self).render()))
+            return "\n".join((subform.render(), self.renderQueryWidget()))
 
         return subform.render()
 
     def __call__(self):
         self.update()
         return self.render()
+
+class QuerySourceCheckboxWidget(
+    QuerySourceRadioWidget, z3c.form.browser.checkbox.CheckBoxWidget):
+    """Query source widget that allows multiple selections."""
     
- at zope.component.adapter(zope.schema.interfaces.IChoice, z3c.form.interfaces.IFormLayer)
+    zope.interface.implementsOnly(z3c.form.interfaces.ICheckBoxWidget)
+
+    @property
+    def source(self):
+        return self.field.value_type.source
+
+    def updateQueryWidget(self):
+        z3c.form.browser.checkbox.CheckBoxWidget.update(self)
+
+    def renderQueryWidget(self):
+        return z3c.form.browser.checkbox.CheckBoxWidget.render(self)
+
 @zope.interface.implementer(z3c.form.interfaces.IFieldWidget)
-def QuerySourceFieldWidget(field, request):
-    return z3c.form.widget.FieldWidget(field, QuerySourceWidget(request))
+def QuerySourceFieldRadioWidget(field, request):
+    return z3c.form.widget.FieldWidget(field, QuerySourceRadioWidget(request))
+
+ at zope.interface.implementer(z3c.form.interfaces.IFieldWidget)
+def QuerySourceFieldCheckboxWidget(field, request):
+    return z3c.form.widget.FieldWidget(field, QuerySourceCheckboxWidget(request))



More information about the Checkins mailing list