[Checkins] SVN: Zope3/branches/3.2/ backported missing zope.app.form bugfixes from trunk (r40546:68015):

Yvo Schubbe y.2006_ at wcm-solutions.de
Mon Sep 25 08:34:16 EDT 2006


Log message for revision 70363:
  backported missing zope.app.form bugfixes from trunk (r40546:68015):
  
  r40641, r41125, r65779, r65781, r65783, r65907, r66579, r67282, small part of r67630

Changed:
  U   Zope3/branches/3.2/doc/CHANGES.txt
  U   Zope3/branches/3.2/src/zope/app/form/__init__.py
  U   Zope3/branches/3.2/src/zope/app/form/browser/configure.zcml
  U   Zope3/branches/3.2/src/zope/app/form/browser/itemswidgets.py
  U   Zope3/branches/3.2/src/zope/app/form/browser/metadirectives.py
  U   Zope3/branches/3.2/src/zope/app/form/browser/objectwidget.py
  U   Zope3/branches/3.2/src/zope/app/form/browser/objectwidget.txt
  U   Zope3/branches/3.2/src/zope/app/form/browser/sequencewidget.py
  U   Zope3/branches/3.2/src/zope/app/form/browser/tests/test_sequencewidget.py

-=-
Modified: Zope3/branches/3.2/doc/CHANGES.txt
===================================================================
--- Zope3/branches/3.2/doc/CHANGES.txt	2006-09-25 11:34:08 UTC (rev 70362)
+++ Zope3/branches/3.2/doc/CHANGES.txt	2006-09-25 12:34:15 UTC (rev 70363)
@@ -10,6 +10,9 @@
 
     Bug fixes
 
+      - Fixed two bugs in SequenceWidgets: an exception was raised
+        on rendering and the errors of the subwidgets weren't displayed.
+
       - Fixed issue 706: Session doesn't expire after aborting transaction.
 
       - Fixed issue 681: IFormFields is missing declaration for omit()

Modified: Zope3/branches/3.2/src/zope/app/form/__init__.py
===================================================================
--- Zope3/branches/3.2/src/zope/app/form/__init__.py	2006-09-25 11:34:08 UTC (rev 70362)
+++ Zope3/branches/3.2/src/zope/app/form/__init__.py	2006-09-25 12:34:15 UTC (rev 70363)
@@ -26,7 +26,7 @@
 
 deprecated('CustomSequenceWidgetFactory',
            'Use CustomWidgetFactory instead. '
-           'The reference will be gone in Zope 3.3.')
+           'The reference will be gone in Zope 3.4.')
 
 class Widget(object):
     """Mixin class providing functionality common across widget types."""
@@ -120,7 +120,7 @@
         return self._create(args)
 
 
-# BBB: Gone in 3.3 (does not satify IViewFactory)
+# BBB: Gone in 3.4 (does not satify IViewFactory)
 class CustomSequenceWidgetFactory(CustomWidgetFactory):
     """Custom sequence widget factory."""
 

Modified: Zope3/branches/3.2/src/zope/app/form/browser/configure.zcml
===================================================================
--- Zope3/branches/3.2/src/zope/app/form/browser/configure.zcml	2006-09-25 11:34:08 UTC (rev 70362)
+++ Zope3/branches/3.2/src/zope/app/form/browser/configure.zcml	2006-09-25 12:34:15 UTC (rev 70363)
@@ -520,5 +520,5 @@
         parent="form"
         />
   </configure>
-  
+
 </configure>

