Commits

James Crasta committed d22fb73

Make all core field types able to handle choices and multiple-choice.

Comments (0)

Files changed (2)

 
     def test_basic(self):
         d = date(2008, 5, 7)
-        form = self.F(DummyPostData(a=['2008-05-07'], b=['05/07', '2008']))
+        form = self.F(DummyPostData(a=['2008-05-07'], b=['05/07 2008']))
         self.assertEqual(form.a.data, d)
         self.assertEqual(form.a._value(), '2008-05-07')
         self.assertEqual(form.b.data, d)
     def test_basic(self):
         d = datetime(2008, 5, 5, 4, 30, 0, 0)
         # Basic test with both inputs
-        form = self.F(DummyPostData(a=['2008-05-05', '04:30:00'], b=['2008-05-05 04:30']))
+        form = self.F(DummyPostData(a=['2008-05-05 04:30:00'], b=['2008-05-05 04:30']))
         self.assertEqual(form.a.data, d)
         self.assertEqual(form.a(), """<input id="a" name="a" type="text" value="2008-05-05 04:30:00">""")
         self.assertEqual(form.b.data, d)

wtforms/fields/core.py

     option_widget = widgets.RadioInput()
 
 
-class StringField(Field):
+
+class CoercingField(SelectFieldBase):
+    """
+    Base class for all the basic fields which do simple data coercion.
+
+    This is used to allow for fields to handle choices and multiple inputs.
+    """
+    def __init__(self, label=None, validators=None, choices=None, multiple=False, **kwargs):
+        super(CoercingField, self).__init__(label, validators, **kwargs)
+        self.choices = choices
+        self.multiple = multiple
+
+    def iter_choices(self):
+        if self.choices is None:
+            raise TypeError('Choices not specified.')
+        for value, label in self.choices:
+            selected = self.data is not None and self.coerce(value) in self.data
+            yield (value, label, selected)
+
+    def process_formdata(self, valuelist):
+        if self.multiple:
+            self.data = []
+            for item in valuelist:
+                try:
+                    self.data.append(self.coerce_formdata(item))
+                except ValueError, e:
+                    self.data.append(None)
+                    self.process_errors.append(e)
+        else:
+            try:
+                if valuelist:
+                    self.data = self.coerce_formdata(valuelist[0])
+                else:
+                    self.data = self.coerce_formdata(None)
+            except ValueError:
+                self.data = None
+                raise
+
+    def coerce_formdata(self, value):
+        raise NotImplementedError()
+
+    def pre_validate(self, form):
+        if self.choices is not None:
+            for v, _ in self.choices:
+                if self.data == v:
+                    break
+            else:
+                raise ValueError(self.gettext("'%(value)s' is not a valid choice for this field") % dict(value=self.data))
+
+
+class StringField(CoercingField):
     """
     This field is the base for most of the more complicated fields, and
     represents an ``<input type="text">``.
     """
     widget = widgets.TextInput()
 
-    def process_formdata(self, valuelist):
-        if valuelist:
-            self.data = valuelist[0]
-        else:
-            self.data = ''
+    def coerce_formdata(self, value):
+        if value is None:
+            return ''
+        return value
 
     def _value(self):
         return text_type(self.data) if self.data is not None else ''
 
 
-class IntegerField(Field):
+class IntegerField(CoercingField):
     """
     A text field, except all input is coerced to an integer.  Erroneous input
     is ignored and will not be accepted as a value.
     """
     widget = widgets.TextInput()
 
-    def __init__(self, label=None, validators=None, **kwargs):
-        super(IntegerField, self).__init__(label, validators, **kwargs)
-
     def _value(self):
         if self.raw_data:
             return self.raw_data[0]
         else:
             return ''
 
-    def process_formdata(self, valuelist):
-        if valuelist:
-            try:
-                self.data = int(valuelist[0])
-            except ValueError:
-                self.data = None
-                raise ValueError(self.gettext('Not a valid integer value'))
+    def coerce_formdata(self, value):
+        if value is None:
+            return None
+        try:
+            return int(value)
+        except ValueError:
+            raise ValueError(self.gettext('Not a valid integer value'))
 
 
-class DecimalField(Field):
+class DecimalField(CoercingField):
     """
     A text field which displays and coerces data of the `decimal.Decimal` type.
 
         else:
             return ''
 
-    def process_formdata(self, valuelist):
-        if valuelist:
-            try:
-                self.data = decimal.Decimal(valuelist[0])
-            except (decimal.InvalidOperation, ValueError):
-                self.data = None
-                raise ValueError(self.gettext('Not a valid decimal value'))
+    def coerce_formdata(self, value):
+        try:
+            return decimal.Decimal(value)
+        except (decimal.InvalidOperation, ValueError):
+            raise ValueError(self.gettext('Not a valid decimal value'))
 
 
-class FloatField(Field):
+class FloatField(CoercingField):
     """
     A text field, except all input is coerced to an float.  Erroneous input
     is ignored and will not be accepted as a value.
         else:
             return ''
 
-    def process_formdata(self, valuelist):
-        if valuelist:
-            try:
-                self.data = float(valuelist[0])
-            except ValueError:
-                self.data = None
-                raise ValueError(self.gettext('Not a valid float value'))
+    def coerce_formdata(self, value):
+        if value is None:
+            return None
+        try:
+            return float(value)
+        except ValueError:
+            raise ValueError(self.gettext('Not a valid float value'))
 
 
 class BooleanField(Field):
             return 'y'
 
 
-class DateTimeField(Field):
+class DateTimeField(CoercingField):
     """
     A text field which stores a `datetime.datetime` matching a format.
     """
         else:
             return self.data and self.data.strftime(self.format) or ''
 
-    def process_formdata(self, valuelist):
-        if valuelist:
-            date_str = ' '.join(valuelist)
-            try:
-                self.data = datetime.datetime.strptime(date_str, self.format)
-            except ValueError:
-                self.data = None
-                raise ValueError(self.gettext('Not a valid datetime value'))
+    def coerce_formdata(self, value):
+        try:
+            return datetime.datetime.strptime(value, self.format)
+        except (TypeError, ValueError):
+            raise ValueError(self.gettext('Not a valid datetime value'))
 
 
 class DateField(DateTimeField):
     def __init__(self, label=None, validators=None, format='%Y-%m-%d', **kwargs):
         super(DateField, self).__init__(label, validators, format, **kwargs)
 
-    def process_formdata(self, valuelist):
-        if valuelist:
-            date_str = ' '.join(valuelist)
-            try:
-                self.data = datetime.datetime.strptime(date_str, self.format).date()
-            except ValueError:
-                self.data = None
-                raise ValueError(self.gettext('Not a valid date value'))
+    def coerce_formdata(self, value):
+        try:
+            return datetime.datetime.strptime(value, self.format).date()
+        except ValueError as e:
+            raise ValueError(self.gettext('Not a valid date value'))
 
 
 class FormField(Field):