[Zope3-checkins] CVS: Zope3/src/zope/app/form/browser - __init__.py:1.3 configure.zcml:1.4 interfaces.py:1.3 itemswidgets.py:1.2 widget.py:1.8 enumerated.py:NONE vocabularywidget.py:NONE vocabularywidget.zcml:NONE

Stephan Richter srichter at cosmos.phy.tufts.edu
Sat Apr 24 19:20:13 EDT 2004


Update of /cvs-repository/Zope3/src/zope/app/form/browser
In directory cvs.zope.org:/tmp/cvs-serv30169/src/zope/app/form/browser

Modified Files:
	__init__.py configure.zcml interfaces.py itemswidgets.py 
	widget.py 
Removed Files:
	enumerated.py vocabularywidget.py vocabularywidget.zcml 
Log Message:


Removed Enumerated widgets. Refactored vocabulary widgets to be views of
fields and vocabularies. Updated tests.




=== Zope3/src/zope/app/form/browser/__init__.py 1.2 => 1.3 ===
--- Zope3/src/zope/app/form/browser/__init__.py:1.2	Wed Mar 17 12:35:46 2004
+++ Zope3/src/zope/app/form/browser/__init__.py	Sat Apr 24 19:19:42 2004
@@ -24,15 +24,35 @@
 from zope.app.form.browser.textwidgets import IntWidget, FloatWidget
 from zope.app.form.browser.textwidgets import DatetimeWidget, DateWidget
 
-from zope.app.form.browser.enumerated import EnumeratedTextWidget
-from zope.app.form.browser.enumerated import EnumeratedIntWidget
-from zope.app.form.browser.enumerated import EnumeratedFloatWidget
-from zope.app.form.browser.enumerated import EnumeratedDatetimeWidget
-from zope.app.form.browser.enumerated import EnumeratedDateWidget
+# Widgets for boolean fields
+from zope.app.form.browser.boolwidgets import CheckBoxWidget
+from zope.app.form.browser.boolwidgets import BooleanRadioWidget
+from zope.app.form.browser.boolwidgets import BooleanSelectWidget
+from zope.app.form.browser.boolwidgets import BooleanDropdownWidget
 
-from zope.app.form.browser.itemswidgets import CheckBoxWidget
-from zope.app.form.browser.itemswidgets import ListWidget, RadioWidget
-from zope.app.form.browser.itemswidgets import MultiListWidget, MultiCheckBoxWidget
+# Choice and Sequence Display Widgets
+from zope.app.form.browser.itemswidgets import ItemDisplayWidget
+from zope.app.form.browser.itemswidgets import ItemsMultiDisplayWidget
+from zope.app.form.browser.itemswidgets import SetDisplayWidget
+from zope.app.form.browser.itemswidgets import ListDisplayWidget
+
+# Widgets for fields with vocabularies.
+# Note that these are only dispatchers for the widgets below.
+from zope.app.form.browser.itemswidgets import ChoiceDisplayWidget
+from zope.app.form.browser.itemswidgets import ChoiceSequenceDisplayWidget
+from zope.app.form.browser.itemswidgets import ChoiceEditWidget
+from zope.app.form.browser.itemswidgets import ChoiceSequenceEditWidget
+
+# Widgets that let you choose a single item from a list
+# These widgets are multi-views on (field, vocabulary)
+from zope.app.form.browser.itemswidgets import SelectWidget
+from zope.app.form.browser.itemswidgets import DropdownWidget
+from zope.app.form.browser.itemswidgets import RadioWidget
+
+# Widgets that let you choose several items from a list
+# These widgets are multi-views on (field, vocabulary)
+from zope.app.form.browser.itemswidgets import MultiSelectWidget
+from zope.app.form.browser.itemswidgets import MultiCheckBoxWidget
 
 from zope.app.form.browser.sequencewidget import SequenceWidget
 from zope.app.form.browser.sequencewidget import TupleSequenceWidget


=== Zope3/src/zope/app/form/browser/configure.zcml 1.3 => 1.4 ===
--- Zope3/src/zope/app/form/browser/configure.zcml:1.3	Sun Apr 11 08:31:48 2004
+++ Zope3/src/zope/app/form/browser/configure.zcml	Sat Apr 24 19:19:42 2004
@@ -1,6 +1,6 @@
 <configure xmlns="http://namespaces.zope.org/zope">
 
-  <!-- Views for -->
+  <!-- Views for Widget Errors -->
 
   <view
       type="zope.publisher.interfaces.browser.IBrowserRequest"     
@@ -134,143 +134,97 @@
       permission="zope.Public"
       />
 
-  <!-- Widgets for enumerated field flavours -->
 
