[Zope3-checkins] SVN: Zope3/trunk/src/zope/app/form/browser/ Fixed a bug: now the SequenceWidget displays errors on the subwidgets if they

Albertas Agejevas alga at pov.lt
Fri Mar 3 16:51:59 EST 2006


Log message for revision 65781:
  Fixed a bug: now the SequenceWidget displays errors on the subwidgets if they
  happen.
  

Changed:
  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/sequencewidget.py
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/sequencewidget.py	2006-03-03 21:31:58 UTC (rev 65780)
+++ Zope3/trunk/src/zope/app/form/browser/sequencewidget.py	2006-03-03 21:51:58 UTC (rev 65781)
@@ -28,6 +28,7 @@
 from zope.app.form.browser.widget import DisplayWidget, renderElement
 from zope.app.i18n import ZopeMessageFactory as _
 
+
 class SequenceWidget(BrowserWidget, InputWidget):
     """A widget baseclass for a sequence of fields.
 
@@ -41,12 +42,14 @@
 
     def __init__(self, context, field, request, subwidget=None):
         super(SequenceWidget, self).__init__(context, request)
-
         self.subwidget = subwidget
 
+        # The subwidgets are cached in this dict if preserve_widgets is True.
+        self._widgets = {}
+        self.preserve_widgets = False
+
     def __call__(self):
-        """Render the widget
-        """
+        """Render the widget"""
         assert self.context.value_type is not None
 
         render = []
@@ -65,11 +68,15 @@
             if num_items > min_length:
                 render.append(
                     '<input class="editcheck" type="checkbox" '
-                    'name="%s.remove_%d" />' %(self.name, i)
+                    'name="%s.remove_%d" />\n' % (self.name, i)
                     )
             widget = self._getWidget(i)
             widget.setRenderedValue(value)
-            render.append(widget() + '</td></tr>')
+            error = widget.error()
+            if error:
+                render.append(error)
+                render.append('\n')
+            render.append(widget() + '</td></tr>\n')
 
         # possibly generate the "remove" and "add" buttons
         buttons = ''
@@ -85,25 +92,40 @@
             button_label = translate(button_label, context=self.request,
                                      default=button_label)
             button_label = button_label % (field.title or field.__name__)
-            buttons += '<input type="submit" name="%s.add" value="%s" />' % (
+            buttons += '<input type="submit" name="%s.add" value="%s" />\n' % (
                 self.name, button_label)
         if buttons:
-            render.append('<tr><td>%s</td></tr>' % buttons)
+            render.append('<tr><td>%s</td></tr>\n' % buttons)
 
-        return ('<table border="0">%s</table>\n%s'
+        return ('<table border="0">\n%s</table>\n%s'
                 % (''.join(render), self._getPresenceMarker(num_items)))
 
     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), IInputWidget)
-        widget.setPrefix('%s.%d.'%(self.name, i))
-        return widget
+        """Return a widget for the i-th number of the sequence.
 
+        Normally this method creates a new widget each time, but when
+        the ``preserve_widgets`` attribute is True, it starts caching
+        widgets.  We need it so that the errors on the subwidgets
+        would appear only if ``getInputValue`` was called.
+
+        ``getInputValue`` on the subwidgets gets called on each
+        request that has data.
+        """
+        if i not in self._widgets:
+            field = self.context.value_type
+            if self.subwidget is not None:
+                widget = self.subwidget(field, self.request)
+            else:
+                widget = zapi.getMultiAdapter((field, self.request),
+                                              IInputWidget)
+            widget.setPrefix('%s.%d.' % (self.name, i))
+            if not self.preserve_widgets:
+                return widget
+            self._widgets[i] = widget
+        return self._widgets[i]
+
     def hidden(self):
-        '''Render the list as hidden fields.'''
+        """Render the list as hidden fields."""
         # length of sequence info
         sequence = self._getRenderedValue()
         num_items = len(sequence)
@@ -118,6 +140,7 @@
         return "\n".join(parts)
 
     def _getRenderedValue(self):
+        """Returns a sequence from the request or _data"""
         if self._renderedValueSet():
             sequence = list(self._data)
         elif self.hasInput():
@@ -148,6 +171,7 @@
         errors encountered, inputting, converting, or validating the data.
         """
         if self.hasInput():
+            self.preserve_widgets = True
             sequence = self._type(self._generateSequence())
             if sequence != self.context.missing_value:
                 self.context.validate(sequence)
@@ -175,8 +199,10 @@
         return (self.name + ".count") in self.request.form
 
     def _generateSequence(self):
