From c843b1b0936631fd51c40dce7b7147f749866854 Mon Sep 17 00:00:00 2001 From: Dmitry Vasiliev Date: Sat, 1 Jul 2006 17:18:56 +0000 Subject: [PATCH] Rolled back the famous Date(time)Widget fix (see revison 68818 for details). Added Date(time)I18nWidget instead. --- browser/__init__.py | 78 +++++++++++++++++++++++++++ browser/ftests/test_datetimewidget.py | 43 +++++++-------- browser/tests/test_datetimewidget.py | 49 ++++++++++++++--- browser/tests/test_datewidget.py | 46 ++++++++++++++-- browser/textwidgets.py | 38 +++++++++++-- 5 files changed, 215 insertions(+), 39 deletions(-) create mode 100644 browser/__init__.py diff --git a/browser/__init__.py b/browser/__init__.py new file mode 100644 index 0000000..eaa497d --- /dev/null +++ b/browser/__init__.py @@ -0,0 +1,78 @@ +############################################################################## +# +# Copyright (c) 2004 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Browser widgets + +$Id$ +""" +__docformat__ = 'restructuredtext' + +from zope.app.form.browser.widget import BrowserWidget, DisplayWidget +from zope.app.form.browser.widget import UnicodeDisplayWidget + +from zope.app.form.browser.textwidgets import TextWidget, BytesWidget +from zope.app.form.browser.textwidgets import TextAreaWidget, BytesAreaWidget +from zope.app.form.browser.textwidgets import PasswordWidget, FileWidget +from zope.app.form.browser.textwidgets import ASCIIWidget, ASCIIAreaWidget +from zope.app.form.browser.textwidgets import IntWidget, FloatWidget +from zope.app.form.browser.textwidgets import DatetimeWidget, DateWidget +from zope.app.form.browser.textwidgets import DatetimeI18nWidget +from zope.app.form.browser.textwidgets import DateI18nWidget +from zope.app.form.browser.textwidgets import DatetimeDisplayWidget +from zope.app.form.browser.textwidgets import DateDisplayWidget +from zope.app.form.browser.textwidgets import BytesDisplayWidget +from zope.app.form.browser.textwidgets import ASCIIDisplayWidget +from zope.app.form.browser.textwidgets import URIDisplayWidget + +# Widgets for boolean fields +from zope.app.form.browser.boolwidgets import CheckBoxWidget +from zope.app.form.browser.boolwidgets import BooleanRadioWidget +from zope.app.form.browser.boolwidgets import BooleanSelectWidget +from zope.app.form.browser.boolwidgets import BooleanDropdownWidget + +# Choice and Sequence Display Widgets +from zope.app.form.browser.itemswidgets import ItemDisplayWidget +from zope.app.form.browser.itemswidgets import ItemsMultiDisplayWidget +from zope.app.form.browser.itemswidgets import SetDisplayWidget +from zope.app.form.browser.itemswidgets import ListDisplayWidget + +# Widgets for fields with vocabularies. +# Note that these are only dispatchers for the widgets below. +from zope.app.form.browser.itemswidgets import ChoiceDisplayWidget +from zope.app.form.browser.itemswidgets import ChoiceInputWidget +from zope.app.form.browser.itemswidgets import CollectionDisplayWidget +from zope.app.form.browser.itemswidgets import CollectionInputWidget +from zope.app.form.browser.itemswidgets import ChoiceCollectionDisplayWidget +from zope.app.form.browser.itemswidgets import ChoiceCollectionInputWidget + +# Widgets that let you choose a single item from a list +# These widgets are multi-views on (field, vocabulary) +from zope.app.form.browser.itemswidgets import SelectWidget +from zope.app.form.browser.itemswidgets import DropdownWidget +from zope.app.form.browser.itemswidgets import RadioWidget + +# Widgets that let you choose several items from a list +# These widgets are multi-views on (field, vocabulary) +from zope.app.form.browser.itemswidgets import MultiSelectWidget +from zope.app.form.browser.itemswidgets import MultiSelectSetWidget +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 diff --git a/browser/ftests/test_datetimewidget.py b/browser/ftests/test_datetimewidget.py index da2c1e3..9fa58d5 100644 --- a/browser/ftests/test_datetimewidget.py +++ b/browser/ftests/test_datetimewidget.py @@ -22,7 +22,7 @@ from datetime import datetime import zope.security.checker -from zope.datetime import tzinfo +from zope.datetime import parseDatetimetz, tzinfo from zope.interface import Interface, implements from zope.schema import Datetime, Choice from zope.traversing.api import traverse @@ -39,22 +39,22 @@ class IDatetimeTest(Interface): d3 = Choice( required=False, values=( - datetime(2003, 9, 15), - datetime(2003, 10, 15)), - missing_value=datetime(2000, 1, 1)) + datetime(2003, 9, 15, tzinfo=tzinfo(0)), + datetime(2003, 10, 15, tzinfo=tzinfo(0))), + missing_value=datetime(2000, 1, 1, tzinfo=tzinfo(0))) d1 = Datetime( required=True, - min=datetime(2003, 1, 1), - max=datetime(2020, 12, 31)) + min=datetime(2003, 1, 1, tzinfo=tzinfo(0)), + max=datetime(2020, 12, 31, tzinfo=tzinfo(0))) class DatetimeTest(Persistent): implements(IDatetimeTest) def __init__(self): - self.d1 = datetime(2003, 4, 6) - self.d2 = datetime(2003, 8, 6) + self.d1 = datetime(2003, 4, 6, tzinfo=tzinfo(0)) + self.d2 = datetime(2003, 8, 6, tzinfo=tzinfo(0)) self.d3 = None class Test(BrowserTestCase): @@ -80,12 +80,7 @@ def getDateForField(self, field, source): m = re.search(pattern, source, re.DOTALL) if m is None: return None - - request = self.makeRequest(env={"HTTP_ACCEPT_LANGUAGE": "ru"}) - - formatter = request.locale.dates.getFormatter("dateTime") - - return formatter.parse(m.group(1)) + return parseDatetimetz(m.group(1)) def test_display_editform(self): self.getRootFolder()['test'] = DatetimeTest() @@ -112,9 +107,9 @@ def test_submit_editform(self): # submit edit view response = self.publish('/test/edit.html', form={ 'UPDATE_SUBMIT' : '', - 'field.d1' : u'Feb 1, 2003 12:00:00 AM', - 'field.d2' : u'Feb 2, 2003 12:00:00 AM', - 'field.d3' : u'2003-10-15 00:00:00' }, + 'field.d1' : u'2003-02-01 00:00:00+00:00', + 'field.d2' : u'2003-02-02 00:00:00+00:00', + 'field.d3' : u'2003-10-15 00:00:00+00:00' }, env={"HTTP_ACCEPT_LANGUAGE": "en"}) self.assertEqual(response.getStatus(), 200) self.assert_(updatedMsgExists(response.getBody())) @@ -122,9 +117,9 @@ def test_submit_editform(self): # check new values in object object = traverse(self.getRootFolder(), 'test') - self.assertEqual(object.d1, datetime(2003, 2, 1)) - self.assertEqual(object.d2, datetime(2003, 2, 2)) - self.assertEqual(object.d3, datetime(2003, 10, 15)) + self.assertEqual(object.d1, datetime(2003, 2, 1, tzinfo=tzinfo(0))) + self.assertEqual(object.d2, datetime(2003, 2, 2, tzinfo=tzinfo(0))) + self.assertEqual(object.d3, datetime(2003, 10, 15, tzinfo=tzinfo(0))) def test_missing_value(self): @@ -143,7 +138,7 @@ def test_missing_value(self): object = traverse(self.getRootFolder(), 'test') self.assert_(object.d2 is None) # default missing_value for dates # 2000-1-1 is missing_value for d3 - self.assertEqual(object.d3, datetime(2000, 1, 1)) + self.assertEqual(object.d3, datetime(2000, 1, 1, tzinfo=tzinfo(0))) def test_required_validation(self): @@ -171,7 +166,7 @@ def test_invalid_value(self): # submit a value for d3 that isn't allowed response = self.publish('/test/edit.html', form={ 'UPDATE_SUBMIT' : '', - 'field.d3' : u'Feb 1, 2003 12:00:00 AM'}) + 'field.d3' : u'2003-02-01 12:00:00+00:00'}) self.assertEqual(response.getStatus(), 200) self.assert_(invalidValueErrorExists('d3', response.getBody())) @@ -183,7 +178,7 @@ def test_min_max_validation(self): # submit value for d1 that is too low response = self.publish('/test/edit.html', form={ 'UPDATE_SUBMIT' : '', - 'field.d1' : u'Dec 31, 2002 12:00:00 AM'}, + 'field.d1' : u'2002-12-31 12:00:00+00:00'}, env={"HTTP_ACCEPT_LANGUAGE": "en"}) self.assertEqual(response.getStatus(), 200) self.assert_(validationErrorExists('d1', 'Value is too small', @@ -192,7 +187,7 @@ def test_min_max_validation(self): # submit value for i1 that is too high response = self.publish('/test/edit.html', form={ 'UPDATE_SUBMIT' : '', - 'field.d1' : u'Dec 1, 2021 12:00:00 AM'}, + 'field.d1' : u'2021-12-01 12:00:00+00:00'}, env={"HTTP_ACCEPT_LANGUAGE": "en"}) self.assertEqual(response.getStatus(), 200) self.assert_(validationErrorExists('d1', 'Value is too big', diff --git a/browser/tests/test_datetimewidget.py b/browser/tests/test_datetimewidget.py index 39dc503..ff8b81e 100644 --- a/browser/tests/test_datetimewidget.py +++ b/browser/tests/test_datetimewidget.py @@ -19,12 +19,13 @@ import unittest, doctest from zope.schema import Datetime -from zope.datetime import tzinfo +from zope.datetime import parseDatetimetz, tzinfo from zope.interface.verify import verifyClass from zope.app.form.browser.tests.test_browserwidget import SimpleInputWidgetTest from zope.app.form.interfaces import IInputWidget from zope.app.form.browser import DatetimeWidget +from zope.app.form.browser import DatetimeI18nWidget from zope.app.form.interfaces import ConversionError, WidgetInputError @@ -38,32 +39,67 @@ class DatetimeWidgetTest(SimpleInputWidgetTest): _FieldFactory = Datetime _WidgetFactory = DatetimeWidget + def testRender(self): + super(DatetimeWidgetTest, self).testRender( + datetime.datetime(2004, 3, 26, 12, 58, 59), + ('type="text"', 'id="field.foo"', 'name="field.foo"', + 'value="2004-03-26 12:58:59"')) + + def test_hasInput(self): + del self._widget.request.form['field.foo'] + self.failIf(self._widget.hasInput()) + # widget has input, even if input is an empty string + self._widget.request.form['field.foo'] = u'' + self.failUnless(self._widget.hasInput()) + self._widget.request.form['field.foo'] = u'2003-03-26 12:00:00' + self.failUnless(self._widget.hasInput()) + + def test_getInputValue(self, + value=u'2004-03-26 12:58:59', + check_value=parseDatetimetz('2004-03-26 12:58:59')): + self._widget.request.form['field.foo'] = u'' + self.assertRaises(WidgetInputError, self._widget.getInputValue) + self._widget.request.form['field.foo'] = value + self.assertEquals(self._widget.getInputValue(), check_value) + self._widget.request.form['field.foo'] = u'abc' + self.assertRaises(ConversionError, self._widget.getInputValue) + +class DatetimeI18nWidgetTest(SimpleInputWidgetTest): + """Documents and tests the i18n datetime widget. + + >>> verifyClass(IInputWidget, DatetimeI18nWidget) + True + """ + + _FieldFactory = Datetime + _WidgetFactory = DatetimeI18nWidget + def testDefaultDisplayStyle(self): self.failIf(self._widget.displayStyle) def testRender(self): - super(DatetimeWidgetTest, self).testRender( + super(DatetimeI18nWidgetTest, self).testRender( datetime.datetime(2004, 3, 26, 12, 58, 59), ('type="text"', 'id="field.foo"', 'name="field.foo"', 'value="26.03.2004 12:58:59"')) def testRenderShort(self): self._widget.displayStyle = "short" - super(DatetimeWidgetTest, self).testRender( + super(DatetimeI18nWidgetTest, self).testRender( datetime.datetime(2004, 3, 26, 12, 58, 59), ('type="text"', 'id="field.foo"', 'name="field.foo"', 'value="26.03.04 12:58"')) def testRenderMedium(self): self._widget.displayStyle = "medium" - super(DatetimeWidgetTest, self).testRender( + super(DatetimeI18nWidgetTest, self).testRender( datetime.datetime(2004, 3, 26, 12, 58, 59), ('type="text"', 'id="field.foo"', 'name="field.foo"', 'value="26.03.2004 12:58:59"')) def testRenderLong(self): self._widget.displayStyle = "long" - super(DatetimeWidgetTest, self).testRender( + super(DatetimeI18nWidgetTest, self).testRender( datetime.datetime(2004, 3, 26, 12, 58, 59), ('type="text"', 'id="field.foo"', 'name="field.foo"', u'value="26 \u041c\u0430\u0440\u0442 2004 \u0433.' @@ -71,7 +107,7 @@ def testRenderLong(self): def testRenderFull(self): self._widget.displayStyle = "full" - super(DatetimeWidgetTest, self).testRender( + super(DatetimeI18nWidgetTest, self).testRender( datetime.datetime(2004, 3, 26, 12, 58, 59), ('type="text"', 'id="field.foo"', 'name="field.foo"', u'value="26 \u041c\u0430\u0440\u0442 2004 \u0433.' @@ -132,6 +168,7 @@ def test_getFullInputValue(self): def test_suite(): return unittest.TestSuite(( unittest.makeSuite(DatetimeWidgetTest), + unittest.makeSuite(DatetimeI18nWidgetTest), doctest.DocTestSuite(), )) diff --git a/browser/tests/test_datewidget.py b/browser/tests/test_datewidget.py index 197fc49..f141a60 100644 --- a/browser/tests/test_datewidget.py +++ b/browser/tests/test_datewidget.py @@ -25,6 +25,7 @@ from zope.app.form.browser.tests.test_browserwidget import SimpleInputWidgetTest from zope.app.form.interfaces import IInputWidget from zope.app.form.browser import DateWidget +from zope.app.form.browser import DateI18nWidget from zope.app.form.interfaces import ConversionError, WidgetInputError @@ -38,39 +39,73 @@ class DateWidgetTest(SimpleInputWidgetTest): _FieldFactory = Date _WidgetFactory = DateWidget + def testRender(self): + super(DateWidgetTest, self).testRender( + datetime.date(2003, 3, 26), + ('type="text"', 'id="field.foo"', 'name="field.foo"', + 'value="2003-03-26"')) + + def test_hasInput(self): + del self._widget.request.form['field.foo'] + self.failIf(self._widget.hasInput()) + self._widget.request.form['field.foo'] = u'' + self.failUnless(self._widget.hasInput()) + self._widget.request.form['field.foo'] = u'2003-03-26' + self.failUnless(self._widget.hasInput()) + + def test_getInputValue(self, + value=u'2004-03-26', + check_value=datetime.date(2004, 3, 26)): + self._widget.request.form['field.foo'] = u'' + self.assertRaises(WidgetInputError, self._widget.getInputValue) + self._widget.request.form['field.foo'] = value + self.assertEquals(self._widget.getInputValue(), check_value) + self._widget.request.form['field.foo'] = u'abc' + self.assertRaises(ConversionError, self._widget.getInputValue) + +class DateI18nWidgetTest(SimpleInputWidgetTest): + """Documents and tests the i18n date widget. + + >>> verifyClass(IInputWidget, DateI18nWidget) + True + """ + + _FieldFactory = Date + _WidgetFactory = DateI18nWidget + def testDefaultDisplayStyle(self): self.failIf(self._widget.displayStyle) def testRender(self): - super(DateWidgetTest, self).testRender( + super(DateI18nWidgetTest, self).testRender( datetime.date(2003, 3, 26), ('type="text"', 'id="field.foo"', 'name="field.foo"', 'value="26.03.2003"')) def testRenderShort(self): self._widget.displayStyle = "short" - super(DateWidgetTest, self).testRender( + super(DateI18nWidgetTest, self).testRender( datetime.datetime(2004, 3, 26, 12, 58, 59), ('type="text"', 'id="field.foo"', 'name="field.foo"', 'value="26.03.04"')) def testRenderMedium(self): self._widget.displayStyle = "medium" - super(DateWidgetTest, self).testRender( + super(DateI18nWidgetTest, self).testRender( datetime.datetime(2004, 3, 26, 12, 58, 59), ('type="text"', 'id="field.foo"', 'name="field.foo"', 'value="26.03.2004"')) def testRenderLong(self): self._widget.displayStyle = "long" - super(DateWidgetTest, self).testRender( + super(DateI18nWidgetTest, self).testRender( datetime.datetime(2004, 3, 26, 12, 58, 59), ('type="text"', 'id="field.foo"', 'name="field.foo"', u'value="26 \u041c\u0430\u0440\u0442 2004 \u0433."')) def testRenderFull(self): self._widget.displayStyle = "full" - super(DateWidgetTest, self).testRender( + super(DateI18nWidgetTest, self).testRender( datetime.datetime(2004, 3, 26, 12, 58, 59), ('type="text"', 'id="field.foo"', 'name="field.foo"', u'value="26 \u041c\u0430\u0440\u0442 2004 \u0433."')) @@ -117,6 +152,7 @@ def test_getFullInputValue(self): def test_suite(): return unittest.TestSuite(( unittest.makeSuite(DateWidgetTest), + unittest.makeSuite(DateI18nWidgetTest), doctest.DocTestSuite(), )) diff --git a/browser/textwidgets.py b/browser/textwidgets.py index 7117f06..8b25d62 100644 --- a/browser/textwidgets.py +++ b/browser/textwidgets.py @@ -19,6 +19,8 @@ from xml.sax import saxutils from zope.interface import implements +from zope.datetime import parseDatetimetz +from zope.datetime import DateTimeError from zope.i18n.format import DateTimeParseError from zope.app.form.interfaces import IInputWidget, ConversionError @@ -483,8 +485,36 @@ def _toFieldValue(self, input): except ValueError, v: raise ConversionError(_("Invalid floating point data"), v) -class DateWidget(TextWidget): +class DatetimeWidget(TextWidget): + """Datetime entry widget.""" + + displayWidth = 20 + + def _toFieldValue(self, input): + if input == self._missing: + return self.context.missing_value + else: + try: + # TODO: Currently datetimes return in local (server) + # time zone if no time zone information was given. + # Maybe offset-naive datetimes should be returned in + # this case? (DV) + return parseDatetimetz(input) + except (DateTimeError, ValueError, IndexError), v: + raise ConversionError(_("Invalid datetime data"), v) + +class DateWidget(DatetimeWidget): """Date entry widget. + """ + + def _toFieldValue(self, input): + v = super(DateWidget, self)._toFieldValue(input) + if v != self.context.missing_value: + v = v.date() + return v + +class DateI18nWidget(TextWidget): + """I18n date entry widget. The `displayStyle` attribute may be set to control the formatting of the value. @@ -513,15 +543,15 @@ def _toFieldValue(self, input): "%s (%r)" % (v, input)) def _toFormValue(self, value): - value = super(DateWidget, self)._toFormValue(value) + value = super(DateI18nWidget, self)._toFormValue(value) if value: formatter = self.request.locale.dates.getFormatter( self._category, (self.displayStyle or None)) value = formatter.format(value) return value -class DatetimeWidget(DateWidget): - """Datetime entry widget. +class DatetimeI18nWidget(DateI18nWidget): + """I18n datetime entry widget. The `displayStyle` attribute may be set to control the formatting of the value.