Modified: Zope3/branches/3.2/src/zope/app/form/browser/itemswidgets.py
===================================================================
--- Zope3/branches/3.2/src/zope/app/form/browser/itemswidgets.py	2006-09-25 11:34:08 UTC (rev 70362)
+++ Zope3/branches/3.2/src/zope/app/form/browser/itemswidgets.py	2006-09-25 12:34:15 UTC (rev 70363)
@@ -527,6 +527,7 @@
         rendered_items = self.renderItems(value)
         return renderElement(self.tag,
                              name=self.name + ':list',
+                             id=self.name,
                              multiple='multiple',
                              size=self.size,
                              contents="\n".join(rendered_items),

Modified: Zope3/branches/3.2/src/zope/app/form/browser/metadirectives.py
===================================================================
--- Zope3/branches/3.2/src/zope/app/form/browser/metadirectives.py	2006-09-25 11:34:08 UTC (rev 70362)
+++ Zope3/branches/3.2/src/zope/app/form/browser/metadirectives.py	2006-09-25 12:34:15 UTC (rev 70363)
@@ -242,7 +242,7 @@
     being rendered.  The addform directive provides a customization
     interface to overcome this difficulty.
 
-    See zope.app.browser.form.interfaces.IAddFormCustomization.
+    See zope.app.form.browser.interfaces.IAddFormCustomization.
     """
 
     description = MessageID(

Modified: Zope3/branches/3.2/src/zope/app/form/browser/objectwidget.py
===================================================================
--- Zope3/branches/3.2/src/zope/app/form/browser/objectwidget.py	2006-09-25 11:34:08 UTC (rev 70362)
+++ Zope3/branches/3.2/src/zope/app/form/browser/objectwidget.py	2006-09-25 12:34:15 UTC (rev 70363)
@@ -101,7 +101,7 @@
         return [self.getSubWidget(name) for name in self.names]
 
     def hidden(self):
-        """Render the list as hidden fields."""
+        """Render the object as hidden fields."""
         result = []
         for name in self.names:
             result.append(getSubwidget(name).hidden())

Modified: Zope3/branches/3.2/src/zope/app/form/browser/objectwidget.txt
===================================================================
--- Zope3/branches/3.2/src/zope/app/form/browser/objectwidget.txt	2006-09-25 11:34:08 UTC (rev 70362)
+++ Zope3/branches/3.2/src/zope/app/form/browser/objectwidget.txt	2006-09-25 12:34:15 UTC (rev 70363)
@@ -37,7 +37,7 @@
   ...                     required=False, 
   ...                     schema=IPerson)
 
-Let's define the class familiy with FieldProperty's mother and father
+Let's define the class family with FieldProperty's mother and father
 FieldProperty validate the values if they get added:
 
   >>> from zope.schema.fieldproperty import FieldProperty

Modified: Zope3/branches/3.2/src/zope/app/form/browser/sequencewidget.py
===================================================================
--- Zope3/branches/3.2/src/zope/app/form/browser/sequencewidget.py	2006-09-25 11:34:08 UTC (rev 70362)
+++ Zope3/branches/3.2/src/zope/app/form/browser/sequencewidget.py	2006-09-25 12:34:15 UTC (rev 70363)
@@ -19,6 +19,7 @@
 
 from zope.interface import implements
 from zope.i18n import translate
+from zope.schema.interfaces import ValidationError
 
 from zope.app import zapi
 from zope.app.form.interfaces import IDisplayWidget, IInputWidget
@@ -28,6 +29,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 +43,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 +69,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 +93,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,19 +141,18 @@
         return "\n".join(parts)
 
     def _getRenderedValue(self):
+        """Returns a sequence from the request or _data"""
         if self._renderedValueSet():
-            sequence = self._data
+            sequence = list(self._data)
         elif self.hasInput():
-            sequence = list(self._generateSequence())
+            sequence = self._generateSequence()
         else:
             sequence = []
         # ensure minimum number of items in the form
-        if len(sequence) < self.context.min_length:
-            sequence = list(sequence)
-            for i in range(self.context.min_length - len(sequence)):
-                # Shouldn't this use self.field.value_type.missing_value,
-                # instead of None?
-                sequence.append(None)
+        while len(sequence) < self.context.min_length:
+            # Shouldn't this use self.field.value_type.missing_value,
+            # instead of None?
+            sequence.append(None)
         return sequence
 
     def _getPresenceMarker(self, count=0):
@@ -150,9 +172,19 @@
         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)
+                # catch and set field errors to ``_error`` attribute
+                try:
+                    self.context.validate(sequence)
+                except WidgetInputError, error:
+                    self._error = error
+                    raise self._error
+                except ValidationError, error:
+                    self._error = WidgetInputError(
+                        self.context.__name__, self.label, error)
+                    raise self._error
             elif self.context.required:
                 raise MissingInputError(self.context.__name__,
                                         self.context.title)
@@ -177,13 +209,12 @@
         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.
         """
-        len_prefix = len(self.name)
-        adding = False
-        removing = []
         if self.context.value_type is None:
             # Why would this ever happen?
             return []
@@ -198,41 +229,38 @@
             raise WidgetInputError(self.context.__name__, self.context.title)
 
         # pre-populate
-        found = {}
+        sequence = [None] * count
 
         # now look through the request for interesting values
-        for i in range(count):
-            remove_key = "%s.remove_%d" % (self.name, i)
-            if remove_key in self.request.form:
-                removing.append(i)
+        # in reverse so that we can remove items as we go
+        removing = self.name + ".remove" in self.request.form
+        for i in reversed(range(count)):
             widget = self._getWidget(i)
             if widget.hasValidInput():
-                found[i] = widget.getInputValue()
-            else:
-                found[i] = None
-        adding = (self.name + ".add") in self.request.form
+                # catch and set sequence widget errors to ``_error`` attribute
+                try:
+                    sequence[i] = widget.getInputValue()
+                except WidgetInputError, error:
+                    self._error = error
+                    raise self._error
 
-        # remove the indicated indexes
-        if (self.name + ".remove") in self.request.form:
-            for i in removing:
-                del found[i]
+            remove_key = "%s.remove_%d" % (self.name, i)
+            if remove_key in self.request.form and removing:
+                del sequence[i]
 
-        # generate the list, sorting the dict's contents by key
-        items = found.items()
-        items.sort()
-        sequence = [value for key, value in items]
-
         # add an entry to the list if the add button has been pressed
-        if adding:
+        if self.name + ".add" in self.request.form:
             # Should this be using self.context.value_type.missing_value
             # instead of None?
             sequence.append(None)
 
         return sequence
 
+
 class TupleSequenceWidget(SequenceWidget):
     _type = tuple
 
+
 class ListSequenceWidget(SequenceWidget):
     _type = list
 
@@ -294,5 +322,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/branches/3.2/src/zope/app/form/browser/tests/test_sequencewidget.py
===================================================================
--- Zope3/branches/3.2/src/zope/app/form/browser/tests/test_sequencewidget.py	2006-09-25 11:34:08 UTC (rev 70362)
+++ Zope3/branches/3.2/src/zope/app/form/browser/tests/test_sequencewidget.py	2006-09-25 12:34:15 UTC (rev 70363)
@@ -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, WidgetInputError
+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())
@@ -139,7 +144,7 @@
         widget = ListSequenceWidget(
             self.field, self.field.value_type, request)
         self.assert_(widget.hasInput())
-        self.assertRaises(ValidationError, widget.getInputValue)
+        self.assertRaises(WidgetInputError, widget.getInputValue)
 
         request = TestRequest(form={'field.foo.0.bar': u'Hello world!',
                                     'field.foo.count': u'1'})
@@ -163,7 +168,7 @@
         widget = TupleSequenceWidget(
             self.field, self.field.value_type, request)
         self.assert_(widget.hasInput())
-        self.assertRaises(ValidationError, widget.getInputValue)
+        self.assertRaises(WidgetInputError, widget.getInputValue)
         check_list = (
             'checkbox', 'field.foo.remove_0', 'input', 'field.foo.0.bar',
             'submit', 'submit', 'field.foo.add'
@@ -279,7 +284,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):
 
@@ -344,10 +388,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 Checkins mailing list