[Zope3-checkins] SVN: Zope3/trunk/src/zope/app/form/browser/ - clean up the sequence input widget

Fred L. Drake, Jr. fdrake at gmail.com
Fri Jun 3 11:22:18 EDT 2005


Log message for revision 30623:
  - clean up the sequence input widget
  - add a general sequence display widget
  

Changed:
  U   Zope3/trunk/src/zope/app/form/browser/__init__.py
  U   Zope3/trunk/src/zope/app/form/browser/configure.zcml
  U   Zope3/trunk/src/zope/app/form/browser/sequencewidget.py
  U   Zope3/trunk/src/zope/app/form/browser/tests/test_sequencewidget.py

-=-
Modified: Zope3/trunk/src/zope/app/form/browser/__init__.py
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/__init__.py	2005-06-03 15:11:49 UTC (rev 30622)
+++ Zope3/trunk/src/zope/app/form/browser/__init__.py	2005-06-03 15:22:18 UTC (rev 30623)
@@ -64,8 +64,11 @@
 from zope.app.form.browser.itemswidgets import MultiCheckBoxWidget
 from zope.app.form.browser.itemswidgets import OrderedMultiSelectWidget
 
+# Widgets that let you enter several items in a sequence
+# These widgets are multi-views on (sequence type, value type)
 from zope.app.form.browser.sequencewidget import SequenceWidget
 from zope.app.form.browser.sequencewidget import TupleSequenceWidget
 from zope.app.form.browser.sequencewidget import ListSequenceWidget
+from zope.app.form.browser.sequencewidget import SequenceDisplayWidget
 
 from zope.app.form.browser.objectwidget import ObjectWidget

Modified: Zope3/trunk/src/zope/app/form/browser/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/configure.zcml	2005-06-03 15:11:49 UTC (rev 30622)
+++ Zope3/trunk/src/zope/app/form/browser/configure.zcml	2005-06-03 15:22:18 UTC (rev 30623)
@@ -265,6 +265,15 @@
       permission="zope.Public"
       />
 
+  <view
+      type="zope.publisher.interfaces.browser.IBrowserRequest"
+      for="zope.schema.interfaces.ISequence
+           zope.schema.interfaces.IField"
+      provides="zope.app.form.interfaces.IDisplayWidget"
+      factory=".SequenceDisplayWidget"
+      permission="zope.Public"
+      />
+
   <!-- Choice collections.  dispatch to field + vocabulary lookup.
        We must register the collection + choice factories for all ICollection
        subclasses because the field (the collection) has precedence: therefore

Modified: Zope3/trunk/src/zope/app/form/browser/sequencewidget.py
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/sequencewidget.py	2005-06-03 15:11:49 UTC (rev 30622)
+++ Zope3/trunk/src/zope/app/form/browser/sequencewidget.py	2005-06-03 15:22:18 UTC (rev 30623)
@@ -22,10 +22,11 @@
 from zope.i18n import translate
 
 from zope.app import zapi
-from zope.app.form.interfaces import IInputWidget
+from zope.app.form.interfaces import IDisplayWidget, IInputWidget
 from zope.app.form.interfaces import WidgetInputError, MissingInputError
 from zope.app.form import InputWidget
 from zope.app.form.browser.widget import BrowserWidget
+from zope.app.form.browser.widget import DisplayWidget, renderElement
 from zope.app.i18n import ZopeMessageIDFactory as _
 
 class SequenceWidget(BrowserWidget, InputWidget):
@@ -38,7 +39,6 @@
     implements(IInputWidget)
 
     _type = tuple
-    _data = None # pre-existing sequence items (from setRenderedValue)
 
     def __init__(self, context, field, request, subwidget=None):
         super(SequenceWidget, self).__init__(context, request)
@@ -96,7 +96,7 @@
 
     def _getWidget(self, i):
         field = self.context.value_type
-        if self.subwidget:
+        if self.subwidget is not None:
             widget = self.subwidget(field, self.request)
         else:
             widget = zapi.getMultiAdapter((field, self.request), IInputWidget)
@@ -119,12 +119,12 @@
         return "\n".join(parts)
 
     def _getRenderedValue(self):
-        sequence = self._data
-        if sequence is None:
-            if self.hasInput():
-                sequence = list(self._generateSequence())
-            else:
-                sequence = []
+        if self._renderedValueSet():
+            sequence = self._data
+        elif self.hasInput():
+            sequence = list(self._generateSequence())
+        else:
+            sequence = []
         # ensure minimum number of items in the form
         if len(sequence) < self.context.min_length:
             sequence = list(sequence)
@@ -132,7 +132,6 @@
                 # Shouldn't this use self.field.value_type.missing_value,
                 # instead of None?
                 sequence.append(None)
