[Checkins] SVN: z3c.form/trunk/ I spoke with Joachim Werner the other day on IRC and he mentioned to me

Stephan Richter srichter at cosmos.phy.tufts.edu
Thu Jun 28 15:53:18 EDT 2007


Log message for revision 77187:
  I spoke with Joachim Werner the other day on IRC and he mentioned to me 
  that he was very annoyed that the widgets in Zope 3 (referring to the 
  ones in zope.app.form) were not internationalized correctly and he would 
  have to constantly write his own custom widgets to make them work for 
  German input.
  
  It occurred to me that the same problem holds true for z3c.form. So I 
  had to change that! :-)
  
  - Feature: Internationalized data conversion for date, time, date/time,
    integer, float and decimal. Now the locale data is used to format and parse
    those data types to provide the bridge to text-based widgets. While those
    features require the latest zope.i18n package, backward compatibility is
    provided.
  
  
  

Changed:
  U   z3c.form/trunk/CHANGES.txt
  U   z3c.form/trunk/src/z3c/form/browser/README.txt
  U   z3c.form/trunk/src/z3c/form/compatibility.py
  U   z3c.form/trunk/src/z3c/form/configure.zcml
  U   z3c.form/trunk/src/z3c/form/converter.py
  U   z3c.form/trunk/src/z3c/form/converter.txt

-=-
Modified: z3c.form/trunk/CHANGES.txt
===================================================================
--- z3c.form/trunk/CHANGES.txt	2007-06-28 19:06:19 UTC (rev 77186)
+++ z3c.form/trunk/CHANGES.txt	2007-06-28 19:53:17 UTC (rev 77187)
@@ -5,6 +5,12 @@
 Version 1.4.0 (??/??/2007)
 -------------------------
 
+- Feature: Internationalized data conversion for date, time, date/time,
+  integer, float and decimal. Now the locale data is used to format and parse
+  those data types to provide the bridge to text-based widgets. While those
+  features require the latest zope.i18n package, backward compatibility is
+  provided.
+
 - Feature: All forms now have an optional label that can be used by the UI.
 
 - Feature: Implemented groups within forms. Groups allow you to combine a set

Modified: z3c.form/trunk/src/z3c/form/browser/README.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/browser/README.txt	2007-06-28 19:06:19 UTC (rev 77186)
+++ z3c.form/trunk/src/z3c/form/browser/README.txt	2007-06-28 19:53:17 UTC (rev 77187)
@@ -189,11 +189,11 @@
   >>> widget.update()
   >>> print widget.render()
   <input type="text" id="foo" name="bar" class="textWidget"
-         value="2007-04-01" />
+         value="07/04/01" />
 
   >>> widget.mode = interfaces.DISPLAY_MODE
   >>> print widget.render()
-  2007-04-01
+  07/04/01
 
 
 Datetime
@@ -204,27 +204,27 @@
   >>> widget.update()
   >>> print widget.render()
   <input type="text" id="foo" name="bar" class="textWidget"
-         value="2007-04-01 12:00:00" />
+         value="07/04/01 12:00" />
 
   >>> widget.mode = interfaces.DISPLAY_MODE
   >>> print widget.render()
-  2007-04-01 12:00:00
+  07/04/01 12:00
 
 
 Decimal
 -------
 
   >>> import decimal
-  >>> field = zope.schema.Decimal(default=decimal.Decimal('12.87'))
+  >>> field = zope.schema.Decimal(default=decimal.Decimal('1265.87'))
   >>> widget = setupWidget(field)
   >>> widget.update()
   >>> print widget.render()
   <input type="text" id="foo" name="bar" class="textWidget"
-         value="12.87" />
+         value="1,265.87" />
 
   >>> widget.mode = interfaces.DISPLAY_MODE
   >>> print widget.render()
-  12.87
+  1,265.87
 
 
 Dict
@@ -252,16 +252,16 @@
 Float
 -----
 
-  >>> field = zope.schema.Float(default=12.8)
+  >>> field = zope.schema.Float(default=1265.8)
   >>> widget = setupWidget(field)
   >>> widget.update()
   >>> print widget.render()
   <input type="text" id="foo" name="bar" class="textWidget"
-         value="12.8" />
+         value="1,265.8" />
 
   >>> widget.mode = interfaces.DISPLAY_MODE
   >>> print widget.render()
-  12.8
+  1,265.8
 
 
 FrozenSet
@@ -305,16 +305,16 @@
 Int
 ---
 
