############################################################################## # # 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 with text-based input $Id: textwidgets.py 70836 2006-10-20 07:30:46Z baijum $ """ __docformat__ = 'restructuredtext' 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 from zope.app.form.browser.interfaces import ITextBrowserWidget from zope.app.form.browser.widget import SimpleInputWidget, renderElement from zope.app.form.browser.widget import DisplayWidget from zope.app.i18n import ZopeMessageFactory as _ def escape(str): if str is not None: str = saxutils.escape(str) return str class TextWidget(SimpleInputWidget): """Text widget. Single-line text (unicode) input >>> from zope.publisher.browser import TestRequest >>> from zope.schema import TextLine >>> field = TextLine(__name__='foo', title=u'on') >>> request = TestRequest(form={'field.foo': u'Bob'}) >>> widget = TextWidget(field, request) >>> widget.hasInput() True >>> widget.getInputValue() u'Bob' >>> def normalize(s): ... return '\\n '.join(filter(None, s.split(' '))) >>> print normalize( widget() ) >>> print normalize( widget.hidden() ) Calling `setRenderedValue` will change what gets output: >>> widget.setRenderedValue("Barry") >>> print normalize( widget() ) Check that HTML is correctly encoded and decoded: >>> request = TestRequest( ... form={'field.foo': u'

©

'}) >>> widget = TextWidget(field, request) >>> widget.getInputValue() u'

©

' >>> print normalize( widget() ) """ implements(ITextBrowserWidget) default = '' displayWidth = 20 displayMaxWidth = "" extra = '' style = '' convert_missing_value = True def __init__(self, *args): super(TextWidget, self).__init__(*args) def __call__(self): value = self._getFormValue() if value is None or value == self.context.missing_value: value = '' kwargs = {'type': self.type, 'name': self.name, 'id': self.name, 'value': value, 'cssClass': self.cssClass, 'style': self.style, 'size': self.displayWidth, 'extra': self.extra} if self.displayMaxWidth: kwargs['maxlength'] = self.displayMaxWidth # TODO This is untested. return renderElement(self.tag, **kwargs) def _toFieldValue(self, input): if self.convert_missing_value and input == self._missing: value = self.context.missing_value else: # We convert everything to unicode. This might seem a bit crude, # but anything contained in a TextWidget should be representable # as a string. Note that you always have the choice of overriding # the method. try: value = unicode(input) except ValueError, v: raise ConversionError(_("Invalid text data"), v) return value class Bytes(SimpleInputWidget): def _toFieldValue(self, input): value = super(Bytes, self)._toFieldValue(input) if type(value) is unicode: try: value = value.encode('ascii') except UnicodeError, v: raise ConversionError(_("Invalid textual data"), v) return value class BytesWidget(Bytes, TextWidget): """Bytes widget. Single-line data (string) input >>> from zope.publisher.browser import TestRequest >>> from zope.schema import BytesLine >>> field = BytesLine(__name__='foo', title=u'on') >>> request = TestRequest(form={'field.foo': u'Bob'}) >>> widget = BytesWidget(field, request) >>> widget.hasInput() True >>> widget.getInputValue() 'Bob' """ class BytesDisplayWidget(DisplayWidget): """Bytes display widget""" def __call__(self): if self._renderedValueSet(): content = self._data else: content = self.context.default return renderElement("pre", contents=escape(content)) class ASCII(Bytes): """ASCII""" class ASCIIWidget(BytesWidget): """ASCII widget. Single-line data (string) input """ class ASCIIDisplayWidget(BytesDisplayWidget): """ASCII display widget""" class URIDisplayWidget(DisplayWidget): """URI display widget. :ivar linkTarget: The value of the ``target`` attribute for the generated hyperlink. If this is not set, no ``target`` attribute is generated. """ linkTarget = None def __call__(self): if self._renderedValueSet(): content = self._data else: content = self.context.default content = escape(content) kw = dict(contents=content, href=content) if self.linkTarget: kw["target"] = self.linkTarget return renderElement("a", **kw) class TextAreaWidget(SimpleInputWidget): """TextArea widget. Multi-line text (unicode) input. >>> from zope.publisher.browser import TestRequest >>> from zope.schema import Text >>> field = Text(__name__='foo', title=u'on') >>> request = TestRequest(form={'field.foo': u'Hello\\r\\nworld!'}) >>> widget = TextAreaWidget(field, request) >>> widget.hasInput() True >>> widget.getInputValue() u'Hello\\nworld!' >>> def normalize(s): ... return '\\n '.join(filter(None, s.split(' '))) >>> print normalize( widget() ) >>> print normalize( widget.hidden() ) Calling `setRenderedValue` will change what gets output: >>> widget.setRenderedValue("Hey\\ndude!") >>> print normalize( widget() ) Check that HTML is correctly encoded and decoded: >>> request = TestRequest( ... form={'field.foo': u'

©

'}) >>> widget = TextAreaWidget(field, request) >>> widget.getInputValue() u'

©