-            sequence = self._type(sequence)
         return sequence
 
     def _getPresenceMarker(self, count=0):
@@ -178,15 +177,6 @@
         """
         return (self.name + ".count") in self.request.form
 
-    def setRenderedValue(self, value):
-        """Set the data that should be rendered by the widget.
-
-        The given value should be used even if the user has entered
-        data.
-        """
-        # the current list of values derived from the "value" parameter
-        self._data = value
-
     def _generateSequence(self):
         """Take sequence info in the self.request and _data.
 
@@ -243,3 +233,66 @@
 
 class ListSequenceWidget(SequenceWidget):
     _type = list
+
+
+# Basic display widget
+
+class SequenceDisplayWidget(DisplayWidget):
+
+    _missingValueMessage = _("sequence-value-not-provided",
+                             u"(no value available)")
+
+    _emptySequenceMessage = _("sequence-value-is-empty",
+                              u"(no values)")
+
+    tag = "ol"
+    itemTag = "li"
+    cssClass = "sequenceWidget"
+    extra = ""
+
+    def __init__(self, context, field, request, subwidget=None):
+        super(SequenceDisplayWidget, self).__init__(context, request)
+        self.subwidget = subwidget
+
+    def __call__(self):
+        # get the data to display:
+        if self._renderedValueSet():
+            data = self._data
+        else:
+            data = self.context.get(self.context.context)
+
+        # deal with special cases:
+        if data == self.context.missing_value:
+            return translate(self._missingValueMessage, self.request,
+                             self._missingValueMessage.default)
+        data = list(data)
+        if not data:
+            return translate(self._emptySequenceMessage, self.request,
+                             self._emptySequenceMessage.default)
+
+        parts = []
+        for i, item in enumerate(data):
+            widget = self._getWidget(i)
+            widget.setRenderedValue(item)
+            s = widget()
+            if self.itemTag:
+                s = "<%s>%s</%s>" % (self.itemTag, s, self.itemTag)
+            parts.append(s)
+        contents = "\n".join(parts)
+        if self.tag:
+            contents = "\n%s\n" % contents
+            contents = renderElement(self.tag,
+                                     cssClass=self.cssClass,
+                                     extra=self.extra,
+                                     contents=contents)
+        return contents
+
+    def _getWidget(self, i):
+        field = self.context.value_type
+        if self.subwidget is not None:
+            widget = self.subwidget(field, self.request)
+        else:
+            widget = zapi.getMultiAdapter(
+                (field, self.request), IDisplayWidget)
+        widget.setPrefix('%s.%d.'%(self.name, i))
+        return widget

Modified: Zope3/trunk/src/zope/app/form/browser/tests/test_sequencewidget.py
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/tests/test_sequencewidget.py	2005-06-03 15:11:49 UTC (rev 30622)
+++ Zope3/trunk/src/zope/app/form/browser/tests/test_sequencewidget.py	2005-06-03 15:22:18 UTC (rev 30623)
@@ -25,24 +25,20 @@
 
 from zope.app import zapi
 from zope.app.testing import ztapi
-from zope.app.form.browser import TextWidget, ObjectWidget
+from zope.app.form.browser import TextWidget, ObjectWidget, DisplayWidget
 from zope.app.form.browser import TupleSequenceWidget, ListSequenceWidget
+from zope.app.form.browser import SequenceDisplayWidget
 from zope.app.form.browser import SequenceWidget
+from zope.app.form.interfaces import IDisplayWidget
 from zope.app.form.interfaces import IInputWidget, MissingInputError
 from zope.app.form import CustomWidgetFactory
 from zope.app.form import CustomSequenceWidgetFactory
 
+from zope.app.form.browser.tests.support import VerifyResults
 from zope.app.form.browser.tests.test_browserwidget import BrowserWidgetTest
 
 
-class SequenceWidgetTest(BrowserWidgetTest):
-    """Documents and tests the tuple and list (sequence) widgets.
-    
-        >>> verifyClass(IInputWidget, TupleSequenceWidget)
-        True
-        >>> verifyClass(IInputWidget, ListSequenceWidget)
-        True
-    """
+class SequenceWidgetTestHelper(object):
 
     def setUpContent(self, desc=u'', title=u'Foo Title'):
         class ITestContent(Interface):
@@ -54,27 +50,35 @@
             implements(ITestContent)
 
         self.content = TestObject()
-        field = ITestContent['foo']
-        field = field.bind(self.content)
-        request = TestRequest(HTTP_ACCEPT_LANGUAGE='pl')
-        request.form['field.foo'] = u'Foo Value'
-        self._widget = self._WidgetFactory(field, TextLine(), request)
+        self.field = ITestContent['foo'].bind(self.content)
+        self.request = TestRequest(HTTP_ACCEPT_LANGUAGE='pl')
+        self.request.form['field.foo'] = u'Foo Value'
+        self._widget = self._WidgetFactory(
+            self.field, self.field.value_type, self.request)
 
     def _FieldFactory(self, **kw):
         kw.update({
             '__name__': u'foo', 
             'value_type': TextLine(__name__=u'bar')})
         return Tuple(**kw)