-  >>> field = zope.schema.Int(default=12)
+  >>> field = zope.schema.Int(default=1200)
   >>> widget = setupWidget(field)
   >>> widget.update()
   >>> print widget.render()
   <input type="text" id="foo" name="bar" class="textWidget"
-         value="12" />
+         value="1,200" />
 
   >>> widget.mode = interfaces.DISPLAY_MODE
   >>> print widget.render()
-  12
+  1,200
 
 
 List
@@ -428,7 +428,8 @@
   >>> widget = setupWidget(field)
   >>> widget.update()
   >>> print widget.render()
-  <textarea id="foo" name="bar" class="textAreaWidget">&lt;source /&gt;</textarea>
+  <textarea id="foo" name="bar"
+            class="textAreaWidget">&lt;source /&gt;</textarea>
 
   >>> widget.mode = interfaces.DISPLAY_MODE
   >>> print widget.render()
@@ -474,11 +475,11 @@
   >>> widget.update()
   >>> print widget.render()
   <input type="text" id="foo" name="bar" class="textWidget"
-         value="12:00:00" />
+         value="12:00" />
 
   >>> widget.mode = interfaces.DISPLAY_MODE
   >>> print widget.render()
-  12:00:00
+  12:00
 
 
 Timedelta

Modified: z3c.form/trunk/src/z3c/form/compatibility.py
===================================================================
--- z3c.form/trunk/src/z3c/form/compatibility.py	2007-06-28 19:06:19 UTC (rev 77186)
+++ z3c.form/trunk/src/z3c/form/compatibility.py	2007-06-28 19:53:17 UTC (rev 77187)
@@ -80,6 +80,282 @@
     zope.schema.Decimal = Decimal
 
 
