Repository: wtforms/wtforms
Pull request #282 (2016-06-22 20:13:33)

Remove use of deprecated TextField and Required
Change `TextField` to `StringField` and `Required` to `DataRequired`.

davidism: I think the coverage decrease is just from changing the proportion of testable code.

Patch:
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/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/wtforms/fields/core.py b/wtforms/fields/core.py
index 768ca36..7ef5420 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
@@ -445,7 +447,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 +837,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..c3d5d62 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
 
-
 __all__ = (
     'BooleanField', 'TextAreaField', 'PasswordField', 'FileField',
-    'HiddenField', 'SubmitField', 'TextField'
+    '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
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


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..b299134 100644
--- a/tests/fields.py
+++ b/tests/fields.py
@@ -33,11 +33,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 +52,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 +60,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 +74,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 +115,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 +134,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 +142,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 +168,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 +183,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 +370,9 @@ class RadioFieldTest(TestCase):
         )
 
 
-class TextFieldTest(TestCase):
+class StringFieldTest(TestCase):
     class F(Form):
-        a = TextField()
+        a = StringField()
 
     def test(self):
         form = self.F()
@@ -593,8 +613,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 +658,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 +682,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 +817,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..ff20bb5 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.
@@ -209,7 +209,7 @@ class MetaTest(TestCase):
         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..2a03771 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