-        """Take sequence info in the self.request and _data.
+        """Extract the values of the subwidgets from the request.
 
+        Returns a list of values.
+
         This can only be called if self.hasInput() returns true.
         """
         if self.context.value_type is None:
@@ -214,9 +240,11 @@
 
         return sequence
 
+
 class TupleSequenceWidget(SequenceWidget):
     _type = tuple
 
+
 class ListSequenceWidget(SequenceWidget):
     _type = list
 
@@ -278,5 +306,5 @@
         else:
             widget = zapi.getMultiAdapter(
                 (field, self.request), IDisplayWidget)
-        widget.setPrefix('%s.%d.'%(self.name, i))
+        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	2006-03-03 21:31:58 UTC (rev 65780)
+++ Zope3/trunk/src/zope/app/form/browser/tests/test_sequencewidget.py	2006-03-03 21:51:58 UTC (rev 65781)
@@ -24,14 +24,17 @@
 from zope.interface.verify import verifyClass
 
 from zope.app import zapi
-from zope.app.testing import ztapi
+from zope.app.testing import ztapi, setup
 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.interfaces import IWidgetInputError
+from zope.app.form.browser.interfaces import IWidgetInputErrorView
 from zope.app.form import CustomWidgetFactory
+from zope.app.form.browser.exception import WidgetInputErrorView
 
 from zope.app.form.browser.tests.support import VerifyResults
 from zope.app.form.browser.tests.test_browserwidget import BrowserWidgetTest
@@ -79,6 +82,8 @@
     def setUp(self):
         super(SequenceWidgetTest, self).setUp()
         ztapi.browserViewProviding(ITextLine, TextWidget, IInputWidget)
+        ztapi.browserViewProviding(IWidgetInputError, WidgetInputErrorView,
+                                   IWidgetInputErrorView)
 
     def test_haveNoData(self):
         self.failIf(self._widget.hasInput())
@@ -280,7 +285,46 @@
         data = widget._generateSequence()
         self.assertEquals(data, [None, u'nonempty'])
 
+    def doctest_widgeterrors(self):
+        """Test that errors on subwidgets appear
 
+            >>> field = Tuple(__name__=u'foo',
+            ...               value_type=TextLine(__name__='bar'))
+            >>> request = TestRequest(form={
+            ...     'field.foo.0.bar': u'',
+            ...     'field.foo.1.bar': u'nonempty',
+            ...     'field.foo.count': u'2'})
+            >>> widget = TupleSequenceWidget(field, field.value_type, request)
+
+         If we render the widget, we see no errors:
+
+            >>> print widget()
+            <BLANKLINE>
+            ...
+            <tr><td><input class="editcheck" type="checkbox"
+                           name="field.foo.remove_0" />
+            <input class="textType" id="field.foo.0.bar" name="field.foo.0.bar"
+                   size="20" type="text" value=""  /></td></tr>
+            ...
+
+         However, if we call getInputValue or hasValidInput, the
+         errors on the widgets are preserved and displayed:
+
+            >>> widget.hasValidInput()
+            False
+
+            >>> print widget()
+            <BLANKLINE>
+            ...
+            <tr><td><input class="editcheck" type="checkbox"
+                           name="field.foo.remove_0" />
+            <span class="error">Required input is missing.</span>
+            <input class="textType" id="field.foo.0.bar" name="field.foo.0.bar"
+                   size="20" type="text" value=""  /></td></tr>
+            ...
+        """
+
+
 class SequenceDisplayWidgetTest(
     VerifyResults, SequenceWidgetTestHelper, unittest.TestCase):
 
@@ -345,10 +389,24 @@
         return super(UppercaseDisplayWidget, self).__call__().upper()
 
 
+def setUp(test):
+    setup.placelessSetUp()
+    ztapi.browserViewProviding(ITextLine, TextWidget, IInputWidget)
+    ztapi.browserViewProviding(IWidgetInputError, WidgetInputErrorView,
+                               IWidgetInputErrorView)
+
+
+def tearDown(test):
+    setup.placelessTearDown()
+
+
 def test_suite():
     return unittest.TestSuite((
         unittest.makeSuite(SequenceWidgetTest),
-        doctest.DocTestSuite(),
+        doctest.DocTestSuite(setUp=setUp, tearDown=tearDown,
+                             optionflags=doctest.ELLIPSIS
+                             |doctest.NORMALIZE_WHITESPACE
+                             |doctest.REPORT_NDIFF),
         unittest.makeSuite(SequenceDisplayWidgetTest),
         ))
 



More information about the Zope3-Checkins mailing list