+def fixNumberFormatter():
+    """Switch the number formatter to the latest version:
+
+         * The decimal symbol is optional during parsing.
+
+         * The type of the parsed number can be specified.
+
+    Target: Zope 3.4b1 and older
+    """
+    from zope.i18n import format
+    formatter = format.NumberFormat('#0.#')
+    try:
+        formatter.parse(u'123')
+    except format.NumberParseError:
+        pass
+    else:
+        # The value parsed, so the version we have is good.
+        return
+
+    import math
+    import re
+    from zope.i18n.format import (
+        INumberFormat, implements, parseNumberPattern, NumberParseError,
+        PADDING1, PADDING2, PADDING3, PADDING4, EXPONENTIAL, SUFFIX, FRACTION,
+        INTEGER, EXPONENTIAL, GROUPING, PREFIX)
+
+    class NumberFormat(object):
+        __doc__ = INumberFormat.__doc__
+
+        implements(INumberFormat)
+
+        type = None
+
+        def __init__(self, pattern=None, symbols={}):
+            # setup default symbols
+            self.symbols = {
+                u'decimal': u'.',
+                u'group': u',',
+                u'list':  u';',
+                u'percentSign': u'%',
+                u'nativeZeroDigit': u'0',
+                u'patternDigit': u'#',
+                u'plusSign': u'+',
+                u'minusSign': u'-',
+                u'exponential': u'E',
+                u'perMille': u'\xe2\x88\x9e',
+                u'infinity': u'\xef\xbf\xbd',
+                u'nan': '' }
+            self.symbols.update(symbols)
+            self._pattern = pattern
+            self._bin_pattern = None
+            if self._pattern is not None:
+                self._bin_pattern = parseNumberPattern(self._pattern)
+
+        def setPattern(self, pattern):
+            "See zope.i18n.interfaces.IFormat"
+            self._pattern = pattern
+            self._bin_pattern = parseNumberPattern(self._pattern)
+
+        def getPattern(self):
+            "See zope.i18n.interfaces.IFormat"
+            return self._pattern
+
+        def parse(self, text, pattern=None):
+            "See zope.i18n.interfaces.IFormat"
+            # Make or get binary form of datetime pattern
+            if pattern is not None:
+                bin_pattern = parseNumberPattern(pattern)
+            else:
+                bin_pattern = self._bin_pattern
+                pattern = self._pattern
+            # Determine sign
+            num_res = [None, None]
+            for sign in (0, 1):
+                regex = ''
+                if bin_pattern[sign][PADDING1] is not None:
+                    regex += '[' + bin_pattern[sign][PADDING1] + ']+'
+                if bin_pattern[sign][PREFIX] != '':
+                    regex += '[' + bin_pattern[sign][PREFIX] + ']'
+                if bin_pattern[sign][PADDING2] is not None:
+                    regex += '[' + bin_pattern[sign][PADDING2] + ']+'
+                regex += '([0-9'
+                min_size = bin_pattern[sign][INTEGER].count('0')
+                if bin_pattern[sign][GROUPING]:
+                    regex += self.symbols['group']
+                    min_size += min_size/3
+                regex += ']{%i,100}' %(min_size)
+                if bin_pattern[sign][FRACTION]:
+                    max_precision = len(bin_pattern[sign][FRACTION])
+                    min_precision = bin_pattern[sign][FRACTION].count('0')
+                    regex += '['+self.symbols['decimal']+']?'
+                    regex += '[0-9]{%i,%i}' %(min_precision, max_precision)
+                if bin_pattern[sign][EXPONENTIAL] != '':
+                    regex += self.symbols['exponential']
+                    min_exp_size = bin_pattern[sign][EXPONENTIAL].count('0')
+                    pre_symbols = self.symbols['minusSign']
+                    if bin_pattern[sign][EXPONENTIAL][0] == '+':
+                        pre_symbols += self.symbols['plusSign']
+                    regex += '[%s]?[0-9]{%i,100}' %(pre_symbols, min_exp_size)
+                regex +=')'
+                if bin_pattern[sign][PADDING3] is not None:
+                    regex += '[' + bin_pattern[sign][PADDING3] + ']+'
+                if bin_pattern[sign][SUFFIX] != '':
+                    regex += '[' + bin_pattern[sign][SUFFIX] + ']'
+                if bin_pattern[sign][PADDING4] is not None:
+                    regex += '[' + bin_pattern[sign][PADDING4] + ']+'
+                num_res[sign] = re.match(regex, text)
+
+            if num_res[0] is not None:
+                num_str = num_res[0].groups()[0]
+                sign = +1
+            elif num_res[1] is not None:
+                num_str = num_res[1].groups()[0]
+                sign = -1
+            else:
+                raise NumberParseError('Not a valid number for this pattern %r.'
+                                        % pattern)
+            # Remove possible grouping separators
+            num_str = num_str.replace(self.symbols['group'], '')
+            # Extract number
+            type = int
+            if self.symbols['decimal'] in num_str:
+                type = float
+                num_str = num_str.replace(self.symbols['decimal'], '.')
+            if self.symbols['exponential'] in num_str:
+                type = float
+                num_str = num_str.replace(self.symbols['exponential'], 'E')
+            if self.type:
+                type = self.type
+            return sign*type(num_str)
+
+        def _format_integer(self, integer, pattern):
+            size = len(integer)
+            min_size = pattern.count('0')
+            if size < min_size:
+                integer = self.symbols['nativeZeroDigit']*(min_size-size) + integer
+            return integer
+
+        def _format_fraction(self, fraction, pattern):
+            max_precision = len(pattern)
+            min_precision = pattern.count('0')
+            precision = len(fraction)
+            roundInt = False
+            if precision > max_precision:
+                round = int(fraction[max_precision]) >= 5
+                fraction = fraction[:max_precision]
+                if round:
+                    if fraction != '':
+                        # add 1 to the fraction, maintaining the decimal
+                        # precision; if the result >= 1, need to roundInt
+                        fractionLen = len(fraction)
+                        rounded = int(fraction) + 1
+                        fraction = ('%0' + str(fractionLen) + 'i') % rounded
+                        if len(fraction) > fractionLen:	# rounded fraction >= 1
+                            roundInt = True
+                            fraction = fraction[1:]
+                    else:
+                        # fraction missing, e.g. 1.5 -> 1. -- need to roundInt
+                        roundInt = True
+
+            if precision < min_precision:
+                fraction += self.symbols['nativeZeroDigit']*(min_precision -
+                                                             precision)
+            if fraction != '':
+                fraction = self.symbols['decimal'] + fraction
+            return fraction, roundInt
+
+        def format(self, obj, pattern=None):
+            "See zope.i18n.interfaces.IFormat"
+            # Make or get binary form of datetime pattern
+            if pattern is not None:
+                bin_pattern = parseNumberPattern(pattern)
+            else:
+                bin_pattern = self._bin_pattern
+            # Get positive or negative sub-pattern
+            if obj >= 0:
+                bin_pattern = bin_pattern[0]
+            else:
+                bin_pattern = bin_pattern[1]
+
+
+            if bin_pattern[EXPONENTIAL] != '':
+                obj_int_frac = str(obj).split('.')
+                # The exponential might have a mandatory sign; remove it from the
+                # bin_pattern and remember the setting
+                exp_bin_pattern = bin_pattern[EXPONENTIAL]
+                plus_sign = u''
+                if exp_bin_pattern.startswith('+'):
+                    plus_sign = self.symbols['plusSign']
+                    exp_bin_pattern = exp_bin_pattern[1:]
+                # We have to remove the possible '-' sign
+                if obj < 0:
+                    obj_int_frac[0] = obj_int_frac[0][1:]
+                if obj_int_frac[0] == '0':
+                    # abs() of number smaller 1
+                    if len(obj_int_frac) > 1:
+                        res = re.match('(0*)[0-9]*', obj_int_frac[1]).groups()[0]
+                        exponent = self._format_integer(str(len(res)+1),
+                                                        exp_bin_pattern)
+                        exponent = self.symbols['minusSign']+exponent
+                        number = obj_int_frac[1][len(res):]
+                    else:
+                        # We have exactly 0
+                        exponent = self._format_integer('0', exp_bin_pattern)
+                        number = self.symbols['nativeZeroDigit']
+                else:
+                    exponent = self._format_integer(str(len(obj_int_frac[0])-1),
+                                                    exp_bin_pattern)
+                    number = ''.join(obj_int_frac)
+
+                fraction, roundInt = self._format_fraction(number[1:],
+                                                           bin_pattern[FRACTION])
+                if roundInt:
+                    number = str(int(number[0]) + 1) + fraction
+                else:
+                    number = number[0] + fraction
+
+                # We might have a plus sign in front of the exponential integer
+                if not exponent.startswith('-'):
+                    exponent = plus_sign + exponent
+
+                pre_padding = len(bin_pattern[FRACTION]) - len(number) + 2
+                post_padding = len(exp_bin_pattern) - len(exponent)
+                number += self.symbols['exponential'] + exponent
+
+            else:
+                obj_int_frac = str(obj).split('.')
+                if len(obj_int_frac) > 1:
+                    fraction, roundInt = self._format_fraction(obj_int_frac[1],
+                                                     bin_pattern[FRACTION])
+                else:
+                    fraction = ''
+                    roundInt = False
+                if roundInt:
+                    obj = round(obj)
+                integer = self._format_integer(str(int(math.fabs(obj))),
+                                               bin_pattern[INTEGER])
+                # Adding grouping
+                if bin_pattern[GROUPING] == 1:
+                    help = ''
+                    for pos in range(1, len(integer)+1):
+                        if (pos-1)%3 == 0 and pos != 1:
+                            help = self.symbols['group'] + help
+                        help = integer[-pos] + help
+                    integer = help
+                pre_padding = len(bin_pattern[INTEGER]) - len(integer)
+                post_padding = len(bin_pattern[FRACTION]) - len(fraction)+1
+                number = integer + fraction
+
+            # Put it all together
+            text = ''
+            if bin_pattern[PADDING1] is not None and pre_padding > 0:
+                text += bin_pattern[PADDING1]*pre_padding
+            text += bin_pattern[PREFIX]
+            if bin_pattern[PADDING2] is not None and pre_padding > 0:
+                if bin_pattern[PADDING1] is not None:
+                    text += bin_pattern[PADDING2]
+                else:
+                    text += bin_pattern[PADDING2]*pre_padding
+            text += number
+            if bin_pattern[PADDING3] is not None and post_padding > 0:
+                if bin_pattern[PADDING4] is not None:
+                    text += bin_pattern[PADDING3]
+                else:
+                    text += bin_pattern[PADDING3]*post_padding
+            text += bin_pattern[SUFFIX]
+            if bin_pattern[PADDING4] is not None and post_padding > 0:
+                text += bin_pattern[PADDING4]*post_padding
+
+            # TODO: Need to make sure unicode is everywhere
+            return unicode(text)
+
+    format.NumberFormat = NumberFormat
+
+
 def apply():
     addTimeField()
     addDecimalField()
