[Zope3-checkins] SVN: zope.formlib/branches/faassen-zaf/src/zope/formlib/ Switch over from zope.app.form.widget to zope.formlib.widget
Martijn Faassen
faassen at startifact.com
Wed Dec 30 13:57:26 EST 2009
Log message for revision 107374:
Switch over from zope.app.form.widget to zope.formlib.widget
Changed:
U zope.formlib/branches/faassen-zaf/src/zope/formlib/bugs.txt
U zope.formlib/branches/faassen-zaf/src/zope/formlib/form.txt
U zope.formlib/branches/faassen-zaf/src/zope/formlib/tests.py
A zope.formlib/branches/faassen-zaf/src/zope/formlib/widget.py
-=-
Modified: zope.formlib/branches/faassen-zaf/src/zope/formlib/bugs.txt
===================================================================
--- zope.formlib/branches/faassen-zaf/src/zope/formlib/bugs.txt 2009-12-30 18:50:13 UTC (rev 107373)
+++ zope.formlib/branches/faassen-zaf/src/zope/formlib/bugs.txt 2009-12-30 18:57:26 UTC (rev 107374)
@@ -32,7 +32,7 @@
>>> form.setUpWidgets()
>>> form.widgets['title']
-<zope.app.form.browser.widget.DisplayWidget object at 0x...>
+<zope.formlib.widget.DisplayWidget object at 0x...>
>>> form.widgets['name']
<zope.app.form.browser.textwidgets.TextWidget object at 0x...>
@@ -47,7 +47,7 @@
>>> form = MyForm(order, request)
>>> form.setUpWidgets()
>>> form.widgets['title']
-<zope.app.form.browser.widget.DisplayWidget object at 0x...>
+<zope.formlib.widget.DisplayWidget object at 0x...>
>>> form.widgets['name']
<zope.app.form.browser.textwidgets.TextWidget object at 0x...>
Modified: zope.formlib/branches/faassen-zaf/src/zope/formlib/form.txt
===================================================================
--- zope.formlib/branches/faassen-zaf/src/zope/formlib/form.txt 2009-12-30 18:50:13 UTC (rev 107373)
+++ zope.formlib/branches/faassen-zaf/src/zope/formlib/form.txt 2009-12-30 18:57:26 UTC (rev 107374)
@@ -1469,9 +1469,9 @@
Let's create a simple custom widget to use in our demonstration::
- >>> import zope.app.form.browser.widget
+ >>> import zope.formlib.widget
- >>> class ISODisplayWidget(zope.app.form.browser.widget.DisplayWidget):
+ >>> class ISODisplayWidget(zope.formlib.widget.DisplayWidget):
...
... def __call__(self):
... return '<span class="iso-datetime">2005-05-04</span>'
Modified: zope.formlib/branches/faassen-zaf/src/zope/formlib/tests.py
===================================================================
--- zope.formlib/branches/faassen-zaf/src/zope/formlib/tests.py 2009-12-30 18:50:13 UTC (rev 107373)
+++ zope.formlib/branches/faassen-zaf/src/zope/formlib/tests.py 2009-12-30 18:57:26 UTC (rev 107374)
@@ -548,7 +548,7 @@
optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
),
# The following test needs some zope.security test setup
- # doctest.DocFileSuite(
+ #doctest.DocFileSuite(
# 'bugs.txt',
# setUp=formSetUp, tearDown=tearDown,
# optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
Copied: zope.formlib/branches/faassen-zaf/src/zope/formlib/widget.py (from rev 107371, zope.app.form/branches/faassen-zaf/src/zope/app/form/browser/widget.py)
===================================================================
--- zope.formlib/branches/faassen-zaf/src/zope/formlib/widget.py (rev 0)
+++ zope.formlib/branches/faassen-zaf/src/zope/formlib/widget.py 2009-12-30 18:57:26 UTC (rev 107374)
@@ -0,0 +1,551 @@
+##############################################################################
+#
+# Copyright (c) 2001-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 Widget Definitions
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from xml.sax.saxutils import quoteattr, escape
+
+from zope.component import getMultiAdapter
+from zope.interface import implements
+from zope.schema.interfaces import ValidationError
+from zope.publisher.browser import BrowserView
+
+from zope.app.form import Widget, InputWidget
+from zope.formlib.interfaces import ConversionError
+from zope.formlib.interfaces import InputErrors
+from zope.formlib.interfaces import WidgetInputError, MissingInputError
+from zope.app.form.browser.interfaces import IBrowserWidget
+from zope.app.form.browser.interfaces import ISimpleInputWidget
+from zope.app.form.browser.interfaces import IWidgetInputErrorView
+
+import warnings
+
+
+if quoteattr("\r") != '"&13;"':
+ _quoteattr = quoteattr
+
+ def quoteattr(data):
+ return _quoteattr(
+ data, {'\n': ' ', '\r': ' ', '\t':'	'})
+
+
+class BrowserWidget(Widget, BrowserView):
+ """Base class for browser widgets.
+
+ >>> setUp()
+
+ The class provides some basic functionality common to all browser widgets.
+
+ Browser widgets have a `required` attribute, which indicates whether or
+ not the underlying field requires input. By default, the widget's required
+ attribute is equal to the field's required attribute:
+
+ >>> from zope.schema import Field
+ >>> from zope.publisher.browser import TestRequest
+ >>> field = Field(required=True)
+ >>> widget = BrowserWidget(field, TestRequest())
+ >>> widget.required
+ True
+ >>> field.required = False
+ >>> widget = BrowserWidget(field, TestRequest())
+ >>> widget.required
+ False
+
+ However, the two `required` values are independent of one another:
+
+ >>> field.required = True
+ >>> widget.required
+ False
+
+ Browser widgets have an error state, which can be rendered in a form using
+ the `error()` method. The error method delegates the error rendering to a
+ view that is registered as providing `IWidgetInputErrorView`. To
+ illustrate, we can create and register a simple error display view:
+
+ >>> from zope.formlib.interfaces import IWidgetInputError
+ >>> class SnippetErrorView:
+ ... implements(IWidgetInputErrorView)
+ ... def __init__(self, context, request):
+ ... self.context = context
+ ... def snippet(self):
+ ... return "The error: " + str(self.context.errors)
+ >>> from zope.app.testing import ztapi
+ >>> ztapi.browserViewProviding(IWidgetInputError, SnippetErrorView,
+ ... IWidgetInputErrorView)
+
+ Whever an error occurs, widgets should set _error:
+
+ >>> widget._error = WidgetInputError('foo', 'Foo', ('Err1', 'Err2'))
+
+ so that it can be displayed using the error() method:
+
+ >>> widget.error()
+ "The error: ('Err1', 'Err2')"
+
+ >>> tearDown()
+
+ """
+
+ implements(IBrowserWidget)
+
+ _error = None
+
+ def __init__(self, context, request):
+ super(BrowserWidget, self).__init__(context, request)
+ self.required = context.required
+
+ def error(self):
+ if self._error:
+ return getMultiAdapter((self._error, self.request),
+ IWidgetInputErrorView).snippet()
+ return ""
+
+ def hidden(self):
+ return ""
+
+
+class SimpleInputWidget(BrowserWidget, InputWidget):
+ """A baseclass for simple HTML form widgets.
+
+ >>> setUp()
+
+ Simple input widgets read input from a browser form. To illustrate, we
+ will use a test request with two form values:
+
+ >>> from zope.publisher.browser import TestRequest
+ >>> request = TestRequest(form={
+ ... 'field.foo': u'hello\\r\\nworld',
+ ... 'baz.foo': u'bye world'})
+
+ Like all widgets, simple input widgets are a view to a field context:
+
+ >>> from zope.schema import Field
+ >>> field = Field(__name__='foo', title=u'Foo')
+ >>> widget = SimpleInputWidget(field, request)
+
+ Widgets are named using their field's name:
+
+ >>> widget.name
+ 'field.foo'
+
+ The default implementation for the widget label is to use the field title:
+
+ >>> widget.label
+ u'Foo'
+
+ According the request, the widget has input because 'field.foo' is
+ present:
+
+ >>> widget.hasInput()
+ True
+ >>> widget.getInputValue()
+ u'hello\\r\\nworld'
+
+ Widgets maintain an error state, which is used to communicate invalid
+ input or other errors:
+
+ >>> widget._error is None
+ True
+ >>> widget.error()
+ ''
+
+ `setRenderedValue` is used to specify the value displayed by the widget to
+ the user. This value, however, is not the same as the input value, which
+ is read from the request:
+
+ >>> widget.setRenderedValue('Hey\\nfolks')
+ >>> widget.getInputValue()
+ u'hello\\r\\nworld'
+ >>> widget._error is None
+ True
+ >>> widget.error()
+ ''
+
+ You can use 'setPrefix' to remove or modify the prefix used to create the
+ widget name as follows:
+
+ >>> widget.setPrefix('')
+ >>> widget.name
+ 'foo'
+ >>> widget.setPrefix('baz')
+ >>> widget.name
+ 'baz.foo'
+
+ `getInputValue` always returns a value that can legally be assigned to
+ the widget field. To illustrate widget validation, we can add a constraint
+ to its field:
+
+ >>> import re
+ >>> field.constraint = re.compile('.*hello.*').match
+
+ Because we modified the widget's name, the widget will now read different
+ form input:
+
+ >>> request.form[widget.name]
+ u'bye world'
+
+ This input violates the new field constraint and therefore causes an
+ error when `getInputValue` is called:
+
+ >>> widget.getInputValue()
+ Traceback (most recent call last):
+ WidgetInputError: ('foo', u'Foo', ConstraintNotSatisfied(u'bye world'))
+
+ Simple input widgets require that input be available in the form request.
+ If input is not present, a ``MissingInputError`` is raised:
+
+ >>> del request.form[widget.name]
+ >>> widget.getInputValue()
+ Traceback (most recent call last):
+ MissingInputError: ('baz.foo', u'Foo', None)
+
+ A ``MissingInputError`` indicates that input is missing from the form
+ altogether. It does not indicate that the user failed to provide a value
+ for a required field. The ``MissingInputError`` above was caused by the
+ fact that the form does have any input for the widget:
+
+ >>> request.form[widget.name]
+ Traceback (most recent call last):
+ KeyError: 'baz.foo'
+
+ If a user fails to provide input for a field, the form will contain the
+ input provided by the user, namely an empty string:
+
+ >>> request.form[widget.name] = ''
+
+ In such a case, if the field is required, a ``WidgetInputError`` will be
+ raised on a call to `getInputValue`:
+
+ >>> field.required = True
+ >>> widget.getInputValue()
+ Traceback (most recent call last):
+ WidgetInputError: ('foo', u'Foo', RequiredMissing('foo'))
+
+ However, if the field is not required, the empty string will be converted
+ by the widget into the field's `missing_value` and read as a legal field
+ value:
+
+ >>> field.required = False
+ >>> widget.getInputValue() is field.missing_value
+ True
+
+ Another type of exception is a conversion error. It is raised when a value
+ cannot be converted to the desired Python object. Here is an example of a
+ floating point.
+
+ >>> from zope.schema import Float
+ >>> field = Float(__name__='price', title=u'Price')
+
+ >>> from zope.formlib.interfaces import ConversionError
+ >>> class FloatWidget(SimpleInputWidget):
+ ... def _toFieldValue(self, input):
+ ... try:
+ ... return float(input)
+ ... except ValueError, v:
+ ... raise ConversionError('Invalid floating point data', v)
+ ...
+ ... def _toFormValue(self, value):
+ ... value = super(FloatWidget, self)._toFormValue(value)
+ ... return '%.2f' % value
+
+ >>> request = TestRequest(form={'field.price': u'32.0'})
+ >>> widget = FloatWidget(field, request)
+ >>> widget.getInputValue()
+ 32.0
+ >>> widget()
+ u'<input class="textType" id="field.price" name="field.price" type="text" value="32.00" />'
+
+ >>> request = TestRequest(form={'field.price': u'<p>foo</p>'})
+ >>> widget = FloatWidget(field, request)
+ >>> try:
+ ... widget.getInputValue()
+ ... except ConversionError, error:
+ ... print error.doc()
+ Invalid floating point data
+ >>> widget()
+ u'<input class="textType" id="field.price" name="field.price" type="text" value="<p>foo</p>" />'
+
+
+ >>> tearDown()
+ """
+
+ implements(ISimpleInputWidget)
+
+ tag = u'input'
+ type = u'text'
+ cssClass = u''
+ extra = u''
+ _missing = u''
+
+ def hasInput(self):
+ """See IWidget.hasInput.
+
+ Returns ``True`` if the submitted request form contains a value for
+ the widget, otherwise returns False.
+
+ Some browser widgets may need to implement a more sophisticated test
+ for input. E.g. checkbox values are not supplied in submitted
+ forms when their value is 'off' -- in this case the widget will
+ need to add a hidden element to signal its presence in the form.
+ """
+ return self.name in self.request.form
+
+ def getInputValue(self):
+ self._error = None
+ field = self.context
+
+ # form input is required, otherwise raise an error
+ if not self.hasInput():
+ raise MissingInputError(self.name, self.label, None)
+
+ # convert input to suitable value - may raise conversion error
+ try:
+ value = self._toFieldValue(self._getFormInput())
+ except ConversionError, error:
+ # ConversionError is already a WidgetInputError
+ self._error = error
+ raise self._error
+
+ # allow missing values only for non-required fields
+ if value == field.missing_value and not field.required:
+ return value
+
+ # value must be valid per the field constraints
+ try:
+ field.validate(value)
+ except ValidationError, v:
+ self._error = WidgetInputError(
+ self.context.__name__, self.label, v)
+ raise self._error
+ return value
+
+ def _getFormInput(self):
+ """Returns current form input.
+
+ The value returned must be in a format that can be used as the 'input'
+ argument to `_toFieldValue`.
+
+ The default implementation returns the form value that corresponds to
+ the widget's name. Subclasses may override this method if their form
+ input consists of more than one form element or use an alternative
+ naming convention.
+ """
+ return self.request.get(self.name)
+
+ def _toFieldValue(self, input):
+ """Converts input to a value appropriate for the field type.
+
+ Widgets for non-string fields should override this method to
+ perform an appropriate conversion.
+
+ This method is used by getInputValue to perform the conversion
+ of form input (provided by `_getFormInput`) to an appropriate field
+ value.
+ """
+ if input == self._missing:
+ return self.context.missing_value
+ else:
+ return input
+
+ def _toFormValue(self, value):
+ """Converts a field value to a string used as an HTML form value.
+
+ This method is used in the default rendering of widgets that can
+ represent their values in a single HTML form value. Widgets whose
+ fields have more complex data structures should disregard this
+ method and override the default rendering method (__call__).
+ """
+ if value == self.context.missing_value:
+ return self._missing
+ else:
+ return value
+
+ def _getCurrentValueHelper(self):
+ """Helper to get the current input value.
+
+ Raises InputErrors if the data could not be validated/converted.
+ """
+ input_value = None
+ if self._renderedValueSet():
+ input_value = self._data
+ else:
+ if self.hasInput():
+ # It's insane to use getInputValue this way. It can
+ # cause _error to get set spuriously. We'll work
+ # around this by saving and restoring _error if
+ # necessary.
+ error = self._error
+ try:
+ input_value = self.getInputValue()
+ finally:
+ self._error = error
+ else:
+ input_value = self._getDefault()
+ return input_value
+
+ def _getCurrentValue(self):
+ """Returns the current input value.
+
+ Returns None if the data could not be validated/converted.
+ """
+ try:
+ input_value = self._getCurrentValueHelper()
+ except InputErrors:
+ input_value = None
+ return input_value
+
+ def _getFormValue(self):
+ """Returns a value suitable for use in an HTML form.
+
+ Detects the status of the widget and selects either the input value
+ that came from the request, the value from the _data attribute or the
+ default value.
+ """
+ try:
+ input_value = self._getCurrentValueHelper()
+ except InputErrors:
+ form_value = self.request.form.get(self.name, self._missing)
+ else:
+ form_value = self._toFormValue(input_value)
+ return form_value
+
+ def _getDefault(self):
+ """Returns the default value for this widget."""
+ return self.context.default
+
+ def __call__(self):
+ return renderElement(self.tag,
+ type=self.type,
+ name=self.name,
+ id=self.name,
+ value=self._getFormValue(),
+ cssClass=self.cssClass,
+ extra=self.extra)
+
+ def hidden(self):
+ return renderElement(self.tag,
+ type='hidden',
+ name=self.name,
+ id=self.name,
+ value=self._getFormValue(),
+ cssClass=self.cssClass,
+ extra=self.extra)
+
+class DisplayWidget(BrowserWidget):
+
+ def __init__(self, context, request):
+ super(DisplayWidget, self).__init__(context, request)
+ self.required = False
+
+
+ def __call__(self):
+ if self._renderedValueSet():
+ value = self._data
+ else:
+ value = self.context.default
+ if value == self.context.missing_value:
+ return ""
+ return escape(value)
+
+
+class UnicodeDisplayWidget(DisplayWidget):
+ """Display widget that converts the value to unicode before display."""
+
+ def __call__(self):
+ if self._renderedValueSet():
+ value = self._data
+ else:
+ value = self.context.default
+ if value == self.context.missing_value:
+ return ""
+ return escape(unicode(value))
+
+
+def renderTag(tag, **kw):
+ """Render the tag. Well, not all of it, as we may want to / it."""
+ attr_list = []
+
+ # special case handling for cssClass
+ cssClass = kw.pop('cssClass', u'')
+
+ # If the 'type' attribute is given, append this plus 'Type' as a
+ # css class. This allows us to do subselector stuff in css without
+ # necessarily having a browser that supports css subselectors.
+ # This is important if you want to style radio inputs differently than
+ # text inputs.
+ cssWidgetType = kw.get('type', u'')
+ if cssWidgetType:
+ cssWidgetType += u'Type'
+ names = [c for c in (cssClass, cssWidgetType) if c]
+ if names:
+ attr_list.append(u'class="%s"' % u' '.join(names))
+
+ style = kw.pop('style', u'')
+ if style:
+ attr_list.append(u'style=%s' % quoteattr(style))
+
+ # special case handling for extra 'raw' code
+ if 'extra' in kw:
+ # could be empty string but we don't care
+ extra = u" " + kw.pop('extra')
+ else:
+ extra = u''
+
+ # handle other attributes
+ if kw:
+ items = kw.items()
+ items.sort()
+ for key, value in items:
+ if value is None:
+ warnings.warn(
+ "None was passed for attribute %r. Passing None "
+ "as attribute values to renderTag is deprecated. "
+ "Passing None as an attribute value will be disallowed "
+ "starting in Zope 3.3."
+ % key,
+ DeprecationWarning, stacklevel=2)
+ value = key
+ attr_list.append(u'%s=%s' % (key, quoteattr(unicode(value))))
+
+ if attr_list:
+ attr_str = u" ".join(attr_list)
+ return u"<%s %s%s" % (tag, attr_str, extra)
+ else:
+ return u"<%s%s" % (tag, extra)
+
+
+def renderElement(tag, **kw):
+ contents = kw.pop('contents', None)
+ if contents is not None:
+ # Do not quote contents, since it often contains generated HTML.
+ return u"%s>%s</%s>" % (renderTag(tag, **kw), contents, tag)
+ else:
+ return renderTag(tag, **kw) + " />"
+
+
+def setUp():
+ import zope.component.testing
+ global setUp
+ setUp = zope.component.testing.setUp
+ setUp()
+
+
+def tearDown():
+ import zope.component.testing
+ global tearDown
+ tearDown = zope.component.testing.tearDown
+ tearDown()
More information about the Zope3-Checkins
mailing list