diff --git a/tests/forms_tests/field_tests/__init__.py b/tests/forms_tests/field_tests/__init__.py
index e69de29bb2..b984c6fb40 100644
--- a/tests/forms_tests/field_tests/__init__.py
+++ b/tests/forms_tests/field_tests/__init__.py
@@ -0,0 +1,9 @@
+from django import forms
+
+
+class FormFieldAssertionsMixin(object):
+
+ def assertWidgetRendersTo(self, field, to):
+ class Form(forms.Form):
+ f = field
+ self.assertHTMLEqual(str(Form()['f']), to)
diff --git a/tests/forms_tests/field_tests/test_base.py b/tests/forms_tests/field_tests/test_base.py
new file mode 100644
index 0000000000..2eea92a149
--- /dev/null
+++ b/tests/forms_tests/field_tests/test_base.py
@@ -0,0 +1,22 @@
+from django.forms import Field
+from django.test import SimpleTestCase
+
+
+class BasicFieldsTests(SimpleTestCase):
+
+ def test_field_sets_widget_is_required(self):
+ self.assertTrue(Field(required=True).widget.is_required)
+ self.assertFalse(Field(required=False).widget.is_required)
+
+ def test_cooperative_multiple_inheritance(self):
+ class A(object):
+ def __init__(self):
+ self.class_a_var = True
+ super(A, self).__init__()
+
+ class ComplexField(Field, A):
+ def __init__(self):
+ super(ComplexField, self).__init__()
+
+ f = ComplexField()
+ self.assertTrue(f.class_a_var)
diff --git a/tests/forms_tests/field_tests/test_booleanfield.py b/tests/forms_tests/field_tests/test_booleanfield.py
new file mode 100644
index 0000000000..9c69c96762
--- /dev/null
+++ b/tests/forms_tests/field_tests/test_booleanfield.py
@@ -0,0 +1,56 @@
+from __future__ import unicode_literals
+
+import pickle
+
+from django.forms import BooleanField, ValidationError
+from django.test import SimpleTestCase
+
+
+class BooleanFieldTest(SimpleTestCase):
+
+ def test_booleanfield_clean_1(self):
+ f = BooleanField()
+ with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
+ f.clean('')
+ with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
+ f.clean(None)
+ self.assertTrue(f.clean(True))
+ with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
+ f.clean(False)
+ self.assertTrue(f.clean(1))
+ with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
+ f.clean(0)
+ self.assertTrue(f.clean('Django rocks'))
+ self.assertTrue(f.clean('True'))
+ with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
+ f.clean('False')
+
+ def test_booleanfield_clean_2(self):
+ f = BooleanField(required=False)
+ self.assertEqual(False, f.clean(''))
+ self.assertEqual(False, f.clean(None))
+ self.assertEqual(True, f.clean(True))
+ self.assertEqual(False, f.clean(False))
+ self.assertEqual(True, f.clean(1))
+ self.assertEqual(False, f.clean(0))
+ self.assertEqual(True, f.clean('1'))
+ self.assertEqual(False, f.clean('0'))
+ self.assertEqual(True, f.clean('Django rocks'))
+ self.assertEqual(False, f.clean('False'))
+ self.assertEqual(False, f.clean('false'))
+ self.assertEqual(False, f.clean('FaLsE'))
+
+ def test_boolean_picklable(self):
+ self.assertIsInstance(pickle.loads(pickle.dumps(BooleanField())), BooleanField)
+
+ def test_booleanfield_changed(self):
+ f = BooleanField()
+ self.assertFalse(f.has_changed(None, None))
+ self.assertFalse(f.has_changed(None, ''))
+ self.assertFalse(f.has_changed('', None))
+ self.assertFalse(f.has_changed('', ''))
+ self.assertTrue(f.has_changed(False, 'on'))
+ self.assertFalse(f.has_changed(True, 'on'))
+ self.assertTrue(f.has_changed(True, ''))
+ # Initial value may have mutated to a string due to show_hidden_initial (#19537)
+ self.assertTrue(f.has_changed('False', 'on'))
diff --git a/tests/forms_tests/field_tests/test_charfield.py b/tests/forms_tests/field_tests/test_charfield.py
new file mode 100644
index 0000000000..82f2035ea5
--- /dev/null
+++ b/tests/forms_tests/field_tests/test_charfield.py
@@ -0,0 +1,109 @@
+from __future__ import unicode_literals
+
+from django.forms import (
+ CharField, PasswordInput, Textarea, TextInput, ValidationError,
+)
+from django.test import SimpleTestCase
+
+from . import FormFieldAssertionsMixin
+
+
+class CharFieldTest(FormFieldAssertionsMixin, SimpleTestCase):
+
+ def test_charfield_1(self):
+ f = CharField()
+ self.assertEqual('1', f.clean(1))
+ self.assertEqual('hello', f.clean('hello'))
+ with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
+ f.clean(None)
+ with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
+ f.clean('')
+ self.assertEqual('[1, 2, 3]', f.clean([1, 2, 3]))
+ self.assertIsNone(f.max_length)
+ self.assertIsNone(f.min_length)
+
+ def test_charfield_2(self):
+ f = CharField(required=False)
+ self.assertEqual('1', f.clean(1))
+ self.assertEqual('hello', f.clean('hello'))
+ self.assertEqual('', f.clean(None))
+ self.assertEqual('', f.clean(''))
+ self.assertEqual('[1, 2, 3]', f.clean([1, 2, 3]))
+ self.assertIsNone(f.max_length)
+ self.assertIsNone(f.min_length)
+
+ def test_charfield_3(self):
+ f = CharField(max_length=10, required=False)
+ self.assertEqual('12345', f.clean('12345'))
+ self.assertEqual('1234567890', f.clean('1234567890'))
+ msg = "'Ensure this value has at most 10 characters (it has 11).'"
+ with self.assertRaisesMessage(ValidationError, msg):
+ f.clean('1234567890a')
+ self.assertEqual(f.max_length, 10)
+ self.assertIsNone(f.min_length)
+
+ def test_charfield_4(self):
+ f = CharField(min_length=10, required=False)
+ self.assertEqual('', f.clean(''))
+ msg = "'Ensure this value has at least 10 characters (it has 5).'"
+ with self.assertRaisesMessage(ValidationError, msg):
+ f.clean('12345')
+ self.assertEqual('1234567890', f.clean('1234567890'))
+ self.assertEqual('1234567890a', f.clean('1234567890a'))
+ self.assertIsNone(f.max_length)
+ self.assertEqual(f.min_length, 10)
+
+ def test_charfield_5(self):
+ f = CharField(min_length=10, required=True)
+ with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
+ f.clean('')
+ msg = "'Ensure this value has at least 10 characters (it has 5).'"
+ with self.assertRaisesMessage(ValidationError, msg):
+ f.clean('12345')
+ self.assertEqual('1234567890', f.clean('1234567890'))
+ self.assertEqual('1234567890a', f.clean('1234567890a'))
+ self.assertIsNone(f.max_length)
+ self.assertEqual(f.min_length, 10)
+
+ def test_charfield_length_not_int(self):
+ """
+ Setting min_length or max_length to something that is not a number
+ raises an exception.
+ """
+ with self.assertRaises(ValueError):
+ CharField(min_length='a')
+ with self.assertRaises(ValueError):
+ CharField(max_length='a')
+ with self.assertRaises(ValueError):
+ CharField('a')
+
+ def test_charfield_widget_attrs(self):
+ """
+ CharField.widget_attrs() always returns a dictionary (#15912).
+ """
+ # Return an empty dictionary if max_length is None
+ f = CharField()
+ self.assertEqual(f.widget_attrs(TextInput()), {})
+ self.assertEqual(f.widget_attrs(Textarea()), {})
+
+ # Otherwise, return a maxlength attribute equal to max_length
+ f = CharField(max_length=10)
+ self.assertEqual(f.widget_attrs(TextInput()), {'maxlength': '10'})
+ self.assertEqual(f.widget_attrs(PasswordInput()), {'maxlength': '10'})
+ self.assertEqual(f.widget_attrs(Textarea()), {'maxlength': '10'})
+
+ def test_charfield_strip(self):
+ """
+ Values have whitespace stripped but not if strip=False.
+ """
+ f = CharField()
+ self.assertEqual(f.clean(' 1'), '1')
+ self.assertEqual(f.clean('1 '), '1')
+
+ f = CharField(strip=False)
+ self.assertEqual(f.clean(' 1'), ' 1')
+ self.assertEqual(f.clean('1 '), '1 ')
+
+ def test_charfield_disabled(self):
+ f = CharField(disabled=True)
+ self.assertWidgetRendersTo(f, '')
diff --git a/tests/forms_tests/field_tests/test_choicefield.py b/tests/forms_tests/field_tests/test_choicefield.py
new file mode 100644
index 0000000000..013766d50b
--- /dev/null
+++ b/tests/forms_tests/field_tests/test_choicefield.py
@@ -0,0 +1,86 @@
+from __future__ import unicode_literals
+
+from django.forms import ChoiceField, Form, ValidationError
+from django.test import SimpleTestCase
+
+from . import FormFieldAssertionsMixin
+
+
+class ChoiceFieldTest(FormFieldAssertionsMixin, SimpleTestCase):
+
+ def test_choicefield_1(self):
+ f = ChoiceField(choices=[('1', 'One'), ('2', 'Two')])
+ with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
+ f.clean('')
+ with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
+ f.clean(None)
+ self.assertEqual('1', f.clean(1))
+ self.assertEqual('1', f.clean('1'))
+ msg = "'Select a valid choice. 3 is not one of the available choices.'"
+ with self.assertRaisesMessage(ValidationError, msg):
+ f.clean('3')
+
+ def test_choicefield_2(self):
+ f = ChoiceField(choices=[('1', 'One'), ('2', 'Two')], required=False)
+ self.assertEqual('', f.clean(''))
+ self.assertEqual('', f.clean(None))
+ self.assertEqual('1', f.clean(1))
+ self.assertEqual('1', f.clean('1'))
+ msg = "'Select a valid choice. 3 is not one of the available choices.'"
+ with self.assertRaisesMessage(ValidationError, msg):
+ f.clean('3')
+
+ def test_choicefield_3(self):
+ f = ChoiceField(choices=[('J', 'John'), ('P', 'Paul')])
+ self.assertEqual('J', f.clean('J'))
+ msg = "'Select a valid choice. John is not one of the available choices.'"
+ with self.assertRaisesMessage(ValidationError, msg):
+ f.clean('John')
+
+ def test_choicefield_4(self):
+ f = ChoiceField(
+ choices=[
+ ('Numbers', (('1', 'One'), ('2', 'Two'))),
+ ('Letters', (('3', 'A'), ('4', 'B'))), ('5', 'Other'),
+ ]
+ )
+ self.assertEqual('1', f.clean(1))
+ self.assertEqual('1', f.clean('1'))
+ self.assertEqual('3', f.clean(3))
+ self.assertEqual('3', f.clean('3'))
+ self.assertEqual('5', f.clean(5))
+ self.assertEqual('5', f.clean('5'))
+ msg = "'Select a valid choice. 6 is not one of the available choices.'"
+ with self.assertRaisesMessage(ValidationError, msg):
+ f.clean('6')
+
+ def test_choicefield_callable(self):
+ def choices():
+ return [('J', 'John'), ('P', 'Paul')]
+ f = ChoiceField(choices=choices)
+ self.assertEqual('J', f.clean('J'))
+
+ def test_choicefield_callable_may_evaluate_to_different_values(self):
+ choices = []
+
+ def choices_as_callable():
+ return choices
+
+ class ChoiceFieldForm(Form):
+ choicefield = ChoiceField(choices=choices_as_callable)
+
+ choices = [('J', 'John')]
+ form = ChoiceFieldForm()
+ self.assertEqual([('J', 'John')], list(form.fields['choicefield'].choices))
+
+ choices = [('P', 'Paul')]
+ form = ChoiceFieldForm()
+ self.assertEqual([('P', 'Paul')], list(form.fields['choicefield'].choices))
+
+ def test_choicefield_disabled(self):
+ f = ChoiceField(choices=[('J', 'John'), ('P', 'Paul')], disabled=True)
+ self.assertWidgetRendersTo(
+ f,
+ ''
+ )
diff --git a/tests/forms_tests/field_tests/test_combofield.py b/tests/forms_tests/field_tests/test_combofield.py
new file mode 100644
index 0000000000..6ca91233bc
--- /dev/null
+++ b/tests/forms_tests/field_tests/test_combofield.py
@@ -0,0 +1,29 @@
+from __future__ import unicode_literals
+
+from django.forms import CharField, ComboField, EmailField, ValidationError
+from django.test import SimpleTestCase
+
+
+class ComboFieldTest(SimpleTestCase):
+
+ def test_combofield_1(self):
+ f = ComboField(fields=[CharField(max_length=20), EmailField()])
+ self.assertEqual('test@example.com', f.clean('test@example.com'))
+ with self.assertRaisesMessage(ValidationError, "'Ensure this value has at most 20 characters (it has 28).'"):
+ f.clean('longemailaddress@example.com')
+ with self.assertRaisesMessage(ValidationError, "'Enter a valid email address.'"):
+ f.clean('not an email')
+ with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
+ f.clean('')
+ with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
+ f.clean(None)
+
+ def test_combofield_2(self):
+ f = ComboField(fields=[CharField(max_length=20), EmailField()], required=False)
+ self.assertEqual('test@example.com', f.clean('test@example.com'))
+ with self.assertRaisesMessage(ValidationError, "'Ensure this value has at most 20 characters (it has 28).'"):
+ f.clean('longemailaddress@example.com')
+ with self.assertRaisesMessage(ValidationError, "'Enter a valid email address.'"):
+ f.clean('not an email')
+ self.assertEqual('', f.clean(''))
+ self.assertEqual('', f.clean(None))
diff --git a/tests/forms_tests/field_tests/test_datefield.py b/tests/forms_tests/field_tests/test_datefield.py
index 6b66cb2d52..75dda3425b 100644
--- a/tests/forms_tests/field_tests/test_datefield.py
+++ b/tests/forms_tests/field_tests/test_datefield.py
@@ -1,6 +1,9 @@
-from datetime import date
+# -*- coding: utf-8 -*-
+from datetime import date, datetime
-from django.forms import DateField, Form, HiddenInput, SelectDateWidget
+from django.forms import (
+ DateField, Form, HiddenInput, SelectDateWidget, ValidationError,
+)
from django.test import SimpleTestCase, override_settings
from django.utils import translation
@@ -40,8 +43,8 @@ class DateFieldTest(SimpleTestCase):
@translation.override('nl')
def test_l10n_date_changed(self):
"""
- Ensure that DateField.has_changed() with SelectDateWidget works
- correctly with a localized date format (#17165).
+ DateField.has_changed() with SelectDateWidget works with a localized
+ date format (#17165).
"""
# With Field.show_hidden_initial=False
b = GetDate({
@@ -109,3 +112,79 @@ class DateFieldTest(SimpleTestCase):
# label tag is correctly associated with first rendered dropdown
a = GetDate({'mydate_month': '1', 'mydate_day': '1', 'mydate_year': '2010'})
self.assertIn('