+    fixNumberFormatter()

Modified: z3c.form/trunk/src/z3c/form/configure.zcml
===================================================================
--- z3c.form/trunk/src/z3c/form/configure.zcml	2007-06-28 19:06:19 UTC (rev 77186)
+++ z3c.form/trunk/src/z3c/form/configure.zcml	2007-06-28 19:53:17 UTC (rev 77187)
@@ -34,6 +34,15 @@
       factory=".converter.FieldDataConverter"
       />
   <adapter
+      factory=".converter.IntegerDataConverter"
+      />
+  <adapter
+      factory=".converter.FloatDataConverter"
+      />
+  <adapter
+      factory=".converter.DecimalDataConverter"
+      />
+  <adapter
       factory=".converter.DateDataConverter"
       />
   <adapter

Modified: z3c.form/trunk/src/z3c/form/converter.py
===================================================================
--- z3c.form/trunk/src/z3c/form/converter.py	2007-06-28 19:06:19 UTC (rev 77186)
+++ z3c.form/trunk/src/z3c/form/converter.py	2007-06-28 19:53:17 UTC (rev 77187)
@@ -17,6 +17,8 @@
 """
 __docformat__ = "reStructuredText"
 import datetime
+import decimal
+import zope.i18n.format
 import zope.interface
 import zope.component
 import zope.schema
@@ -26,16 +28,15 @@
 from z3c.form import interfaces
 
 
-class FieldDataConverter(object):
-    """A data converter using the field's ``fromUnicode()`` method."""
-    zope.component.adapts(
-        zope.schema.interfaces.IFromUnicode, interfaces.IWidget)
+class BaseDataConverter(object):
+    """A base implementation of the data converter."""
     zope.interface.implements(interfaces.IDataConverter)
 
     def __init__(self, field, widget):
         self.field = field
         self.widget = widget
 
