[Zope3-checkins] SVN: Zope3/trunk/src/zope/app/form/browser/ make the sequence input widget conform to the widget API

Fred L. Drake, Jr. fdrake at gmail.com
Wed Jun 1 23:12:41 EDT 2005


Log message for revision 30596:
  make the sequence input widget conform to the widget API

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	2005-06-01 22:14:18 UTC (rev 30595)
+++ Zope3/trunk/src/zope/app/form/browser/sequencewidget.py	2005-06-02 03:12:38 UTC (rev 30596)
@@ -23,6 +23,7 @@
 
 from zope.app import zapi
 from zope.app.form.interfaces import 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.i18n import ZopeMessageIDFactory as _
@@ -37,7 +38,7 @@
     implements(IInputWidget)
 
     _type = tuple
-    _data = () # pre-existing sequence items (from setRenderedValue)
+    _data = None # pre-existing sequence items (from setRenderedValue)
 
     def __init__(self, context, field, request, subwidget=None):
         super(SequenceWidget, self).__init__(context, request)
@@ -52,17 +53,11 @@
         render = []
 
         # length of sequence info
-        sequence = list(self._generateSequence())
+        sequence = self._getRenderedValue()
         num_items = len(sequence)
         min_length = self.context.min_length
         max_length = self.context.max_length
 
-        # ensure minimum number of items in the form
-        if num_items < min_length:
-            for i in range(min_length - num_items):
-                sequence.append(None)
-        num_items = len(sequence)
-
         # generate each widget from items in the sequence - adding a
         # "remove" button for each one
         for i in range(num_items):
@@ -80,12 +75,11 @@
         # possibly generate the "remove" and "add" buttons
         buttons = ''
         if render and num_items > min_length:
-            remove_botton_name = 'remove-selected-items-of-seq-' + self.name
             button_label = _('remove-selected-items', "Remove selected items")
             button_label = translate(button_label, context=self.request,
                                      default=button_label)
-            buttons += '<input type="submit" value="%s" name="%s"/>' % (
-                button_label, remove_botton_name)
+            buttons += ('<input type="submit" value="%s" name="%s.remove"/>'
+                        % (button_label, self.name))
         if max_length is None or num_items < max_length:
             field = self.context.value_type
             button_label = _('Add %s')
@@ -97,7 +91,8 @@
         if buttons:
             render.append('<tr><td>%s</td></tr>' % buttons)
 
-        return '<table border="0">' + ''.join(render) + '</table>'
+        return ('<table border="0">%s</table>\n%s'
+                % (''.join(render), self._getPresenceMarker(num_items)))
 
     def _getWidget(self, i):
         field = self.context.value_type
@@ -109,27 +104,41 @@
         return widget
 
     def hidden(self):
-        ''' Render the list as hidden fields '''
+        '''Render the list as hidden fields.'''
         # length of sequence info
-        sequence = self._generateSequence()
+        sequence = self._getRenderedValue()
         num_items = len(sequence)
-        min_length = self.context.min_length
 
-        # ensure minimum number of items in the form
-        if num_items < min_length:
-            for i in range(min_length - num_items):
-                sequence.append(None)
-        num_items = len(sequence)
-
         # generate hidden fields for each value
-        s = ''
+        parts = [self._getPresenceMarker(num_items)]
         for i in range(num_items):
             value = sequence[i]
             widget = self._getWidget(i)
             widget.setRenderedValue(value)
-            s += widget.hidden()
-        return s
+            parts.append(widget.hidden())
+        return "\n".join(parts)
 
+    def _getRenderedValue(self):
+        sequence = self._data
+        if sequence is None:
+            if 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)
+            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)
+            sequence = self._type(sequence)
+        return sequence
+
+    def _getPresenceMarker(self, count=0):
+        return ('<input type="hidden" name="%s.count" value="%d" />'
+                % (self.name, count))
+
     def getInputValue(self):
         """Return converted and validated widget data.
 
@@ -139,14 +148,18 @@
         If there is no user input and the field is not required, then
         the field default value will be returned.
 
-        A ``WidgetInputError`` is returned in the case of one or more
+        A ``WidgetInputError`` is raised in the case of one or more
         errors encountered, inputting, converting, or validating the data.
         """
-        sequence = self._generateSequence()
-        # validate the input values
-        for value in sequence:
-            self.context.value_type.validate(value)
-        return self._type(sequence)
+        if self.hasInput():
+            sequence = self._type(self._generateSequence())
+            if sequence != self.context.missing_value:
+                self.context.validate(sequence)
+            elif self.context.required:
+                raise MissingInputError(self.context.__name__,
+                                        self.context.title)
+            return sequence
+        raise MissingInputError(self.context.__name__, self.context.title)
 
     # TODO: applyChanges isn't reporting "change" correctly (we're
     # re-generating the sequence with every edit, and need to be smarter)
