Anonymous avatar 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))
+
+
+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):