+
     def toWidgetValue(self, value):
         """See interfaces.IDataConverter"""
         if value is self.field.missing_value:
@@ -49,10 +50,23 @@
         return self.field.fromUnicode(value)
 
     def __repr__(self):
-        return '<DataConverter from %s to %s>' %(
-            self.field.__class__.__name__, self.widget.__class__.__name__)
+        return '<%s converts from %s to %s>' %(
+            self.__class__.__name__,
+            self.field.__class__.__name__,
+            self.widget.__class__.__name__)
 
 
+class FieldDataConverter(BaseDataConverter):
+    """A data converter using the field's ``fromUnicode()`` method."""
+    zope.component.adapts(
+        zope.schema.interfaces.IField, interfaces.IWidget)
+
+    def __init__(self, field, widget):
+        super(FieldDataConverter, self).__init__(field, widget)
+        if not zope.schema.interfaces.IFromUnicode.providedBy(field):
+            raise TypeError('Field must provide ``IFromUnicode``.')
+
+
 @zope.component.adapter(interfaces.IFieldWidget)
 @zope.interface.implementer(interfaces.IDataConverter)
 def FieldWidgetDataConverter(widget):
@@ -61,50 +75,115 @@
         (widget.field, widget), interfaces.IDataConverter)
 
 
-class DateDataConverter(FieldDataConverter):
-    """A special data converter for dates."""
-    zope.component.adapts(
-        zope.schema.interfaces.IDate, interfaces.IWidget)
+class FormatterValidationError(zope.schema.ValidationError):
 
+    def __init__(self, message, value):
+        zope.schema.ValidationError.__init__(self, message, value)
+        self.message = message
+
+    def doc(self):
+        return self.message
+
+class NumberDataConverter(BaseDataConverter):
+    """A general data converter for numbers."""
+
+    type = None
+
+    def __init__(self, field, widget):
+        super(NumberDataConverter, self).__init__(field, widget)
+        locale = self.widget.request.locale
+        self.formatter = locale.numbers.getFormatter('decimal')
+        self.formatter.type = self.type
+
+    def toWidgetValue(self, value):
+        """See interfaces.IDataConverter"""
+        if value is self.field.missing_value:
+            return u''
+        return self.formatter.format(value)
+
     def toFieldValue(self, value):
         """See interfaces.IDataConverter"""
         if value == u'':
             return self.field.missing_value
-        return datetime.date(*[int(part) for part in value.split('-')])
+        try:
+            return self.formatter.parse(value)
+        except zope.i18n.format.NumberParseError, err:
+            raise FormatterValidationError(err.args[0], value)
 
+class IntegerDataConverter(NumberDataConverter):
+    """A data converter for integers."""
+    zope.component.adapts(
+        zope.schema.interfaces.IInt, interfaces.IWidget)
+    type = int
 
-class TimeDataConverter(FieldDataConverter):
-    """A special data converter for times."""
+class FloatDataConverter(NumberDataConverter):
+    """A data converter for integers."""
     zope.component.adapts(
-        zope.schema.interfaces.ITime, interfaces.IWidget)
+        zope.schema.interfaces.IFloat, interfaces.IWidget)
+    type = float
 