@@ -163,10 +176,10 @@
 
         Return ``True`` if there is data and ``False`` otherwise.
         """
-        return len(self._generateSequence()) != 0
+        return (self.name + ".count") in self.request.form
 
     def setRenderedValue(self, value):
-        """Set the default data for the widget.
+        """Set the data that should be rendered by the widget.
 
         The given value should be used even if the user has entered
         data.
@@ -176,51 +189,41 @@
 
     def _generateSequence(self):
         """Take sequence info in the self.request and _data.
+
+        This can only be called if self.hasInput() returns true.
         """
         len_prefix = len(self.name)
         adding = False
         removing = []
-        subprefix = re.compile(r'(\d+)\.(.*)$')
-        remove_botton_name = 'remove-selected-items-of-seq-' + self.name
         if self.context.value_type is None:
+            # Why would this ever happen?
             return []
+        # the marker field tells how many individual items were
+        # included in the input; we check for exactly that many input
+        # widgets
+        try:
+            count = int(self.request.form[self.name + ".count"])
+        except ValueError:
+            # could not convert to int; the input was not generated
+            # from the widget as implemented here
+            raise WidgetInputError(self.context.__name__, self.context.title)
 
         # pre-populate
         found = {}
-        if self._data is not None:
-            found = dict(enumerate(self._data))
 
         # now look through the request for interesting values
-        for key in self.request.keys():
-            if not key.startswith(self.name):
-                continue
-            token = key[len_prefix+1:]        # skip the '.'
-            if token == 'add':
-                # append a new blank field to the sequence
-                adding = True
-            elif token.startswith('remove_') and \
-                    remove_botton_name in self.request:
-                # remove the index indicated if we press 
-                # the "Remove selected items" button.
-                # Otherwise we delete the items if we check
-                # the box and push the "Change" button.
-                removing.append(int(token[7:]))
-            else:
-                match = subprefix.match(token)
-                if match is None:
-                    continue
-                # key refers to a sub field
-                i = int(match.group(1))
+        for i in range(count):
+            remove_key = "%s.remove_%d" % (self.name, i)
+            if remove_key in self.request.form:
+                removing.append(i)
+            widget = self._getWidget(i)
+            found[i] = widget.getInputValue()
+        adding = (self.name + ".add") in self.request.form
 
-                # find a widget for the sub-field and use that to parse the
-                # request data
-                widget = self._getWidget(i)
-                value = widget.getInputValue()
-                found[i] = value
-
         # remove the indicated indexes
-        for i in removing:
-            del found[i]
+        if (self.name + ".remove") in self.request.form:
+            for i in removing:
+                del found[i]
 
         # generate the list, sorting the dict's contents by key
         items = found.items()
@@ -229,6 +232,8 @@
 
         # add an entry to the list if the add button has been pressed
         if adding:
+            # Should this be using self.context.value_type.missing_value
+            # instead of None?
             sequence.append(None)
 
         return sequence

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-01 22:14:18 UTC (rev 30595)
+++ Zope3/trunk/src/zope/app/form/browser/tests/test_sequencewidget.py	2005-06-02 03:12:38 UTC (rev 30596)
@@ -28,7 +28,7 @@
 from zope.app.form.browser import TextWidget, ObjectWidget
 from zope.app.form.browser import TupleSequenceWidget, ListSequenceWidget
 from zope.app.form.browser import SequenceWidget
-from zope.app.form.interfaces import IInputWidget
+from zope.app.form.interfaces import IInputWidget, MissingInputError
 from zope.app.form import CustomWidgetFactory
 from zope.app.form import CustomSequenceWidgetFactory
 
@@ -81,7 +81,7 @@
         self.failIf(self._widget.hasInput())
 
     def test_hasInput(self):
-        self._widget.request.form['field.foo.0.bar'] = u'hi, mum'
+        self._widget.request.form['field.foo.count'] = u'0'
         self.failUnless(self._widget.hasInput())
 
     def test_customWidgetFactory(self):
@@ -127,14 +127,16 @@
         request = TestRequest()
         widget = ListSequenceWidget(self.field, TextLine(), request)
         self.failIf(widget.hasInput())
-        self.assertEquals(widget.getInputValue(), [])
+        self.assertRaises(MissingInputError, widget.getInputValue)
 
-        request = TestRequest(form={'field.foo.add': u'Add bar'})
+        request = TestRequest(form={'field.foo.add': u'Add bar',
+                                    'field.foo.count': u'0'})
         widget = ListSequenceWidget(self.field, TextLine(), request)
         self.assert_(widget.hasInput())
         self.assertRaises(ValidationError, widget.getInputValue)
 
-        request = TestRequest(form={'field.foo.0.bar': u'Hello world!'})
+        request = TestRequest(form={'field.foo.0.bar': u'Hello world!',
+                                    'field.foo.count': u'1'})
         widget = ListSequenceWidget(self.field, TextLine(), request)
         self.assert_(widget.hasInput())
         self.assertEquals(widget.getInputValue(), [u'Hello world!'])
@@ -143,12 +145,13 @@
         request = TestRequest()
         widget = TupleSequenceWidget(self.field, TextLine(), request)
         self.failIf(widget.hasInput())
-        self.assertEquals(widget.getInputValue(), ())
+        self.assertRaises(MissingInputError, widget.getInputValue)
         check_list = ('input', 'name="field.foo.add"')
         self.verifyResult(widget(), check_list)
 
     def test_add(self):
-        request = TestRequest(form={'field.foo.add': u'Add bar'})
+        request = TestRequest(form={'field.foo.add': u'Add bar',
+                                    'field.foo.count': u'0'})
         widget = TupleSequenceWidget(self.field, TextLine(), request)
         self.assert_(widget.hasInput())
         self.assertRaises(ValidationError, widget.getInputValue)
@@ -159,7 +162,8 @@
         self.verifyResult(widget(), check_list, inorder=True)
 
     def test_request(self):
-        request = TestRequest(form={'field.foo.0.bar': u'Hello world!'})
+        request = TestRequest(form={'field.foo.0.bar': u'Hello world!',
+                                    'field.foo.count': u'1'})
         widget = TupleSequenceWidget(self.field, TextLine(), request)
         self.assert_(widget.hasInput())
         self.assertEquals(widget.getInputValue(), (u'Hello world!',))
@@ -168,37 +172,44 @@
         request = TestRequest()
         widget = TupleSequenceWidget(self.field, TextLine(), request)
         widget.setRenderedValue((u'existing',))
-        self.assert_(widget.hasInput())
-        self.assertEquals(widget.getInputValue(), (u'existing',))
+        self.failIf(widget.hasInput())
+        self.assertRaises(MissingInputError, widget.getInputValue)
         check_list = (
             'checkbox', 'field.foo.remove_0', 'input', 'field.foo.0.bar',
                 'existing',
-            'submit', 'submit', 'field.foo.add'
+            'submit', 'submit', 'field.foo.add',
+            'field.foo.count" value="1"',
         )
         self.verifyResult(widget(), check_list, inorder=True)
         widget.setRenderedValue((u'existing', u'second'))
-        self.assert_(widget.hasInput())
-        self.assertEquals(widget.getInputValue(), (u'existing', u'second'))
+        self.failIf(widget.hasInput())
+        self.assertRaises(MissingInputError, widget.getInputValue)
         check_list = (
             'checkbox', 'field.foo.remove_0', 'input', 'field.foo.0.bar',
                 'existing',
             'checkbox', 'field.foo.remove_1', 'input', 'field.foo.1.bar',
                 'second',
-            'submit', 'submit', 'field.foo.add'
+            'submit', 'submit', 'field.foo.add',
+            'field.foo.count" value="2"',
         )
         self.verifyResult(widget(), check_list, inorder=True)
 
     def test_remove(self):
-        request = TestRequest(form={'field.foo.remove_0': u'Hello world!',
+        request = TestRequest(form={
+            'field.foo.remove_0': u'1',
             'field.foo.0.bar': u'existing', 'field.foo.1.bar': u'second',
-            'remove-selected-items-of-seq-field.foo': u'Remove selected items'})
+            'field.foo.remove': u'Remove selected items',
+            'field.foo.count': u'2'})
         widget = TupleSequenceWidget(self.field, TextLine(), request)
         widget.setRenderedValue((u'existing', u'second'))
         self.assertEquals(widget.getInputValue(), (u'second',))
         check_list = (
             'checkbox', 'field.foo.remove_0', 'input', 'field.foo.0.bar',
+                'existing',
+            'checkbox', 'field.foo.remove_1', 'input', 'field.foo.1.bar',
                 'second',
-            'submit', 'submit', 'field.foo.add'
+            'submit', 'submit', 'field.foo.add',
+            'field.foo.count" value="2"',
         )
         self.verifyResult(widget(), check_list, inorder=True)
 
@@ -207,7 +218,7 @@
         self.field.min_length = 2
         widget = TupleSequenceWidget(self.field, TextLine(), request)
         widget.setRenderedValue((u'existing',))
-        self.assertEquals(widget.getInputValue(), (u'existing',))
+        self.assertRaises(MissingInputError, widget.getInputValue)
         check_list = (
             'input', 'field.foo.0.bar', 'existing',
             'input', 'field.foo.1.bar', 'value=""',
@@ -222,7 +233,7 @@
         self.field.max_length = 1
         widget = TupleSequenceWidget(self.field, TextLine(), request)
         widget.setRenderedValue((u'existing',))
-        self.assertEquals(widget.getInputValue(), (u'existing',))
+        self.assertRaises(MissingInputError, widget.getInputValue)
         s = widget()
         self.assertEquals(s.find('field.foo.add'), -1)
 



More information about the Zope3-Checkins mailing list