-  <view
-      type="zope.publisher.interfaces.browser.IBrowserRequest"
-      for="zope.schema.interfaces.IEnumeratedTextLine"
-      provides="zope.app.form.interfaces.IInputWidget"
-      factory=".enumerated.EnumeratedTextWidget"
-      permission="zope.Public"
-      />
+  <!-- Items-related widgets; they are proxies for the multiviews below. -->
 
   <view
       type="zope.publisher.interfaces.browser.IBrowserRequest"
-      for="zope.schema.interfaces.IEnumeratedInt"
-      provides="zope.app.form.interfaces.IInputWidget"
-      factory=".enumerated.EnumeratedIntWidget"
+      for="zope.schema.interfaces.IChoice"
+      provides="zope.app.form.interfaces.IDisplayWidget"
+      factory=".ChoiceDisplayWidget"
       permission="zope.Public"
       />
 
   <view
       type="zope.publisher.interfaces.browser.IBrowserRequest"
-      for="zope.schema.interfaces.IEnumeratedFloat"
+      for="zope.schema.interfaces.IChoice"
       provides="zope.app.form.interfaces.IInputWidget"
-      factory=".enumerated.EnumeratedFloatWidget"
+      factory=".ChoiceEditWidget"
       permission="zope.Public"
       />
 
   <view
       type="zope.publisher.interfaces.browser.IBrowserRequest"
-      for="zope.schema.interfaces.IEnumeratedDatetime"
-      provides="zope.app.form.interfaces.IInputWidget"
-      factory=".enumerated.EnumeratedDatetimeWidget"
+      for="zope.schema.interfaces.IChoiceSequence"
+      provides="zope.app.form.interfaces.IDisplayWidget"
+      factory=".ChoiceSequenceDisplayWidget"
       permission="zope.Public"
       />
 
   <view
       type="zope.publisher.interfaces.browser.IBrowserRequest"
-      for="zope.schema.interfaces.IEnumeratedDate"
+      for="zope.schema.interfaces.IChoiceSequence"
       provides="zope.app.form.interfaces.IInputWidget"
-      factory=".enumerated.EnumeratedDateWidget"
+      factory=".ChoiceSequenceEditWidget"
       permission="zope.Public"
       />
 
-  <!-- Vocabulary fields share special widget factories that redirect
-       to the vocabularies they reference. -->
-
-  <!-- Single selection -->
+  <!-- Default Multi-Views for fields and vocabularies -->
 
   <view
       type="zope.publisher.interfaces.browser.IBrowserRequest"
-      for="zope.schema.interfaces.IVocabularyField"
+      for="zope.schema.interfaces.IChoice
+           zope.schema.interfaces.IIterableVocabulary"
       provides="zope.app.form.interfaces.IDisplayWidget"
-      factory=".vocabularywidget.VocabularyFieldDisplayWidget"
+      factory=".ItemDisplayWidget"
       permission="zope.Public"
       />
 
   <view
       type="zope.publisher.interfaces.browser.IBrowserRequest"
-      for="zope.schema.interfaces.IVocabularyField"
+      for="zope.schema.interfaces.IChoice
+           zope.schema.interfaces.IIterableVocabulary"
       provides="zope.app.form.interfaces.IInputWidget"
-      factory=".vocabularywidget.VocabularyFieldEditWidget"
+      factory=".DropdownWidget"
       permission="zope.Public"
       />
 
-  <!-- Bags -->
-
   <view
       type="zope.publisher.interfaces.browser.IBrowserRequest"
-      for="zope.schema.interfaces.IVocabularyBagField"
-      provides="zope.app.form.interfaces.IDisplayWidget"
-      factory=".vocabularywidget.VocabularyBagFieldDisplayWidget"
-      permission="zope.Public"
-      />
-      
-  <view
-      type="zope.publisher.interfaces.browser.IBrowserRequest"
-      for="zope.schema.interfaces.IVocabularyBagField"
+      for="zope.schema.interfaces.IChoiceSequence
+           zope.schema.interfaces.IIterableVocabulary"
       provides="zope.app.form.interfaces.IInputWidget"
-      factory=".vocabularywidget.VocabularyBagFieldEditWidget"
+      factory=".MultiSelectWidget"
       permission="zope.Public"
       />
 
-  <!-- Lists -->
-
   <view
       type="zope.publisher.interfaces.browser.IBrowserRequest"
-      for="zope.schema.interfaces.IVocabularyListField"
+      for="zope.schema.interfaces.IChoiceSequence
+           zope.schema.interfaces.IIterableVocabulary"
       provides="zope.app.form.interfaces.IDisplayWidget"
-      factory=".vocabularywidget.VocabularyListFieldDisplayWidget"
-      permission="zope.Public"
-      />
-
-  <view
-      type="zope.publisher.interfaces.browser.IBrowserRequest"
-      for="zope.schema.interfaces.IVocabularyListField"
-      provides="zope.app.form.interfaces.IInputWidget"
-      factory=".vocabularywidget.VocabularyListFieldEditWidget"
+      factory=".SetDisplayWidget"
       permission="zope.Public"
       />
 