+class DecimalDataConverter(NumberDataConverter):
+    """A data converter for integers."""
+    zope.component.adapts(
+        zope.schema.interfaces.IDecimal, interfaces.IWidget)
+    type = decimal.Decimal
+
+
+class CalendarDataConverter(BaseDataConverter):
+    """A special data converter for calendar-related values."""
+
+    type = None
+    length = 'short'
+
+    def __init__(self, field, widget):
+        super(CalendarDataConverter, self).__init__(field, widget)
+        locale = self.widget.request.locale
+        self.formatter = locale.dates.getFormatter(self.type, self.length)
+
+    def toWidgetValue(self, value):
+        """See interfaces.IDataConverter"""
+        if value is self.field.missing_value:
+            return u''
+        return self.formatter.format(value)
+
     def toFieldValue(self, value):
         """See interfaces.IDataConverter"""
         if value == u'':
             return self.field.missing_value
-        return datetime.time(*[int(part) for part in value.split(':')])
+        try:
+            return self.formatter.parse(value)
+        except zope.i18n.format.DateTimeParseError, err:
+            raise FormatterValidationError(err.args[0], value)
 
 
-class DatetimeDataConverter(FieldDataConverter):
+class DateDataConverter(CalendarDataConverter):
+    """A special data converter for dates."""
+    zope.component.adapts(
+        zope.schema.interfaces.IDate, interfaces.IWidget)
+    type = 'date'
+
+class TimeDataConverter(CalendarDataConverter):
+    """A special data converter for times."""
+    zope.component.adapts(
+        zope.schema.interfaces.ITime, interfaces.IWidget)
+    type = 'time'
+
+class DatetimeDataConverter(CalendarDataConverter):
     """A special data converter for datetimes."""
     zope.component.adapts(
         zope.schema.interfaces.IDatetime, interfaces.IWidget)
+    type = 'dateTime'
 
-    def toFieldValue(self, value):
-        """See interfaces.IDataConverter"""
-        if value == u'':
-            return self.field.missing_value
-        dateString, timeString = value.split(' ')
-        dt = [int(part) for part in dateString.split('-')]
-        dt += [int(part) for part in timeString.split(':')]
-        return datetime.datetime(*dt)
 
-
 class TimedeltaDataConverter(FieldDataConverter):
     """A special data converter for timedeltas."""
     zope.component.adapts(
         zope.schema.interfaces.ITimedelta, interfaces.IWidget)
 
+    def __init__(self, field, widget):
+        self.field = field
+        self.widget = widget
+
     def toFieldValue(self, value):
         """See interfaces.IDataConverter"""
         if value == u'':
@@ -116,7 +195,7 @@
         return datetime.timedelta(days, sum(seconds))
 
 
