Commits

Anonymous committed 3388236

Turn fields and widgets into packages. Tests run now, but there could still be missing imports mucking up the works.

Comments (0)

Files changed (6)

wtforms/fields.py

-import datetime
-import decimal
-import itertools
-import time
-
-from wtforms import widgets
-from wtforms.validators import StopValidation
-
-
-__all__ = (
-    'BooleanField', 'DecimalField', 'DateField', 'DateTimeField', 'FieldList',
-    'FileField', 'FloatField', 'FormField', 'HiddenField', 'IntegerField',
-    'PasswordField', 'RadioField', 'SelectField', 'SelectMultipleField',
-    'SubmitField', 'TextField', 'TextAreaField',
-)
-
-
-_unset_value = object()
-
-
-class DummyTranslations(object):
-    def gettext(self, string):
-        return string
-
-    def ngettext(self, singular, plural, n):
-        if n == 1:
-            return singular
-
-        return plural
-
-
-class Field(object):
-    """
-    Field base class
-    """
-    widget = None
-    errors = tuple()
-    process_errors = tuple()
-    _formfield = True
-    _translations = DummyTranslations()
-
-    def __new__(cls, *args, **kwargs):
-        if '_form' in kwargs and '_name' in kwargs:
-            return super(Field, cls).__new__(cls)
-        else:
-            return UnboundField(cls, *args, **kwargs)
-
-    def __init__(self, label=None, validators=None, filters=tuple(),
-                 description=u'', id=None, default=None, widget=None,
-                 _form=None, _name=None, _prefix='', _translations=None):
-        """
-        Construct a new field.
-
-        :param label:
-            The label of the field. 
-        :param validators:
-            A sequence of validators to call when `validate` is called.
-        :param filters:
-            A sequence of filters which are run on input data by `process`.
-        :param description:
-            A description for the field, typically used for help text.
-        :param id:
-            An id to use for the field. A reasonable default is set by the form,
-            and you shouldn't need to set this manually.
-        :param default:
-            The default value to assign to the field, if no form or object
-            input is provided. May be a callable.
-        :param widget:
-            If provided, overrides the widget used to render the field.
-        :param _form:
-            The form holding this field. It is passed by the form itself during
-            construction. You should never pass this value yourself.
-        :param _name:
-            The name of this field, passed by the enclosing form during its
-            construction. You should never pass this value yourself.
-        :param _prefix:
-            The prefix to prepend to the form name of this field, passed by
-            the enclosing form during construction.
-
-        If `_form` and `_name` isn't provided, an :class:`UnboundField` will be
-        returned instead. Call its :func:`bind` method with a form instance and
-        a name to construct the field.
-        """
-        self.short_name = _name
-        self.name = _prefix + _name
-        if _translations is not None:
-            self._translations = _translations
-        self.id = id or self.name
-        if label is None:
-            label = _name.replace('_', ' ').title()
-        self.label = Label(self.id, label) 
-        if validators is None:
-            validators = []
-        self.validators = validators
-        self.filters = filters
-        self.description = description
-        self.type = type(self).__name__
-        self.default = default
-        self.raw_data = None
-        if widget:
-            self.widget = widget
-        self.flags = Flags()
-        for v in validators:
-            flags = getattr(v, 'field_flags', ())
-            for f in flags:
-                setattr(self.flags, f, True)
-
-    def __unicode__(self):
-        """
-        Returns a HTML representation of the field. For more powerful rendering,
-        see the `__call__` method.
-        """
-        return self()
-
-    def __str__(self):
-        """
-        Returns a HTML representation of the field. For more powerful rendering,
-        see the `__call__` method.
-        """
-        return self()
-
-    def __html__(self):
-        """
-        Returns a HTML representation of the field. For more powerful rendering,
-        see the `__call__` method.
-        """
-        return self()
-
-    def __call__(self, **kwargs):
-        """
-        Render this field as HTML, using keyword args as additional attributes.
-
-        Any HTML attribute passed to the method will be added to the tag
-        and entity-escaped properly.
-        """
-        return self.widget(self, **kwargs)
-
-    def gettext(self, string):
-        return self._translations.gettext(string)
-
-    def ngettext(self, singular, plural, n):
-        return self._translations.ngettext(singular, plural, n)
-
-    def validate(self, form, extra_validators=tuple()):
-        """
-        Validates the field and returns True or False. `self.errors` will
-        contain any errors raised during validation. This is usually only
-        called by `Form.validate`.
-
-        Subfields shouldn't override this, but rather override either
-        `pre_validate`, `post_validate` or both, depending on needs.
-
-        :param form: The form the field belongs to.
-        :param extra_validators: A list of extra validators to run.
-        """
-        self.errors = list(self.process_errors)
-        stop_validation = False
-
-        # Call pre_validate
-        try:
-            self.pre_validate(form)
-        except StopValidation, e:
-            if e.args and e.args[0]:
-                self.errors.append(e.args[0])
-            stop_validation = True
-        except ValueError, e:
-            self.errors.append(e.args[0])
-
-        # Run validators
-        if not stop_validation:
-            for validator in itertools.chain(self.validators, extra_validators):
-                try:
-                    validator(form, self)
-                except StopValidation, e:
-                    if e.args and e.args[0]:
-                        self.errors.append(e.args[0])
-                    stop_validation = True
-                    break
-                except ValueError, e:
-                    self.errors.append(e.args[0])
-
-        # Call post_validate
-        try:
-            self.post_validate(form, stop_validation)
-        except ValueError, e:
-            self.errors.append(e.args[0])
-
-        return len(self.errors) == 0
-
-    def pre_validate(self, form):
-        """
-        Override if you need field-level validation. Runs before any other
-        validators.
-
-        :param form: The form the field belongs to.
-        """
-        pass
-
-    def post_validate(self, form, validation_stopped):
-        """
-        Override if you need to run any field-level validation tasks after
-        normal validation. This shouldn't be needed in most cases.
-
-        :param form: The form the field belongs to.
-        :param validation_stopped:
-            `True` if any validator raised StopValidation.
-        """
-        pass
-
-    def process(self, formdata, data=_unset_value):
-        """
-        Process incoming data, calling process_data, process_formdata as needed,
-        and run filters.
-
-        If `data` is not provided, process_data will be called on the field's
-        default.
-
-        Field subclasses usually won't override this, instead overriding the
-        process_formdata and process_data methods. Only override this for
-        special advanced processing, such as when a field encapsulates many
-        inputs.
-        """
-        self.process_errors = []
-        if data is _unset_value:
-            try:
-                data = self.default()
-            except TypeError:
-                data = self.default
-        try:
-            self.process_data(data)
-        except ValueError, e:
-            self.process_errors.append(e.args[0])
-
-        if formdata:
-            try:
-                if self.name in formdata:
-                    self.raw_data = formdata.getlist(self.name)
-                else:
-                    self.raw_data = []
-                self.process_formdata(self.raw_data)
-            except ValueError, e:
-                self.process_errors.append(e.args[0])
-
-        for filter in self.filters:
-            try:
-                self.data = filter(self.data)
-            except ValueError, e:
-                self.process_errors.append(e.args[0])
-
-    def process_data(self, value):
-        """
-        Process the Python data applied to this field and store the result.
-
-        This will be called during form construction by the form's `kwargs` or
-        `obj` argument.
-
-        :param value: The python object containing the value to process.
-        """
-        self.data = value
-
-    def process_formdata(self, valuelist):
-        """
-        Process data received over the wire from a form.
-
-        This will be called during form construction with data supplied
-        through the `formdata` argument.
-
-        :param valuelist: A list of strings to process.
-        """
-        if valuelist:
-            self.data = valuelist[0]
-
-    def populate_obj(self, obj, name):
-        """
-        Populates `obj.<name>` with the field's data.
-
-        :note: This is a destructive operation. If `obj.<name>` already exists,
-               it will be overridden. Use with caution.
-        """
-        setattr(obj, name, self.data)
-
-
-class UnboundField(object):
-    _formfield = True
-    creation_counter = 0
-
-    def __init__(self, field_class, *args, **kwargs):
-        UnboundField.creation_counter += 1
-        self.field_class = field_class
-        self.args = args
-        self.kwargs = kwargs
-        self.creation_counter = UnboundField.creation_counter
-
-    def bind(self, form, name, prefix='', translations=None, **kwargs):
-        return self.field_class(_form=form, _prefix=prefix, _name=name, _translations=translations, *self.args, **dict(self.kwargs, **kwargs))
-
-    def __repr__(self):
-        return '<UnboundField(%s, %r, %r)>' % (self.field_class.__name__, self.args, self.kwargs)
-
-
-class Flags(object):
-    """
-    Holds a set of boolean flags as attributes.
-
-    Accessing a non-existing attribute returns False for its value.
-    """
-    def __getattr__(self, name):
-        return False
-
-    def __contains__(self, name):
-        return getattr(self, name)
-
-    def __repr__(self):
-        flags = (name for name in dir(self) if not name.startswith('_'))
-        return '<wtforms.fields.Flags: {%s}>' % ', '.join(flags)
-
-
-class Label(object):
-    """
-    An HTML form label.
-    """
-    def __init__(self, field_id, text):
-        self.field_id = field_id
-        self.text = text
-
-    def __str__(self):
-        return self()
-
-    def __unicode__(self):
-        return self()
-
-    def __html__(self):
-        return self()
-
-    def __call__(self, text=None, **kwargs):
-        kwargs['for'] = self.field_id
-        attributes = widgets.html_params(**kwargs)
-        return widgets.HTMLString(u'<label %s>%s</label>' % (attributes, text or self.text))
-
-    def __repr__(self):
-        return 'Label(%r, %r)' % (self.field_id, self.text)
-
-
-class SelectFieldBase(Field):
-    option_widget = widgets.Option()
-
-    """
-    Base class for fields which can be iterated to produce options.
-
-    This isn't a field, but an abstract base class for fields which want to
-    provide this functionality.
-    """
-    def __init__(self, label=None, validators=None, option_widget=None, **kwargs):
-        super(SelectFieldBase, self).__init__(label, validators, **kwargs)
-
-        if option_widget is not None:
-            self.option_widget = option_widget
-
-    def iter_choices(self):
-        """
-        Provides data for choice widget rendering. Must return a sequence or
-        iterable of (value, label, selected) tuples.
-        """
-        raise NotImplementedError()
-
-    def __iter__(self):
-        opts = dict(widget=self.option_widget, _name=self.name, _form=None)
-        for i, (value, label, checked) in enumerate(self.iter_choices()):
-            opt = self._Option(label=label, id=u'%s-%d' % (self.id, i), **opts)
-            opt.process(None, value)
-            opt.checked = checked
-            yield opt
-
-    class _Option(Field):
-        checked = False
-
-        def _value(self):
-            return self.data
-
-
-class SelectField(SelectFieldBase):
-    widget = widgets.Select()
-
-    def __init__(self, label=None, validators=None, coerce=unicode, choices=None, **kwargs):
-        super(SelectField, self).__init__(label, validators, **kwargs)
-        self.coerce = coerce
-        self.choices = choices
-
-    def iter_choices(self):
-        for value, label in self.choices:
-            yield (value, label, self.coerce(value) == self.data)
-
-    def process_data(self, value):
-        try:
-            self.data = self.coerce(value)
-        except (ValueError, TypeError):
-            self.data = None
-
-    def process_formdata(self, valuelist):
-        if valuelist:
-            try:
-                self.data = self.coerce(valuelist[0])
-            except ValueError:
-                raise ValueError(self.gettext(u'Invalid Choice: could not coerce'))
-
-    def pre_validate(self, form):
-        for v, _ in self.choices:
-            if self.data == v:
-                break
-        else:
-            raise ValueError(self.gettext(u'Not a valid choice'))
-
-
-class SelectMultipleField(SelectField):
-    """
-    No different from a normal select field, except this one can take (and
-    validate) multiple choices.  You'll need to specify the HTML `rows`
-    attribute to the select field when rendering.
-    """
-    widget = widgets.Select(multiple=True)
-
-    def iter_choices(self):
-        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_data(self, value):
-        try:
-            self.data = list(self.coerce(v) for v in value)
-        except (ValueError, TypeError):
-            self.data = None
-
-    def process_formdata(self, valuelist):
-        try:
-            self.data = list(self.coerce(x) for x in valuelist)
-        except ValueError:
-            raise ValueError(self.gettext(u'Invalid choice(s): one or more data inputs could not be coerced'))
-
-    def pre_validate(self, form):
-        if self.data:
-            values = list(c[0] for c in self.choices)
-            for d in self.data:
-                if d not in values:
-                    raise ValueError(self.gettext(u"'%(value)s' is not a valid choice for this field") % dict(value=d))
-
-
-class RadioField(SelectField):
-    """
-    Like a SelectField, except displays a list of radio buttons.
-
-    Iterating the field will produce subfields (each containing a label as
-    well) in order to allow custom rendering of the individual radio fields.
-    """
-    widget = widgets.ListWidget(prefix_label=False)
-    option_widget = widgets.RadioInput()
-
-
-class TextField(Field):
-    """
-    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 = u''
-
-    def _value(self):
-        return self.data is not None and unicode(self.data) or u''
-
-
-class HiddenField(TextField):
-    """
-    Represents an ``<input type="hidden">``.
-    """
-    widget = widgets.HiddenInput()
-
-
-class TextAreaField(TextField):
-    """
-    This field represents an HTML ``<textarea>`` and can be used to take
-    multi-line input.
-    """
-    widget = widgets.TextArea()
-
-
-class PasswordField(TextField):
-    """
-    Represents an ``<input type="password">``.
-    """
-    widget = widgets.PasswordInput()
-
-
-class FileField(TextField):
-    """
-    Can render a file-upload field.  Will take any passed filename value, if
-    any is sent by the browser in the post params.  This field will NOT
-    actually handle the file upload portion, as wtforms does not deal with
-    individual frameworks' file handling capabilities.
-    """
-    widget = widgets.FileInput()
-
-
-class IntegerField(TextField):
-    """
-    A text field, except all input is coerced to an integer.  Erroneous input
-    is ignored and will not be accepted as a value.
-    """
-    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]
-        elif self.data is not None:
-            return unicode(self.data)
-        else:
-            return u''
-
-    def process_formdata(self, valuelist):
-        if valuelist:
-            try:
-                self.data = int(valuelist[0])
-            except ValueError:
-                raise ValueError(self.gettext(u'Not a valid integer value'))
-
-
-class DecimalField(TextField):
-    """
-    A text field which displays and coerces data of the `decimal.Decimal` type.
-
-    :param places:
-        How many decimal places to quantize the value to for display on form.
-        If None, does not quantize value.
-    :param rounding:
-        How to round the value during quantize, for example
-        `decimal.ROUND_UP`. If unset, uses the rounding value from the
-        current thread's context.
-    """
-
-    def __init__(self, label=None, validators=None, places=2, rounding=None, **kwargs):
-        super(DecimalField, self).__init__(label, validators, **kwargs)
-        self.places = places
-        self.rounding = rounding
-
-    def _value(self):
-        if self.raw_data:
-            return self.raw_data[0]
-        elif self.data is not None:
-            if self.places is not None:
-                if hasattr(self.data, 'quantize'):
-                    exp = decimal.Decimal('.1') ** self.places
-                    quantized = self.data.quantize(exp, rounding=self.rounding)
-                    return unicode(quantized)
-                else:
-                    # If for some reason, data is a float or int, then format
-                    # as we would for floats using string formatting.
-                    format = u'%%0.%df' % self.places
-                    return format % self.data
-            else:
-                return unicode(self.data)
-        else:
-            return u''
-
-    def process_formdata(self, valuelist):
-        if valuelist:
-            try:
-                self.data = decimal.Decimal(valuelist[0])
-            except (decimal.InvalidOperation, ValueError):
-                raise ValueError(self.gettext(u'Not a valid decimal value'))
-
-
-class FloatField(TextField):
-    """
-    A text field, except all input is coerced to an float.  Erroneous input
-    is ignored and will not be accepted as a value.
-    """
-    def __init__(self, label=None, validators=None, **kwargs):
-        super(FloatField, self).__init__(label, validators, **kwargs)
-
-    def _value(self):
-        if self.raw_data:
-            return self.raw_data[0]
-        elif self.data is not None:
-            return unicode(self.data)
-        else:
-            return u''
-
-    def process_formdata(self, valuelist):
-        if valuelist:
-            try:
-                self.data = float(valuelist[0])
-            except ValueError:
-                raise ValueError(self.gettext(u'Not a valid float value'))
-
-
-class BooleanField(Field):
-    """
-    Represents an ``<input type="checkbox">``.
-    """
-    widget = widgets.CheckboxInput()
-
-    def __init__(self, label=None, validators=None, **kwargs):
-        super(BooleanField, self).__init__(label, validators, **kwargs)
-
-    def process_data(self, value):
-        self.data = bool(value)
-
-    def process_formdata(self, valuelist):
-        self.data = bool(valuelist)
-
-    def _value(self):
-        if self.raw_data:
-            return unicode(self.raw_data[0])
-        else:
-            return u'y'
-
-
-class DateTimeField(Field):
-    """
-    A text field which stores a `datetime.datetime` matching a format.
-    """
-    widget = widgets.TextInput()
-
-    def __init__(self, label=None, validators=None, format='%Y-%m-%d %H:%M:%S', **kwargs):
-        super(DateTimeField, self).__init__(label, validators, **kwargs)
-        self.format = format
-
-    def _value(self):
-        if self.raw_data:
-            return u' '.join(self.raw_data)
-        else:
-            return self.data and self.data.strftime(self.format) or u''
-
-    def process_formdata(self, valuelist):
-        if valuelist:
-            date_str = u' '.join(valuelist)
-            try:
-                timetuple = time.strptime(date_str, self.format)
-                self.data = datetime.datetime(*timetuple[:6])
-            except ValueError:
-                self.data = None
-                raise
-
-
-class DateField(DateTimeField):
-    """
-    Same as DateTimeField, except stores a `datetime.date`.
-    """
-    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 = u' '.join(valuelist)
-            try:
-                timetuple = time.strptime(date_str, self.format)
-                self.data = datetime.date(*timetuple[:3])
-            except ValueError:
-                self.data = None
-                raise
-
-
-class SubmitField(BooleanField):
-    """
-    Represents an ``<input type="submit">``.  This allows checking if a given
-    submit button has been pressed.
-    """
-    widget = widgets.SubmitInput()
-
-
-class FormField(Field):
-    """
-    Encapsulate a form as a field in another form.
-
-    :param form_class:
-        A subclass of Form that will be encapsulated.
-    :param separator:
-        A string which will be suffixed to this field's name to create the
-        prefix to enclosed fields. The default is fine for most uses.
-    """
-    widget = widgets.TableWidget()
-
-    def __init__(self, form_class, label=None, validators=None, separator='-', **kwargs):
-        super(FormField, self).__init__(label, validators, **kwargs)
-        self.form_class = form_class
-        self.separator = separator
-        self._obj = None
-        if self.filters:
-            raise TypeError('FormField cannot take filters, as the encapsulated data is not mutable.')
-        if validators:
-            raise TypeError('FormField does not accept any validators. Instead, define them on the enclosed form.')
-
-    def process(self, formdata, data=_unset_value):
-        if data is _unset_value:
-            try:
-                data = self.default()
-            except TypeError:
-                data = self.default
-            self._obj = data
-
-        prefix = self.name + self.separator
-        if isinstance(data, dict):
-            self.form = self.form_class(formdata=formdata, prefix=prefix, **data)
-        else:
-            self.form = self.form_class(formdata=formdata, obj=data, prefix=prefix)
-
-    def validate(self, form, extra_validators=tuple()):
-        if extra_validators:
-            raise TypeError('FormField does not accept in-line validators, as it gets errors from the enclosed form.')
-        return self.form.validate()
-
-    def populate_obj(self, obj, name):
-        candidate = getattr(obj, name, None)
-        if candidate is None:
-            if self._obj is None:
-                raise TypeError('populate_obj: cannot find a value to populate from the provided obj or input data/defaults')
-            candidate = self._obj
-            setattr(obj, name, candidate)
-
-        self.form.populate_obj(candidate)
-
-    def __iter__(self):
-        return iter(self.form)
-
-    def __getitem__(self, name):
-        return self.form[name]
-
-    def __getattr__(self, name):
-        return getattr(self.form, name)
-
-    @property
-    def data(self):
-        return self.form.data
-
-    @property
-    def errors(self):
-        return self.form.errors
-
-
-class FieldList(Field):
-    """
-    Encapsulate an ordered list of multiple instances of the same field type,
-    keeping data as a list.
-
-    >>> authors = FieldList(TextField('Name', [validators.required()]))
-
-    :param unbound_field:
-        A partially-instantiated field definition, just like that would be
-        defined on a form directly.
-    :param min_entries:
-        if provided, always have at least this many entries on the field,
-        creating blank ones if the provided input does not specify a sufficient
-        amount.
-    :param max_entries:
-        accept no more than this many entries as input, even if more exist in
-        formdata.
-    """
-    widget=widgets.ListWidget()
-
-    def __init__(self, unbound_field, label=None, validators=None, min_entries=0,
-                 max_entries=None, default=tuple(), **kwargs):
-        super(FieldList, self).__init__(label, validators, default=default, **kwargs)
-        if self.filters:
-            raise TypeError('FieldList does not accept any filters. Instead, define them on the enclosed field.')
-        if validators:
-            raise TypeError('FieldList does not accept any validators. Instead, define them on the enclosed field.')
-        assert isinstance(unbound_field, UnboundField), 'Field must be unbound, not a field class'
-        self.unbound_field = unbound_field
-        self.min_entries = min_entries
-        self.max_entries = max_entries
-        self.last_index = -1
-        self._prefix = kwargs.get('_prefix', '')
-
-    def process(self, formdata, data=_unset_value):
-        self.entries = []
-        if data is _unset_value or not data:
-            try:
-                data = self.default()
-            except TypeError:
-                data = self.default
-
-        if formdata:
-            indices = sorted(set(self._extract_indices(self.name, formdata)))
-            if self.max_entries:
-                indices = indices[:self.max_entries]
-
-            idata = iter(data)
-            for index in indices:
-                try:
-                    obj_data = idata.next()
-                except StopIteration:
-                    obj_data = _unset_value
-                self._add_entry(formdata, obj_data, index=index)
-        else:
-            for obj_data in data:
-                self._add_entry(formdata, obj_data)
-
-        while len(self.entries) < self.min_entries:
-            self._add_entry(formdata)
-
-    def _extract_indices(self, prefix, formdata):
-        """
-        Yield indices of any keys with given prefix.
-
-        formdata must be an object which will produce keys when iterated.  For
-        example, if field 'foo' contains keys 'foo-0-bar', 'foo-1-baz', then
-        the numbers 0 and 1 will be yielded, but not neccesarily in order.
-        """
-        offset = len(prefix) + 1
-        for k in formdata:
-            if k.startswith(prefix):
-                k = k[offset:].split('-', 1)[0]
-                if k.isdigit():
-                    yield int(k)
-
-    def validate(self, form, extra_validators=tuple()):
-        self.errors = []
-        success = True
-        for subfield in self.entries:
-            if not subfield.validate(form):
-                success = False
-                self.errors.append(subfield.errors)
-        return success
-
-    def populate_obj(self, obj, name):
-        values = getattr(obj, name, None)
-        try:
-            ivalues = iter(values)
-        except TypeError:
-            ivalues = iter([])
-
-        candidates = itertools.chain(ivalues, itertools.repeat(None))
-        _fake = type('_fake', (object, ), {})
-        output = []
-        for field, data in itertools.izip(self.entries, candidates):
-            fake_obj = _fake()
-            fake_obj.data = data
-            field.populate_obj(fake_obj, 'data')
-            output.append(fake_obj.data)
-
-        setattr(obj, name, output)
-
-    def _add_entry(self, formdata=None, data=_unset_value, index=None):
-        assert not self.max_entries or len(self.entries) < self.max_entries, \
-            'You cannot have more than max_entries entries in this FieldList'
-        new_index = self.last_index = index or (self.last_index + 1)
-        name = '%s-%d' % (self.short_name, new_index)
-        id   = '%s-%d' % (self.id, new_index)
-        field = self.unbound_field.bind(form=None, name=name, prefix=self._prefix, id=id)
-        field.process(formdata, data)
-        self.entries.append(field)
-        return field
-
-    def append_entry(self, data=_unset_value):
-        """
-        Create a new entry with optional default data.
-
-        Entries added in this way will *not* receive formdata however, and can
-        only receive object data.
-        """
-        return self._add_entry(data=data)
-
-    def pop_entry(self):
-        """ Removes the last entry from the list and returns it. """
-        entry = self.entries.pop()
-        self.last_index -= 1
-        return entry
-
-    def __iter__(self):
-        return iter(self.entries)
-
-    def __len__(self):
-        return len(self.entries)
-
-    def __getitem__(self, index):
-        return self.entries[index]
-
-    @property
-    def data(self):
-        return [f.data for f in self.entries]

wtforms/fields/__init__.py

+from wtforms.fields.core import *
+
+# Compatibility imports
+from wtforms.fields.core import Label, Field, _unset_value

wtforms/fields/core.py

+import datetime
+import decimal
+import itertools
+import time
+
+from wtforms import widgets
+from wtforms.validators import StopValidation
+
+
+__all__ = (
+    'BooleanField', 'DecimalField', 'DateField', 'DateTimeField', 'FieldList',
+    'FileField', 'FloatField', 'FormField', 'HiddenField', 'IntegerField',
+    'PasswordField', 'RadioField', 'SelectField', 'SelectMultipleField',
+    'SubmitField', 'TextField', 'TextAreaField',
+)
+
+
+_unset_value = object()
+
+
+class DummyTranslations(object):
+    def gettext(self, string):
+        return string
+
+    def ngettext(self, singular, plural, n):
+        if n == 1:
+            return singular
+
+        return plural
+
+
+class Field(object):
+    """
+    Field base class
+    """
+    widget = None
+    errors = tuple()
+    process_errors = tuple()
+    _formfield = True
+    _translations = DummyTranslations()
+
+    def __new__(cls, *args, **kwargs):
+        if '_form' in kwargs and '_name' in kwargs:
+            return super(Field, cls).__new__(cls)
+        else:
+            return UnboundField(cls, *args, **kwargs)
+
+    def __init__(self, label=None, validators=None, filters=tuple(),
+                 description=u'', id=None, default=None, widget=None,
+                 _form=None, _name=None, _prefix='', _translations=None):
+        """
+        Construct a new field.
+
+        :param label:
+            The label of the field. 
+        :param validators:
+            A sequence of validators to call when `validate` is called.
+        :param filters:
+            A sequence of filters which are run on input data by `process`.
+        :param description:
+            A description for the field, typically used for help text.
+        :param id:
+            An id to use for the field. A reasonable default is set by the form,
+            and you shouldn't need to set this manually.
+        :param default:
+            The default value to assign to the field, if no form or object
+            input is provided. May be a callable.
+        :param widget:
+            If provided, overrides the widget used to render the field.
+        :param _form:
+            The form holding this field. It is passed by the form itself during
+            construction. You should never pass this value yourself.
+        :param _name:
+            The name of this field, passed by the enclosing form during its
+            construction. You should never pass this value yourself.
+        :param _prefix:
+            The prefix to prepend to the form name of this field, passed by
+            the enclosing form during construction.
+
+        If `_form` and `_name` isn't provided, an :class:`UnboundField` will be
+        returned instead. Call its :func:`bind` method with a form instance and
+        a name to construct the field.
+        """
+        self.short_name = _name
+        self.name = _prefix + _name
+        if _translations is not None:
+            self._translations = _translations
+        self.id = id or self.name
+        if label is None:
+            label = _name.replace('_', ' ').title()
+        self.label = Label(self.id, label) 
+        if validators is None:
+            validators = []
+        self.validators = validators
+        self.filters = filters
+        self.description = description
+        self.type = type(self).__name__
+        self.default = default
+        self.raw_data = None
+        if widget:
+            self.widget = widget
+        self.flags = Flags()
+        for v in validators:
+            flags = getattr(v, 'field_flags', ())
+            for f in flags:
+                setattr(self.flags, f, True)
+
+    def __unicode__(self):
+        """
+        Returns a HTML representation of the field. For more powerful rendering,
+        see the `__call__` method.
+        """
+        return self()
+
+    def __str__(self):
+        """
+        Returns a HTML representation of the field. For more powerful rendering,
+        see the `__call__` method.
+        """
+        return self()
+
+    def __html__(self):
+        """
+        Returns a HTML representation of the field. For more powerful rendering,
+        see the `__call__` method.
+        """
+        return self()
+
+    def __call__(self, **kwargs):
+        """
+        Render this field as HTML, using keyword args as additional attributes.
+
+        Any HTML attribute passed to the method will be added to the tag
+        and entity-escaped properly.
+        """
+        return self.widget(self, **kwargs)
+
+    def gettext(self, string):
+        return self._translations.gettext(string)
+
+    def ngettext(self, singular, plural, n):
+        return self._translations.ngettext(singular, plural, n)
+
+    def validate(self, form, extra_validators=tuple()):
+        """
+        Validates the field and returns True or False. `self.errors` will
+        contain any errors raised during validation. This is usually only
+        called by `Form.validate`.
+
+        Subfields shouldn't override this, but rather override either
+        `pre_validate`, `post_validate` or both, depending on needs.
+
+        :param form: The form the field belongs to.
+        :param extra_validators: A list of extra validators to run.
+        """
+        self.errors = list(self.process_errors)
+        stop_validation = False
+
+        # Call pre_validate
+        try:
+            self.pre_validate(form)
+        except StopValidation, e:
+            if e.args and e.args[0]:
+                self.errors.append(e.args[0])
+            stop_validation = True
+        except ValueError, e:
+            self.errors.append(e.args[0])
+
+        # Run validators
+        if not stop_validation:
+            for validator in itertools.chain(self.validators, extra_validators):
+                try:
+                    validator(form, self)
+                except StopValidation, e:
+                    if e.args and e.args[0]:
+                        self.errors.append(e.args[0])
+                    stop_validation = True
+                    break
+                except ValueError, e:
+                    self.errors.append(e.args[0])
+
+        # Call post_validate
+        try:
+            self.post_validate(form, stop_validation)
+        except ValueError, e:
+            self.errors.append(e.args[0])
+
+        return len(self.errors) == 0
+
+    def pre_validate(self, form):
+        """
+        Override if you need field-level validation. Runs before any other
+        validators.
+
+        :param form: The form the field belongs to.
+        """
+        pass
+
+    def post_validate(self, form, validation_stopped):
+        """
+        Override if you need to run any field-level validation tasks after
+        normal validation. This shouldn't be needed in most cases.
+
+        :param form: The form the field belongs to.
+        :param validation_stopped:
+            `True` if any validator raised StopValidation.
+        """
+        pass
+
+    def process(self, formdata, data=_unset_value):
+        """
+        Process incoming data, calling process_data, process_formdata as needed,
+        and run filters.
+
+        If `data` is not provided, process_data will be called on the field's
+        default.
+
+        Field subclasses usually won't override this, instead overriding the
+        process_formdata and process_data methods. Only override this for
+        special advanced processing, such as when a field encapsulates many
+        inputs.
+        """
+        self.process_errors = []
+        if data is _unset_value:
+            try:
+                data = self.default()
+            except TypeError:
+                data = self.default
+        try:
+            self.process_data(data)
+        except ValueError, e:
+            self.process_errors.append(e.args[0])
+
+        if formdata:
+            try:
+                if self.name in formdata:
+                    self.raw_data = formdata.getlist(self.name)
+                else:
+                    self.raw_data = []
+                self.process_formdata(self.raw_data)
+            except ValueError, e:
+                self.process_errors.append(e.args[0])
+
+        for filter in self.filters:
+            try:
+                self.data = filter(self.data)
+            except ValueError, e:
+                self.process_errors.append(e.args[0])
+
+    def process_data(self, value):
+        """
+        Process the Python data applied to this field and store the result.
+
+        This will be called during form construction by the form's `kwargs` or
+        `obj` argument.
+
+        :param value: The python object containing the value to process.
+        """
+        self.data = value
+
+    def process_formdata(self, valuelist):
+        """
+        Process data received over the wire from a form.
+
+        This will be called during form construction with data supplied
+        through the `formdata` argument.
+
+        :param valuelist: A list of strings to process.
+        """
+        if valuelist:
+            self.data = valuelist[0]
+
+    def populate_obj(self, obj, name):
+        """
+        Populates `obj.<name>` with the field's data.
+
+        :note: This is a destructive operation. If `obj.<name>` already exists,
+               it will be overridden. Use with caution.
+        """
+        setattr(obj, name, self.data)
+
+
+class UnboundField(object):
+    _formfield = True
+    creation_counter = 0
+
+    def __init__(self, field_class, *args, **kwargs):
+        UnboundField.creation_counter += 1
+        self.field_class = field_class
+        self.args = args
+        self.kwargs = kwargs
+        self.creation_counter = UnboundField.creation_counter
+
+    def bind(self, form, name, prefix='', translations=None, **kwargs):
+        return self.field_class(_form=form, _prefix=prefix, _name=name, _translations=translations, *self.args, **dict(self.kwargs, **kwargs))
+
+    def __repr__(self):
+        return '<UnboundField(%s, %r, %r)>' % (self.field_class.__name__, self.args, self.kwargs)
+
+
+class Flags(object):
+    """
+    Holds a set of boolean flags as attributes.
+
+    Accessing a non-existing attribute returns False for its value.
+    """
+    def __getattr__(self, name):
+        return False
+
+    def __contains__(self, name):
+        return getattr(self, name)
+
+    def __repr__(self):
+        flags = (name for name in dir(self) if not name.startswith('_'))
+        return '<wtforms.fields.Flags: {%s}>' % ', '.join(flags)
+
+
+class Label(object):
+    """
+    An HTML form label.
+    """
+    def __init__(self, field_id, text):
+        self.field_id = field_id
+        self.text = text
+
+    def __str__(self):
+        return self()
+
+    def __unicode__(self):
+        return self()
+
+    def __html__(self):
+        return self()
+
+    def __call__(self, text=None, **kwargs):
+        kwargs['for'] = self.field_id
+        attributes = widgets.html_params(**kwargs)
+        return widgets.HTMLString(u'<label %s>%s</label>' % (attributes, text or self.text))
+
+    def __repr__(self):
+        return 'Label(%r, %r)' % (self.field_id, self.text)
+
+
+class SelectFieldBase(Field):
+    option_widget = widgets.Option()
+
+    """
+    Base class for fields which can be iterated to produce options.
+
+    This isn't a field, but an abstract base class for fields which want to
+    provide this functionality.
+    """
+    def __init__(self, label=None, validators=None, option_widget=None, **kwargs):
+        super(SelectFieldBase, self).__init__(label, validators, **kwargs)
+
+        if option_widget is not None:
+            self.option_widget = option_widget
+
+    def iter_choices(self):
+        """
+        Provides data for choice widget rendering. Must return a sequence or
+        iterable of (value, label, selected) tuples.
+        """
+        raise NotImplementedError()
+
+    def __iter__(self):
+        opts = dict(widget=self.option_widget, _name=self.name, _form=None)
+        for i, (value, label, checked) in enumerate(self.iter_choices()):
+            opt = self._Option(label=label, id=u'%s-%d' % (self.id, i), **opts)
+            opt.process(None, value)
+            opt.checked = checked
+            yield opt
+
+    class _Option(Field):
+        checked = False
+
+        def _value(self):
+            return self.data
+
+
+class SelectField(SelectFieldBase):
+    widget = widgets.Select()
+
+    def __init__(self, label=None, validators=None, coerce=unicode, choices=None, **kwargs):
+        super(SelectField, self).__init__(label, validators, **kwargs)
+        self.coerce = coerce
+        self.choices = choices
+
+    def iter_choices(self):
+        for value, label in self.choices:
+            yield (value, label, self.coerce(value) == self.data)
+
+    def process_data(self, value):
+        try:
+            self.data = self.coerce(value)
+        except (ValueError, TypeError):
+            self.data = None
+
+    def process_formdata(self, valuelist):
+        if valuelist:
+            try:
+                self.data = self.coerce(valuelist[0])
+            except ValueError:
+                raise ValueError(self.gettext(u'Invalid Choice: could not coerce'))
+
+    def pre_validate(self, form):
+        for v, _ in self.choices:
+            if self.data == v:
+                break
+        else:
+            raise ValueError(self.gettext(u'Not a valid choice'))
+
+
+class SelectMultipleField(SelectField):
+    """
+    No different from a normal select field, except this one can take (and
+    validate) multiple choices.  You'll need to specify the HTML `rows`
+    attribute to the select field when rendering.
+    """
+    widget = widgets.Select(multiple=True)
+
+    def iter_choices(self):
+        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_data(self, value):
+        try:
+            self.data = list(self.coerce(v) for v in value)
+        except (ValueError, TypeError):
+            self.data = None
+
+    def process_formdata(self, valuelist):
+        try:
+            self.data = list(self.coerce(x) for x in valuelist)
+        except ValueError:
+            raise ValueError(self.gettext(u'Invalid choice(s): one or more data inputs could not be coerced'))
+
+    def pre_validate(self, form):
+        if self.data:
+            values = list(c[0] for c in self.choices)
+            for d in self.data:
+                if d not in values:
+                    raise ValueError(self.gettext(u"'%(value)s' is not a valid choice for this field") % dict(value=d))
+
+
+class RadioField(SelectField):
+    """
+    Like a SelectField, except displays a list of radio buttons.
+
+    Iterating the field will produce subfields (each containing a label as
+    well) in order to allow custom rendering of the individual radio fields.
+    """
+    widget = widgets.ListWidget(prefix_label=False)
+    option_widget = widgets.RadioInput()
+
+
+class TextField(Field):
+    """
+    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 = u''
+
+    def _value(self):
+        return self.data is not None and unicode(self.data) or u''
+
+
+class HiddenField(TextField):
+    """
+    Represents an ``<input type="hidden">``.
+    """
+    widget = widgets.HiddenInput()
+
+
+class TextAreaField(TextField):
+    """
+    This field represents an HTML ``<textarea>`` and can be used to take
+    multi-line input.
+    """
+    widget = widgets.TextArea()
+
+
+class PasswordField(TextField):
+    """
+    Represents an ``<input type="password">``.
+    """
+    widget = widgets.PasswordInput()
+
+
+class FileField(TextField):
+    """
+    Can render a file-upload field.  Will take any passed filename value, if
+    any is sent by the browser in the post params.  This field will NOT
+    actually handle the file upload portion, as wtforms does not deal with
+    individual frameworks' file handling capabilities.
+    """
+    widget = widgets.FileInput()
+
+
+class IntegerField(TextField):
+    """
+    A text field, except all input is coerced to an integer.  Erroneous input
+    is ignored and will not be accepted as a value.
+    """
+    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]
+        elif self.data is not None:
+            return unicode(self.data)
+        else:
+            return u''
+
+    def process_formdata(self, valuelist):
+        if valuelist:
+            try:
+                self.data = int(valuelist[0])
+            except ValueError:
+                raise ValueError(self.gettext(u'Not a valid integer value'))
+
+
+class DecimalField(TextField):
+    """
+    A text field which displays and coerces data of the `decimal.Decimal` type.
+
+    :param places:
+        How many decimal places to quantize the value to for display on form.
+        If None, does not quantize value.
+    :param rounding:
+        How to round the value during quantize, for example
+        `decimal.ROUND_UP`. If unset, uses the rounding value from the
+        current thread's context.
+    """
+
+    def __init__(self, label=None, validators=None, places=2, rounding=None, **kwargs):
+        super(DecimalField, self).__init__(label, validators, **kwargs)
+        self.places = places
+        self.rounding = rounding
+
+    def _value(self):
+        if self.raw_data:
+            return self.raw_data[0]
+        elif self.data is not None:
+            if self.places is not None:
+                if hasattr(self.data, 'quantize'):
+                    exp = decimal.Decimal('.1') ** self.places
+                    quantized = self.data.quantize(exp, rounding=self.rounding)
+                    return unicode(quantized)
+                else:
+                    # If for some reason, data is a float or int, then format
+                    # as we would for floats using string formatting.
+                    format = u'%%0.%df' % self.places
+                    return format % self.data
+            else:
+                return unicode(self.data)
+        else:
+            return u''
+
+    def process_formdata(self, valuelist):
+        if valuelist:
+            try:
+                self.data = decimal.Decimal(valuelist[0])
+            except (decimal.InvalidOperation, ValueError):
+                raise ValueError(self.gettext(u'Not a valid decimal value'))
+
+
+class FloatField(TextField):
+    """
+    A text field, except all input is coerced to an float.  Erroneous input
+    is ignored and will not be accepted as a value.
+    """
+    def __init__(self, label=None, validators=None, **kwargs):
+        super(FloatField, self).__init__(label, validators, **kwargs)
+
+    def _value(self):
+        if self.raw_data:
+            return self.raw_data[0]
+        elif self.data is not None:
+            return unicode(self.data)
+        else:
+            return u''
+
+    def process_formdata(self, valuelist):
+        if valuelist:
+            try:
+                self.data = float(valuelist[0])
+            except ValueError:
+                raise ValueError(self.gettext(u'Not a valid float value'))
+
+
+class BooleanField(Field):
+    """
+    Represents an ``<input type="checkbox">``.
+    """
+    widget = widgets.CheckboxInput()
+
+    def __init__(self, label=None, validators=None, **kwargs):
+        super(BooleanField, self).__init__(label, validators, **kwargs)
+
+    def process_data(self, value):
+        self.data = bool(value)
+
+    def process_formdata(self, valuelist):
+        self.data = bool(valuelist)
+
+    def _value(self):
+        if self.raw_data:
+            return unicode(self.raw_data[0])
+        else:
+            return u'y'
+
+
+class DateTimeField(Field):
+    """
+    A text field which stores a `datetime.datetime` matching a format.
+    """
+    widget = widgets.TextInput()
+
+    def __init__(self, label=None, validators=None, format='%Y-%m-%d %H:%M:%S', **kwargs):
+        super(DateTimeField, self).__init__(label, validators, **kwargs)
+        self.format = format
+
+    def _value(self):
+        if self.raw_data:
+            return u' '.join(self.raw_data)
+        else:
+            return self.data and self.data.strftime(self.format) or u''
+
+    def process_formdata(self, valuelist):
+        if valuelist:
+            date_str = u' '.join(valuelist)
+            try:
+                timetuple = time.strptime(date_str, self.format)
+                self.data = datetime.datetime(*timetuple[:6])
+            except ValueError:
+                self.data = None
+                raise
+
+
+class DateField(DateTimeField):
+    """
+    Same as DateTimeField, except stores a `datetime.date`.
+    """
+    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 = u' '.join(valuelist)
+            try:
+                timetuple = time.strptime(date_str, self.format)
+                self.data = datetime.date(*timetuple[:3])
+            except ValueError:
+                self.data = None
+                raise
+
+
+class SubmitField(BooleanField):
+    """
+    Represents an ``<input type="submit">``.  This allows checking if a given
+    submit button has been pressed.
+    """
+    widget = widgets.SubmitInput()
+
+
+class FormField(Field):
+    """
+    Encapsulate a form as a field in another form.
+
+    :param form_class:
+        A subclass of Form that will be encapsulated.
+    :param separator:
+        A string which will be suffixed to this field's name to create the
+        prefix to enclosed fields. The default is fine for most uses.
+    """
+    widget = widgets.TableWidget()
+
+    def __init__(self, form_class, label=None, validators=None, separator='-', **kwargs):
+        super(FormField, self).__init__(label, validators, **kwargs)
+        self.form_class = form_class
+        self.separator = separator
+        self._obj = None
+        if self.filters:
+            raise TypeError('FormField cannot take filters, as the encapsulated data is not mutable.')
+        if validators:
+            raise TypeError('FormField does not accept any validators. Instead, define them on the enclosed form.')
+
+    def process(self, formdata, data=_unset_value):
+        if data is _unset_value:
+            try:
+                data = self.default()
+            except TypeError:
+                data = self.default
+            self._obj = data
+
+        prefix = self.name + self.separator
+        if isinstance(data, dict):
+            self.form = self.form_class(formdata=formdata, prefix=prefix, **data)
+        else:
+            self.form = self.form_class(formdata=formdata, obj=data, prefix=prefix)
+
+    def validate(self, form, extra_validators=tuple()):
+        if extra_validators:
+            raise TypeError('FormField does not accept in-line validators, as it gets errors from the enclosed form.')
+        return self.form.validate()
+
+    def populate_obj(self, obj, name):
+        candidate = getattr(obj, name, None)
+        if candidate is None:
+            if self._obj is None:
+                raise TypeError('populate_obj: cannot find a value to populate from the provided obj or input data/defaults')
+            candidate = self._obj
+            setattr(obj, name, candidate)
+
+        self.form.populate_obj(candidate)
+
+    def __iter__(self):
+        return iter(self.form)
+
+    def __getitem__(self, name):
+        return self.form[name]
+
+    def __getattr__(self, name):
+        return getattr(self.form, name)
+
+    @property
+    def data(self):
+        return self.form.data
+
+    @property
+    def errors(self):
+        return self.form.errors
+
+
+class FieldList(Field):
+    """
+    Encapsulate an ordered list of multiple instances of the same field type,
+    keeping data as a list.
+
+    >>> authors = FieldList(TextField('Name', [validators.required()]))
+
+    :param unbound_field:
+        A partially-instantiated field definition, just like that would be
+        defined on a form directly.
+    :param min_entries:
+        if provided, always have at least this many entries on the field,
+        creating blank ones if the provided input does not specify a sufficient
+        amount.
+    :param max_entries:
+        accept no more than this many entries as input, even if more exist in
+        formdata.
+    """
+    widget=widgets.ListWidget()
+
+    def __init__(self, unbound_field, label=None, validators=None, min_entries=0,
+                 max_entries=None, default=tuple(), **kwargs):
+        super(FieldList, self).__init__(label, validators, default=default, **kwargs)
+        if self.filters:
+            raise TypeError('FieldList does not accept any filters. Instead, define them on the enclosed field.')
+        if validators:
+            raise TypeError('FieldList does not accept any validators. Instead, define them on the enclosed field.')
+        assert isinstance(unbound_field, UnboundField), 'Field must be unbound, not a field class'
+        self.unbound_field = unbound_field
+        self.min_entries = min_entries
+        self.max_entries = max_entries
+        self.last_index = -1
+        self._prefix = kwargs.get('_prefix', '')
+
+    def process(self, formdata, data=_unset_value):
+        self.entries = []
+        if data is _unset_value or not data:
+            try:
+                data = self.default()
+            except TypeError:
+                data = self.default
+
+        if formdata:
+            indices = sorted(set(self._extract_indices(self.name, formdata)))
+            if self.max_entries:
+                indices = indices[:self.max_entries]
+
+            idata = iter(data)
+            for index in indices:
+                try:
+                    obj_data = idata.next()
+                except StopIteration:
+                    obj_data = _unset_value
+                self._add_entry(formdata, obj_data, index=index)
+        else:
+            for obj_data in data:
+                self._add_entry(formdata, obj_data)
+
+        while len(self.entries) < self.min_entries:
+            self._add_entry(formdata)
+
+    def _extract_indices(self, prefix, formdata):
+        """
+        Yield indices of any keys with given prefix.
+
+        formdata must be an object which will produce keys when iterated.  For
+        example, if field 'foo' contains keys 'foo-0-bar', 'foo-1-baz', then
+        the numbers 0 and 1 will be yielded, but not neccesarily in order.
+        """
+        offset = len(prefix) + 1
+        for k in formdata:
+            if k.startswith(prefix):
+                k = k[offset:].split('-', 1)[0]
+                if k.isdigit():
+                    yield int(k)
+
+    def validate(self, form, extra_validators=tuple()):
+        self.errors = []
+        success = True
+        for subfield in self.entries:
+            if not subfield.validate(form):
+                success = False
+                self.errors.append(subfield.errors)
+        return success
+
+    def populate_obj(self, obj, name):
+        values = getattr(obj, name, None)
+        try:
+            ivalues = iter(values)
+        except TypeError:
+            ivalues = iter([])
+
+        candidates = itertools.chain(ivalues, itertools.repeat(None))
+        _fake = type('_fake', (object, ), {})
+        output = []
+        for field, data in itertools.izip(self.entries, candidates):
+            fake_obj = _fake()
+            fake_obj.data = data
+            field.populate_obj(fake_obj, 'data')
+            output.append(fake_obj.data)
+
+        setattr(obj, name, output)
+
+    def _add_entry(self, formdata=None, data=_unset_value, index=None):
+        assert not self.max_entries or len(self.entries) < self.max_entries, \
+            'You cannot have more than max_entries entries in this FieldList'
+        new_index = self.last_index = index or (self.last_index + 1)
+        name = '%s-%d' % (self.short_name, new_index)
+        id   = '%s-%d' % (self.id, new_index)
+        field = self.unbound_field.bind(form=None, name=name, prefix=self._prefix, id=id)
+        field.process(formdata, data)
+        self.entries.append(field)
+        return field
+
+    def append_entry(self, data=_unset_value):
+        """
+        Create a new entry with optional default data.
+
+        Entries added in this way will *not* receive formdata however, and can
+        only receive object data.
+        """
+        return self._add_entry(data=data)
+
+    def pop_entry(self):
+        """ Removes the last entry from the list and returns it. """
+        entry = self.entries.pop()
+        self.last_index -= 1
+        return entry
+
+    def __iter__(self):
+        return iter(self.entries)
+
+    def __len__(self):
+        return len(self.entries)
+
+    def __getitem__(self, index):
+        return self.entries[index]
+
+    @property
+    def data(self):
+        return [f.data for f in self.entries]