-  <!-- Sets -->
+  <!-- Register a couple default vocabulary query views. -->
 
   <view
       type="zope.publisher.interfaces.browser.IBrowserRequest"
-      for="zope.schema.interfaces.IVocabularySetField"
-      provides="zope.app.form.interfaces.IDisplayWidget"
-      factory=".vocabularywidget.VocabularySetFieldDisplayWidget"
+      for="zope.schema.interfaces.IVocabularyQuery
+           zope.schema.interfaces.IChoice"
+      provides="zope.app.form.browser.interfaces.IVocabularyQueryView"
+      factory=".vocabularyquery.IterableVocabularyQueryView"
       permission="zope.Public"
       />
-	  
-  <view
-      type="zope.publisher.interfaces.browser.IBrowserRequest"
-      for="zope.schema.interfaces.IVocabularySetField"
-      provides="zope.app.form.interfaces.IInputWidget"
-      factory=".vocabularywidget.VocabularySetFieldEditWidget"
-      permission="zope.Public"
-      />
-
-  <!-- Unique lists -->
 
   <view
       type="zope.publisher.interfaces.browser.IBrowserRequest"
-      for="zope.schema.interfaces.IVocabularyUniqueListField"
-      provides="zope.app.form.interfaces.IDisplayWidget"
-      factory=".vocabularywidget.VocabularyUniqueListFieldDisplayWidget"
-      permission="zope.Public"
-      />
-      
-  <view
-      type="zope.publisher.interfaces.browser.IBrowserRequest"
-      for="zope.schema.interfaces.IVocabularyUniqueListField"
-      provides="zope.app.form.interfaces.IInputWidget"
-      factory=".vocabularywidget.VocabularyUniqueListFieldEditWidget"
+      for="zope.schema.interfaces.IVocabularyQuery
+           zope.schema.interfaces.IChoiceSequence"
+      provides="zope.app.form.browser.interfaces.IVocabularyQueryView"
+      factory=".vocabularyquery.IterableVocabularyQueryMultiView"
       permission="zope.Public"
       />
-	  
-  <!-- implementation support for vocabulary field widgets -->
-  
-  <include file="vocabularywidget.zcml" />  
 
 </configure>


=== Zope3/src/zope/app/form/browser/interfaces.py 1.2 => 1.3 ===
--- Zope3/src/zope/app/form/browser/interfaces.py:1.2	Sun Apr 11 08:31:48 2004
+++ Zope3/src/zope/app/form/browser/interfaces.py	Sat Apr 24 19:19:42 2004
@@ -145,6 +145,9 @@
     edit widgets to support query and result presentations.
     """
 
+    def __init__(query, field, request):
+        """This is a multiview, which is looked up for (query, field) pairs."""
+
     def setName(name):
         """Set the name used to compute the form field names.
 
@@ -155,6 +158,7 @@
         This method will be called after the IVocabularyQueryView has
         been created and before performAction() is called.
         """
+
 
     def setWidget(widget):
         """Set the widget using this query view.


=== Zope3/src/zope/app/form/browser/itemswidgets.py 1.1 => 1.2 ===
--- Zope3/src/zope/app/form/browser/itemswidgets.py:1.1	Wed Mar 17 12:35:02 2004
+++ Zope3/src/zope/app/form/browser/itemswidgets.py	Sat Apr 24 19:19:42 2004
@@ -18,234 +18,378 @@
 from zope.interface import implements
 from zope.i18n import translate
 from zope.proxy import removeAllProxies
+from zope.schema.interfaces import ValidationError, InvalidValue
+from zope.schema.interfaces import ConstraintNotSatisfied
 
-from zope.app.form.interfaces import IInputWidget
+from zope.app import zapi
 from zope.app.form.browser.widget import BrowserWidget, renderElement
-
-ListTypes = list, tuple
-
-class CheckBoxWidget(BrowserWidget):
-    """A checkbox widget used to display Bool fields.
-    
-    For more detailed documentation, including sample code, see
-    tests/test_checkboxwidget.py.
-    """
+from zope.app.form.browser.interfaces import IVocabularyQueryView
+from zope.app.form.interfaces import IInputWidget, IDisplayWidget
+from zope.app.form.interfaces import WidgetInputError
+from zope.app.i18n import ZopeMessageIDFactory as _
+
+
+# For fields with vocabularies, we really want to make the widget a view
+# of the field and vocabulary.
+
+def ChoiceDisplayWidget(field, request):
+    return zapi.getMultiView((field, field.vocabulary), request,
+                             IDisplayWidget)
+
+def ChoiceSequenceDisplayWidget(field, request):
+    return zapi.getMultiView((field, field.value_type.vocabulary), request,
+                             IDisplayWidget)
+
+def ChoiceEditWidget(field, request):
+    return zapi.getMultiView((field, field.vocabulary), request,
+                             IInputWidget)
+
+def ChoiceSequenceEditWidget(field, request):
+    return zapi.getMultiView((field, field.value_type.vocabulary), request,
+                             IInputWidget)
     
