Repository: wtforms/wtforms
Pull request #276 (2016-05-29 18:11:49)

Convert readthedocs link for their .org -> .io migration for hosted projects
As per [their blog post of the 27th April](https://blog.readthedocs.com/securing-subdomains/) ‘Securing subdomains’:

> Starting today, Read the Docs will start hosting projects from subdomains on the domain readthedocs.io, instead of on readthedocs.org. This change addresses some security concerns around site cookies while hosting user generated data on the same domain as our dashboard.

Test Plan: Manually visited all the links I’ve modified.


Patch:
diff --git a/.travis.yml b/.travis.yml
index 5085d67..eabd036 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,6 +5,7 @@ python:
   - 2.7
   - 3.3
   - 3.4
+  - 3.5
   - pypy
 
 install:
diff --git a/README.md b/README.md
index 6cf848e..6c8b163 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,11 @@
 What is WTForms?
 ================
 
-[![Build Status](https://travis-ci.org/wtforms/wtforms.svg?branch=master)](https://travis-ci.org/wtforms/wtforms) [![Coverage Status](https://coveralls.io/repos/wtforms/wtforms/badge.svg?branch=master&service=github)](https://coveralls.io/github/wtforms/wtforms?branch=master) [![Documentation Status](https://readthedocs.org/projects/wtforms/badge/?version=latest)](http://wtforms.readthedocs.org/en/latest/?badge=latest)
+[![Build Status](https://travis-ci.org/wtforms/wtforms.svg?branch=master)](https://travis-ci.org/wtforms/wtforms) [![Coverage Status](https://coveralls.io/repos/wtforms/wtforms/badge.svg?branch=master&service=github)](https://coveralls.io/github/wtforms/wtforms?branch=master) [![Documentation Status](https://readthedocs.org/projects/wtforms/badge/?version=latest)](https://wtforms.readthedocs.io/en/latest/?badge=latest)
 
 WTForms is a flexible forms validation and rendering library for python web development.
 
-To get started using WTForms, we recommend reading the [crash course][] on the docs site: http://wtforms.readthedocs.org/en/stable/
+To get started using WTForms, we recommend reading the [crash course][] on the docs site: https://wtforms.readthedocs.io/en/stable/
 
 If you downloaded the package from PyPI, there will also be a prebuilt copy of the html documentation in the `docs/html/` directory.
 
@@ -17,8 +17,8 @@ Why use WTForms?
  * [Framework-agnostic][]; works with your web framework and template engine of choice.
  * Rich ecosystem of [library integrations](#library-integrations)
 
-[crash course]: http://wtforms.readthedocs.org/en/stable/crash_course.html
-[Framework-agnostic]: http://wtforms.readthedocs.org/en/stable/faq.html#does-wtforms-work-with-library-here
+[crash course]: https://wtforms.readthedocs.io/en/stable/crash_course.html
+[Framework-agnostic]: https://wtforms.readthedocs.io/en/stable/faq.html#does-wtforms-work-with-library-here
 
 
 Installation
@@ -41,9 +41,9 @@ Third-Party Library Integrations
 <a id="library-integrations" name="library-integrations"></a>
 WTForms works with most web frameworks very well; but there are a number of tools available that make integration with database ORM's, environments, and frameworks even better - either reducing boilerplate or adding features like query fields, etc.
 
- * [Flask-WTF](https://flask-wtf.readthedocs.org/en/latest/) is an integration with the Flask framework providing a solid default CSRF configuration, file upload support, flask-i18n integration, and more.
+ * [Flask-WTF](https://flask-wtf.readthedocs.io/en/latest/) is an integration with the Flask framework providing a solid default CSRF configuration, file upload support, flask-i18n integration, and more.
  * [WTForms-Appengine](https://github.com/wtforms/wtforms-appengine) provides ORM-backed fields and form generation from Appengine db/ndb schema
  * [WTForms-SQLAlchemy](https://github.com/wtforms/wtforms-sqlalchemy) provides ORM-backed fields and generation of forms from models.
- * [WTForms-Alchemy](https://wtforms-alchemy.readthedocs.org/en/latest/) provides powerful model forms support 
+ * [WTForms-Alchemy](https://wtforms-alchemy.readthedocs.io/en/latest/) provides powerful model forms support 
 
 
diff --git a/docs/csrf.rst b/docs/csrf.rst
index 55a9363..7fd4b3e 100644
--- a/docs/csrf.rst
+++ b/docs/csrf.rst
@@ -18,9 +18,9 @@ frameworks for session-based hash secure keying,
 Using CSRF
 ----------
 
-CSRF in WTForms 2.0 is now driven through a number of variables on 
+CSRF in WTForms 2.0 is now driven through a number of variables on
 :mod:`class Meta <wtforms.meta>`. After choosing a CSRF implementation,
-import it and configure it on the class Meta of a subclass of Form 
+import it and configure it on the class Meta of a subclass of Form
 like such::
 
     from somemodule import SomeCSRF
@@ -38,7 +38,7 @@ of `MyBaseForm`:
 .. code-block:: python
 
     class UserForm(MyBaseForm):
-        name = TextField()
+        name = StringField()
         age = IntegerField()
 
     def view():
@@ -86,7 +86,7 @@ a cryptographic hash function against some data which would be hard to forge.
 .. module:: wtforms.csrf.core
 
 .. autoclass:: CSRFTokenField
-    
+
     .. automethod:: __init__
 
     .. autoattribute:: current_token
@@ -109,7 +109,7 @@ a cryptographic hash function against some data which would be hard to forge.
 
     .. autoattribute:: field_class
 
-        The class of the token field we're going to construct. Can be 
+        The class of the token field we're going to construct. Can be
         overridden in subclasses if need be.
 
 
@@ -161,7 +161,7 @@ Now that we have this taken care of, let's write a simple form and view which wo
 
     def register(request):
         form = RegistrationForm(
-            request.POST, 
+            request.POST,
             meta={'csrf_context': request.ip}
         )
 
@@ -241,15 +241,15 @@ Session-based CSRF implementation
     **Meta Values**
 
     * ``csrf_secret`` A byte string which is the master key by which we encode
-      all values. Set to a sufficiently long string of characters that is 
-      difficult to guess or bruteforce (recommended at least 16 characters) 
+      all values. Set to a sufficiently long string of characters that is
+      difficult to guess or bruteforce (recommended at least 16 characters)
       for example the output of ``os.urandom(16)``.
 
-    * ``csrf_time_limit`` if `None`, tokens last forever (not recommended.) 
-      Otherwise, set to a ``datetime.timedelta`` that will define how long 
+    * ``csrf_time_limit`` if `None`, tokens last forever (not recommended.)
+      Otherwise, set to a ``datetime.timedelta`` that will define how long
       CSRF tokens are valid for. Defaults to 30 minutes.
 
-    * ``csrf_context`` This should be a ``request.session``-style object. 
+    * ``csrf_context`` This should be a ``request.session``-style object.
       Usually given in the Form constructor.
 
 
@@ -275,8 +275,8 @@ Example
         form = Registration(request.POST, meta={'csrf_context': request.session})
         # rest of view here
 
-Note that request.session is passed as the ``csrf_context`` override to the 
-meta info, this is so that the CSRF token can be stored in your session for 
+Note that request.session is passed as the ``csrf_context`` override to the
+meta info, this is so that the CSRF token can be stored in your session for
 comparison on a later request.
 
 
@@ -288,7 +288,7 @@ as such sometimes things seem like they are more work to use, but with some
 smart integration, you can actually clean up your code substantially.
 
 For example, if you were going to integrate with `Flask`_, and wanted to use
-the SessionCSRF implementation, here's one way to get the CSRF context to be 
+the SessionCSRF implementation, here's one way to get the CSRF context to be
 available without passing it all the time:
 
 .. code-block:: python
@@ -306,7 +306,7 @@ available without passing it all the time:
             def csrf_context(self):
                 return session
 
-Now with any subclasses of MyBaseForm, you don't need to pass in the csrf 
+Now with any subclasses of MyBaseForm, you don't need to pass in the csrf
 context, and on top of that, we grab the secret key out of your normal app
 configuration.
 
diff --git a/docs/fields.rst b/docs/fields.rst
index cf80f10..9f79680 100644
--- a/docs/fields.rst
+++ b/docs/fields.rst
@@ -16,7 +16,7 @@ Fields are defined as members on a form in a declarative fashion::
         address = TextAreaField(u'Mailing Address', [validators.optional(), validators.length(max=200)])
 
 When a field is defined on a form, the construction parameters are saved until
-the form is instantiated. At form instantiation time, a copy of the field is 
+the form is instantiated. At form instantiation time, a copy of the field is
 made with all the parameters specified in the definition. Each instance of the
 field keeps its own field data and errors list.
 
@@ -63,7 +63,7 @@ The Field base class
 
     .. automethod:: process(formdata [, data])
     .. automethod:: process_data
-    .. automethod:: process_formdata 
+    .. automethod:: process_formdata
     .. attribute:: data
 
         Contains the resulting (sanitized) value of calling either of the
@@ -120,7 +120,7 @@ The Field base class
 
     .. attribute:: name
 
-        The HTML form name of this field. This is the name as defined in your 
+        The HTML form name of this field. This is the name as defined in your
         Form prefixed with the `prefix` passed to the Form constructor.
 
     .. attribute:: short_name
@@ -241,6 +241,8 @@ refer to a single input from the form.
                 image_data = request.FILES[form.image.name].read()
                 open(os.path.join(UPLOAD_PATH, form.image.data), 'w').write(image_data)
 
+.. autoclass:: MultipleFileField(default field arguments)
+
 .. autoclass:: FloatField(default field arguments)
 
    For the majority of uses, :class:`DecimalField` is preferable to FloatField,
@@ -290,11 +292,11 @@ refer to a single input from the form.
             form = UserDetails(request.POST, obj=user)
             form.group_id.choices = [(g.id, g.name) for g in Group.query.order_by('name')]
 
-    Note we didn't pass a `choices` to the :class:`~wtforms.fields.SelectField` 
-    constructor, but rather created the list in the view function. Also, the 
-    `coerce` keyword arg to :class:`~wtforms.fields.SelectField` says that we 
-    use :func:`int()` to coerce form data.  The default coerce is 
-    :func:`unicode()`. 
+    Note we didn't pass a `choices` to the :class:`~wtforms.fields.SelectField`
+    constructor, but rather created the list in the view function. Also, the
+    `coerce` keyword arg to :class:`~wtforms.fields.SelectField` says that we
+    use :func:`int()` to coerce form data.  The default coerce is
+    :func:`unicode()`.
 
     **Advanced functionality**
 
@@ -325,7 +327,7 @@ Convenience Fields
 
     HiddenField is useful for providing data from a model or the application to
     be used on the form handler side for making choices or finding records.
-    Very frequently, CRUD forms will use the hidden field for an object's id.   
+    Very frequently, CRUD forms will use the hidden field for an object's id.
 
     Hidden fields are like any other field in that they can take validators and
     values and be accessed on the form object.   You should consider validating
@@ -373,7 +375,7 @@ complex data structures such as lists and nested objects can be represented.
     In the example, we reused the TelephoneForm to encapsulate the common
     telephone entry instead of writing a custom field to handle the 3
     sub-fields. The `data` property of the mobile_phone field will return the
-    :attr:`~wtforms.form.Form.data` dict of the enclosed form. Similarly, the 
+    :attr:`~wtforms.form.Form.data` dict of the enclosed form. Similarly, the
     `errors` property encapsulate the forms' errors.
 
 .. autoclass:: FieldList(unbound_field, default field arguments, min_entries=0, max_entries=None)
diff --git a/docs/meta.rst b/docs/meta.rst
index 4277124..12a8775 100644
--- a/docs/meta.rst
+++ b/docs/meta.rst
@@ -14,7 +14,7 @@ Typical usage looks something like::
             csrf = True
             locales = ('en_US', 'en')
 
-        name = TextField(...)
+        name = StringField(...)
         # and so on...
 
 For the majority of users, using a class Meta is mostly going to be done for
diff --git a/docs/whats_new.rst b/docs/whats_new.rst
index 2419c2f..de9bae7 100644
--- a/docs/whats_new.rst
+++ b/docs/whats_new.rst
@@ -67,7 +67,7 @@ the new API's unless it needs to work across both WTForms 1.x and 2.x
     (`docs <WTForms-Alchemy-docs>`_)
 
 .. _WTForms-Alchemy: https://pypi.python.org/pypi/WTForms-Alchemy
-.. _WTForms-Alchemy-docs: http://wtforms-alchemy.readthedocs.org/en/latest/
+.. _WTForms-Alchemy-docs: https://wtforms-alchemy.readthedocs.io/en/latest/
 .. _WTForms-Appengine: https://github.com/wtforms/wtforms-appengine
 .. _WTForms-Django: https://github.com/wtforms/wtforms-django
 
diff --git a/wtforms/fields/core.py b/wtforms/fields/core.py
index 768ca36..6ea64e1 100644
--- a/wtforms/fields/core.py
+++ b/wtforms/fields/core.py
@@ -4,6 +4,8 @@ import datetime
 import decimal
 import itertools
 
+from copy import copy
+
 from wtforms import widgets
 from wtforms.compat import text_type, izip
 from wtforms.i18n import DummyTranslations
@@ -278,12 +280,13 @@ class Field(object):
         except ValueError as e:
             self.process_errors.append(e.args[0])
 
-        if formdata:
+        if formdata is not None:
+            if self.name in formdata:
+                self.raw_data = formdata.getlist(self.name)
+            else:
+                self.raw_data = []
+
             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 as e:
                 self.process_errors.append(e.args[0])
@@ -445,7 +448,7 @@ class SelectField(SelectFieldBase):
     def __init__(self, label=None, validators=None, coerce=text_type, choices=None, **kwargs):
         super(SelectField, self).__init__(label, validators, **kwargs)
         self.coerce = coerce
-        self.choices = choices
+        self.choices = copy(choices)
 
     def iter_choices(self):
         for value, label in self.choices:
@@ -835,7 +838,7 @@ class FieldList(Field):
     Encapsulate an ordered list of multiple instances of the same field type,
     keeping data as a list.
 
-    >>> authors = FieldList(StringField('Name', [validators.required()]))
+    >>> authors = FieldList(StringField('Name', [validators.DataRequired()]))
 
     :param unbound_field:
         A partially-instantiated field definition, just like that would be
diff --git a/wtforms/fields/simple.py b/wtforms/fields/simple.py
index c147b74..3983d9d 100644
--- a/wtforms/fields/simple.py
+++ b/wtforms/fields/simple.py
@@ -1,29 +1,12 @@
-import warnings
-
 from .. import widgets
-from .core import StringField, BooleanField
-
+from .core import Field, StringField, BooleanField
 
 __all__ = (
-    'BooleanField', 'TextAreaField', 'PasswordField', 'FileField',
-    'HiddenField', 'SubmitField', 'TextField'
+    'BooleanField', 'TextAreaField', 'PasswordField', 'FileField', 'MultipleFileField',
+    'HiddenField', 'SubmitField'
 )
 
 
-class TextField(StringField):
-    """
-    Legacy alias for StringField
-
-    .. deprecated:: 2.0
-    """
-    def __init__(self, *args, **kw):
-        super(TextField, self).__init__(*args, **kw)
-        warnings.warn(
-            'The TextField alias for StringField has been deprecated and will be removed in WTForms 3.0',
-            DeprecationWarning, stacklevel=2
-        )
-
-
 class TextAreaField(StringField):
     """
     This field represents an HTML ``<textarea>`` and can be used to take
@@ -42,15 +25,30 @@ class PasswordField(StringField):
     widget = widgets.PasswordInput()
 
 
-class FileField(StringField):
-    """
-    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.
+class FileField(Field):
+    """Renders a file upload field.
+
+    By default, the value will be the filename sent in the form data.
+    WTForms **does not** deal with frameworks' file handling capabilities.
+    A WTForms extension for a framework may replace the filename value
+    with an object representing the uploaded data.
     """
+
     widget = widgets.FileInput()
 
+    def _value(self):
+        # browser ignores value of file input for security
+        return False
+
+
+class MultipleFileField(FileField):
+    """A :class:`FileField` that allows choosing multiple files."""
+
+    widget = widgets.FileInput(multiple=True)
+
+    def process_formdata(self, valuelist):
+        self.data = valuelist
+
 
 class HiddenField(StringField):
     """
diff --git a/wtforms/validators.py b/wtforms/validators.py
index 12d678a..5b2cd71 100644
--- a/wtforms/validators.py
+++ b/wtforms/validators.py
@@ -2,7 +2,6 @@ from __future__ import unicode_literals
 
 import re
 import uuid
-import warnings
 
 from wtforms.compat import string_types, text_type
 
@@ -10,7 +9,7 @@ __all__ = (
     'DataRequired', 'data_required', 'Email', 'email', 'EqualTo', 'equal_to',
     'IPAddress', 'ip_address', 'InputRequired', 'input_required', 'Length',
     'length', 'NumberRange', 'number_range', 'Optional', 'optional',
-    'Required', 'required', 'Regexp', 'regexp', 'URL', 'url', 'AnyOf',
+    'Regexp', 'regexp', 'URL', 'url', 'AnyOf',
     'any_of', 'NoneOf', 'none_of', 'MacAddress', 'mac_address', 'UUID'
 )
 
@@ -209,22 +208,6 @@ class DataRequired(object):
             raise StopValidation(message)
 
 
-class Required(DataRequired):
-    """
-    Legacy alias for DataRequired.
-
-    This is needed over simple aliasing for those who require that the
-    class-name of required be 'Required.'
-
-    """
-    def __init__(self, *args, **kwargs):
-        super(Required, self).__init__(*args, **kwargs)
-        warnings.warn(
-            'Required is going away in WTForms 3.0, use DataRequired',
-            DeprecationWarning, stacklevel=2
-        )
-
-
 class InputRequired(object):
     """
     Validates that input was provided for this field.
@@ -554,7 +537,6 @@ mac_address = MacAddress
 length = Length
 number_range = NumberRange
 optional = Optional
-required = Required
 input_required = InputRequired
 data_required = DataRequired
 regexp = Regexp
diff --git a/wtforms/widgets/core.py b/wtforms/widgets/core.py
index 7617744..176e834 100644
--- a/wtforms/widgets/core.py
+++ b/wtforms/widgets/core.py
@@ -227,14 +227,26 @@ class RadioInput(Input):
         return super(RadioInput, self).__call__(field, **kwargs)
 
 
-class FileInput(object):
-    """
-    Renders a file input chooser field.
+class FileInput(Input):
+    """Render a file chooser input.
+
+    :param multiple: allow choosing multiple files
     """
 
+    input_type = 'file'
+
+    def __init__(self, multiple=False):
+        super(FileInput, self).__init__()
+        self.multiple = multiple
+
     def __call__(self, field, **kwargs):
-        kwargs.setdefault('id', field.id)
-        return HTMLString('<input %s>' % html_params(name=field.name, type='file', **kwargs))
+        # browser ignores value of file input for security
+        kwargs['value'] = False
+
+        if self.multiple:
+            kwargs['multiple'] = True
+
+        return super(FileInput, self).__call__(field, **kwargs)
 
 
 class SubmitInput(Input):


Test patch:
diff --git a/tests/csrf.py b/tests/csrf.py
index b1d8af4..d244bd3 100644
--- a/tests/csrf.py
+++ b/tests/csrf.py
@@ -4,7 +4,7 @@ from contextlib import contextmanager
 from functools import partial
 from unittest import TestCase
 
-from wtforms.fields import TextField
+from wtforms.fields import StringField
 from wtforms.form import Form
 from wtforms.csrf.core import CSRF
 from wtforms.csrf.session import SessionCSRF
@@ -54,7 +54,7 @@ class DummyCSRFTest(TestCase):
         class Meta:
             csrf = True
             csrf_class = DummyCSRF
-        a = TextField()
+        a = StringField()
 
     def test_base_class(self):
         self.assertRaises(NotImplementedError, self.F, meta={'csrf_class': CSRF})
@@ -90,7 +90,7 @@ class SessionCSRFTest(TestCase):
             csrf = True
             csrf_secret = b'foobar'
 
-        a = TextField()
+        a = StringField()
 
     class NoTimeLimit(F):
         class Meta:
diff --git a/tests/fields.py b/tests/fields.py
index 0a845a8..b54833a 100644
--- a/tests/fields.py
+++ b/tests/fields.py
@@ -13,6 +13,7 @@ from wtforms.form import Form
 from wtforms.compat import text_type
 from wtforms.utils import unset_value
 from tests.common import DummyPostData
+from wtforms.widgets import TextInput
 
 PYTHON_VERSION = sys.version_info
 
@@ -33,11 +34,11 @@ class DefaultsTest(TestCase):
         def default_callable():
             return expected
 
-        test_value = TextField(default=expected).bind(Form(), 'a')
+        test_value = StringField(default=expected).bind(Form(), 'a')
         test_value.process(None)
         self.assertEqual(test_value.data, expected)
 
-        test_callable = TextField(default=default_callable).bind(Form(), 'a')
+        test_callable = StringField(default=default_callable).bind(Form(), 'a')
         test_callable.process(None)
         self.assertEqual(test_callable.data, expected)
 
@@ -52,7 +53,7 @@ class LabelTest(TestCase):
         self.assertEqual(label.__html__(), expected)
         self.assertEqual(label().__html__(), expected)
         self.assertEqual(label('hello'), """<label for="test">hello</label>""")
-        self.assertEqual(TextField('hi').bind(Form(), 'a').label.text, 'hi')
+        self.assertEqual(StringField('hi').bind(Form(), 'a').label.text, 'hi')
         if PYTHON_VERSION < (3,):
             self.assertEqual(repr(label), "Label(u'test', u'Caption')")
         else:
@@ -60,10 +61,10 @@ class LabelTest(TestCase):
             self.assertEqual(label.__unicode__(), expected)
 
     def test_auto_label(self):
-        t1 = TextField().bind(Form(), 'foo_bar')
+        t1 = StringField().bind(Form(), 'foo_bar')
         self.assertEqual(t1.label.text, 'Foo Bar')
 
-        t2 = TextField('').bind(Form(), 'foo_bar')
+        t2 = StringField('').bind(Form(), 'foo_bar')
         self.assertEqual(t2.label.text, '')
 
     def test_override_for(self):
@@ -74,7 +75,7 @@ class LabelTest(TestCase):
 
 class FlagsTest(TestCase):
     def setUp(self):
-        t = TextField(validators=[validators.Required()]).bind(Form(), 'a')
+        t = StringField(validators=[validators.DataRequired()]).bind(Form(), 'a')
         self.flags = t.flags
 
     def test_existing_values(self):
@@ -115,8 +116,8 @@ class UnsetValueTest(TestCase):
 
 class FiltersTest(TestCase):
     class F(Form):
-        a = TextField(default=' hello', filters=[lambda x: x.strip()])
-        b = TextField(default='42', filters=[int, lambda x: -x])
+        a = StringField(default=' hello', filters=[lambda x: x.strip()])
+        b = StringField(default='42', filters=[int, lambda x: -x])
 
     def test_working(self):
         form = self.F()
@@ -134,7 +135,7 @@ class FiltersTest(TestCase):
 
 class FieldTest(TestCase):
     class F(Form):
-        a = TextField(default='hello', render_kw={'readonly': True, 'foo': u'bar'})
+        a = StringField(default='hello', render_kw={'readonly': True, 'foo': u'bar'})
 
     def setUp(self):
         self.field = self.F().a
@@ -142,10 +143,10 @@ class FieldTest(TestCase):
     def test_unbound_field(self):
         unbound = self.F.a
         assert unbound.creation_counter != 0
-        assert unbound.field_class is TextField
+        assert unbound.field_class is StringField
         self.assertEqual(unbound.args, ())
         self.assertEqual(unbound.kwargs, {'default': 'hello', 'render_kw': {'readonly': True, 'foo': u'bar'}})
-        assert repr(unbound).startswith('<UnboundField(TextField')
+        assert repr(unbound).startswith('<UnboundField(StringField')
 
     def test_htmlstring(self):
         self.assertTrue(isinstance(self.field.__html__(), widgets.HTMLString))
@@ -168,11 +169,11 @@ class FieldTest(TestCase):
 
         # Can we pass in meta via _meta?
         form_meta = meta.DefaultMeta()
-        field = TextField(_name='Foo', _form=None, _meta=form_meta)
+        field = StringField(_name='Foo', _form=None, _meta=form_meta)
         assert field.meta is form_meta
 
         # Do we fail if both _meta and _form are None?
-        self.assertRaises(TypeError, TextField, _name='foo', _form=None)
+        self.assertRaises(TypeError, StringField, _name='foo', _form=None)
 
     def test_render_kw(self):
         form = self.F()
@@ -183,8 +184,28 @@ class FieldTest(TestCase):
             u'<input foo="baz" id="a" name="a" other="hello" type="text" value="hello">'
         )
 
+    def test_select_field_copies_choices(self):
+        class F(Form):
+            items = SelectField(choices=[])
 
-class PrePostTestField(TextField):
+            def __init__(self, *args, **kwargs):
+                super(F, self).__init__(*args, **kwargs)
+
+            def add_choice(self, choice):
+                self.items.choices.append((choice, choice))
+
+        f1 = F()
+        f2 = F()
+
+        f1.add_choice('a')
+        f2.add_choice('b')
+
+        self.assertEqual(f1.items.choices, [('a', 'a')])
+        self.assertEqual(f2.items.choices, [('b', 'b')])
+        self.assertTrue(f1.items.choices is not f2.items.choices)
+
+
+class PrePostTestField(StringField):
     def pre_validate(self, form):
         if self.data == "stoponly":
             raise validators.StopValidation()
@@ -350,9 +371,9 @@ class RadioFieldTest(TestCase):
         )
 
 
-class TextFieldTest(TestCase):
+class StringFieldTest(TestCase):
     class F(Form):
-        a = TextField()
+        a = StringField()
 
     def test(self):
         form = self.F()
@@ -396,12 +417,29 @@ class PasswordFieldTest(TestCase):
 
 
 class FileFieldTest(TestCase):
-    class F(Form):
-        a = FileField(default="LE DEFAULT")
+    def test_file_field(self):
+        class F(Form):
+            file = FileField()
 
-    def test(self):
-        form = self.F()
-        self.assertEqual(form.a(), """<input id="a" name="a" type="file">""")
+        self.assertEqual(F(DummyPostData(file=['test.txt'])).file.data, 'test.txt')
+        self.assertEqual(F(DummyPostData()).file.data, None)
+        self.assertEqual(F(DummyPostData(file=['test.txt', 'multiple.txt'])).file.data, 'test.txt')
+
+    def test_multiple_file_field(self):
+        class F(Form):
+            files = MultipleFileField()
+
+        self.assertEqual(F(DummyPostData(files=['test.txt'])).files.data, ['test.txt'])
+        self.assertEqual(F(DummyPostData()).files.data, [])
+        self.assertEqual(F(DummyPostData(files=['test.txt', 'multiple.txt'])).files.data, ['test.txt', 'multiple.txt'])
+
+    def test_file_field_without_file_input(self):
+        class F(Form):
+            file = FileField(widget=TextInput())
+
+        f = F(DummyPostData(file=['test.txt']))
+        self.assertEqual(f.file.data, 'test.txt')
+        self.assertEqual(f.file(), '<input id="file" name="file" type="text">')
 
 
 class IntegerFieldTest(TestCase):
@@ -593,8 +631,8 @@ class SubmitFieldTest(TestCase):
 class FormFieldTest(TestCase):
     def setUp(self):
         F = make_form(
-            a=TextField(validators=[validators.required()]),
-            b=TextField(),
+            a=StringField(validators=[validators.DataRequired()]),
+            b=StringField(),
         )
         self.F1 = make_form('F1', a=FormField(F))
         self.F2 = make_form('F2', a=FormField(F, separator='::'))
@@ -638,7 +676,7 @@ class FormFieldTest(TestCase):
 
     def test_no_validators_or_filters(self):
         class A(Form):
-            a = FormField(self.F1, validators=[validators.required()])
+            a = FormField(self.F1, validators=[validators.DataRequired()])
         self.assertRaises(TypeError, A)
 
         class B(Form):
@@ -662,7 +700,7 @@ class FormFieldTest(TestCase):
 
 
 class FieldListTest(TestCase):
-    t = TextField(validators=[validators.Required()])
+    t = StringField(validators=[validators.DataRequired()])
 
     def test_form(self):
         F = make_form(a=FieldList(self.t))
@@ -797,7 +835,7 @@ class FieldListTest(TestCase):
         self.assertEqual(form.a.data, data)
 
 
-class MyCustomField(TextField):
+class MyCustomField(StringField):
     def process_data(self, data):
         if data == 'fail':
             raise ValueError('Contrived Failure')
diff --git a/tests/form.py b/tests/form.py
index d7204b0..6a3fd3c 100644
--- a/tests/form.py
+++ b/tests/form.py
@@ -4,7 +4,7 @@ from unittest import TestCase
 
 from wtforms.form import BaseForm, Form
 from wtforms.meta import DefaultMeta
-from wtforms.fields import TextField, IntegerField
+from wtforms.fields import StringField, IntegerField
 from wtforms.validators import ValidationError
 from tests.common import DummyPostData
 
@@ -15,7 +15,7 @@ class BaseFormTest(TestCase):
             if field.data != 'foobar':
                 raise ValidationError('error')
 
-        return BaseForm({'test': TextField(validators=[validate_test])}, **kwargs)
+        return BaseForm({'test': StringField(validators=[validate_test])}, **kwargs)
 
     def test_data_proxy(self):
         form = self.get_form()
@@ -47,7 +47,7 @@ class BaseFormTest(TestCase):
     def test_field_adding(self):
         form = self.get_form()
         self.assertEqual(len(list(form)), 1)
-        form['foo'] = TextField()
+        form['foo'] = StringField()
         self.assertEqual(len(list(form)), 2)
         form.process(DummyPostData(foo=['hello']))
         self.assertEqual(form['foo'].data, 'hello')
@@ -85,12 +85,12 @@ class BaseFormTest(TestCase):
 class FormMetaTest(TestCase):
     def test_monkeypatch(self):
         class F(Form):
-            a = TextField()
+            a = StringField()
 
         self.assertEqual(F._unbound_fields, None)
         F()
         self.assertEqual(F._unbound_fields, [('a', F.a)])
-        F.b = TextField()
+        F.b = StringField()
         self.assertEqual(F._unbound_fields, None)
         F()
         self.assertEqual(F._unbound_fields, [('a', F.a), ('b', F.b)])
@@ -98,17 +98,17 @@ class FormMetaTest(TestCase):
         self.assertRaises(AttributeError, lambda: F.a)
         F()
         self.assertEqual(F._unbound_fields, [('b', F.b)])
-        F._m = TextField()
+        F._m = StringField()
         self.assertEqual(F._unbound_fields, [('b', F.b)])
 
     def test_subclassing(self):
         class A(Form):
-            a = TextField()
-            c = TextField()
+            a = StringField()
+            c = StringField()
 
         class B(A):
-            b = TextField()
-            c = TextField()
+            b = StringField()
+            c = StringField()
         A()
         B()
 
@@ -138,7 +138,7 @@ class FormMetaTest(TestCase):
 
 class FormTest(TestCase):
     class F(Form):
-        test = TextField()
+        test = StringField()
 
         def validate_test(form, field):
             if field.data != 'foobar':
@@ -153,7 +153,7 @@ class FormTest(TestCase):
 
     def test_field_adding_disabled(self):
         form = self.F()
-        self.assertRaises(TypeError, form.__setitem__, 'foo', TextField())
+        self.assertRaises(TypeError, form.__setitem__, 'foo', StringField())
 
     def test_field_removal(self):
         form = self.F()
@@ -180,16 +180,16 @@ class FormTest(TestCase):
 
     def test_ordered_fields(self):
         class MyForm(Form):
-            strawberry = TextField()
-            banana = TextField()
-            kiwi = TextField()
+            strawberry = StringField()
+            banana = StringField()
+            kiwi = StringField()
 
         self.assertEqual([x.name for x in MyForm()], ['strawberry', 'banana', 'kiwi'])
-        MyForm.apple = TextField()
+        MyForm.apple = StringField()
         self.assertEqual([x.name for x in MyForm()], ['strawberry', 'banana', 'kiwi', 'apple'])
         del MyForm.banana
         self.assertEqual([x.name for x in MyForm()], ['strawberry', 'kiwi', 'apple'])
-        MyForm.strawberry = TextField()
+        MyForm.strawberry = StringField()
         self.assertEqual([x.name for x in MyForm()], ['kiwi', 'apple', 'strawberry'])
         # Ensure sort is stable: two fields with the same creation counter
         # should be subsequently sorted by name.
@@ -203,13 +203,19 @@ class FormTest(TestCase):
         form = self.F(data=data, test='bar')
         self.assertEqual(form.test.data, 'bar')
 
+    def test_empty_formdata(self):
+        """"If formdata is empty, field.process_formdata should still run to handle empty data."""
+
+        self.assertEqual(self.F(DummyPostData({'other': 'other'})).test.data, '')
+        self.assertEqual(self.F(DummyPostData()).test.data, '')
+
 
 class MetaTest(TestCase):
     class F(Form):
         class Meta:
             foo = 9
 
-        test = TextField()
+        test = StringField()
 
     class G(Form):
         class Meta:
diff --git a/tests/i18n.py b/tests/i18n.py
index 33d0db4..7ab1e4b 100644
--- a/tests/i18n.py
+++ b/tests/i18n.py
@@ -1,7 +1,7 @@
 from __future__ import unicode_literals
 
 from unittest import TestCase
-from wtforms import form, TextField, validators
+from wtforms import form, StringField, validators
 from wtforms.i18n import get_translations
 
 
@@ -62,13 +62,13 @@ class CoreFormTest(TestCase):
     class F(form.Form):
         class Meta:
             locales = ['en_US', 'en']
-        a = TextField(validators=[validators.Required()])
+        a = StringField(validators=[validators.DataRequired()])
 
     class F2(form.Form):
-        a = TextField(validators=[validators.Required(), validators.Length(max=3)])
+        a = StringField(validators=[validators.DataRequired(), validators.Length(max=3)])
 
     class F3(form.Form):
-        a = TextField(validators=[validators.Length(max=1)])
+        a = StringField(validators=[validators.Length(max=1)])
 
     def _common_test(self, expected_error, form_kwargs, form_class=None):
         if not form_class:
@@ -120,14 +120,14 @@ class CoreFormTest(TestCase):
 
 class TranslationsTest(TestCase):
     class F(form.Form):
-        a = TextField(validators=[validators.Length(max=5)])
+        a = StringField(validators=[validators.Length(max=5)])
 
     class F2(form.Form):
         class Meta:
             def get_translations(self, form):
                 return Lower_Translator()
 
-        a = TextField('', [validators.Length(max=5)])
+        a = StringField('', [validators.Length(max=5)])
 
     def setUp(self):
         self.a = self.F().a
diff --git a/tests/validators.py b/tests/validators.py
index 3efd15d..31b4b32 100644
--- a/tests/validators.py
+++ b/tests/validators.py
@@ -3,7 +3,7 @@ from unittest import TestCase
 from wtforms.compat import text_type
 from wtforms.validators import (
     StopValidation, ValidationError, email, equal_to,
-    ip_address, length, required, optional, regexp,
+    ip_address, length, optional, regexp,
     url, NumberRange, AnyOf, NoneOf, mac_address, UUID,
     input_required, data_required
 )
@@ -110,8 +110,8 @@ class ValidatorsTest(TestCase):
         self.assertTrue('between 2 and 5' in grab(min=2, max=5))
 
     def test_required(self):
-        self.assertEqual(required()(self.form, DummyField('foobar')), None)
-        self.assertRaises(StopValidation, required(), self.form, DummyField(''))
+        self.assertEqual(data_required()(self.form, DummyField('foobar')), None)
+        self.assertRaises(StopValidation, data_required(), self.form, DummyField(''))
 
     def test_data_required(self):
         # Make sure we stop the validation chain
@@ -224,7 +224,7 @@ class ValidatorsTest(TestCase):
         self.assertTrue(equal_to('fieldname', message=message))
         self.assertTrue(length(min=1, message=message))
         self.assertTrue(NumberRange(1, 5, message=message))
-        self.assertTrue(required(message=message))
+        self.assertTrue(data_required(message=message))
         self.assertTrue(regexp('.+', message=message))
         self.assertTrue(email(message=message))
         self.assertTrue(ip_address(message=message))
diff --git a/tests/widgets.py b/tests/widgets.py
index 7a4888b..a8598fa 100644
--- a/tests/widgets.py
+++ b/tests/widgets.py
@@ -1,13 +1,12 @@
 from __future__ import unicode_literals
 
 from unittest import TestCase
-from wtforms.widgets import html_params, Input
 from wtforms.widgets import *
 from wtforms.widgets import html5
 
 
 class DummyField(object):
-    def __init__(self, data, name='f', label='', id='', type='TextField'):
+    def __init__(self, data, name='f', label='', id='', type='StringField'):
         self.data = data
         self.name = name
         self.label = label
@@ -111,6 +110,10 @@ class BasicWidgetsTest(TestCase):
         f = DummyField('hi<>bye')
         self.assertEqual(TextArea()(f), '<textarea id="" name="f">hi&lt;&gt;bye</textarea>')
 
+    def test_file(self):
+        self.assertEqual(FileInput()(self.field), '<input id="id" name="bar" type="file">')
+        self.assertEqual(FileInput(multiple=True)(self.field), '<input id="id" multiple name="bar" type="file">')
+
 
 class SelectTest(TestCase):
     field = DummyField([('foo', 'lfoo', True), ('bar', 'lbar', False)])