wtforms/widgets.py

-from cgi import escape
-
-
-__all__ = (
-    'CheckboxInput', 'FileInput', 'HiddenInput', 'ListWidget', 'PasswordInput',
-    'RadioInput', 'Select', 'SubmitInput', 'TableWidget', 'TextArea',
-    'TextInput',
-)
-
-
-def html_params(**kwargs):
-    """
-    Generate HTML parameters from inputted keyword arguments.
-
-    The output value is sorted by the passed keys, to provide consistent output
-    each time this function is called with the same parameters.  Because of the
-    frequent use of the normally reserved keywords `class` and `for`, suffixing
-    these with an underscore will allow them to be used.
-
-    >>> html_params(name='text1', id='f', class_='text')
-    u'class="text" id="f" name="text1"'
-    """
-    params = []
-    for k,v in sorted(kwargs.iteritems()):
-        if k in ('class_', 'class__', 'for_'):
-            k = k[:-1]
-        params.append(u'%s="%s"' % (unicode(k), escape(unicode(v), quote=True)))
-    return u' '.join(params)
-
-class HTMLString(unicode):
-    def __html__(self):
-        return self
-
-
-class ListWidget(object):
-    """
-    Renders a list of fields as a `ul` or `ol` list.
-
-    This is used for fields which encapsulate many inner fields as subfields.
-    The widget will try to iterate the field to get access to the subfields and
-    call them to render them.
-
-    If `prefix_label` is set, the subfield's label is printed before the field,
-    otherwise afterwards. The latter is useful for iterating radios or
-    checkboxes.
-    """
-    def __init__(self, html_tag='ul', prefix_label=True):
-        assert html_tag in ('ol', 'ul')
-        self.html_tag = html_tag
-        self.prefix_label = prefix_label
-
-    def __call__(self, field, **kwargs):
-        kwargs.setdefault('id', field.id)
-        html = [u'<%s %s>' % (self.html_tag, html_params(**kwargs))]
-        for subfield in field:
-            if self.prefix_label:
-                html.append(u'<li>%s: %s</li>' % (subfield.label, subfield()))
-            else:
-                html.append(u'<li>%s %s</li>' % (subfield(), subfield.label))
-        html.append(u'</%s>' % self.html_tag)
-        return HTMLString(u''.join(html))
-
-
-class TableWidget(object):
-    """
-    Renders a list of fields as a set of table rows with th/td pairs.
-
-    If `with_table_tag` is True, then an enclosing <table> is placed around the
-    rows.
-
-    Hidden fields will not be displayed with a row, instead the field will be 
-    pushed into a subsequent table row to ensure XHTML validity. Hidden fields
-    at the end of the field list will appear outside the table.
-    """
-    def __init__(self, with_table_tag=True):
-        self.with_table_tag = with_table_tag
-
-    def __call__(self, field, **kwargs):
-        html = []
-        if self.with_table_tag:
-            kwargs.setdefault('id', field.id)
-            html.append(u'<table %s>' % html_params(**kwargs))
-        hidden = u''
-        for subfield in field:
-            if subfield.type == 'HiddenField':
-                hidden += unicode(subfield)
-            else:
-                html.append(u'<tr><th>%s</th><td>%s%s</td></tr>' % (unicode(subfield.label), hidden, unicode(subfield)))
-                hidden = u''
-        if self.with_table_tag:
-            html.append(u'</table>')
-        if hidden:
-            html.append(hidden)
-        return HTMLString(u''.join(html))
-
-
-class Input(object):
-    """
-    Render a basic ``<input>`` field.
-
-    This is used as the basis for most of the other input fields.
-
-    By default, the `_value()` method will be called upon the associated field
-    to provide the ``value=`` HTML attribute.
-    """
-    def __init__(self, input_type=None):
-        if input_type is not None:
-            self.input_type = input_type
-
-    def __call__(self, field, **kwargs):
-        kwargs.setdefault('id', field.id)
-        kwargs.setdefault('type', self.input_type)
-        if 'value' not in kwargs:
-            kwargs['value'] = field._value()
-        return HTMLString(u'<input %s />' % html_params(name=field.name, **kwargs))
-
-
-class TextInput(Input):
-    """
-    Render a single-line text input.
-    """
-    input_type = 'text'
-
-
-class PasswordInput(Input):
-    """
-    Render a password input.
-
-    For security purposes, this field will not reproduce the value on a form
-    submit by default. To have the value filled in, set `hide_value` to
-    `False`.
-    """
-    input_type = 'password'
-
-    def __init__(self, hide_value=True):
-        self.hide_value = hide_value
-
-    def __call__(self, field, **kwargs): 
-        if self.hide_value:
-            kwargs['value'] = ''
-        return super(PasswordInput, self).__call__(field, **kwargs)
-
-
-class HiddenInput(Input):
-    """
-    Render a hidden input.
-    """
-    input_type = 'hidden'
-
-
-class CheckboxInput(Input):
-    """
-    Render a checkbox.
-
-    The ``checked`` HTML attribute is set if the field's data is a non-false value.
-    """
-    input_type = 'checkbox'
-
-    def __call__(self, field, **kwargs):
-        if getattr(field, 'checked', field.data):
-            kwargs['checked'] = u'checked'
-        return super(CheckboxInput, self).__call__(field, **kwargs)
-
-
-class RadioInput(Input):
-    """
-    Render a single radio button.
-
-    This widget is most commonly used in conjunction with ListWidget or some
-    other listing, as singular radio buttons are not very useful.
-    """
-    input_type = 'radio'
-
-    def __call__(self, field, **kwargs):
-        if field.checked:
-            kwargs['checked'] = u'checked'
-        return super(RadioInput, self).__call__(field, **kwargs)
-
-
-class FileInput(object):
-    """
-    Renders a file input chooser field.
-    """
-
-    def __call__(self, field, **kwargs):
-        kwargs.setdefault('id', field.id)
-        value = field._value()
-        if value:
-            kwargs.setdefault('value', value)
-        return HTMLString(u'<input %s />' % html_params(name=field.name, type=u'file', **kwargs))
-
-
-class SubmitInput(Input):
-    """
-    Renders a submit button.
-
-    The field's label is used as the text of the submit button instead of the
-    data on the field.
-    """
-    input_type = 'submit'
-
-    def __call__(self, field, **kwargs): 
-        kwargs.setdefault('value', field.label.text)
-        return super(SubmitInput, self).__call__(field, **kwargs)
-
-
-class TextArea(object):
-    """
-    Renders a multi-line text area.
-
-    `rows` and `cols` ought to be passed as keyword args when rendering.
-    """
-    def __call__(self, field, **kwargs): 
-        kwargs.setdefault('id', field.id)
-        return HTMLString(u'<textarea %s>%s</textarea>' % (html_params(name=field.name, **kwargs), escape(unicode(field._value()))))
-
-
-class Select(object):
-    """
-    Renders a select field.
-
-    If `multiple` is True, then the `size` property should be specified on
-    rendering to make the field useful.
-
-    The field must provide an `iter_choices()` method which the widget will
-    call on rendering; this method must yield tuples of 
-    `(value, label, selected)`.
-    """
-    def __init__(self, multiple=False):
-        self.multiple = multiple
-
-    def __call__(self, field, **kwargs):
-        kwargs.setdefault('id', field.id)
-        if self.multiple:
-            kwargs['multiple'] = 'multiple'
-        html = [u'<select %s>' % html_params(name=field.name, **kwargs)]
-        for val, label, selected in field.iter_choices():
-            html.append(self.render_option(val, label, selected))
-        html.append(u'</select>')
-        return HTMLString(u''.join(html))
-
-    @classmethod
-    def render_option(cls, value, label, selected):
-        options = {'value': value}
-        if selected:
-            options['selected'] = u'selected'
-        return HTMLString(u'<option %s>%s</option>' % (html_params(**options), escape(unicode(label))))
-
-
-class Option(object):
-    """
-    Renders the individual option from a select field. 
-    
-    This is just a convenience for various custom rendering situations, and an
-    option by itself does not constitute an entire field.
-    """
-    def __call__(self, field, **kwargs):
-        return Select.render_option(field._value(), field.label.text, field.checked)
-