-    implements(IInputWidget)
     
-    type = 'checkbox'
-    default = 0
-    extra = ''
+class TranslationHook(object):
+    """A mixin class that provides the translation capabilities."""
 
-    def __call__(self):
-        data = self._showData()
-        if data:
-            kw = {'checked': None}
-        else:
-            kw = {}
-        return "%s %s" % (
-            renderElement(self.tag,
-                          type='hidden',
-                          name=self.name+".used",
-                          id=self.name+".used",
-                          value=""
-                          ),
-            renderElement(self.tag,
-                             type=self.type,
-                             name=self.name,
-                             id=self.name,
-                             cssClass=self.cssClass,
-                             extra=self.extra,
-                             **kw),
-            )
+    def translate(self, msgid):
+        return translate(self.context, msgid, context=self.request,
+                         default=msgid)
+
+def message(msgid, default):
+    """Add a default value to a i18n message id."""
+    msgid.default = default
+    return msgid
+
+
+class ItemsWidgetBase(TranslationHook, BrowserWidget):
+    """Convenience base class for widgets displaying items/choices."""
+
+    extra = ""
+
+    def __init__(self, field, vocabulary, request):
+        """Initialize the widget."""
+        # only allow this to happen for a bound field
+        assert field.context is not None
+        self.vocabulary = vocabulary
+        super(ItemsWidgetBase, self).__init__(field, request)
+        self.empty_marker_name = self.name + "-empty-marker"
+
+    def setPrefix(self, prefix):
+        """Set the prefixes for the field names of the form."""
+        super(ItemsWidgetBase, self).setPrefix(prefix)
+        # names for other information from the form
+        self.empty_marker_name = self.name + "-empty-marker"
 
-    def _convert(self, value):
-        return value == 'on'
+    def __call__(self):
+        """Render the widget to HTML."""
+        raise NotImplementedError(
+            "__call__() must be implemented by a subclass; use _showData()")
+
+    def textForValue(self, term):
+        """Extract a string from the term.
+
+        The term must be a vocabulary tokenized term. 
+
+        This can be overridden to support more complex term objects. The token
+        is returned here since it's the only thing known to be a string, or
+        str()able."""
+        return term.token
+
+    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:
+                term = self.vocabulary.getTermByToken(token)
+            except LookupError, error:
+                raise InvalidValue, \
+                      "token %r not found in vocabulary" %token
+            else:
+                values.append(term.value)
+        return values
 
-    def _unconvert(self, value):
-        return value and "on" or ""
-        return value == 'on'
+    def getInputValue(self):
+        """Get the value that was inputed."""
+        input = self.request.form.get(self.name, self._data_marker)
+        field = self.context
+
+        # missing value is okay if field is not required
+        if input == self._data_marker and not field.required and \
+               self.empty_marker_name in self.request.form:
+            return field.missing_value
+
+        # Now let's see whether we have a valid input value
+        try:
+            value = self._convert(input)
+        except ValidationError, error:
+            self._error = WidgetInputError(
+                self.context.__name__,
+                self.context.title,
+                error)
+
+            raise self._error
+
+        return value
+
+    def _emptyMarker(self):
+        """Mark the form so that empty selections are also valid."""
+        return '<input name="%s" type="hidden" value="1" />' % (
+            self.empty_marker_name)
 
     def hasInput(self):
-        return self.name + ".used" in self.request.form or \
-            super(CheckBoxWidget, self).hasInput()
+        """Check whether we have any input."""
+        return (self.name in self.request.form or
+                self.empty_marker_name in self.request.form)
+
+    def setRenderedValue(self, value):
+        """Store the ready-for-HTML value."""
+        self._data = value
+
+    def _convert(self, input):
+        """See BrowserWidget"""
+        raise NotImplementedError(
+            "_convert(input) must be implemented by a subclass\n"
+            "It may be inherited from the mix-in classes SingleDataHelper\n"
+            "or MultiDataHelper")
+
+    def _showData(self):
+        if self._data is self._data_marker:
+            # The data has not been retrieved from the form, so let's do that
+            if self.hasInput():
+                try:
+                    value = self.getInputValue()
+                except WidgetInputError:
+                    value = self.request.form.get(self.name, self._missing)
+            else:
+                value = self._getDefault()
+        else:
+            value = self._data
+        return value
 
-    def getInputValue(self):
-        # When it's checked, its value is 'on'.
-        # When a checkbox is unchecked, it does not appear in the form data.
-        value = self.request.form.get(self.name, 'off')
-        return value == 'on'
+    def _unconvert(self, value):
+        """Disregard this method as suggested by BrowserWidget."""
+        raise NotImplementedError(
+            "vocabulary-based widgets don't use the _unconvert() method")
 
