[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