' >>> print normalize( widget() ) """ default = "" width = 60 height = 15 extra = "" style = '' def _toFieldValue(self, value): value = super(TextAreaWidget, self)._toFieldValue(value) if value: try: value = unicode(value) except ValueError, v: raise ConversionError(_("Invalid unicode data"), v) else: value = value.replace("\r\n", "\n") return value def _toFormValue(self, value): value = super(TextAreaWidget, self)._toFormValue(value) if value: value = value.replace("\n", "\r\n") value = escape(value) else: value = u'' return value def __call__(self): return renderElement("textarea", name=self.name, id=self.name, cssClass=self.cssClass, rows=self.height, cols=self.width, style=self.style, contents=self._getFormValue(), extra=self.extra) class BytesAreaWidget(Bytes, TextAreaWidget): """BytesArea widget. Multi-line string input. >>> from zope.publisher.browser import TestRequest >>> from zope.schema import Bytes >>> field = Bytes(__name__='foo', title=u'on') >>> request = TestRequest(form={'field.foo': u'Hello\\r\\nworld!'}) >>> widget = BytesAreaWidget(field, request) >>> widget.hasInput() True >>> widget.getInputValue() 'Hello\\nworld!' """ class ASCIIAreaWidget(ASCII, TextAreaWidget): """ASCIIArea widget. Multi-line string input. >>> from zope.publisher.browser import TestRequest >>> from zope.schema import ASCII >>> field = ASCII(__name__='foo', title=u'on') >>> request = TestRequest(form={'field.foo': u'Hello\\r\\nworld!'}) >>> widget = ASCIIAreaWidget(field, request) >>> widget.hasInput() True >>> widget.getInputValue() 'Hello\\nworld!' """ class PasswordWidget(TextWidget): """Password Widget""" type = 'password' def __call__(self): displayMaxWidth = self.displayMaxWidth or 0 if displayMaxWidth > 0: return renderElement(self.tag, type=self.type, name=self.name, id=self.name, value='', cssClass=self.cssClass, style=self.style, size=self.displayWidth, maxlength=displayMaxWidth, extra=self.extra) else: return renderElement(self.tag, type=self.type, name=self.name, id=self.name, value='', cssClass=self.cssClass, style=self.style, size=self.displayWidth, extra=self.extra) def hidden(self): raise NotImplementedError( 'Cannot get a hidden tag for a password field') class FileWidget(TextWidget): """File Widget""" type = 'file' def __call__(self): displayMaxWidth = self.displayMaxWidth or 0 hidden = renderElement(self.tag, type='hidden', name=self.name+".used", id=self.name+".used", value="") if displayMaxWidth > 0: elem = renderElement(self.tag, type=self.type, name=self.name, id=self.name, cssClass=self.cssClass, size=self.displayWidth, maxlength=displayMaxWidth, extra=self.extra) else: elem = renderElement(self.tag, type=self.type, name=self.name, id=self.name, cssClass=self.cssClass, size=self.displayWidth, extra=self.extra) return "%s %s" % (hidden, elem) def _toFieldValue(self, input): if input is None or input == '': return self.context.missing_value try: seek = input.seek read = input.read except AttributeError, e: raise ConversionError(_('Form input is not a file object'), e) else: seek(0) data = read() if data or getattr(input, 'filename', ''): return data else: return self.context.missing_value def hasInput(self): return ((self.name+".used" in self.request.form) or (self.name in self.request.form) ) class IntWidget(TextWidget): """Integer number widget. Let's make sure that zeroes are rendered properly: >>> from zope.schema import Int >>> field = Int(__name__='foo', title=u'on') >>> widget = IntWidget(field, None) >>> widget.setRenderedValue(0) >>> 'value="0"' in widget() True """ displayWidth = 10 def _toFieldValue(self, input): if input == self._missing: return self.context.missing_value else: try: return int(input) except ValueError, v: raise ConversionError(_("Invalid integer data"), v) class FloatWidget(TextWidget): implements(IInputWidget) displayWidth = 10 def _toFieldValue(self, input): if input == self._missing: return self.context.missing_value else: try: return float(input) except ValueError, v: raise ConversionError(_("Invalid floating point data"), v) 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. `displayStyle` must be one of 'full', 'long', 'medium', 'short', or None ('' is accepted an an alternative to None to support provision of a value from ZCML). """ _category = "date" displayWidth = 20 displayStyle = None def _toFieldValue(self, input): if input == self._missing: return self.context.missing_value else: try: formatter = self.request.locale.dates.getFormatter( self._category, (self.displayStyle or None)) return formatter.parse(input) except (DateTimeParseError, ValueError), v: raise ConversionError(_("Invalid datetime data"), "%s (%r)" % (v, input)) def _toFormValue(self, 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 DatetimeI18nWidget(DateI18nWidget): """I18n datetime entry widget. The `displayStyle` attribute may be set to control the formatting of the value. `displayStyle` must be one of 'full', 'long', 'medium', 'short', or None ('' is accepted an an alternative to None to support provision of a value from ZCML). NOTE: If you need timezone information you need to set `displayStyle` to either 'long' or 'full' since other display styles just ignore it. """ _category = "dateTime" class DateDisplayWidget(DisplayWidget): """Date display widget. The `cssClass` and `displayStyle` attributes may be set to control the formatting of the value. `displayStyle` must be one of 'full', 'long', 'medium', 'short', or None ('' is accepted an an alternative to None to support provision of a value from ZCML). """ cssClass = "date" displayStyle = None _category = "date" def __call__(self): if self._renderedValueSet(): content = self._data else: content = self.context.default if content == self.context.missing_value: return "" formatter = self.request.locale.dates.getFormatter( self._category, (self.displayStyle or None)) content = formatter.format(content) return renderElement("span", contents=escape(content), cssClass=self.cssClass) class DatetimeDisplayWidget(DateDisplayWidget): """Datetime display widget. The `cssClass` and `displayStyle` attributes may be set to control the formatting of the value. `displayStyle` must be one of 'full', 'long', 'medium', 'short', or None ('' is accepted an an alternative to None to support provision of a value from ZCML). """ cssClass = "dateTime" _category = "dateTime"