+
+
+class SequenceWidgetTest(SequenceWidgetTestHelper, BrowserWidgetTest):
+    """Documents and tests the tuple and list (sequence) widgets.
+    
+        >>> verifyClass(IInputWidget, TupleSequenceWidget)
+        True
+        >>> verifyClass(IInputWidget, ListSequenceWidget)
+        True
+    """
+
     _WidgetFactory = TupleSequenceWidget
 
     def testRender(self):
         pass
 
     def setUp(self):
-        BrowserWidgetTest.setUp(self)
-        self.field = Tuple(
-            __name__=u'foo',
-            value_type=TextLine(__name__=u'bar'))
+        super(SequenceWidgetTest, self).setUp()
         ztapi.browserViewProviding(ITextLine, TextWidget, IInputWidget)
 
     def test_haveNoData(self):
@@ -111,13 +115,14 @@
         """This test verifies that the specified subwidget is not ignored.
         (Issue #293)
         """
-        self.field = List( __name__=u'foo',
-                           value_type=TextLine(__name__=u'bar'))
+        self.field = List(__name__=u'foo',
+                          value_type=TextLine(__name__=u'bar'))
         request = TestRequest()
 
         class PollOption(object) : pass
         ow = CustomWidgetFactory(ObjectWidget, PollOption)
-        widget = SequenceWidget(self.field, TextLine(), request, subwidget=ow)
+        widget = SequenceWidget(
+            self.field, self.field.value_type, request, subwidget=ow)
         assert widget.subwidget is ow
 
     def test_list(self):
@@ -125,25 +130,29 @@
             __name__=u'foo',
             value_type=TextLine(__name__=u'bar'))
         request = TestRequest()
-        widget = ListSequenceWidget(self.field, TextLine(), request)
+        widget = ListSequenceWidget(
+            self.field, self.field.value_type, request)
         self.failIf(widget.hasInput())
         self.assertRaises(MissingInputError, widget.getInputValue)
 
         request = TestRequest(form={'field.foo.add': u'Add bar',
                                     'field.foo.count': u'0'})
-        widget = ListSequenceWidget(self.field, TextLine(), request)
+        widget = ListSequenceWidget(
+            self.field, self.field.value_type, request)
         self.assert_(widget.hasInput())
         self.assertRaises(ValidationError, widget.getInputValue)
 
         request = TestRequest(form={'field.foo.0.bar': u'Hello world!',
                                     'field.foo.count': u'1'})
-        widget = ListSequenceWidget(self.field, TextLine(), request)
+        widget = ListSequenceWidget(
+            self.field, self.field.value_type, request)
         self.assert_(widget.hasInput())
         self.assertEquals(widget.getInputValue(), [u'Hello world!'])
 
     def test_new(self):
         request = TestRequest()
-        widget = TupleSequenceWidget(self.field, TextLine(), request)
+        widget = TupleSequenceWidget(
+            self.field, self.field.value_type, request)
         self.failIf(widget.hasInput())
         self.assertRaises(MissingInputError, widget.getInputValue)
         check_list = ('input', 'name="field.foo.add"')
@@ -152,7 +161,8 @@
     def test_add(self):
         request = TestRequest(form={'field.foo.add': u'Add bar',
                                     'field.foo.count': u'0'})
-        widget = TupleSequenceWidget(self.field, TextLine(), request)
+        widget = TupleSequenceWidget(
+            self.field, self.field.value_type, request)
         self.assert_(widget.hasInput())
         self.assertRaises(ValidationError, widget.getInputValue)
         check_list = (
@@ -164,13 +174,15 @@
     def test_request(self):
         request = TestRequest(form={'field.foo.0.bar': u'Hello world!',
                                     'field.foo.count': u'1'})
-        widget = TupleSequenceWidget(self.field, TextLine(), request)
+        widget = TupleSequenceWidget(
+            self.field, self.field.value_type, request)
         self.assert_(widget.hasInput())
         self.assertEquals(widget.getInputValue(), (u'Hello world!',))
 
     def test_existing(self):
         request = TestRequest()
-        widget = TupleSequenceWidget(self.field, TextLine(), request)
+        widget = TupleSequenceWidget(
+            self.field, self.field.value_type, request)
         widget.setRenderedValue((u'existing',))
         self.failIf(widget.hasInput())
         self.assertRaises(MissingInputError, widget.getInputValue)
@@ -200,7 +212,8 @@
             'field.foo.0.bar': u'existing', 'field.foo.1.bar': u'second',
             'field.foo.remove': u'Remove selected items',
             'field.foo.count': u'2'})