wtforms/widgets/__init__.py

+from wtforms.widgets.core import *
+
+# Compatibility imports
+from wtforms.widgets.core import html_params, Input, HTMLString

wtforms/widgets/core.py

+from cgi import escape
+
+
+__all__ = (
+    'CheckboxInput', 'FileInput', 'HiddenInput', 'ListWidget', 'PasswordInput',
+    'RadioInput', 'Select', 'SubmitInput', 'TableWidget', 'TextArea',
+    'TextInput', 'Option'
+)
+
+
+def html_params(**kwargs):
+    """
+    Generate HTML parameters from inputted keyword arguments.
+
+    The output value is sorted by the passed keys, to provide consistent output
+    each time this function is called with the same parameters.  Because of the
+    frequent use of the normally reserved keywords `class` and `for`, suffixing
+    these with an underscore will allow them to be used.
+
+    >>> html_params(name='text1', id='f', class_='text')
+    u'class="text" id="f" name="text1"'
+    """
+    params = []
+    for k,v in sorted(kwargs.iteritems()):
+        if k in ('class_', 'class__', 'for_'):
+            k = k[:-1]
+        params.append(u'%s="%s"' % (unicode(k), escape(unicode(v), quote=True)))
+    return u' '.join(params)
+
+class HTMLString(unicode):
+    def __html__(self):
+        return self
+
+
+class ListWidget(object):
+    """
+    Renders a list of fields as a `ul` or `ol` list.
+
+    This is used for fields which encapsulate many inner fields as subfields.
+    The widget will try to iterate the field to get access to the subfields and
+    call them to render them.
+
+    If `prefix_label` is set, the subfield's label is printed before the field,
+    otherwise afterwards. The latter is useful for iterating radios or
+    checkboxes.
+    """
+    def __init__(self, html_tag='ul', prefix_label=True):
+        assert html_tag in ('ol', 'ul')
+        self.html_tag = html_tag
+        self.prefix_label = prefix_label
+
+    def __call__(self, field, **kwargs):
+        kwargs.setdefault('id', field.id)
+        html = [u'<%s %s>' % (self.html_tag, html_params(**kwargs))]
+        for subfield in field:
+            if self.prefix_label:
+                html.append(u'<li>%s: %s</li>' % (subfield.label, subfield()))
+            else:
+                html.append(u'<li>%s %s</li>' % (subfield(), subfield.label))
+        html.append(u'</%s>' % self.html_tag)
+        return HTMLString(u''.join(html))
+
+
+class TableWidget(object):
+    """
+    Renders a list of fields as a set of table rows with th/td pairs.
+
+    If `with_table_tag` is True, then an enclosing <table> is placed around the
+    rows.
+
+    Hidden fields will not be displayed with a row, instead the field will be 
+    pushed into a subsequent table row to ensure XHTML validity. Hidden fields
+    at the end of the field list will appear outside the table.
+    """
+    def __init__(self, with_table_tag=True):
+        self.with_table_tag = with_table_tag
+
+    def __call__(self, field, **kwargs):
+        html = []
+        if self.with_table_tag:
+            kwargs.setdefault('id', field.id)
+            html.append(u'<table %s>' % html_params(**kwargs))
+        hidden = u''
+        for subfield in field:
+            if subfield.type == 'HiddenField':
+                hidden += unicode(subfield)
+            else:
+                html.append(u'<tr><th>%s</th><td>%s%s</td></tr>' % (unicode(subfield.label), hidden, unicode(subfield)))
+                hidden = u''
+        if self.with_table_tag:
+            html.append(u'</table>')
+        if hidden:
+            html.append(hidden)
+        return HTMLString(u''.join(html))
+
+
+class Input(object):
+    """
+    Render a basic ``<input>`` field.
+
+    This is used as the basis for most of the other input fields.
+
+    By default, the `_value()` method will be called upon the associated field
+    to provide the ``value=`` HTML attribute.
+    """
+    def __init__(self, input_type=None):
+        if input_type is not None:
+            self.input_type = input_type
+
+    def __call__(self, field, **kwargs):
+        kwargs.setdefault('id', field.id)
+        kwargs.setdefault('type', self.input_type)
+        if 'value' not in kwargs:
+            kwargs['value'] = field._value()
+        return HTMLString(u'<input %s />' % html_params(name=field.name, **kwargs))
+
+
+class TextInput(Input):
+    """
+    Render a single-line text input.
+    """
+    input_type = 'text'
+
+
+class PasswordInput(Input):
+    """
+    Render a password input.
+
+    For security purposes, this field will not reproduce the value on a form
+    submit by default. To have the value filled in, set `hide_value` to
+    `False`.
+    """
+    input_type = 'password'
+
+    def __init__(self, hide_value=True):
+        self.hide_value = hide_value
+
+    def __call__(self, field, **kwargs): 
+        if self.hide_value:
+            kwargs['value'] = ''
+        return super(PasswordInput, self).__call__(field, **kwargs)
+
+
+class HiddenInput(Input):
+    """
+    Render a hidden input.
+    """
+    input_type = 'hidden'
+
+
+class CheckboxInput(Input):
+    """
+    Render a checkbox.
+
+    The ``checked`` HTML attribute is set if the field's data is a non-false value.
+    """
+    input_type = 'checkbox'
+
+    def __call__(self, field, **kwargs):
+        if getattr(field, 'checked', field.data):
+            kwargs['checked'] = u'checked'
+        return super(CheckboxInput, self).__call__(field, **kwargs)
+
+
+class RadioInput(Input):
+    """
+    Render a single radio button.
+
+    This widget is most commonly used in conjunction with ListWidget or some
+    other listing, as singular radio buttons are not very useful.
+    """
+    input_type = 'radio'
+
+    def __call__(self, field, **kwargs):
+        if field.checked:
+            kwargs['checked'] = u'checked'
+        return super(RadioInput, self).__call__(field, **kwargs)
+
+
+class FileInput(object):
+    """
+    Renders a file input chooser field.
+    """
+
+    def __call__(self, field, **kwargs):
+        kwargs.setdefault('id', field.id)
+        value = field._value()
+        if value:
+            kwargs.setdefault('value', value)
+        return HTMLString(u'<input %s />' % html_params(name=field.name, type=u'file', **kwargs))
+