-class ItemsWidget(BrowserWidget):
-    """A widget that has a number of items in it."""
-    implements(IInputWidget)
 
-class SingleItemsWidget(ItemsWidget):
-    """A widget with a number of items that has only a single
-    selectable item."""
-    
-    default = ""
-    firstItem = False
+class SingleDataHelper(object):
+    """Mix-in helper class for getting the term from the HTML form.
 
-    def textForValue(self, value):
-        '''Returns the text for the given value.
+    This is used when we expect a single input, i.e. the Choice field. 
+    """
 
-        Override this in subclasses.'''
-        # The text could be a MessageID, in which case we should try to
-        # translate it.
-        return translate(self.context, value, context=self.request,
-                         default=value)
+    def _convert(self, input):
+        """See BrowserWidget"""
+        if input:
+            return self.convertTokensToValues([input])[0]
+        else:
+            return None
 
-    def renderItems(self, value):
-        name = self.name
-        # get items
-        items = self.context.allowed_values
 
-        # check if we want to select first item
-        if (not value and getattr(self.context, 'firstItem', False)
-            and len(items) > 0):
-            value = items[0]
+class MultiDataHelper(object):
+    """Mix-in helper class for getting the term from the HTML form.
 
-        cssClass = self.cssClass
+    This is used when we expect a multiple inputs, i.e. Sequence fields with a
+    Choice field as value_type.
+    """
 
-        # FIXME: what if we run into multiple items with same value?
-        rendered_items = []
-        count = 0
-        for item_value in items:
-            item_text = self.textForValue(item_value)
+    def _convert(self, input):
+        """See BrowserWidget"""
+        if input is self._data_marker:
+            return []
+        if not isinstance(input, list):
+            input = [input]
+        return self.convertTokensToValues(input)
 
-            if item_value == value:
-                rendered_item = self.renderSelectedItem(count,
-                                                        item_text,
-                                                        item_value,
-                                                        name,
-                                                        cssClass)
-            else:
-                rendered_item = self.renderItem(count,
-                                                item_text,
-                                                item_value,
-                                                name,
-                                                cssClass)
+    def _getDefault(self):
+        # Return the default value for this widget;
+        # may be overridden by subclasses.
+        val = self.context.default
+        if val is None:
+            val = []
+        return val
 
-            rendered_items.append(rendered_item)
-            count += 1
 
-        return rendered_items
+## Display-Widgets for Items-related fields.
 
-class ListWidget(SingleItemsWidget):
-    """List widget."""
-    
-    size = 5
+class ItemDisplayWidget(SingleDataHelper, ItemsWidgetBase):
+    """Simple single-selection display that can be used in many cases."""
+
+    _messageNoValue = message(_("item-missing-single-value-for-display"), "")
 
     def __call__(self):
-        renderedItems = self.renderItems(self._showData())
-        return renderElement('select',
-                              name=self.name,
-                              id=self.name,
-                              cssClass=self.cssClass,
-                              size=self.size,
-                              contents="\n".join(renderedItems),
-                              extra=self.extra)
+        """See IBrowserWidget."""
+        value = self._showData()
+        if value is None:
+            return self.translate(self._messageNoValue)
+        else:
+            term = self.vocabulary.getTerm(value)
+            return self.textForValue(term)
 
-    def renderItem(self, index, text, value, name, cssClass):
-        return renderElement('option', contents=text, value=value,
-                              cssClass=cssClass)
 
-    def renderSelectedItem(self, index, text, value, name, cssClass):
-        return renderElement('option', contents=text, value=value,
-                              cssClass=cssClass, selected=None)
+class ItemsMultiDisplayWidget(MultiDataHelper, ItemsWidgetBase):
+    """Displays a sequence of items."""
 
+    _messageNoValue = message(
+        _("vocabulary-missing-multiple-value-for-display"), "")
 
-class RadioWidget(SingleItemsWidget):
-    """Radio buttons widget."""
-    
-    orientation = "vertical"
+    itemTag = 'li'
+    tag = 'ol'
 
     def __call__(self):
-        rendered_items = self.renderItems(self._showData())
-        orientation = self.orientation
-        if orientation == 'horizontal':
-            return "&nbsp;&nbsp;".join(rendered_items)
+        """See IBrowserWidget."""
+        value = self._showData()
+        if value:
+            rendered_items = self.renderItems(value)
+            return renderElement(self.tag,
+                                 type=self.type,
+                                 name=self.name,
+                                 id=self.name,
+                                 cssClass=self.cssClass,
+                                 contents="\n".join(rendered_items),
+                                 extra=self.extra)
         else:
-            return '<br />'.join(rendered_items)
+            return self.translate(self._messageNoValue)
 
-    def _renderItem(self, index, text, value, name, cssClass, checked):
-        id = '%s.%s' % (name, index)
-        if checked:
-            element = renderElement('input',
-                                    type="radio",
-                                    cssClass=cssClass,
-                                    name=name,
-                                    id=id,
-                                    value=value,
-                                    checked=None)
-        else:
-            element = renderElement('input',
-                                    type="radio",
-                                    cssClass=cssClass,
-                                    name=name,
-                                    id=id,
-                                    value=value)
+    def renderItems(self, value):
+        """Render items of sequence."""
+        items = []
+        cssClass = self.cssClass or ''
+        if cssClass:
+            cssClass += "-item"
+        tag = self.itemTag
+        for item in value:
+            term = self.vocabulary.getTerm(item)
+            items.append(renderElement(tag,
+                                       cssClass=cssClass,
+                                       contents=self.textForValue(term)))
+        return items
 
-        return '%s<label for="%s">%s</label>' % (element, id, text)
+class ListDisplayWidget(ItemsMultiDisplayWidget):
+    """Display widget for ordered multi-selection fields.
 
-    def renderItem(self, index, text, value, name, cssClass):
-        return self._renderItem(index, text, value, name, cssClass, False)
+    This can be used for both Sequence, List, and Tuple fields.
+    """
+    tag = 'ol'
 
-    def renderSelectedItem(self, index, text, value, name, cssClass):
-        return self._renderItem(index, text, value, name, cssClass, True)
 
-    def label(self):
-        return translate(self.context, self.title, context=self.request,
-                         default=self.title)
-
-    def row(self):
-        return ('<div class="%s"><label for="%s">%s</label></div>'
-                '<div class="field" id="%s">%s</div>' % (
-                self.labelClass(), self.name, self.label(), self.name, self()))
-                
-
-class MultiItemsWidget(ItemsWidget):
-    """A widget with a number of items that has multiple selectable items."""
-        
-    default = []
+class SetDisplayWidget(ItemsMultiDisplayWidget):
+    """Display widget for unordered multi-selection fields.
 
-    def _convert(self, value):
-        if not value:
-            return []
-        if isinstance(value, ListTypes):
-            return value
-        return [value]
+    This can be used for both Set field.
+    """
+    tag = 'ul'
 
-    def renderItems(self, value):
-        # need to deal with single item selects
-        value = removeAllProxies(value)
 
-        if not isinstance(value, ListTypes):
-            value = [value]
-        name = self.name
-        items = self.context.allowed_values
+## Edit-Widgets for Items-related fields.
+
+class ItemsEditWidgetBase(SingleDataHelper, ItemsWidgetBase):
+    """Widget Base for rendering item-related fields.
+
+    These widgets work with Choice fields and Sequence fields that have Choice
+    as value_type.
+    """
+    implements(IInputWidget)
+    
+    size = 5
+    tag = 'select'
+    firstItem = False
+
+    def __init__(self, field, vocabulary, request):
+        """Initialize the widget."""
+        super(ItemsEditWidgetBase, self).__init__(field, vocabulary, request)
+
+        # Queries are used in items widgets to reduce the amount of choices,
+        # since some vocabularies could literally provide thousands of terms.
+        self.queryview = None
+        query = vocabulary.getQuery()
+        if query is not None:
+            view = zapi.queryMultiView((query, field), request,
+                                       IVocabularyQueryView)
+            if view is not None:
+                self.queryview = view
+                self.queryview.setWidget(self)
+                self.queryview.setName(self.name + "-query")
+
+
+    def setPrefix(self, prefix):
+        """Set the prefix of the input name.
+
+        Once we set the prefix of input field, we use the name of the input
+        field and the postfix '-query' for the associated query view.
+        """
+        super(ItemsEditWidgetBase, self).setPrefix(prefix)
+        if self.queryview is not None:
+            self.queryview.setName(self.name + "-query")
+
+
+    def __call__(self):
+        """See IBrowserWidget."""
+        value = self._showData()
+        contents = []
+        have_results = False
+
+        # If a query view was specified, provide the necessary UI
+        if self.queryview is not None:
+            html = self.queryview.renderResults(value)
+            if html:
+                contents.append(self._div('queryresults', html))
+                html = self.queryview.renderInput()
+                contents.append(self._div('queryinput', html))
+                have_results = True
+
+        contents.append(self._div('value', self.renderValue(value)))
+        if not self.context.required:
+            contents.append(self._emptyMarker())
+
+        if self.queryview is not None and not have_results:
+            html = self.queryview.renderInput()
+            if html:
+                contents.append(self._div('queryinput', html))
+
+        return self._div(self.cssClass, "\n".join(contents),
+                         id=self.name)
+
+
+    def _div(self, cssClass, contents, **kw):
+        """Render a simple div tag."""
+        if contents:
+            return renderElement('div',
+                                 cssClass=cssClass,
+                                 contents="\n%s\n" % contents,
+                                 **kw)
+        return ""
+
+
+    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 item in items:
-            try:
-                item_value, item_text = item
-            except ValueError:
-                item_value = item
-                item_text = item
+        for term in self.vocabulary:
+            item_text = self.textForValue(term)
 
-            if item_value in value:
+            if term.value in values:
                 rendered_item = self.renderSelectedItem(count,
                                                         item_text,
-                                                        item_value,
-                                                        name,
+                                                        term.token,
+                                                        self.name,
                                                         cssClass)
             else:
                 rendered_item = self.renderItem(count,
                                                 item_text,
-                                                item_value,
-                                                name,
+                                                term.token,
+                                                self.name,
                                                 cssClass)
 
             rendered_items.append(rendered_item)
@@ -253,57 +397,212 @@
 
         return rendered_items
 
+    def renderItem(self, index, text, value, name, cssClass):
+        """Render an item for a particular value."""
+        return renderElement('option',
+                             contents=text,
+                             value=value,
+                             cssClass=cssClass)
+
+    def renderSelectedItem(self, index, text, value, name, cssClass):
+        """Render an item for a particular value that is selected."""
+        return renderElement('option',
+                             contents=text,
+                             value=value,
+                             cssClass=cssClass,
+                             selected='selected')
+
+
+class SelectWidget(ItemsEditWidgetBase):
+    """Provide a selection list for the item."""
+
+    _messageNoValue = message(_("vocabulary-missing-single-value-for-edit"),
+                              "(no value)")
+
+    def renderValue(self, value):
+        rendered_items = self.renderItems(value)
+        contents = "\n%s\n" %"\n".join(rendered_items)
+        return renderElement('select',
+                             name=self.name,
+                             contents=contents,
+                             size=self.size,
+                             extra=self.extra)
 
-class MultiListWidget(MultiItemsWidget):
-    """List widget with multiple select."""
+    def renderItems(self, value):
+        # check if we want to select first item
+        if (value == self.context.missing_value
+            and getattr(self, 'firstItem', False)
+            and len(self.vocabulary) > 0):
+            # Grab the first item from the iterator:
+            values = [iter(self.vocabulary).next().value]
+        elif value != self.context.missing_value:
+            values = [value]
+        else:
+            values = []
+        items = self.renderItemsWithValues(values)
+        if not self.context.required:
+            option = ('<option value="">%s</option>'
+                      %(self.translate(self._messageNoValue)))
+            items.insert(0, option)
+        return items
 
-    size = 5
 
-    def __call__(self):
-        rendered_items = self.renderItems(self._showData())
-        return renderElement('select',
-                              name=self.name,
-                              id=self.name,
-                              multiple=None,
-                              cssClass=self.cssClass,
-                              size=self.size,
-                              contents="\n".join(rendered_items),
-                              extra=self.extra)
+class DropdownWidget(SelectWidget):
+    """Variation of the SelectWidget that uses a drop-down list."""
+    size = 1
+
+
+class RadioWidget(ItemsEditWidgetBase):
+    """Radio widget for single item choices.
+
+    This widget can be used when the number of selections is going
+    to be small.
+    """
+    orientation = "vertical"
+
+    _messageNoValue = message(_("vocabulary-missing-single-value-for-edit"),
+                              "(no value)")
+
+    _joinButtonToMessageTemplate = u"%s&nbsp;%s"
 
     def renderItem(self, index, text, value, name, cssClass):
-        return renderElement('option', contents=text, value=value)
+        """Render an item of the list."""
+        id = '%s.%s' % (name, index)
+        elem = renderElement('input',
+                             value=value,
+                             name=name,
+                             id=id,
+                             cssClass=cssClass,
+                             type='radio')
+        return self._joinButtonToMessageTemplate %(elem, text)
 
     def renderSelectedItem(self, index, text, value, name, cssClass):
-        return renderElement('option', contents=text, value=value,
-                              selected=None)
+        """Render a selected item of the list."""
+        id = '%s.%s' % (name, index)
+        elem = renderElement('input',
+                             value=value,
+                             name=name,
+                             id=id,
+                             cssClass=cssClass,
+                             checked=None,
+                             type='radio')
+        return self._joinButtonToMessageTemplate %(elem, text)
+    
+    def renderItems(self, value):
+        # check if we want to select first item, the previously selected item
+        # or the "no value" item.
+        no_value = None
+        if (value == self.context.missing_value
+            and getattr(self, 'firstItem', False)
+            and len(self.vocabulary) > 0):
+            if self.context.required:
+                # Grab the first item from the iterator:
+                values = [iter(self.vocabulary).next().value]
+            else:
+                # the "no value" option will be checked
+                no_value = 'checked'
+        elif value != self.context.missing_value:
+            values = [value]
+        else:
+            values = []
+
+        items = self.renderItemsWithValues(values)
+        if not self.context.required:
+            kwargs = {
+                'value': '',
+                'name': self.name,
+                'cssClass': self.cssClass,
+                'type': 'radio'}
+            if no_value:
+                kwargs['checked']=no_value
+            option = renderElement('input', **kwargs)
+            option = self._joinButtonToMessageTemplate %(
+                option, self.translate(self._messageNoValue))
+            items.insert(0, option)
+
+        return items
+
+    def renderValue(self, value):
+        rendered_items = self.renderItems(value)
+        if self.orientation == 'horizontal':
+            return "&nbsp;&nbsp;".join(rendered_items)
+        else:
+            return "<br />".join(rendered_items)
+
 
+class ItemsMultiEditWidgetBase(MultiDataHelper, ItemsEditWidgetBase):
+    """Items widget supporting multiple selections."""
 
-class MultiCheckBoxWidget(MultiItemsWidget):
-    """Multiple checkbox widget."""
+    _messageNoValue = message(
+        _("vocabulary-missing-multiple-value-for-edit"), "(no values)")
+
+    def renderItems(self, value):
+        if value == self.context.missing_value:
+            values = []
+        else:
+            values = list(value)
+        return self.renderItemsWithValues(values)
+
+    def renderValue(self, value):
+        # All we really add here is the ':list' in the name argument
+        # and mutliple=None to renderElement().
+        rendered_items = self.renderItems(value)
+        return renderElement(self.tag,
+                             name=self.name + ':list',
+                             multiple=None,
+                             size=self.size,
+                             contents="\n".join(rendered_items),
+                             extra=self.extra)
+    
+    def hidden(self):
+        items = []
+        for item in self._showData():
+            items.append(
+                renderElement(self.tag,
+                              type='hidden',
+                              name=self.name+':list',
+                              id=self.name,
+                              value=item,
+                              cssClass=self.cssClass,
+                              extra=self.extra))
+        return '\n'.join(items)
+         
+
+class MultiSelectWidget(ItemsMultiEditWidgetBase):
+    """Provide a selection list for the list to be selected."""
+
+
+class MultiCheckBoxWidget(ItemsMultiEditWidgetBase):
+    """Provide a list of checkboxes that provide the choice for the list."""
 
     orientation = "vertical"
 
-    def __call__(self):
-        rendered_items = self.renderItems(self._showData())
-        orientation = self.orientation
-        if orientation == 'horizontal':
+    _joinButtonToMessageTemplate = u"%s&nbsp;%s"
+
+    def renderValue(self, value):
+        rendered_items = self.renderItems(value)
+        if self.orientation == 'horizontal':
             return "&nbsp;&nbsp;".join(rendered_items)
         else:
             return "<br />".join(rendered_items)
 
     def renderItem(self, index, text, value, name, cssClass):
-        return renderElement('input',
-                              type="checkbox",
-                              cssClass=cssClass,
-                              name=name,
-                              id=name,
-                              value=value) + text
+        id = '%s.%s' % (name, index)
+        elem = renderElement('input',
+                             type="checkbox",
+                             cssClass=cssClass,
+                             name=name,
+                             id=id,
+                             value=value)
+        return self._joinButtonToMessageTemplate %(elem, text)
 
     def renderSelectedItem(self, index, text, value, name, cssClass):
-        return renderElement('input',
-                              type="checkbox",
-                              cssClass=cssClass,
-                              name=name,
-                              id=name,
-                              value=value,
-                              checked=None) + text
+        id = '%s.%s' % (name, index)
+        elem = renderElement('input',
+                             type="checkbox",
+                             cssClass=cssClass,
+                             name=name,
+                             id=id,
+                             value=value,
+                             checked=None)
+        return self._joinButtonToMessageTemplate %(elem, text)


=== Zope3/src/zope/app/form/browser/widget.py 1.7 => 1.8 ===
--- Zope3/src/zope/app/form/browser/widget.py:1.7	Sun Apr 11 08:31:48 2004
+++ Zope3/src/zope/app/form/browser/widget.py	Sat Apr 24 19:19:42 2004
@@ -155,6 +155,7 @@
         return self.name in self.request.form
 
     def hasValidInput(self):
+        """See IWidget."""
         try:
             self.getInputValue()
             return True
@@ -162,6 +163,7 @@
             return False
 
     def getInputValue(self):
+        """See IWidget."""
         self._error = None
         field = self.context
 

=== Removed File Zope3/src/zope/app/form/browser/enumerated.py ===

=== Removed File Zope3/src/zope/app/form/browser/vocabularywidget.py ===

=== Removed File Zope3/src/zope/app/form/browser/vocabularywidget.zcml ===




More information about the Zope3-Checkins mailing list