-        widget = TupleSequenceWidget(self.field, TextLine(), request)
+        widget = TupleSequenceWidget(
+            self.field, self.field.value_type, request)
         widget.setRenderedValue((u'existing', u'second'))
         self.assertEquals(widget.getInputValue(), (u'second',))
         check_list = (
@@ -216,7 +229,8 @@
     def test_min(self):
         request = TestRequest()
         self.field.min_length = 2
-        widget = TupleSequenceWidget(self.field, TextLine(), request)
+        widget = TupleSequenceWidget(
+            self.field, self.field.value_type, request)
         widget.setRenderedValue((u'existing',))
         self.assertRaises(MissingInputError, widget.getInputValue)
         check_list = (
@@ -231,7 +245,8 @@
     def test_max(self):
         request = TestRequest()
         self.field.max_length = 1
-        widget = TupleSequenceWidget(self.field, TextLine(), request)
+        widget = TupleSequenceWidget(
+            self.field, self.field.value_type, request)
         widget.setRenderedValue((u'existing',))
         self.assertRaises(MissingInputError, widget.getInputValue)
         s = widget()
@@ -240,7 +255,8 @@
     def test_anonymousfield(self):
         self.field = Tuple(__name__=u'foo', value_type=TextLine())
         request = TestRequest()
-        widget = TupleSequenceWidget(self.field, TextLine(), request)
+        widget = TupleSequenceWidget(
+            self.field, self.field.value_type, request)
         widget.setRenderedValue((u'existing',))
         s = widget()
         check_list = (
@@ -251,13 +267,76 @@
         self.verifyResult(s, check_list, inorder=True)
 
 
+class SequenceDisplayWidgetTest(
+    VerifyResults, SequenceWidgetTestHelper, unittest.TestCase):
+
+    def _WidgetFactory(self, *args, **kw):
+        w = SequenceDisplayWidget(*args, **kw)
+        w.cssClass = "testwidget"
+        return w
+
+    def setUp(self):
+        self.setUpContent()
+        self.request = TestRequest()
+        self.widget = self._WidgetFactory(
+            self.field, self.field.value_type, self.request)
+        ztapi.browserViewProviding(ITextLine, DisplayWidget, IDisplayWidget)
+
+    def test_render_empty(self):
+        self.content.foo = ()
+        self.assertEquals(self.widget(), '(no values)')
+
+    def test_render_missing(self):
+        self.content.foo = self.field.missing_value
+        self.assertEquals(self.widget(), '(no value available)')
+
+    def test_render_single(self):
+        self.content.foo = (u'one value',)
+        check_list = ['<ol', 'class=', 'testwidget',
+                      '<li', 'one value', '</li', '</ol']
+        self.verifyResult(self.widget(), check_list, inorder=True)
+
+    def test_render_multiple(self):
+        self.content.foo = (u'one', u'two', u'three', u'four')
+        check_list = ['<ol', 'class=', 'testwidget',
+                      '<li', 'one', '</li',
+                      '<li', 'two', '</li',
+                      '<li', 'three', '</li',
+                      '<li', 'four', '</li',
+                      '</ol']
+        self.verifyResult(self.widget(), check_list, inorder=True)
+
+    def test_render_alternate_cssClass(self):
+        self.content.foo = (u'one value',)
+        check_list = ['<ol', 'class=', 'altclass',
+                      '<li', 'one value', '</li', '</ol']
+        self.widget.cssClass = 'altclass'
+        self.verifyResult(self.widget(), check_list, inorder=True)
+
+    def test_honors_subwidget(self):
+        self.widget = self._WidgetFactory(
+            self.field, self.field.value_type, self.request,
+            subwidget=UppercaseDisplayWidget)
+        self.content.foo = (u'first value', u'second value')
+        check_list = ['<ol', 'class=', 'testwidget',
+                      '<li', 'FIRST VALUE', '</li',
+                      '<li', 'SECOND VALUE', '</li',
+                      '</ol']
+        self.verifyResult(self.widget(), check_list, inorder=True)
+
+
+class UppercaseDisplayWidget(DisplayWidget):
+
+    def __call__(self):
+        return super(UppercaseDisplayWidget, self).__call__().upper()
+
+
 def test_suite():
     return unittest.TestSuite((
         unittest.makeSuite(SequenceWidgetTest),
         doctest.DocTestSuite(),
+        unittest.makeSuite(SequenceDisplayWidgetTest),
         ))
 
 if __name__=='__main__':
     unittest.main(defaultTest='test_suite')
-
-



More information about the Zope3-Checkins mailing list