-class FileUploadDataConverter(FieldDataConverter):
+class FileUploadDataConverter(BaseDataConverter):
     """A special data converter for bytes, supporting also FileUpload.
 
     Since IBytes represents a file upload too, this converter can handle
@@ -154,7 +233,7 @@
             return unicode(value)
 
 
-class SequenceDataConverter(FieldDataConverter):
+class SequenceDataConverter(BaseDataConverter):
     """Basic data converter for ISequenceWidget."""
 
     zope.component.adapts(
@@ -183,7 +262,7 @@
         return terms.getValue(value[0])
 
 
-class CollectionSequenceDataConverter(FieldDataConverter):
+class CollectionSequenceDataConverter(BaseDataConverter):
     """A special converter between collections and sequence widgets."""
 
     zope.component.adapts(

Modified: z3c.form/trunk/src/z3c/form/converter.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/converter.txt	2007-06-28 19:06:19 UTC (rev 77186)
+++ z3c.form/trunk/src/z3c/form/converter.txt	2007-06-28 19:53:17 UTC (rev 77187)
@@ -26,6 +26,21 @@
   >>> from z3c.form import converter
   >>> conv = converter.FieldDataConverter(age, text)
 
+The field data converter is a generic data converter that can be used for all
+fields that implement ``IFromUnicode``. If, for example, a ``Date`` field
+-- which does not provide ``IFromUnicode`` -- is passed in, then a type error
+is raised:
+
+  >>> converter.FieldDataConverter(zope.schema.Date(), text)
+  Traceback (most recent call last):
+  ...
+  TypeError: Field must provide ``IFromUnicode``.
+
+However, the ``FieldDataConverter`` is registered for ``IField``, since many
+fields (like ``Decimal``) for which we want to create custom converters
+provide ``IFromUnicode`` more specifically than their characterizing interface
+(like ``IDecimal``).
+
 The converter can now convert any integer to a the value the test widget deals
 with, which is an ASCII string:
 
@@ -79,7 +94,7 @@
   >>> zope.interface.alsoProvides(text, interfaces.ITextWidget)
 
   >>> zope.component.getMultiAdapter((age, text), interfaces.IDataConverter)
-  <DataConverter from Int to Widget>
+  <FieldDataConverter converts from Int to Widget>
 
 For field-widgets there is a helper adapter that makes the lookup even
 simpler:
@@ -93,9 +108,106 @@
 we can now lookup the data converter adapter just by the field widget itself:
 
   >>> interfaces.IDataConverter(fieldtext)
-  <DataConverter from Int to Widget>
+  <FieldDataConverter converts from Int to Widget>
 
 
+Number Data Converters
+----------------------
+
+As hinted on above, the package provides a specific data converter for each of
+the three main numerical types: ``int``, ``float``, ``Decimal``. Specifically,
+those data converters support full localization of the number formatting.
+
+  >>> age = zope.schema.Int()
+  >>> intdc = converter.IntegerDataConverter(age, text)
+  >>> intdc
+  <IntegerDataConverter converts from Int to Widget>
+
+Since the age is so small, the formatting is trivial:
+
+  >>> intdc.toWidgetValue(34)
+  u'34'
+
+But if we increase the number, the grouping seprator will be used:
+
+  >>> intdc.toWidgetValue(3400)
+  u'3,400'
+
+An empty string is returned, if the missing value is passed in:
+
+  >>> intdc.toWidgetValue(None)
+  u''
+
+Of course, parsing these outputs again, works as well:
+
+  >>> intdc.toFieldValue(u'34')
+  34
+
+But if we increase the number, the grouping seprator will be used:
+
+  >>> intdc.toFieldValue(u'3,400')
+  3400
+
+Luckily our parser is somewhat forgiving, and even allows for missing group
+characters:
+
+  >>> intdc.toFieldValue(u'3400')
+  3400
+
+If an empty string is passed in, the missing value of the field is returned:
+
+  >>> intdc.toFieldValue(u'')
+
+Finally, if the input does not match at all, then a validation error is
+returned:
+
+  >>> intdc.toFieldValue(u'fff')
+  Traceback (most recent call last):
+  ...
+  FormatterValidationError:
+      ("Not a valid number for this pattern u'#,##0.###;-#,##0.###'.", u'fff')
+
+Let's now look at the float data converter.
+
+  >>> rating = zope.schema.Float()
+  >>> floatdc = converter.FloatDataConverter(rating, text)
+  >>> floatdc
+  <FloatDataConverter converts from Float to Widget>
+
+Again, you can format and parse values:
+
+  >>> floatdc.toWidgetValue(7.43)
+  u'7.43'
+  >>> floatdc.toWidgetValue(10239.43)
+  u'10,239.43'
+
+  >>> floatdc.toFieldValue(u'7.43')
+  7.4299999999999997
+  >>> floatdc.toFieldValue(u'10,239.43')
+  10239.43
+
+The decimal converter works like the other two before.
+
+  >>> money = zope.schema.Decimal()
+  >>> decimaldc = converter.DecimalDataConverter(money, text)
+  >>> decimaldc
+  <DecimalDataConverter converts from Decimal to Widget>
+
+Formatting and parsing should work just fine:
+
+  >>> import decimal
+
+  >>> decimaldc.toWidgetValue(decimal.Decimal('7.43'))
+  u'7.43'
+  >>> decimaldc.toWidgetValue(decimal.Decimal('10239.43'))
+  u'10,239.43'
+
+  >>> decimaldc.toFieldValue(u'7.43')
+  Decimal("7.43")
+  >>> decimaldc.toFieldValue(u'10,239.43')
+  Decimal("10239.43")
+
+
 Date Data Converter
 -------------------
 
@@ -107,7 +219,7 @@
 
   >>> ddc = converter.DateDataConverter(date, text)
   >>> ddc
-  <DataConverter from Date to Widget>
+  <DateDataConverter converts from Date to Widget>
 
 Dates are simply converted to ISO format:
 
@@ -115,19 +227,18 @@
   >>> bday = datetime.date(1980, 1, 25)
 
   >>> ddc.toWidgetValue(bday)
-  u'1980-01-25'
+  u'80/01/25'
 
 The converter only knows how to convert this particular format back to a
 datetime value:
 
-  >>> ddc.toFieldValue(u'1980-01-25')
+  >>> ddc.toFieldValue(u'80/01/25')
   datetime.date(1980, 1, 25)
 
 By default the converter converts missing input to missin_input value:
 
   >>> ddc.toFieldValue(u'') is None
   True
-  
 
 
 Time Data Converter
@@ -141,19 +252,19 @@
 
   >>> tdc = converter.TimeDataConverter(time, text)
   >>> tdc
-  <DataConverter from Time to Widget>
+  <TimeDataConverter converts from Time to Widget>
 
 Dates are simply converted to ISO format:
 
   >>> noon = datetime.time(12, 0, 0)
 
   >>> tdc.toWidgetValue(noon)
-  u'12:00:00'
+  u'12:00'
 
 The converter only knows how to convert this particular format back to a
 datetime value:
 
-  >>> tdc.toFieldValue(u'12:00:00')
+  >>> tdc.toFieldValue(u'12:00')
   datetime.time(12, 0)
 
 By default the converter converts missing input to missin_input value:
@@ -173,19 +284,19 @@
 
   >>> dtdc = converter.DatetimeDataConverter(dtField, text)
   >>> dtdc
-  <DataConverter from Datetime to Widget>
+  <DatetimeDataConverter converts from Datetime to Widget>
 
 Dates are simply converted to ISO format:
 
   >>> bdayNoon = datetime.datetime(1980, 1, 25, 12, 0, 0)
 
   >>> dtdc.toWidgetValue(bdayNoon)
-  u'1980-01-25 12:00:00'
+  u'80/01/25 12:00'
 
 The converter only knows how to convert this particular format back to a
 datetime value:
 
-  >>> dtdc.toFieldValue(u'1980-01-25 12:00:00')
+  >>> dtdc.toFieldValue(u'80/01/25 12:00')
   datetime.datetime(1980, 1, 25, 12, 0)
 
 By default the converter converts missing input to missin_input value:
@@ -205,7 +316,7 @@
 
   >>> tddc = converter.TimedeltaDataConverter(timedelta, text)
   >>> tddc
-  <DataConverter from Timedelta to Widget>
+  <TimedeltaDataConverter converts from Timedelta to Widget>
 
 Dates are simply converted to ISO format:
 
@@ -238,7 +349,7 @@
 
   >>> fudc = converter.FileUploadDataConverter(bytes, fileWidget)
   >>> fudc
-  <DataConverter from Bytes to FileWidget>
+  <FileUploadDataConverter converts from Bytes to FileWidget>
 
 Bytes are converted to unicode:
 
@@ -246,7 +357,7 @@
   >>> fudc.toFieldValue(simple)
   u'foobar'
 
-The converter can also convert FileUpload objects. Setup a fields storage 
+The converter can also convert FileUpload objects. Setup a fields storage
 stub...
 
   >>> class FieldStorageStub:
@@ -290,8 +401,8 @@
   >>> fudc.toFieldValue(myUpload) is None
   True
 
-There is also a ValueError if we don't get a seekable file from the FieldStorage 
-during the upload:
+There is also a ValueError if we don't get a seekable file from the
+FieldStorage during the upload:
 
   >>> myfile = ''
   >>> aFieldStorage = FieldStorageStub(myfile)
@@ -303,44 +414,13 @@
   ...
   ValueError: (u'Bytes data is not a file object', <...AttributeError...>)
 
+When the file upload widget is not used and a text-based widget is desired,
+then the regular field data converter will be chosen. Using a text widget,
+however, must be setup manually in the form with code like this::
 
+  fields['bytesField'].widgetFactory = TextWidget
 
 
-Bytes Data Converter
---------------------
-
-The IBytes field uses a file upload widget which uses a file upload data 
-converter. This requires that we test the IBytes converter for normal IWidget.
-Note that IBytes always will get a FileUpload widget, if you need to use a
-text input widget for bytes, you have to register a custom widget using the 
-widgetFactory attribute of the z3c.form.interfaces.IField. e.g.
-``fields['foobar'].widgetFactory = TextWidget``
-
-  >>> bytes = zope.schema.Bytes()
-
-  >>> fdc = converter.FieldDataConverter(bytes, text)
-  >>> fdc
-  <DataConverter from Bytes to Widget>
-
-BytesLine are simply converted to string:
-
-  >>> value = 'foobar'
-
-  >>> fdc.toWidgetValue(value)
-  u'foobar'
-
-The converter only knows how to convert this particular format back to a
-datetime value:
-
-  >>> fdc.toFieldValue(u'foobar')
-  'foobar'
-
-By default the converter converts missing input to missin_input value:
-
-  >>> fdc.toFieldValue(u'') is None
-  True
-
-
 Sequence Data Converter
 -----------------------
 



More information about the Checkins mailing list