diff options
| author | Berker Peksag <berker.peksag@gmail.com> | 2016-04-09 20:17:34 +0300 |
|---|---|---|
| committer | Tim Graham <timograham@gmail.com> | 2016-04-11 12:48:23 -0400 |
| commit | 0247c9b08f8da4a2d93b9cede6c615011552b55a (patch) | |
| tree | efd8075e0f615ccf931c304d653bff621e194660 /tests/forms_tests | |
| parent | 353d436e7cb33cb832a3e8c74b051e3d2ba76018 (diff) | |
Split form's test_fields.py into different files.
Diffstat (limited to 'tests/forms_tests')
29 files changed, 2022 insertions, 1897 deletions
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, '<input type="text" name="f" id="id_f" disabled />') 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, + '<select id="id_f" name="f" disabled><option value="J">John</option>' + '<option value="P">Paul</option></select>' + ) 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('<label for="id_mydate_day">', a.as_p()) + + def test_datefield_1(self): + f = DateField() + self.assertEqual(date(2006, 10, 25), f.clean(date(2006, 10, 25))) + self.assertEqual(date(2006, 10, 25), f.clean(datetime(2006, 10, 25, 14, 30))) + self.assertEqual(date(2006, 10, 25), f.clean(datetime(2006, 10, 25, 14, 30, 59))) + self.assertEqual(date(2006, 10, 25), f.clean(datetime(2006, 10, 25, 14, 30, 59, 200))) + self.assertEqual(date(2006, 10, 25), f.clean('2006-10-25')) + self.assertEqual(date(2006, 10, 25), f.clean('10/25/2006')) + self.assertEqual(date(2006, 10, 25), f.clean('10/25/06')) + self.assertEqual(date(2006, 10, 25), f.clean('Oct 25 2006')) + self.assertEqual(date(2006, 10, 25), f.clean('October 25 2006')) + self.assertEqual(date(2006, 10, 25), f.clean('October 25, 2006')) + self.assertEqual(date(2006, 10, 25), f.clean('25 October 2006')) + self.assertEqual(date(2006, 10, 25), f.clean('25 October, 2006')) + with self.assertRaisesMessage(ValidationError, "'Enter a valid date.'"): + f.clean('2006-4-31') + with self.assertRaisesMessage(ValidationError, "'Enter a valid date.'"): + f.clean('200a-10-25') + with self.assertRaisesMessage(ValidationError, "'Enter a valid date.'"): + f.clean('25/10/06') + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean(None) + + def test_datefield_2(self): + f = DateField(required=False) + self.assertIsNone(f.clean(None)) + self.assertEqual('None', repr(f.clean(None))) + self.assertIsNone(f.clean('')) + self.assertEqual('None', repr(f.clean(''))) + + def test_datefield_3(self): + f = DateField(input_formats=['%Y %m %d']) + self.assertEqual(date(2006, 10, 25), f.clean(date(2006, 10, 25))) + self.assertEqual(date(2006, 10, 25), f.clean(datetime(2006, 10, 25, 14, 30))) + self.assertEqual(date(2006, 10, 25), f.clean('2006 10 25')) + with self.assertRaisesMessage(ValidationError, "'Enter a valid date.'"): + f.clean('2006-10-25') + with self.assertRaisesMessage(ValidationError, "'Enter a valid date.'"): + f.clean('10/25/2006') + with self.assertRaisesMessage(ValidationError, "'Enter a valid date.'"): + f.clean('10/25/06') + + def test_datefield_4(self): + # Test whitespace stripping behavior (#5714) + f = DateField() + self.assertEqual(date(2006, 10, 25), f.clean(' 10/25/2006 ')) + self.assertEqual(date(2006, 10, 25), f.clean(' 10/25/06 ')) + self.assertEqual(date(2006, 10, 25), f.clean(' Oct 25 2006 ')) + self.assertEqual(date(2006, 10, 25), f.clean(' October 25 2006 ')) + self.assertEqual(date(2006, 10, 25), f.clean(' October 25, 2006 ')) + self.assertEqual(date(2006, 10, 25), f.clean(' 25 October 2006 ')) + with self.assertRaisesMessage(ValidationError, "'Enter a valid date.'"): + f.clean(' ') + + def test_datefield_5(self): + # Test null bytes (#18982) + f = DateField() + with self.assertRaisesMessage(ValidationError, "'Enter a valid date.'"): + f.clean('a\x00b') + + def test_datefield_changed(self): + format = '%d/%m/%Y' + f = DateField(input_formats=[format]) + d = date(2007, 9, 17) + self.assertFalse(f.has_changed(d, '17/09/2007')) + + def test_datefield_strptime(self): + """field.strptime() doesn't raise a UnicodeEncodeError (#16123)""" + f = DateField() + try: + f.strptime('31 мая 2011', '%d-%b-%y') + except Exception as e: + # assertIsInstance or assertRaises cannot be used because UnicodeEncodeError + # is a subclass of ValueError + self.assertEqual(e.__class__, ValueError) diff --git a/tests/forms_tests/field_tests/test_datetimefield.py b/tests/forms_tests/field_tests/test_datetimefield.py new file mode 100644 index 0000000000..f83b9b7f76 --- /dev/null +++ b/tests/forms_tests/field_tests/test_datetimefield.py @@ -0,0 +1,88 @@ +from __future__ import unicode_literals + +import datetime + +from django.forms import DateTimeField, ValidationError +from django.test import SimpleTestCase + + +class DateTimeFieldTest(SimpleTestCase): + + def test_datetimefield_1(self): + f = DateTimeField() + self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean(datetime.date(2006, 10, 25))) + self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean(datetime.datetime(2006, 10, 25, 14, 30))) + self.assertEqual( + datetime.datetime(2006, 10, 25, 14, 30, 59), + f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59)) + ) + self.assertEqual( + datetime.datetime(2006, 10, 25, 14, 30, 59, 200), + f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59, 200)) + ) + self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45, 200), f.clean('2006-10-25 14:30:45.000200')) + self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45, 200), f.clean('2006-10-25 14:30:45.0002')) + self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45), f.clean('2006-10-25 14:30:45')) + self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('2006-10-25 14:30:00')) + self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('2006-10-25 14:30')) + self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean('2006-10-25')) + self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45, 200), f.clean('10/25/2006 14:30:45.000200')) + self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45), f.clean('10/25/2006 14:30:45')) + self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('10/25/2006 14:30:00')) + self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('10/25/2006 14:30')) + self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean('10/25/2006')) + self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45, 200), f.clean('10/25/06 14:30:45.000200')) + self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45), f.clean('10/25/06 14:30:45')) + self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('10/25/06 14:30:00')) + self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('10/25/06 14:30')) + self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean('10/25/06')) + with self.assertRaisesMessage(ValidationError, "'Enter a valid date/time.'"): + f.clean('hello') + with self.assertRaisesMessage(ValidationError, "'Enter a valid date/time.'"): + f.clean('2006-10-25 4:30 p.m.') + + def test_datetimefield_2(self): + f = DateTimeField(input_formats=['%Y %m %d %I:%M %p']) + self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean(datetime.date(2006, 10, 25))) + self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean(datetime.datetime(2006, 10, 25, 14, 30))) + self.assertEqual( + datetime.datetime(2006, 10, 25, 14, 30, 59), + f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59)) + ) + self.assertEqual( + datetime.datetime(2006, 10, 25, 14, 30, 59, 200), + f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59, 200)) + ) + self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('2006 10 25 2:30 PM')) + with self.assertRaisesMessage(ValidationError, "'Enter a valid date/time.'"): + f.clean('2006-10-25 14:30:45') + + def test_datetimefield_3(self): + f = DateTimeField(required=False) + self.assertIsNone(f.clean(None)) + self.assertEqual('None', repr(f.clean(None))) + self.assertIsNone(f.clean('')) + self.assertEqual('None', repr(f.clean(''))) + + def test_datetimefield_4(self): + f = DateTimeField() + # Test whitespace stripping behavior (#5714) + self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45), f.clean(' 2006-10-25 14:30:45 ')) + self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean(' 2006-10-25 ')) + self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45), f.clean(' 10/25/2006 14:30:45 ')) + self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean(' 10/25/2006 14:30 ')) + self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean(' 10/25/2006 ')) + self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45), f.clean(' 10/25/06 14:30:45 ')) + self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean(' 10/25/06 ')) + with self.assertRaisesMessage(ValidationError, "'Enter a valid date/time.'"): + f.clean(' ') + + def test_datetimefield_5(self): + f = DateTimeField(input_formats=['%Y.%m.%d %H:%M:%S.%f']) + self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45, 200), f.clean('2006.10.25 14:30:45.0002')) + + def test_datetimefield_changed(self): + format = '%Y %m %d %I:%M %p' + f = DateTimeField(input_formats=[format]) + d = datetime.datetime(2006, 9, 17, 14, 30, 0) + self.assertFalse(f.has_changed(d, '2006 09 17 2:30 PM')) diff --git a/tests/forms_tests/field_tests/test_decimalfield.py b/tests/forms_tests/field_tests/test_decimalfield.py new file mode 100644 index 0000000000..3b23ced714 --- /dev/null +++ b/tests/forms_tests/field_tests/test_decimalfield.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import decimal + +from django.forms import DecimalField, NumberInput, ValidationError, Widget +from django.test import SimpleTestCase +from django.utils import formats, translation + +from . import FormFieldAssertionsMixin + + +class DecimalFieldTest(FormFieldAssertionsMixin, SimpleTestCase): + + def test_decimalfield_1(self): + f = DecimalField(max_digits=4, decimal_places=2) + self.assertWidgetRendersTo(f, '<input id="id_f" step="0.01" type="number" name="f" />') + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean('') + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean(None) + self.assertEqual(f.clean('1'), decimal.Decimal("1")) + self.assertIsInstance(f.clean('1'), decimal.Decimal) + self.assertEqual(f.clean('23'), decimal.Decimal("23")) + self.assertEqual(f.clean('3.14'), decimal.Decimal("3.14")) + self.assertEqual(f.clean(3.14), decimal.Decimal("3.14")) + self.assertEqual(f.clean(decimal.Decimal('3.14')), decimal.Decimal("3.14")) + with self.assertRaisesMessage(ValidationError, "'Enter a number.'"): + f.clean('NaN') + with self.assertRaisesMessage(ValidationError, "'Enter a number.'"): + f.clean('Inf') + with self.assertRaisesMessage(ValidationError, "'Enter a number.'"): + f.clean('-Inf') + with self.assertRaisesMessage(ValidationError, "'Enter a number.'"): + f.clean('a') + with self.assertRaisesMessage(ValidationError, "'Enter a number.'"): + f.clean('łąść') + self.assertEqual(f.clean('1.0 '), decimal.Decimal("1.0")) + self.assertEqual(f.clean(' 1.0'), decimal.Decimal("1.0")) + self.assertEqual(f.clean(' 1.0 '), decimal.Decimal("1.0")) + with self.assertRaisesMessage(ValidationError, "'Enter a number.'"): + f.clean('1.0a') + with self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 4 digits in total.'"): + f.clean('123.45') + with self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 2 decimal places.'"): + f.clean('1.234') + msg = "'Ensure that there are no more than 2 digits before the decimal point.'" + with self.assertRaisesMessage(ValidationError, msg): + f.clean('123.4') + self.assertEqual(f.clean('-12.34'), decimal.Decimal("-12.34")) + with self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 4 digits in total.'"): + f.clean('-123.45') + self.assertEqual(f.clean('-.12'), decimal.Decimal("-0.12")) + self.assertEqual(f.clean('-00.12'), decimal.Decimal("-0.12")) + self.assertEqual(f.clean('-000.12'), decimal.Decimal("-0.12")) + with self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 2 decimal places.'"): + f.clean('-000.123') + with self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 4 digits in total.'"): + f.clean('-000.12345') + with self.assertRaisesMessage(ValidationError, "'Enter a number.'"): + f.clean('--0.12') + self.assertEqual(f.max_digits, 4) + self.assertEqual(f.decimal_places, 2) + self.assertIsNone(f.max_value) + self.assertIsNone(f.min_value) + + def test_decimalfield_2(self): + f = DecimalField(max_digits=4, decimal_places=2, required=False) + self.assertIsNone(f.clean('')) + self.assertIsNone(f.clean(None)) + self.assertEqual(f.clean('1'), decimal.Decimal("1")) + self.assertEqual(f.max_digits, 4) + self.assertEqual(f.decimal_places, 2) + self.assertIsNone(f.max_value) + self.assertIsNone(f.min_value) + + def test_decimalfield_3(self): + f = DecimalField( + max_digits=4, decimal_places=2, + max_value=decimal.Decimal('1.5'), + min_value=decimal.Decimal('0.5') + ) + self.assertWidgetRendersTo(f, '<input step="0.01" name="f" min="0.5" max="1.5" type="number" id="id_f" />') + with self.assertRaisesMessage(ValidationError, "'Ensure this value is less than or equal to 1.5.'"): + f.clean('1.6') + with self.assertRaisesMessage(ValidationError, "'Ensure this value is greater than or equal to 0.5.'"): + f.clean('0.4') + self.assertEqual(f.clean('1.5'), decimal.Decimal("1.5")) + self.assertEqual(f.clean('0.5'), decimal.Decimal("0.5")) + self.assertEqual(f.clean('.5'), decimal.Decimal("0.5")) + self.assertEqual(f.clean('00.50'), decimal.Decimal("0.50")) + self.assertEqual(f.max_digits, 4) + self.assertEqual(f.decimal_places, 2) + self.assertEqual(f.max_value, decimal.Decimal('1.5')) + self.assertEqual(f.min_value, decimal.Decimal('0.5')) + + def test_decimalfield_4(self): + f = DecimalField(decimal_places=2) + with self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 2 decimal places.'"): + f.clean('0.00000001') + + def test_decimalfield_5(self): + f = DecimalField(max_digits=3) + # Leading whole zeros "collapse" to one digit. + self.assertEqual(f.clean('0000000.10'), decimal.Decimal("0.1")) + # But a leading 0 before the . doesn't count towards max_digits + self.assertEqual(f.clean('0000000.100'), decimal.Decimal("0.100")) + # Only leading whole zeros "collapse" to one digit. + self.assertEqual(f.clean('000000.02'), decimal.Decimal('0.02')) + with self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 3 digits in total.'"): + f.clean('000000.0002') + self.assertEqual(f.clean('.002'), decimal.Decimal("0.002")) + + def test_decimalfield_6(self): + f = DecimalField(max_digits=2, decimal_places=2) + self.assertEqual(f.clean('.01'), decimal.Decimal(".01")) + msg = "'Ensure that there are no more than 0 digits before the decimal point.'" + with self.assertRaisesMessage(ValidationError, msg): + f.clean('1.1') + + def test_decimalfield_scientific(self): + f = DecimalField(max_digits=2, decimal_places=2) + self.assertEqual(f.clean('1E+2'), decimal.Decimal('1E+2')) + self.assertEqual(f.clean('1e+2'), decimal.Decimal('1E+2')) + with self.assertRaisesMessage(ValidationError, "Ensure that there are no more"): + f.clean('0.546e+2') + + def test_decimalfield_widget_attrs(self): + f = DecimalField(max_digits=6, decimal_places=2) + self.assertEqual(f.widget_attrs(Widget()), {}) + self.assertEqual(f.widget_attrs(NumberInput()), {'step': '0.01'}) + f = DecimalField(max_digits=10, decimal_places=0) + self.assertEqual(f.widget_attrs(NumberInput()), {'step': '1'}) + f = DecimalField(max_digits=19, decimal_places=19) + self.assertEqual(f.widget_attrs(NumberInput()), {'step': '1e-19'}) + f = DecimalField(max_digits=20) + self.assertEqual(f.widget_attrs(NumberInput()), {'step': 'any'}) + f = DecimalField(max_digits=6, widget=NumberInput(attrs={'step': '0.01'})) + self.assertWidgetRendersTo(f, '<input step="0.01" name="f" type="number" id="id_f" />') + + def test_decimalfield_localized(self): + """ + A localized DecimalField's widget renders to a text input without + number input specific attributes. + """ + f = DecimalField(localize=True) + self.assertWidgetRendersTo(f, '<input id="id_f" name="f" type="text" />') + + def test_decimalfield_changed(self): + f = DecimalField(max_digits=2, decimal_places=2) + d = decimal.Decimal("0.1") + self.assertFalse(f.has_changed(d, '0.10')) + self.assertTrue(f.has_changed(d, '0.101')) + + with translation.override('fr'), self.settings(USE_L10N=True): + f = DecimalField(max_digits=2, decimal_places=2, localize=True) + localized_d = formats.localize_input(d) # -> '0,1' in French + self.assertFalse(f.has_changed(d, localized_d)) diff --git a/tests/forms_tests/field_tests/test_durationfield.py b/tests/forms_tests/field_tests/test_durationfield.py new file mode 100644 index 0000000000..800d5d6ae3 --- /dev/null +++ b/tests/forms_tests/field_tests/test_durationfield.py @@ -0,0 +1,39 @@ +from __future__ import unicode_literals + +import datetime + +from django.forms import DurationField +from django.test import SimpleTestCase +from django.utils.duration import duration_string + +from . import FormFieldAssertionsMixin + + +class DurationFieldTest(FormFieldAssertionsMixin, SimpleTestCase): + + def test_durationfield_clean(self): + f = DurationField() + self.assertEqual(datetime.timedelta(seconds=30), f.clean('30')) + self.assertEqual(datetime.timedelta(minutes=15, seconds=30), f.clean('15:30')) + self.assertEqual(datetime.timedelta(hours=1, minutes=15, seconds=30), f.clean('1:15:30')) + self.assertEqual( + datetime.timedelta(days=1, hours=1, minutes=15, seconds=30, milliseconds=300), + f.clean('1 1:15:30.3') + ) + + def test_durationfield_render(self): + self.assertWidgetRendersTo( + DurationField(initial=datetime.timedelta(hours=1)), + '<input id="id_f" type="text" name="f" value="01:00:00">', + ) + + def test_durationfield_integer_value(self): + f = DurationField() + self.assertEqual(datetime.timedelta(0, 10800), f.clean(10800)) + + def test_durationfield_prepare_value(self): + field = DurationField() + td = datetime.timedelta(minutes=15, seconds=30) + self.assertEqual(field.prepare_value(td), duration_string(td)) + self.assertEqual(field.prepare_value('arbitrary'), 'arbitrary') + self.assertIsNone(field.prepare_value(None)) diff --git a/tests/forms_tests/field_tests/test_emailfield.py b/tests/forms_tests/field_tests/test_emailfield.py new file mode 100644 index 0000000000..98c317a9ee --- /dev/null +++ b/tests/forms_tests/field_tests/test_emailfield.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.forms import EmailField, ValidationError +from django.test import SimpleTestCase + +from . import FormFieldAssertionsMixin + + +class EmailFieldTest(FormFieldAssertionsMixin, SimpleTestCase): + + def test_emailfield_1(self): + f = EmailField() + self.assertWidgetRendersTo(f, '<input type="email" name="f" id="id_f" />') + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean('') + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean(None) + self.assertEqual('person@example.com', f.clean('person@example.com')) + with self.assertRaisesMessage(ValidationError, "'Enter a valid email address.'"): + f.clean('foo') + self.assertEqual( + 'local@domain.with.idn.xyz\xe4\xf6\xfc\xdfabc.part.com', + f.clean('local@domain.with.idn.xyzäöüßabc.part.com') + ) + + def test_email_regexp_for_performance(self): + f = EmailField() + # Check for runaway regex security problem. This will take a long time + # if the security fix isn't in place. + addr = 'viewx3dtextx26qx3d@yahoo.comx26latlngx3d15854521645943074058' + self.assertEqual(addr, f.clean(addr)) + + def test_emailfield_not_required(self): + f = EmailField(required=False) + self.assertEqual('', f.clean('')) + self.assertEqual('', f.clean(None)) + self.assertEqual('person@example.com', f.clean('person@example.com')) + self.assertEqual('example@example.com', f.clean(' example@example.com \t \t ')) + with self.assertRaisesMessage(ValidationError, "'Enter a valid email address.'"): + f.clean('foo') + + def test_emailfield_min_max_length(self): + f = EmailField(min_length=10, max_length=15) + self.assertWidgetRendersTo(f, '<input id="id_f" type="email" name="f" maxlength="15" />') + with self.assertRaisesMessage(ValidationError, "'Ensure this value has at least 10 characters (it has 9).'"): + f.clean('a@foo.com') + self.assertEqual('alf@foo.com', f.clean('alf@foo.com')) + with self.assertRaisesMessage(ValidationError, "'Ensure this value has at most 15 characters (it has 20).'"): + f.clean('alf123456788@foo.com') diff --git a/tests/forms_tests/field_tests/test_filefield.py b/tests/forms_tests/field_tests/test_filefield.py new file mode 100644 index 0000000000..2c08075f3f --- /dev/null +++ b/tests/forms_tests/field_tests/test_filefield.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import pickle + +from django.core.files.uploadedfile import SimpleUploadedFile +from django.forms import FileField, ValidationError +from django.test import SimpleTestCase + + +class FileFieldTest(SimpleTestCase): + + def test_filefield_1(self): + f = FileField() + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean('') + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean('', '') + self.assertEqual('files/test1.pdf', f.clean('', 'files/test1.pdf')) + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean(None) + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean(None, '') + self.assertEqual('files/test2.pdf', f.clean(None, 'files/test2.pdf')) + no_file_msg = "'No file was submitted. Check the encoding type on the form.'" + with self.assertRaisesMessage(ValidationError, no_file_msg): + f.clean(SimpleUploadedFile('', b'')) + with self.assertRaisesMessage(ValidationError, no_file_msg): + f.clean(SimpleUploadedFile('', b''), '') + self.assertEqual('files/test3.pdf', f.clean(None, 'files/test3.pdf')) + with self.assertRaisesMessage(ValidationError, no_file_msg): + f.clean('some content that is not a file') + with self.assertRaisesMessage(ValidationError, "'The submitted file is empty.'"): + f.clean(SimpleUploadedFile('name', None)) + with self.assertRaisesMessage(ValidationError, "'The submitted file is empty.'"): + f.clean(SimpleUploadedFile('name', b'')) + self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('name', b'Some File Content')))) + self.assertIsInstance( + f.clean(SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', 'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह'.encode('utf-8'))), + SimpleUploadedFile + ) + self.assertIsInstance( + f.clean(SimpleUploadedFile('name', b'Some File Content'), 'files/test4.pdf'), + SimpleUploadedFile + ) + + def test_filefield_2(self): + f = FileField(max_length=5) + with self.assertRaisesMessage(ValidationError, "'Ensure this filename has at most 5 characters (it has 18).'"): + f.clean(SimpleUploadedFile('test_maxlength.txt', b'hello world')) + self.assertEqual('files/test1.pdf', f.clean('', 'files/test1.pdf')) + self.assertEqual('files/test2.pdf', f.clean(None, 'files/test2.pdf')) + self.assertIsInstance(f.clean(SimpleUploadedFile('name', b'Some File Content')), SimpleUploadedFile) + + def test_filefield_3(self): + f = FileField(allow_empty_file=True) + self.assertIsInstance(f.clean(SimpleUploadedFile('name', b'')), SimpleUploadedFile) + + def test_filefield_changed(self): + """ + The value of data will more than likely come from request.FILES. The + value of initial data will likely be a filename stored in the database. + Since its value is of no use to a FileField it is ignored. + """ + f = FileField() + + # No file was uploaded and no initial data. + self.assertFalse(f.has_changed('', None)) + + # A file was uploaded and no initial data. + self.assertTrue(f.has_changed('', {'filename': 'resume.txt', 'content': 'My resume'})) + + # A file was not uploaded, but there is initial data + self.assertFalse(f.has_changed('resume.txt', None)) + + # A file was uploaded and there is initial data (file identity is not dealt + # with here) + self.assertTrue(f.has_changed('resume.txt', {'filename': 'resume.txt', 'content': 'My resume'})) + + def test_file_picklable(self): + self.assertIsInstance(pickle.loads(pickle.dumps(FileField())), FileField) diff --git a/tests/forms_tests/field_tests/test_filepathfield.py b/tests/forms_tests/field_tests/test_filepathfield.py new file mode 100644 index 0000000000..e747189c3e --- /dev/null +++ b/tests/forms_tests/field_tests/test_filepathfield.py @@ -0,0 +1,121 @@ +from __future__ import unicode_literals + +import os.path + +from django.forms import FilePathField, ValidationError, forms +from django.test import SimpleTestCase +from django.utils import six +from django.utils._os import upath + + +def fix_os_paths(x): + if isinstance(x, six.string_types): + return x.replace('\\', '/') + elif isinstance(x, tuple): + return tuple(fix_os_paths(list(x))) + elif isinstance(x, list): + return [fix_os_paths(y) for y in x] + else: + return x + + +class FilePathFieldTest(SimpleTestCase): + + def test_filepathfield_1(self): + path = os.path.abspath(upath(forms.__file__)) + path = os.path.dirname(path) + '/' + self.assertTrue(fix_os_paths(path).endswith('/django/forms/')) + + def test_filepathfield_2(self): + path = upath(forms.__file__) + path = os.path.dirname(os.path.abspath(path)) + '/' + f = FilePathField(path=path) + f.choices = [p for p in f.choices if p[0].endswith('.py')] + f.choices.sort() + expected = [ + ('/django/forms/__init__.py', '__init__.py'), + ('/django/forms/boundfield.py', 'boundfield.py'), + ('/django/forms/fields.py', 'fields.py'), + ('/django/forms/forms.py', 'forms.py'), + ('/django/forms/formsets.py', 'formsets.py'), + ('/django/forms/models.py', 'models.py'), + ('/django/forms/utils.py', 'utils.py'), + ('/django/forms/widgets.py', 'widgets.py') + ] + for exp, got in zip(expected, fix_os_paths(f.choices)): + self.assertEqual(exp[1], got[1]) + self.assertTrue(got[0].endswith(exp[0])) + msg = "'Select a valid choice. fields.py is not one of the available choices.'" + with self.assertRaisesMessage(ValidationError, msg): + f.clean('fields.py') + self.assertTrue(fix_os_paths(f.clean(path + 'fields.py')).endswith('/django/forms/fields.py')) + + def test_filepathfield_3(self): + path = upath(forms.__file__) + path = os.path.dirname(os.path.abspath(path)) + '/' + f = FilePathField(path=path, match='^.*?\.py$') + f.choices.sort() + expected = [ + ('/django/forms/__init__.py', '__init__.py'), + ('/django/forms/boundfield.py', 'boundfield.py'), + ('/django/forms/fields.py', 'fields.py'), + ('/django/forms/forms.py', 'forms.py'), + ('/django/forms/formsets.py', 'formsets.py'), + ('/django/forms/models.py', 'models.py'), + ('/django/forms/utils.py', 'utils.py'), + ('/django/forms/widgets.py', 'widgets.py') + ] + for exp, got in zip(expected, fix_os_paths(f.choices)): + self.assertEqual(exp[1], got[1]) + self.assertTrue(got[0].endswith(exp[0])) + + def test_filepathfield_4(self): + path = os.path.abspath(upath(forms.__file__)) + path = os.path.dirname(path) + '/' + f = FilePathField(path=path, recursive=True, match='^.*?\.py$') + f.choices.sort() + expected = [ + ('/django/forms/__init__.py', '__init__.py'), + ('/django/forms/boundfield.py', 'boundfield.py'), + ('/django/forms/extras/__init__.py', 'extras/__init__.py'), + ('/django/forms/extras/widgets.py', 'extras/widgets.py'), + ('/django/forms/fields.py', 'fields.py'), + ('/django/forms/forms.py', 'forms.py'), + ('/django/forms/formsets.py', 'formsets.py'), + ('/django/forms/models.py', 'models.py'), + ('/django/forms/utils.py', 'utils.py'), + ('/django/forms/widgets.py', 'widgets.py') + ] + for exp, got in zip(expected, fix_os_paths(f.choices)): + self.assertEqual(exp[1], got[1]) + self.assertTrue(got[0].endswith(exp[0])) + + def test_filepathfield_folders(self): + path = os.path.abspath(os.path.join(upath(__file__), '..', '..')) + '/tests/filepath_test_files/' + f = FilePathField(path=path, allow_folders=True, allow_files=False) + f.choices.sort() + expected = [ + ('/forms_tests/tests/filepath_test_files/directory', 'directory'), + ] + actual = fix_os_paths(f.choices) + self.assertEqual(len(expected), len(actual)) + for exp, got in zip(expected, actual): + self.assertEqual(exp[1], got[1]) + self.assertTrue(got[0].endswith(exp[0])) + + f = FilePathField(path=path, allow_folders=True, allow_files=True) + f.choices.sort() + expected = [ + ('/forms_tests/tests/filepath_test_files/.dot-file', '.dot-file'), + ('/forms_tests/tests/filepath_test_files/1x1.bmp', '1x1.bmp'), + ('/forms_tests/tests/filepath_test_files/1x1.png', '1x1.png'), + ('/forms_tests/tests/filepath_test_files/directory', 'directory'), + ('/forms_tests/tests/filepath_test_files/fake-image.jpg', 'fake-image.jpg'), + ('/forms_tests/tests/filepath_test_files/real-text-file.txt', 'real-text-file.txt'), + ] + + actual = fix_os_paths(f.choices) + self.assertEqual(len(expected), len(actual)) + for exp, got in zip(expected, actual): + self.assertEqual(exp[1], got[1]) + self.assertTrue(got[0].endswith(exp[0])) diff --git a/tests/forms_tests/field_tests/test_floatfield.py b/tests/forms_tests/field_tests/test_floatfield.py new file mode 100644 index 0000000000..5ae799d24b --- /dev/null +++ b/tests/forms_tests/field_tests/test_floatfield.py @@ -0,0 +1,81 @@ +from __future__ import unicode_literals + +from django.forms import FloatField, NumberInput, ValidationError +from django.test import SimpleTestCase +from django.utils import formats, translation + +from . import FormFieldAssertionsMixin + + +class FloatFieldTest(FormFieldAssertionsMixin, SimpleTestCase): + + def test_floatfield_1(self): + f = FloatField() + self.assertWidgetRendersTo(f, '<input step="any" type="number" name="f" id="id_f" />') + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean('') + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean(None) + self.assertEqual(1.0, f.clean('1')) + self.assertIsInstance(f.clean('1'), float) + self.assertEqual(23.0, f.clean('23')) + self.assertEqual(3.1400000000000001, f.clean('3.14')) + self.assertEqual(3.1400000000000001, f.clean(3.14)) + self.assertEqual(42.0, f.clean(42)) + with self.assertRaisesMessage(ValidationError, "'Enter a number.'"): + f.clean('a') + self.assertEqual(1.0, f.clean('1.0 ')) + self.assertEqual(1.0, f.clean(' 1.0')) + self.assertEqual(1.0, f.clean(' 1.0 ')) + with self.assertRaisesMessage(ValidationError, "'Enter a number.'"): + f.clean('1.0a') + self.assertIsNone(f.max_value) + self.assertIsNone(f.min_value) + with self.assertRaisesMessage(ValidationError, "'Enter a number.'"): + f.clean('Infinity') + with self.assertRaisesMessage(ValidationError, "'Enter a number.'"): + f.clean('NaN') + with self.assertRaisesMessage(ValidationError, "'Enter a number.'"): + f.clean('-Inf') + + def test_floatfield_2(self): + f = FloatField(required=False) + self.assertIsNone(f.clean('')) + self.assertIsNone(f.clean(None)) + self.assertEqual(1.0, f.clean('1')) + self.assertIsNone(f.max_value) + self.assertIsNone(f.min_value) + + def test_floatfield_3(self): + f = FloatField(max_value=1.5, min_value=0.5) + self.assertWidgetRendersTo(f, '<input step="any" name="f" min="0.5" max="1.5" type="number" id="id_f" />') + with self.assertRaisesMessage(ValidationError, "'Ensure this value is less than or equal to 1.5.'"): + f.clean('1.6') + with self.assertRaisesMessage(ValidationError, "'Ensure this value is greater than or equal to 0.5.'"): + f.clean('0.4') + self.assertEqual(1.5, f.clean('1.5')) + self.assertEqual(0.5, f.clean('0.5')) + self.assertEqual(f.max_value, 1.5) + self.assertEqual(f.min_value, 0.5) + + def test_floatfield_widget_attrs(self): + f = FloatField(widget=NumberInput(attrs={'step': 0.01, 'max': 1.0, 'min': 0.0})) + self.assertWidgetRendersTo(f, '<input step="0.01" name="f" min="0.0" max="1.0" type="number" id="id_f" />') + + def test_floatfield_localized(self): + """ + A localized FloatField's widget renders to a text input without any + number input specific attributes. + """ + f = FloatField(localize=True) + self.assertWidgetRendersTo(f, '<input id="id_f" name="f" type="text" />') + + def test_floatfield_changed(self): + f = FloatField() + n = 4.35 + self.assertFalse(f.has_changed(n, '4.3500')) + + with translation.override('fr'), self.settings(USE_L10N=True): + f = FloatField(localize=True) + localized_n = formats.localize_input(n) # -> '4,35' in French + self.assertFalse(f.has_changed(n, localized_n)) diff --git a/tests/forms_tests/field_tests/test_genericipaddressfield.py b/tests/forms_tests/field_tests/test_genericipaddressfield.py new file mode 100644 index 0000000000..011630fb0d --- /dev/null +++ b/tests/forms_tests/field_tests/test_genericipaddressfield.py @@ -0,0 +1,129 @@ +from __future__ import unicode_literals + +from django.forms import GenericIPAddressField, ValidationError +from django.test import SimpleTestCase + + +class GenericIPAddressFieldTest(SimpleTestCase): + + def test_generic_ipaddress_invalid_arguments(self): + with self.assertRaises(ValueError): + GenericIPAddressField(protocol='hamster') + with self.assertRaises(ValueError): + GenericIPAddressField(protocol='ipv4', unpack_ipv4=True) + + def test_generic_ipaddress_as_generic(self): + # The edge cases of the IPv6 validation code are not deeply tested + # here, they are covered in the tests for django.utils.ipv6 + f = GenericIPAddressField() + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean('') + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean(None) + self.assertEqual(f.clean(' 127.0.0.1 '), '127.0.0.1') + with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 or IPv6 address.'"): + f.clean('foo') + with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 or IPv6 address.'"): + f.clean('127.0.0.') + with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 or IPv6 address.'"): + f.clean('1.2.3.4.5') + with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 or IPv6 address.'"): + f.clean('256.125.1.5') + self.assertEqual(f.clean(' fe80::223:6cff:fe8a:2e8a '), 'fe80::223:6cff:fe8a:2e8a') + self.assertEqual(f.clean(' 2a02::223:6cff:fe8a:2e8a '), '2a02::223:6cff:fe8a:2e8a') + with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): + f.clean('12345:2:3:4') + with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): + f.clean('1::2:3::4') + with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): + f.clean('foo::223:6cff:fe8a:2e8a') + with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): + f.clean('1::2:3:4:5:6:7:8') + with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): + f.clean('1:2') + + def test_generic_ipaddress_as_ipv4_only(self): + f = GenericIPAddressField(protocol="IPv4") + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean('') + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean(None) + self.assertEqual(f.clean(' 127.0.0.1 '), '127.0.0.1') + with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 address.'"): + f.clean('foo') + with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 address.'"): + f.clean('127.0.0.') + with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 address.'"): + f.clean('1.2.3.4.5') + with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 address.'"): + f.clean('256.125.1.5') + with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 address.'"): + f.clean('fe80::223:6cff:fe8a:2e8a') + with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 address.'"): + f.clean('2a02::223:6cff:fe8a:2e8a') + + def test_generic_ipaddress_as_ipv6_only(self): + f = GenericIPAddressField(protocol="IPv6") + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean('') + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean(None) + with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv6 address.'"): + f.clean('127.0.0.1') + with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv6 address.'"): + f.clean('foo') + with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv6 address.'"): + f.clean('127.0.0.') + with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv6 address.'"): + f.clean('1.2.3.4.5') + with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv6 address.'"): + f.clean('256.125.1.5') + self.assertEqual(f.clean(' fe80::223:6cff:fe8a:2e8a '), 'fe80::223:6cff:fe8a:2e8a') + self.assertEqual(f.clean(' 2a02::223:6cff:fe8a:2e8a '), '2a02::223:6cff:fe8a:2e8a') + with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): + f.clean('12345:2:3:4') + with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): + f.clean('1::2:3::4') + with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): + f.clean('foo::223:6cff:fe8a:2e8a') + with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): + f.clean('1::2:3:4:5:6:7:8') + with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): + f.clean('1:2') + + def test_generic_ipaddress_as_generic_not_required(self): + f = GenericIPAddressField(required=False) + self.assertEqual(f.clean(''), '') + self.assertEqual(f.clean(None), '') + self.assertEqual(f.clean('127.0.0.1'), '127.0.0.1') + with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 or IPv6 address.'"): + f.clean('foo') + with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 or IPv6 address.'"): + f.clean('127.0.0.') + with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 or IPv6 address.'"): + f.clean('1.2.3.4.5') + with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 or IPv6 address.'"): + f.clean('256.125.1.5') + self.assertEqual(f.clean(' fe80::223:6cff:fe8a:2e8a '), 'fe80::223:6cff:fe8a:2e8a') + self.assertEqual(f.clean(' 2a02::223:6cff:fe8a:2e8a '), '2a02::223:6cff:fe8a:2e8a') + with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): + f.clean('12345:2:3:4') + with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): + f.clean('1::2:3::4') + with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): + f.clean('foo::223:6cff:fe8a:2e8a') + with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): + f.clean('1::2:3:4:5:6:7:8') + with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): + f.clean('1:2') + + def test_generic_ipaddress_normalization(self): + # Test the normalizing code + f = GenericIPAddressField() + self.assertEqual(f.clean(' ::ffff:0a0a:0a0a '), '::ffff:10.10.10.10') + self.assertEqual(f.clean(' ::ffff:10.10.10.10 '), '::ffff:10.10.10.10') + self.assertEqual(f.clean(' 2001:000:a:0000:0:fe:fe:beef '), '2001:0:a::fe:fe:beef') + self.assertEqual(f.clean(' 2001::a:0000:0:fe:fe:beef '), '2001:0:a::fe:fe:beef') + + f = GenericIPAddressField(unpack_ipv4=True) + self.assertEqual(f.clean(' ::ffff:0a0a:0a0a'), '10.10.10.10') diff --git a/tests/forms_tests/field_tests/test_imagefield.py b/tests/forms_tests/field_tests/test_imagefield.py new file mode 100644 index 0000000000..ee0e1e3b73 --- /dev/null +++ b/tests/forms_tests/field_tests/test_imagefield.py @@ -0,0 +1,60 @@ +from __future__ import unicode_literals + +import os +import unittest + +from django.core.files.uploadedfile import SimpleUploadedFile +from django.forms import ImageField +from django.test import SimpleTestCase +from django.utils._os import upath + +try: + from PIL import Image +except ImportError: + Image = None + + +def get_img_path(path): + return os.path.join(os.path.abspath(os.path.join(upath(__file__), '..', '..')), 'tests', path) + + +@unittest.skipUnless(Image, "Pillow is required to test ImageField") +class ImageFieldTest(SimpleTestCase): + + def test_imagefield_annotate_with_image_after_clean(self): + f = ImageField() + + img_path = get_img_path('filepath_test_files/1x1.png') + with open(img_path, 'rb') as img_file: + img_data = img_file.read() + + img_file = SimpleUploadedFile('1x1.png', img_data) + img_file.content_type = 'text/plain' + + uploaded_file = f.clean(img_file) + + self.assertEqual('PNG', uploaded_file.image.format) + self.assertEqual('image/png', uploaded_file.content_type) + + def test_imagefield_annotate_with_bitmap_image_after_clean(self): + """ + This also tests the situation when Pillow doesn't detect the MIME type + of the image (#24948). + """ + from PIL.BmpImagePlugin import BmpImageFile + try: + Image.register_mime(BmpImageFile.format, None) + f = ImageField() + img_path = get_img_path('filepath_test_files/1x1.bmp') + with open(img_path, 'rb') as img_file: + img_data = img_file.read() + + img_file = SimpleUploadedFile('1x1.bmp', img_data) + img_file.content_type = 'text/plain' + + uploaded_file = f.clean(img_file) + + self.assertEqual('BMP', uploaded_file.image.format) + self.assertIsNone(uploaded_file.content_type) + finally: + Image.register_mime(BmpImageFile.format, 'image/bmp') diff --git a/tests/forms_tests/field_tests/test_integerfield.py b/tests/forms_tests/field_tests/test_integerfield.py new file mode 100644 index 0000000000..15bbca1ee8 --- /dev/null +++ b/tests/forms_tests/field_tests/test_integerfield.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.forms import IntegerField, Textarea, ValidationError +from django.test import SimpleTestCase + +from . import FormFieldAssertionsMixin + + +class IntegerFieldTest(FormFieldAssertionsMixin, SimpleTestCase): + + def test_integerfield_1(self): + f = IntegerField() + self.assertWidgetRendersTo(f, '<input type="number" name="f" id="id_f" />') + 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.assertIsInstance(f.clean('1'), int) + self.assertEqual(23, f.clean('23')) + with self.assertRaisesMessage(ValidationError, "'Enter a whole number.'"): + f.clean('a') + self.assertEqual(42, f.clean(42)) + with self.assertRaisesMessage(ValidationError, "'Enter a whole number.'"): + f.clean(3.14) + self.assertEqual(1, f.clean('1 ')) + self.assertEqual(1, f.clean(' 1')) + self.assertEqual(1, f.clean(' 1 ')) + with self.assertRaisesMessage(ValidationError, "'Enter a whole number.'"): + f.clean('1a') + self.assertIsNone(f.max_value) + self.assertIsNone(f.min_value) + + def test_integerfield_2(self): + f = IntegerField(required=False) + self.assertIsNone(f.clean('')) + self.assertEqual('None', repr(f.clean(''))) + self.assertIsNone(f.clean(None)) + self.assertEqual('None', repr(f.clean(None))) + self.assertEqual(1, f.clean('1')) + self.assertIsInstance(f.clean('1'), int) + self.assertEqual(23, f.clean('23')) + with self.assertRaisesMessage(ValidationError, "'Enter a whole number.'"): + f.clean('a') + self.assertEqual(1, f.clean('1 ')) + self.assertEqual(1, f.clean(' 1')) + self.assertEqual(1, f.clean(' 1 ')) + with self.assertRaisesMessage(ValidationError, "'Enter a whole number.'"): + f.clean('1a') + self.assertIsNone(f.max_value) + self.assertIsNone(f.min_value) + + def test_integerfield_3(self): + f = IntegerField(max_value=10) + self.assertWidgetRendersTo(f, '<input max="10" type="number" name="f" id="id_f" />') + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean(None) + self.assertEqual(1, f.clean(1)) + self.assertEqual(10, f.clean(10)) + with self.assertRaisesMessage(ValidationError, "'Ensure this value is less than or equal to 10.'"): + f.clean(11) + self.assertEqual(10, f.clean('10')) + with self.assertRaisesMessage(ValidationError, "'Ensure this value is less than or equal to 10.'"): + f.clean('11') + self.assertEqual(f.max_value, 10) + self.assertIsNone(f.min_value) + + def test_integerfield_4(self): + f = IntegerField(min_value=10) + self.assertWidgetRendersTo(f, '<input id="id_f" type="number" name="f" min="10" />') + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean(None) + with self.assertRaisesMessage(ValidationError, "'Ensure this value is greater than or equal to 10.'"): + f.clean(1) + self.assertEqual(10, f.clean(10)) + self.assertEqual(11, f.clean(11)) + self.assertEqual(10, f.clean('10')) + self.assertEqual(11, f.clean('11')) + self.assertIsNone(f.max_value) + self.assertEqual(f.min_value, 10) + + def test_integerfield_5(self): + f = IntegerField(min_value=10, max_value=20) + self.assertWidgetRendersTo(f, '<input id="id_f" max="20" type="number" name="f" min="10" />') + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean(None) + with self.assertRaisesMessage(ValidationError, "'Ensure this value is greater than or equal to 10.'"): + f.clean(1) + self.assertEqual(10, f.clean(10)) + self.assertEqual(11, f.clean(11)) + self.assertEqual(10, f.clean('10')) + self.assertEqual(11, f.clean('11')) + self.assertEqual(20, f.clean(20)) + with self.assertRaisesMessage(ValidationError, "'Ensure this value is less than or equal to 20.'"): + f.clean(21) + self.assertEqual(f.max_value, 20) + self.assertEqual(f.min_value, 10) + + def test_integerfield_localized(self): + """ + A localized IntegerField's widget renders to a text input without any + number input specific attributes. + """ + f1 = IntegerField(localize=True) + self.assertWidgetRendersTo(f1, '<input id="id_f" name="f" type="text" />') + + def test_integerfield_float(self): + f = IntegerField() + self.assertEqual(1, f.clean(1.0)) + self.assertEqual(1, f.clean('1.0')) + self.assertEqual(1, f.clean(' 1.0 ')) + self.assertEqual(1, f.clean('1.')) + self.assertEqual(1, f.clean(' 1. ')) + with self.assertRaisesMessage(ValidationError, "'Enter a whole number.'"): + f.clean('1.5') + with self.assertRaisesMessage(ValidationError, "'Enter a whole number.'"): + f.clean('…') + + def test_integerfield_big_num(self): + f = IntegerField() + self.assertEqual(9223372036854775808, f.clean(9223372036854775808)) + self.assertEqual(9223372036854775808, f.clean('9223372036854775808')) + self.assertEqual(9223372036854775808, f.clean('9223372036854775808.0')) + + def test_integerfield_subclass(self): + """ + Class-defined widget is not overwritten by __init__() (#22245). + """ + class MyIntegerField(IntegerField): + widget = Textarea + + f = MyIntegerField() + self.assertEqual(f.widget.__class__, Textarea) + f = MyIntegerField(localize=True) + self.assertEqual(f.widget.__class__, Textarea) diff --git a/tests/forms_tests/field_tests/test_multiplechoicefield.py b/tests/forms_tests/field_tests/test_multiplechoicefield.py new file mode 100644 index 0000000000..85b7049854 --- /dev/null +++ b/tests/forms_tests/field_tests/test_multiplechoicefield.py @@ -0,0 +1,72 @@ +from __future__ import unicode_literals + +from django.forms import MultipleChoiceField, ValidationError +from django.test import SimpleTestCase + + +class MultipleChoiceFieldTest(SimpleTestCase): + + def test_multiplechoicefield_1(self): + f = MultipleChoiceField(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'])) + self.assertEqual(['1', '2'], f.clean(['1', '2'])) + self.assertEqual(['1', '2'], f.clean([1, '2'])) + self.assertEqual(['1', '2'], f.clean((1, '2'))) + with self.assertRaisesMessage(ValidationError, "'Enter a list of values.'"): + f.clean('hello') + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean([]) + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean(()) + msg = "'Select a valid choice. 3 is not one of the available choices.'" + with self.assertRaisesMessage(ValidationError, msg): + f.clean(['3']) + + def test_multiplechoicefield_2(self): + f = MultipleChoiceField(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'])) + self.assertEqual(['1', '2'], f.clean(['1', '2'])) + self.assertEqual(['1', '2'], f.clean([1, '2'])) + self.assertEqual(['1', '2'], f.clean((1, '2'))) + with self.assertRaisesMessage(ValidationError, "'Enter a list of values.'"): + f.clean('hello') + self.assertEqual([], f.clean([])) + self.assertEqual([], f.clean(())) + msg = "'Select a valid choice. 3 is not one of the available choices.'" + with self.assertRaisesMessage(ValidationError, msg): + f.clean(['3']) + + def test_multiplechoicefield_3(self): + f = MultipleChoiceField( + 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(['1', '5'], f.clean([1, 5])) + self.assertEqual(['1', '5'], f.clean([1, '5'])) + self.assertEqual(['1', '5'], f.clean(['1', 5])) + self.assertEqual(['1', '5'], f.clean(['1', '5'])) + msg = "'Select a valid choice. 6 is not one of the available choices.'" + with self.assertRaisesMessage(ValidationError, msg): + f.clean(['6']) + msg = "'Select a valid choice. 6 is not one of the available choices.'" + with self.assertRaisesMessage(ValidationError, msg): + f.clean(['1', '6']) + + def test_multiplechoicefield_changed(self): + f = MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two'), ('3', 'Three')]) + self.assertFalse(f.has_changed(None, None)) + self.assertFalse(f.has_changed([], None)) + self.assertTrue(f.has_changed(None, ['1'])) + self.assertFalse(f.has_changed([1, 2], ['1', '2'])) + self.assertFalse(f.has_changed([2, 1], ['1', '2'])) + self.assertTrue(f.has_changed([1, 2], ['1'])) + self.assertTrue(f.has_changed([1, 2], ['1', '3'])) diff --git a/tests/forms_tests/field_tests/test_multivaluefield.py b/tests/forms_tests/field_tests/test_multivaluefield.py index 258d67bdcd..79a3425155 100644 --- a/tests/forms_tests/field_tests/test_multivaluefield.py +++ b/tests/forms_tests/field_tests/test_multivaluefield.py @@ -79,9 +79,7 @@ class MultiValueFieldTest(SimpleTestCase): self.field.clean(['some text', ['JP']]) def test_has_changed_no_initial(self): - self.assertTrue(self.field.has_changed( - None, ['some text', ['J', 'P'], ['2007-04-25', '6:24:00']], - )) + self.assertTrue(self.field.has_changed(None, ['some text', ['J', 'P'], ['2007-04-25', '6:24:00']])) def test_has_changed_same(self): self.assertFalse(self.field.has_changed( @@ -157,6 +155,4 @@ class MultiValueFieldTest(SimpleTestCase): 'field1_2_1': '06:24:00', }) form.is_valid() - self.assertEqual( - form.cleaned_data['field1'], 'some text,JP,2007-04-25 06:24:00', - ) + self.assertEqual(form.cleaned_data['field1'], 'some text,JP,2007-04-25 06:24:00') diff --git a/tests/forms_tests/field_tests/test_nullbooleanfield.py b/tests/forms_tests/field_tests/test_nullbooleanfield.py new file mode 100644 index 0000000000..74a2d7c173 --- /dev/null +++ b/tests/forms_tests/field_tests/test_nullbooleanfield.py @@ -0,0 +1,69 @@ +from __future__ import unicode_literals + +from django.forms import Form, HiddenInput, NullBooleanField, RadioSelect +from django.test import SimpleTestCase + +from . import FormFieldAssertionsMixin + + +class NullBooleanFieldTest(FormFieldAssertionsMixin, SimpleTestCase): + + def test_nullbooleanfield_clean(self): + f = NullBooleanField() + self.assertIsNone(f.clean('')) + self.assertTrue(f.clean(True)) + self.assertFalse(f.clean(False)) + self.assertIsNone(f.clean(None)) + self.assertFalse(f.clean('0')) + self.assertTrue(f.clean('1')) + self.assertIsNone(f.clean('2')) + self.assertIsNone(f.clean('3')) + self.assertIsNone(f.clean('hello')) + self.assertTrue(f.clean('true')) + self.assertFalse(f.clean('false')) + + def test_nullbooleanfield_2(self): + # The internal value is preserved if using HiddenInput (#7753). + class HiddenNullBooleanForm(Form): + hidden_nullbool1 = NullBooleanField(widget=HiddenInput, initial=True) + hidden_nullbool2 = NullBooleanField(widget=HiddenInput, initial=False) + f = HiddenNullBooleanForm() + self.assertHTMLEqual( + '<input type="hidden" name="hidden_nullbool1" value="True" id="id_hidden_nullbool1" />' + '<input type="hidden" name="hidden_nullbool2" value="False" id="id_hidden_nullbool2" />', + str(f) + ) + + def test_nullbooleanfield_3(self): + class HiddenNullBooleanForm(Form): + hidden_nullbool1 = NullBooleanField(widget=HiddenInput, initial=True) + hidden_nullbool2 = NullBooleanField(widget=HiddenInput, initial=False) + f = HiddenNullBooleanForm({'hidden_nullbool1': 'True', 'hidden_nullbool2': 'False'}) + self.assertIsNone(f.full_clean()) + self.assertTrue(f.cleaned_data['hidden_nullbool1']) + self.assertFalse(f.cleaned_data['hidden_nullbool2']) + + def test_nullbooleanfield_4(self): + # Make sure we're compatible with MySQL, which uses 0 and 1 for its + # boolean values (#9609). + NULLBOOL_CHOICES = (('1', 'Yes'), ('0', 'No'), ('', 'Unknown')) + + class MySQLNullBooleanForm(Form): + nullbool0 = NullBooleanField(widget=RadioSelect(choices=NULLBOOL_CHOICES)) + nullbool1 = NullBooleanField(widget=RadioSelect(choices=NULLBOOL_CHOICES)) + nullbool2 = NullBooleanField(widget=RadioSelect(choices=NULLBOOL_CHOICES)) + f = MySQLNullBooleanForm({'nullbool0': '1', 'nullbool1': '0', 'nullbool2': ''}) + self.assertIsNone(f.full_clean()) + self.assertTrue(f.cleaned_data['nullbool0']) + self.assertFalse(f.cleaned_data['nullbool1']) + self.assertIsNone(f.cleaned_data['nullbool2']) + + def test_nullbooleanfield_changed(self): + f = NullBooleanField() + self.assertTrue(f.has_changed(False, None)) + self.assertTrue(f.has_changed(None, False)) + self.assertFalse(f.has_changed(None, None)) + self.assertFalse(f.has_changed(False, False)) + self.assertTrue(f.has_changed(True, False)) + self.assertTrue(f.has_changed(True, None)) + self.assertTrue(f.has_changed(True, False)) diff --git a/tests/forms_tests/field_tests/test_regexfield.py b/tests/forms_tests/field_tests/test_regexfield.py new file mode 100644 index 0000000000..ece958e509 --- /dev/null +++ b/tests/forms_tests/field_tests/test_regexfield.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import re + +from django.forms import RegexField, ValidationError +from django.test import SimpleTestCase +from django.utils import six + + +class RegexFieldTest(SimpleTestCase): + + def test_regexfield_1(self): + f = RegexField('^[0-9][A-F][0-9]$') + self.assertEqual('2A2', f.clean('2A2')) + self.assertEqual('3F3', f.clean('3F3')) + with self.assertRaisesMessage(ValidationError, "'Enter a valid value.'"): + f.clean('3G3') + with self.assertRaisesMessage(ValidationError, "'Enter a valid value.'"): + f.clean(' 2A2') + with self.assertRaisesMessage(ValidationError, "'Enter a valid value.'"): + f.clean('2A2 ') + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean('') + + def test_regexfield_2(self): + f = RegexField('^[0-9][A-F][0-9]$', required=False) + self.assertEqual('2A2', f.clean('2A2')) + self.assertEqual('3F3', f.clean('3F3')) + with self.assertRaisesMessage(ValidationError, "'Enter a valid value.'"): + f.clean('3G3') + self.assertEqual('', f.clean('')) + + def test_regexfield_3(self): + f = RegexField(re.compile('^[0-9][A-F][0-9]$')) + self.assertEqual('2A2', f.clean('2A2')) + self.assertEqual('3F3', f.clean('3F3')) + with self.assertRaisesMessage(ValidationError, "'Enter a valid value.'"): + f.clean('3G3') + with self.assertRaisesMessage(ValidationError, "'Enter a valid value.'"): + f.clean(' 2A2') + with self.assertRaisesMessage(ValidationError, "'Enter a valid value.'"): + f.clean('2A2 ') + + def test_regexfield_4(self): + f = RegexField('^[0-9]+$', min_length=5, max_length=10) + with self.assertRaisesMessage(ValidationError, "'Ensure this value has at least 5 characters (it has 3).'"): + f.clean('123') + six.assertRaisesRegex( + self, ValidationError, + "'Ensure this value has at least 5 characters \(it has 3\)\.'," + " u?'Enter a valid value\.'", + f.clean, 'abc' + ) + self.assertEqual('12345', f.clean('12345')) + self.assertEqual('1234567890', f.clean('1234567890')) + with self.assertRaisesMessage(ValidationError, "'Ensure this value has at most 10 characters (it has 11).'"): + f.clean('12345678901') + with self.assertRaisesMessage(ValidationError, "'Enter a valid value.'"): + f.clean('12345a') + + def test_regexfield_unicode_characters(self): + f = RegexField('^\w+$') + self.assertEqual('éèøçÎÎ你好', f.clean('éèøçÎÎ你好')) + + def test_change_regex_after_init(self): + f = RegexField('^[a-z]+$') + f.regex = '^[0-9]+$' + self.assertEqual('1234', f.clean('1234')) + with self.assertRaisesMessage(ValidationError, "'Enter a valid value.'"): + f.clean('abcd') diff --git a/tests/forms_tests/field_tests/test_slugfield.py b/tests/forms_tests/field_tests/test_slugfield.py new file mode 100644 index 0000000000..1a522c9d4f --- /dev/null +++ b/tests/forms_tests/field_tests/test_slugfield.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.forms import SlugField +from django.test import SimpleTestCase + + +class SlugFieldTest(SimpleTestCase): + + def test_slugfield_normalization(self): + f = SlugField() + self.assertEqual(f.clean(' aa-bb-cc '), 'aa-bb-cc') + + def test_slugfield_unicode_normalization(self): + f = SlugField(allow_unicode=True) + self.assertEqual(f.clean('a'), 'a') + self.assertEqual(f.clean('1'), '1') + self.assertEqual(f.clean('a1'), 'a1') + self.assertEqual(f.clean('你好'), '你好') + self.assertEqual(f.clean(' 你-好 '), '你-好') + self.assertEqual(f.clean('ıçğüş'), 'ıçğüş') + self.assertEqual(f.clean('foo-ıç-bar'), 'foo-ıç-bar') diff --git a/tests/forms_tests/field_tests/test_splitdatetimefield.py b/tests/forms_tests/field_tests/test_splitdatetimefield.py new file mode 100644 index 0000000000..1febb57b28 --- /dev/null +++ b/tests/forms_tests/field_tests/test_splitdatetimefield.py @@ -0,0 +1,64 @@ +from __future__ import unicode_literals + +import datetime + +from django.forms import SplitDateTimeField, ValidationError +from django.forms.widgets import SplitDateTimeWidget +from django.test import SimpleTestCase +from django.utils import six + + +class SplitDateTimeFieldTest(SimpleTestCase): + + def test_splitdatetimefield_1(self): + f = SplitDateTimeField() + self.assertIsInstance(f.widget, SplitDateTimeWidget) + self.assertEqual( + datetime.datetime(2006, 1, 10, 7, 30), + f.clean([datetime.date(2006, 1, 10), datetime.time(7, 30)]) + ) + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean(None) + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean('') + with self.assertRaisesMessage(ValidationError, "'Enter a list of values.'"): + f.clean('hello') + with six.assertRaisesRegex(self, ValidationError, "'Enter a valid date\.', u?'Enter a valid time\.'"): + f.clean(['hello', 'there']) + with self.assertRaisesMessage(ValidationError, "'Enter a valid time.'"): + f.clean(['2006-01-10', 'there']) + with self.assertRaisesMessage(ValidationError, "'Enter a valid date.'"): + f.clean(['hello', '07:30']) + + def test_splitdatetimefield_2(self): + f = SplitDateTimeField(required=False) + self.assertEqual( + datetime.datetime(2006, 1, 10, 7, 30), + f.clean([datetime.date(2006, 1, 10), datetime.time(7, 30)]) + ) + self.assertEqual(datetime.datetime(2006, 1, 10, 7, 30), f.clean(['2006-01-10', '07:30'])) + self.assertIsNone(f.clean(None)) + self.assertIsNone(f.clean('')) + self.assertIsNone(f.clean([''])) + self.assertIsNone(f.clean(['', ''])) + with self.assertRaisesMessage(ValidationError, "'Enter a list of values.'"): + f.clean('hello') + with six.assertRaisesRegex(self, ValidationError, "'Enter a valid date\.', u?'Enter a valid time\.'"): + f.clean(['hello', 'there']) + with self.assertRaisesMessage(ValidationError, "'Enter a valid time.'"): + f.clean(['2006-01-10', 'there']) + with self.assertRaisesMessage(ValidationError, "'Enter a valid date.'"): + f.clean(['hello', '07:30']) + with self.assertRaisesMessage(ValidationError, "'Enter a valid time.'"): + f.clean(['2006-01-10', '']) + with self.assertRaisesMessage(ValidationError, "'Enter a valid time.'"): + f.clean(['2006-01-10']) + with self.assertRaisesMessage(ValidationError, "'Enter a valid date.'"): + f.clean(['', '07:30']) + + def test_splitdatetimefield_changed(self): + f = SplitDateTimeField(input_date_formats=['%d/%m/%Y']) + self.assertFalse(f.has_changed(['11/01/2012', '09:18:15'], ['11/01/2012', '09:18:15'])) + self.assertTrue(f.has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['2008-05-06', '12:40:00'])) + self.assertFalse(f.has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:40'])) + self.assertTrue(f.has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:41'])) diff --git a/tests/forms_tests/field_tests/test_timefield.py b/tests/forms_tests/field_tests/test_timefield.py new file mode 100644 index 0000000000..3c73cc37b6 --- /dev/null +++ b/tests/forms_tests/field_tests/test_timefield.py @@ -0,0 +1,47 @@ +from __future__ import unicode_literals + +import datetime + +from django.forms import TimeField, ValidationError +from django.test import SimpleTestCase + +from . import FormFieldAssertionsMixin + + +class TimeFieldTest(FormFieldAssertionsMixin, SimpleTestCase): + + def test_timefield_1(self): + f = TimeField() + self.assertEqual(datetime.time(14, 25), f.clean(datetime.time(14, 25))) + self.assertEqual(datetime.time(14, 25, 59), f.clean(datetime.time(14, 25, 59))) + self.assertEqual(datetime.time(14, 25), f.clean('14:25')) + self.assertEqual(datetime.time(14, 25, 59), f.clean('14:25:59')) + with self.assertRaisesMessage(ValidationError, "'Enter a valid time.'"): + f.clean('hello') + with self.assertRaisesMessage(ValidationError, "'Enter a valid time.'"): + f.clean('1:24 p.m.') + + def test_timefield_2(self): + f = TimeField(input_formats=['%I:%M %p']) + self.assertEqual(datetime.time(14, 25), f.clean(datetime.time(14, 25))) + self.assertEqual(datetime.time(14, 25, 59), f.clean(datetime.time(14, 25, 59))) + self.assertEqual(datetime.time(4, 25), f.clean('4:25 AM')) + self.assertEqual(datetime.time(16, 25), f.clean('4:25 PM')) + with self.assertRaisesMessage(ValidationError, "'Enter a valid time.'"): + f.clean('14:30:45') + + def test_timefield_3(self): + f = TimeField() + # Test whitespace stripping behavior (#5714) + self.assertEqual(datetime.time(14, 25), f.clean(' 14:25 ')) + self.assertEqual(datetime.time(14, 25, 59), f.clean(' 14:25:59 ')) + with self.assertRaisesMessage(ValidationError, "'Enter a valid time.'"): + f.clean(' ') + + def test_timefield_changed(self): + t1 = datetime.time(12, 51, 34, 482548) + t2 = datetime.time(12, 51) + f = TimeField(input_formats=['%H:%M', '%H:%M %p']) + self.assertTrue(f.has_changed(t1, '12:51')) + self.assertFalse(f.has_changed(t2, '12:51')) + self.assertFalse(f.has_changed(t2, '12:51 PM')) diff --git a/tests/forms_tests/field_tests/test_typedchoicefield.py b/tests/forms_tests/field_tests/test_typedchoicefield.py new file mode 100644 index 0000000000..26a7a25d05 --- /dev/null +++ b/tests/forms_tests/field_tests/test_typedchoicefield.py @@ -0,0 +1,79 @@ +from __future__ import unicode_literals + +import decimal + +from django.forms import TypedChoiceField, ValidationError +from django.test import SimpleTestCase +from django.utils import six + + +class TypedChoiceFieldTest(SimpleTestCase): + + def test_typedchoicefield_1(self): + f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int) + self.assertEqual(1, f.clean('1')) + msg = "'Select a valid choice. 2 is not one of the available choices.'" + with self.assertRaisesMessage(ValidationError, msg): + f.clean('2') + + def test_typedchoicefield_2(self): + # Different coercion, same validation. + f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=float) + self.assertEqual(1.0, f.clean('1')) + + def test_typedchoicefield_3(self): + # This can also cause weirdness: be careful (bool(-1) == True, remember) + f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=bool) + self.assertTrue(f.clean('-1')) + + def test_typedchoicefield_4(self): + # Even more weirdness: if you have a valid choice but your coercion function + # can't coerce, you'll still get a validation error. Don't do this! + f = TypedChoiceField(choices=[('A', 'A'), ('B', 'B')], coerce=int) + msg = "'Select a valid choice. B is not one of the available choices.'" + with self.assertRaisesMessage(ValidationError, msg): + f.clean('B') + # Required fields require values + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean('') + + def test_typedchoicefield_5(self): + # Non-required fields aren't required + f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=False) + self.assertEqual('', f.clean('')) + # If you want cleaning an empty value to return a different type, tell the field + + def test_typedchoicefield_6(self): + f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=False, empty_value=None) + self.assertIsNone(f.clean('')) + + def test_typedchoicefield_has_changed(self): + # has_changed should not trigger required validation + f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=True) + self.assertFalse(f.has_changed(None, '')) + self.assertFalse(f.has_changed(1, '1')) + self.assertFalse(f.has_changed('1', '1')) + + f = TypedChoiceField( + choices=[('', '---------'), ('a', "a"), ('b', "b")], coerce=six.text_type, + required=False, initial=None, empty_value=None, + ) + self.assertFalse(f.has_changed(None, '')) + self.assertTrue(f.has_changed('', 'a')) + self.assertFalse(f.has_changed('a', 'a')) + + def test_typedchoicefield_special_coerce(self): + """ + A coerce function which results in a value not present in choices + should raise an appropriate error (#21397). + """ + def coerce_func(val): + return decimal.Decimal('1.%s' % val) + + f = TypedChoiceField(choices=[(1, "1"), (2, "2")], coerce=coerce_func, required=True) + self.assertEqual(decimal.Decimal('1.2'), f.clean('2')) + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean('') + msg = "'Select a valid choice. 3 is not one of the available choices.'" + with self.assertRaisesMessage(ValidationError, msg): + f.clean('3') diff --git a/tests/forms_tests/field_tests/test_typedmultiplechoicefield.py b/tests/forms_tests/field_tests/test_typedmultiplechoicefield.py new file mode 100644 index 0000000000..94823e0594 --- /dev/null +++ b/tests/forms_tests/field_tests/test_typedmultiplechoicefield.py @@ -0,0 +1,76 @@ +from __future__ import unicode_literals + +import decimal + +from django.forms import TypedMultipleChoiceField, ValidationError +from django.test import SimpleTestCase + + +class TypedMultipleChoiceFieldTest(SimpleTestCase): + + def test_typedmultiplechoicefield_1(self): + f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int) + self.assertEqual([1], f.clean(['1'])) + msg = "'Select a valid choice. 2 is not one of the available choices.'" + with self.assertRaisesMessage(ValidationError, msg): + f.clean(['2']) + + def test_typedmultiplechoicefield_2(self): + # Different coercion, same validation. + f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=float) + self.assertEqual([1.0], f.clean(['1'])) + + def test_typedmultiplechoicefield_3(self): + # This can also cause weirdness: be careful (bool(-1) == True, remember) + f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=bool) + self.assertEqual([True], f.clean(['-1'])) + + def test_typedmultiplechoicefield_4(self): + f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int) + self.assertEqual([1, -1], f.clean(['1', '-1'])) + msg = "'Select a valid choice. 2 is not one of the available choices.'" + with self.assertRaisesMessage(ValidationError, msg): + f.clean(['1', '2']) + + def test_typedmultiplechoicefield_5(self): + # Even more weirdness: if you have a valid choice but your coercion function + # can't coerce, you'll still get a validation error. Don't do this! + f = TypedMultipleChoiceField(choices=[('A', 'A'), ('B', 'B')], coerce=int) + msg = "'Select a valid choice. B is not one of the available choices.'" + with self.assertRaisesMessage(ValidationError, msg): + f.clean(['B']) + # Required fields require values + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean([]) + + def test_typedmultiplechoicefield_6(self): + # Non-required fields aren't required + f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=False) + self.assertEqual([], f.clean([])) + + def test_typedmultiplechoicefield_7(self): + # If you want cleaning an empty value to return a different type, tell the field + f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=False, empty_value=None) + self.assertIsNone(f.clean([])) + + def test_typedmultiplechoicefield_has_changed(self): + # has_changed should not trigger required validation + f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=True) + self.assertFalse(f.has_changed(None, '')) + + def test_typedmultiplechoicefield_special_coerce(self): + """ + A coerce function which results in a value not present in choices + should raise an appropriate error (#21397). + """ + def coerce_func(val): + return decimal.Decimal('1.%s' % val) + + f = TypedMultipleChoiceField( + choices=[(1, "1"), (2, "2")], coerce=coerce_func, required=True) + self.assertEqual([decimal.Decimal('1.2')], f.clean(['2'])) + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean([]) + msg = "'Select a valid choice. 3 is not one of the available choices.'" + with self.assertRaisesMessage(ValidationError, msg): + f.clean(['3']) diff --git a/tests/forms_tests/field_tests/test_urlfield.py b/tests/forms_tests/field_tests/test_urlfield.py new file mode 100644 index 0000000000..48e4a39328 --- /dev/null +++ b/tests/forms_tests/field_tests/test_urlfield.py @@ -0,0 +1,153 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.forms import URLField, ValidationError +from django.test import SimpleTestCase + +from . import FormFieldAssertionsMixin + + +class URLFieldTest(FormFieldAssertionsMixin, SimpleTestCase): + + def test_urlfield_1(self): + f = URLField() + self.assertWidgetRendersTo(f, '<input type="url" name="f" id="id_f" />') + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean('') + with self.assertRaisesMessage(ValidationError, "'This field is required.'"): + f.clean(None) + self.assertEqual('http://localhost', f.clean('http://localhost')) + self.assertEqual('http://example.com', f.clean('http://example.com')) + self.assertEqual('http://example.com.', f.clean('http://example.com.')) + self.assertEqual('http://www.example.com', f.clean('http://www.example.com')) + self.assertEqual('http://www.example.com:8000/test', f.clean('http://www.example.com:8000/test')) + self.assertEqual('http://valid-with-hyphens.com', f.clean('valid-with-hyphens.com')) + self.assertEqual('http://subdomain.domain.com', f.clean('subdomain.domain.com')) + self.assertEqual('http://200.8.9.10', f.clean('http://200.8.9.10')) + self.assertEqual('http://200.8.9.10:8000/test', f.clean('http://200.8.9.10:8000/test')) + with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): + f.clean('foo') + with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): + f.clean('http://') + with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): + f.clean('http://example') + with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): + f.clean('http://example.') + with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): + f.clean('com.') + with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): + f.clean('.') + with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): + f.clean('http://.com') + with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): + f.clean('http://invalid-.com') + with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): + f.clean('http://-invalid.com') + with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): + f.clean('http://inv-.alid-.com') + with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): + f.clean('http://inv-.-alid.com') + self.assertEqual('http://valid-----hyphens.com', f.clean('http://valid-----hyphens.com')) + self.assertEqual( + 'http://some.idn.xyz\xe4\xf6\xfc\xdfabc.domain.com:123/blah', + f.clean('http://some.idn.xyzäöüßabc.domain.com:123/blah') + ) + self.assertEqual( + 'http://www.example.com/s/http://code.djangoproject.com/ticket/13804', + f.clean('www.example.com/s/http://code.djangoproject.com/ticket/13804') + ) + with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): + f.clean('[a') + with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): + f.clean('http://[a') + + def test_url_regex_ticket11198(self): + f = URLField() + # hangs "forever" if catastrophic backtracking in ticket:#11198 not fixed + with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): + f.clean('http://%s' % ("X" * 200,)) + + # a second test, to make sure the problem is really addressed, even on + # domains that don't fail the domain label length check in the regex + with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): + f.clean('http://%s' % ("X" * 60,)) + + def test_urlfield_2(self): + f = URLField(required=False) + self.assertEqual('', f.clean('')) + self.assertEqual('', f.clean(None)) + self.assertEqual('http://example.com', f.clean('http://example.com')) + self.assertEqual('http://www.example.com', f.clean('http://www.example.com')) + with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): + f.clean('foo') + with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): + f.clean('http://') + with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): + f.clean('http://example') + with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): + f.clean('http://example.') + with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): + f.clean('http://.com') + + def test_urlfield_5(self): + f = URLField(min_length=15, max_length=20) + self.assertWidgetRendersTo(f, '<input id="id_f" type="url" name="f" maxlength="20" />') + with self.assertRaisesMessage(ValidationError, "'Ensure this value has at least 15 characters (it has 12).'"): + f.clean('http://f.com') + self.assertEqual('http://example.com', f.clean('http://example.com')) + with self.assertRaisesMessage(ValidationError, "'Ensure this value has at most 20 characters (it has 37).'"): + f.clean('http://abcdefghijklmnopqrstuvwxyz.com') + + def test_urlfield_6(self): + f = URLField(required=False) + self.assertEqual('http://example.com', f.clean('example.com')) + self.assertEqual('', f.clean('')) + self.assertEqual('https://example.com', f.clean('https://example.com')) + + def test_urlfield_7(self): + f = URLField() + self.assertEqual('http://example.com', f.clean('http://example.com')) + self.assertEqual('http://example.com/test', f.clean('http://example.com/test')) + self.assertEqual( + 'http://example.com?some_param=some_value', + f.clean('http://example.com?some_param=some_value') + ) + + def test_urlfield_9(self): + f = URLField() + urls = ( + 'http://עברית.idn.icann.org/', + 'http://sãopaulo.com/', + 'http://sãopaulo.com.br/', + 'http://пример.испытание/', + 'http://مثال.إختبار/', + 'http://例子.测试/', + 'http://例子.測試/', + 'http://उदाहरण.परीक्षा/', + 'http://例え.テスト/', + 'http://مثال.آزمایشی/', + 'http://실례.테스트/', + 'http://العربية.idn.icann.org/', + ) + for url in urls: + # Valid IDN + self.assertEqual(url, f.clean(url)) + + def test_urlfield_10(self): + """URLField correctly validates IPv6 (#18779).""" + f = URLField() + urls = ( + 'http://[12:34::3a53]/', + 'http://[a34:9238::]:8080/', + ) + for url in urls: + self.assertEqual(url, f.clean(url)) + + def test_urlfield_not_string(self): + f = URLField(required=False) + with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): + f.clean(23) + + def test_urlfield_normalization(self): + f = URLField() + self.assertEqual(f.clean('http://example.com/ '), 'http://example.com/') diff --git a/tests/forms_tests/field_tests/test_uuidfield.py b/tests/forms_tests/field_tests/test_uuidfield.py new file mode 100644 index 0000000000..05a3f0d869 --- /dev/null +++ b/tests/forms_tests/field_tests/test_uuidfield.py @@ -0,0 +1,30 @@ +from __future__ import unicode_literals + +import uuid + +from django.forms import UUIDField, ValidationError +from django.test import SimpleTestCase + + +class UUIDFieldTest(SimpleTestCase): + + def test_uuidfield_1(self): + field = UUIDField() + value = field.clean('550e8400e29b41d4a716446655440000') + self.assertEqual(value, uuid.UUID('550e8400e29b41d4a716446655440000')) + + def test_uuidfield_2(self): + field = UUIDField(required=False) + value = field.clean('') + self.assertEqual(value, None) + + def test_uuidfield_3(self): + field = UUIDField() + with self.assertRaises(ValidationError) as cm: + field.clean('550e8400') + self.assertEqual(cm.exception.messages[0], 'Enter a valid UUID.') + + def test_uuidfield_4(self): + field = UUIDField() + value = field.prepare_value(uuid.UUID('550e8400e29b41d4a716446655440000')) + self.assertEqual(value, '550e8400e29b41d4a716446655440000') diff --git a/tests/forms_tests/tests/test_fields.py b/tests/forms_tests/tests/test_fields.py deleted file mode 100644 index 41a3a5185e..0000000000 --- a/tests/forms_tests/tests/test_fields.py +++ /dev/null @@ -1,1887 +0,0 @@ -# -*- coding: utf-8 -*- -""" -########## -# Fields # -########## - -Each Field class does some sort of validation. Each Field has a clean() method, -which either raises django.forms.ValidationError or returns the "clean" -data -- usually a Unicode object, but, in some rare cases, a list. - -Each Field's __init__() takes at least these parameters: - required -- Boolean that specifies whether the field is required. - True by default. - widget -- A Widget class, or instance of a Widget class, that should be - used for this Field when displaying it. Each Field has a default - Widget that it'll use if you don't specify this. In most cases, - the default widget is TextInput. - label -- A verbose name for this field, for use in displaying this field in - a form. By default, Django will use a "pretty" version of the form - field name, if the Field is part of a Form. - initial -- A value to use in this Field's initial display. This value is - *not* used as a fallback if data isn't given. - -Other than that, the Field subclasses have class-specific options for -__init__(). For example, CharField has a max_length option. -""" -from __future__ import unicode_literals - -import datetime -import os -import pickle -import re -import uuid -from decimal import Decimal -from unittest import skipIf - -from django.core.files.uploadedfile import SimpleUploadedFile -from django.forms import ( - BooleanField, CharField, ChoiceField, ComboField, DateField, DateTimeField, - DecimalField, DurationField, EmailField, Field, FileField, FilePathField, - FloatField, Form, GenericIPAddressField, HiddenInput, ImageField, - IntegerField, MultipleChoiceField, NullBooleanField, NumberInput, - PasswordInput, RadioSelect, RegexField, SlugField, SplitDateTimeField, - Textarea, TextInput, TimeField, TypedChoiceField, TypedMultipleChoiceField, - URLField, UUIDField, ValidationError, Widget, forms, -) -from django.test import SimpleTestCase -from django.utils import formats, six, translation -from django.utils._os import upath -from django.utils.duration import duration_string - -try: - from PIL import Image -except ImportError: - Image = None - - -def fix_os_paths(x): - if isinstance(x, six.string_types): - return x.replace('\\', '/') - elif isinstance(x, tuple): - return tuple(fix_os_paths(list(x))) - elif isinstance(x, list): - return [fix_os_paths(y) for y in x] - else: - return x - - -class FieldsTests(SimpleTestCase): - - def assertWidgetRendersTo(self, field, to): - class _Form(Form): - f = field - self.assertHTMLEqual(str(_Form()['f']), to) - - 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) - - # CharField ################################################################### - - 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.assertEqual(f.max_length, None) - self.assertEqual(f.min_length, None) - - 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.assertEqual(f.max_length, None) - self.assertEqual(f.min_length, None) - - 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.assertEqual(f.min_length, None) - - 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.assertEqual(f.max_length, None) - 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.assertEqual(f.max_length, None) - self.assertEqual(f.min_length, 10) - - def test_charfield_length_not_int(self): - """ - Ensure that setting min_length or max_length to something that is not a - number returns 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): - """ - Ensure that CharField.widget_attrs() always returns a dictionary. - Refs #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): - """ - Ensure that values have whitespace stripped and that strip=False works. - """ - 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, '<input type="text" name="f" id="id_f" disabled />') - - # IntegerField ################################################################ - - def test_integerfield_1(self): - f = IntegerField() - self.assertWidgetRendersTo(f, '<input type="number" name="f" id="id_f" />') - 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.assertIsInstance(f.clean('1'), int) - self.assertEqual(23, f.clean('23')) - with self.assertRaisesMessage(ValidationError, "'Enter a whole number.'"): - f.clean('a') - self.assertEqual(42, f.clean(42)) - with self.assertRaisesMessage(ValidationError, "'Enter a whole number.'"): - f.clean(3.14) - self.assertEqual(1, f.clean('1 ')) - self.assertEqual(1, f.clean(' 1')) - self.assertEqual(1, f.clean(' 1 ')) - with self.assertRaisesMessage(ValidationError, "'Enter a whole number.'"): - f.clean('1a') - self.assertEqual(f.max_value, None) - self.assertEqual(f.min_value, None) - - def test_integerfield_2(self): - f = IntegerField(required=False) - self.assertIsNone(f.clean('')) - self.assertEqual('None', repr(f.clean(''))) - self.assertIsNone(f.clean(None)) - self.assertEqual('None', repr(f.clean(None))) - self.assertEqual(1, f.clean('1')) - self.assertIsInstance(f.clean('1'), int) - self.assertEqual(23, f.clean('23')) - with self.assertRaisesMessage(ValidationError, "'Enter a whole number.'"): - f.clean('a') - self.assertEqual(1, f.clean('1 ')) - self.assertEqual(1, f.clean(' 1')) - self.assertEqual(1, f.clean(' 1 ')) - with self.assertRaisesMessage(ValidationError, "'Enter a whole number.'"): - f.clean('1a') - self.assertEqual(f.max_value, None) - self.assertEqual(f.min_value, None) - - def test_integerfield_3(self): - f = IntegerField(max_value=10) - self.assertWidgetRendersTo(f, '<input max="10" type="number" name="f" id="id_f" />') - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean(None) - self.assertEqual(1, f.clean(1)) - self.assertEqual(10, f.clean(10)) - with self.assertRaisesMessage(ValidationError, "'Ensure this value is less than or equal to 10.'"): - f.clean(11) - self.assertEqual(10, f.clean('10')) - with self.assertRaisesMessage(ValidationError, "'Ensure this value is less than or equal to 10.'"): - f.clean('11') - self.assertEqual(f.max_value, 10) - self.assertEqual(f.min_value, None) - - def test_integerfield_4(self): - f = IntegerField(min_value=10) - self.assertWidgetRendersTo(f, '<input id="id_f" type="number" name="f" min="10" />') - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean(None) - with self.assertRaisesMessage(ValidationError, "'Ensure this value is greater than or equal to 10.'"): - f.clean(1) - self.assertEqual(10, f.clean(10)) - self.assertEqual(11, f.clean(11)) - self.assertEqual(10, f.clean('10')) - self.assertEqual(11, f.clean('11')) - self.assertEqual(f.max_value, None) - self.assertEqual(f.min_value, 10) - - def test_integerfield_5(self): - f = IntegerField(min_value=10, max_value=20) - self.assertWidgetRendersTo(f, '<input id="id_f" max="20" type="number" name="f" min="10" />') - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean(None) - with self.assertRaisesMessage(ValidationError, "'Ensure this value is greater than or equal to 10.'"): - f.clean(1) - self.assertEqual(10, f.clean(10)) - self.assertEqual(11, f.clean(11)) - self.assertEqual(10, f.clean('10')) - self.assertEqual(11, f.clean('11')) - self.assertEqual(20, f.clean(20)) - with self.assertRaisesMessage(ValidationError, "'Ensure this value is less than or equal to 20.'"): - f.clean(21) - self.assertEqual(f.max_value, 20) - self.assertEqual(f.min_value, 10) - - def test_integerfield_localized(self): - """ - Make sure localized IntegerField's widget renders to a text input with - no number input specific attributes. - """ - f1 = IntegerField(localize=True) - self.assertWidgetRendersTo(f1, '<input id="id_f" name="f" type="text" />') - - def test_integerfield_float(self): - f = IntegerField() - self.assertEqual(1, f.clean(1.0)) - self.assertEqual(1, f.clean('1.0')) - self.assertEqual(1, f.clean(' 1.0 ')) - self.assertEqual(1, f.clean('1.')) - self.assertEqual(1, f.clean(' 1. ')) - with self.assertRaisesMessage(ValidationError, "'Enter a whole number.'"): - f.clean('1.5') - with self.assertRaisesMessage(ValidationError, "'Enter a whole number.'"): - f.clean('…') - - def test_integerfield_big_num(self): - f = IntegerField() - self.assertEqual(9223372036854775808, f.clean(9223372036854775808)) - self.assertEqual(9223372036854775808, f.clean('9223372036854775808')) - self.assertEqual(9223372036854775808, f.clean('9223372036854775808.0')) - - def test_integerfield_subclass(self): - """ - Test that class-defined widget is not overwritten by __init__ (#22245). - """ - class MyIntegerField(IntegerField): - widget = Textarea - - f = MyIntegerField() - self.assertEqual(f.widget.__class__, Textarea) - f = MyIntegerField(localize=True) - self.assertEqual(f.widget.__class__, Textarea) - - # FloatField ################################################################## - - def test_floatfield_1(self): - f = FloatField() - self.assertWidgetRendersTo(f, '<input step="any" type="number" name="f" id="id_f" />') - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean('') - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean(None) - self.assertEqual(1.0, f.clean('1')) - self.assertIsInstance(f.clean('1'), float) - self.assertEqual(23.0, f.clean('23')) - self.assertEqual(3.1400000000000001, f.clean('3.14')) - self.assertEqual(3.1400000000000001, f.clean(3.14)) - self.assertEqual(42.0, f.clean(42)) - with self.assertRaisesMessage(ValidationError, "'Enter a number.'"): - f.clean('a') - self.assertEqual(1.0, f.clean('1.0 ')) - self.assertEqual(1.0, f.clean(' 1.0')) - self.assertEqual(1.0, f.clean(' 1.0 ')) - with self.assertRaisesMessage(ValidationError, "'Enter a number.'"): - f.clean('1.0a') - self.assertEqual(f.max_value, None) - self.assertEqual(f.min_value, None) - with self.assertRaisesMessage(ValidationError, "'Enter a number.'"): - f.clean('Infinity') - with self.assertRaisesMessage(ValidationError, "'Enter a number.'"): - f.clean('NaN') - with self.assertRaisesMessage(ValidationError, "'Enter a number.'"): - f.clean('-Inf') - - def test_floatfield_2(self): - f = FloatField(required=False) - self.assertIsNone(f.clean('')) - self.assertIsNone(f.clean(None)) - self.assertEqual(1.0, f.clean('1')) - self.assertEqual(f.max_value, None) - self.assertEqual(f.min_value, None) - - def test_floatfield_3(self): - f = FloatField(max_value=1.5, min_value=0.5) - self.assertWidgetRendersTo(f, '<input step="any" name="f" min="0.5" max="1.5" type="number" id="id_f" />') - with self.assertRaisesMessage(ValidationError, "'Ensure this value is less than or equal to 1.5.'"): - f.clean('1.6') - with self.assertRaisesMessage(ValidationError, "'Ensure this value is greater than or equal to 0.5.'"): - f.clean('0.4') - self.assertEqual(1.5, f.clean('1.5')) - self.assertEqual(0.5, f.clean('0.5')) - self.assertEqual(f.max_value, 1.5) - self.assertEqual(f.min_value, 0.5) - - def test_floatfield_widget_attrs(self): - f = FloatField(widget=NumberInput(attrs={'step': 0.01, 'max': 1.0, 'min': 0.0})) - self.assertWidgetRendersTo(f, '<input step="0.01" name="f" min="0.0" max="1.0" type="number" id="id_f" />') - - def test_floatfield_localized(self): - """ - Make sure localized FloatField's widget renders to a text input with - no number input specific attributes. - """ - f = FloatField(localize=True) - self.assertWidgetRendersTo(f, '<input id="id_f" name="f" type="text" />') - - def test_floatfield_changed(self): - f = FloatField() - n = 4.35 - self.assertFalse(f.has_changed(n, '4.3500')) - - with translation.override('fr'), self.settings(USE_L10N=True): - f = FloatField(localize=True) - localized_n = formats.localize_input(n) # -> '4,35' in French - self.assertFalse(f.has_changed(n, localized_n)) - - # DecimalField ################################################################ - - def test_decimalfield_1(self): - f = DecimalField(max_digits=4, decimal_places=2) - self.assertWidgetRendersTo(f, '<input id="id_f" step="0.01" type="number" name="f" />') - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean('') - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean(None) - self.assertEqual(f.clean('1'), Decimal("1")) - self.assertIsInstance(f.clean('1'), Decimal) - self.assertEqual(f.clean('23'), Decimal("23")) - self.assertEqual(f.clean('3.14'), Decimal("3.14")) - self.assertEqual(f.clean(3.14), Decimal("3.14")) - self.assertEqual(f.clean(Decimal('3.14')), Decimal("3.14")) - with self.assertRaisesMessage(ValidationError, "'Enter a number.'"): - f.clean('NaN') - with self.assertRaisesMessage(ValidationError, "'Enter a number.'"): - f.clean('Inf') - with self.assertRaisesMessage(ValidationError, "'Enter a number.'"): - f.clean('-Inf') - with self.assertRaisesMessage(ValidationError, "'Enter a number.'"): - f.clean('a') - with self.assertRaisesMessage(ValidationError, "'Enter a number.'"): - f.clean('łąść') - self.assertEqual(f.clean('1.0 '), Decimal("1.0")) - self.assertEqual(f.clean(' 1.0'), Decimal("1.0")) - self.assertEqual(f.clean(' 1.0 '), Decimal("1.0")) - with self.assertRaisesMessage(ValidationError, "'Enter a number.'"): - f.clean('1.0a') - with self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 4 digits in total.'"): - f.clean('123.45') - with self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 2 decimal places.'"): - f.clean('1.234') - msg = "'Ensure that there are no more than 2 digits before the decimal point.'" - with self.assertRaisesMessage(ValidationError, msg): - f.clean('123.4') - self.assertEqual(f.clean('-12.34'), Decimal("-12.34")) - with self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 4 digits in total.'"): - f.clean('-123.45') - self.assertEqual(f.clean('-.12'), Decimal("-0.12")) - self.assertEqual(f.clean('-00.12'), Decimal("-0.12")) - self.assertEqual(f.clean('-000.12'), Decimal("-0.12")) - with self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 2 decimal places.'"): - f.clean('-000.123') - with self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 4 digits in total.'"): - f.clean('-000.12345') - with self.assertRaisesMessage(ValidationError, "'Enter a number.'"): - f.clean('--0.12') - self.assertEqual(f.max_digits, 4) - self.assertEqual(f.decimal_places, 2) - self.assertEqual(f.max_value, None) - self.assertEqual(f.min_value, None) - - def test_decimalfield_2(self): - f = DecimalField(max_digits=4, decimal_places=2, required=False) - self.assertIsNone(f.clean('')) - self.assertIsNone(f.clean(None)) - self.assertEqual(f.clean('1'), Decimal("1")) - self.assertEqual(f.max_digits, 4) - self.assertEqual(f.decimal_places, 2) - self.assertEqual(f.max_value, None) - self.assertEqual(f.min_value, None) - - def test_decimalfield_3(self): - f = DecimalField(max_digits=4, decimal_places=2, max_value=Decimal('1.5'), min_value=Decimal('0.5')) - self.assertWidgetRendersTo(f, '<input step="0.01" name="f" min="0.5" max="1.5" type="number" id="id_f" />') - with self.assertRaisesMessage(ValidationError, "'Ensure this value is less than or equal to 1.5.'"): - f.clean('1.6') - with self.assertRaisesMessage(ValidationError, "'Ensure this value is greater than or equal to 0.5.'"): - f.clean('0.4') - self.assertEqual(f.clean('1.5'), Decimal("1.5")) - self.assertEqual(f.clean('0.5'), Decimal("0.5")) - self.assertEqual(f.clean('.5'), Decimal("0.5")) - self.assertEqual(f.clean('00.50'), Decimal("0.50")) - self.assertEqual(f.max_digits, 4) - self.assertEqual(f.decimal_places, 2) - self.assertEqual(f.max_value, Decimal('1.5')) - self.assertEqual(f.min_value, Decimal('0.5')) - - def test_decimalfield_4(self): - f = DecimalField(decimal_places=2) - with self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 2 decimal places.'"): - f.clean('0.00000001') - - def test_decimalfield_5(self): - f = DecimalField(max_digits=3) - # Leading whole zeros "collapse" to one digit. - self.assertEqual(f.clean('0000000.10'), Decimal("0.1")) - # But a leading 0 before the . doesn't count towards max_digits - self.assertEqual(f.clean('0000000.100'), Decimal("0.100")) - # Only leading whole zeros "collapse" to one digit. - self.assertEqual(f.clean('000000.02'), Decimal('0.02')) - with self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 3 digits in total.'"): - f.clean('000000.0002') - self.assertEqual(f.clean('.002'), Decimal("0.002")) - - def test_decimalfield_6(self): - f = DecimalField(max_digits=2, decimal_places=2) - self.assertEqual(f.clean('.01'), Decimal(".01")) - msg = "'Ensure that there are no more than 0 digits before the decimal point.'" - with self.assertRaisesMessage(ValidationError, msg): - f.clean('1.1') - - def test_decimalfield_scientific(self): - f = DecimalField(max_digits=2, decimal_places=2) - self.assertEqual(f.clean('1E+2'), Decimal('1E+2')) - self.assertEqual(f.clean('1e+2'), Decimal('1E+2')) - with self.assertRaisesMessage(ValidationError, "Ensure that there are no more"): - f.clean('0.546e+2') - - def test_decimalfield_widget_attrs(self): - f = DecimalField(max_digits=6, decimal_places=2) - self.assertEqual(f.widget_attrs(Widget()), {}) - self.assertEqual(f.widget_attrs(NumberInput()), {'step': '0.01'}) - f = DecimalField(max_digits=10, decimal_places=0) - self.assertEqual(f.widget_attrs(NumberInput()), {'step': '1'}) - f = DecimalField(max_digits=19, decimal_places=19) - self.assertEqual(f.widget_attrs(NumberInput()), {'step': '1e-19'}) - f = DecimalField(max_digits=20) - self.assertEqual(f.widget_attrs(NumberInput()), {'step': 'any'}) - f = DecimalField(max_digits=6, widget=NumberInput(attrs={'step': '0.01'})) - self.assertWidgetRendersTo(f, '<input step="0.01" name="f" type="number" id="id_f" />') - - def test_decimalfield_localized(self): - """ - Make sure localized DecimalField's widget renders to a text input with - no number input specific attributes. - """ - f = DecimalField(localize=True) - self.assertWidgetRendersTo(f, '<input id="id_f" name="f" type="text" />') - - def test_decimalfield_changed(self): - f = DecimalField(max_digits=2, decimal_places=2) - d = Decimal("0.1") - self.assertFalse(f.has_changed(d, '0.10')) - self.assertTrue(f.has_changed(d, '0.101')) - - with translation.override('fr'), self.settings(USE_L10N=True): - f = DecimalField(max_digits=2, decimal_places=2, localize=True) - localized_d = formats.localize_input(d) # -> '0,1' in French - self.assertFalse(f.has_changed(d, localized_d)) - - # DateField ################################################################### - - def test_datefield_1(self): - f = DateField() - self.assertEqual(datetime.date(2006, 10, 25), f.clean(datetime.date(2006, 10, 25))) - self.assertEqual(datetime.date(2006, 10, 25), f.clean(datetime.datetime(2006, 10, 25, 14, 30))) - self.assertEqual(datetime.date(2006, 10, 25), f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59))) - self.assertEqual(datetime.date(2006, 10, 25), f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59, 200))) - self.assertEqual(datetime.date(2006, 10, 25), f.clean('2006-10-25')) - self.assertEqual(datetime.date(2006, 10, 25), f.clean('10/25/2006')) - self.assertEqual(datetime.date(2006, 10, 25), f.clean('10/25/06')) - self.assertEqual(datetime.date(2006, 10, 25), f.clean('Oct 25 2006')) - self.assertEqual(datetime.date(2006, 10, 25), f.clean('October 25 2006')) - self.assertEqual(datetime.date(2006, 10, 25), f.clean('October 25, 2006')) - self.assertEqual(datetime.date(2006, 10, 25), f.clean('25 October 2006')) - self.assertEqual(datetime.date(2006, 10, 25), f.clean('25 October, 2006')) - with self.assertRaisesMessage(ValidationError, "'Enter a valid date.'"): - f.clean('2006-4-31') - with self.assertRaisesMessage(ValidationError, "'Enter a valid date.'"): - f.clean('200a-10-25') - with self.assertRaisesMessage(ValidationError, "'Enter a valid date.'"): - f.clean('25/10/06') - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean(None) - - def test_datefield_2(self): - f = DateField(required=False) - self.assertIsNone(f.clean(None)) - self.assertEqual('None', repr(f.clean(None))) - self.assertIsNone(f.clean('')) - self.assertEqual('None', repr(f.clean(''))) - - def test_datefield_3(self): - f = DateField(input_formats=['%Y %m %d']) - self.assertEqual(datetime.date(2006, 10, 25), f.clean(datetime.date(2006, 10, 25))) - self.assertEqual(datetime.date(2006, 10, 25), f.clean(datetime.datetime(2006, 10, 25, 14, 30))) - self.assertEqual(datetime.date(2006, 10, 25), f.clean('2006 10 25')) - with self.assertRaisesMessage(ValidationError, "'Enter a valid date.'"): - f.clean('2006-10-25') - with self.assertRaisesMessage(ValidationError, "'Enter a valid date.'"): - f.clean('10/25/2006') - with self.assertRaisesMessage(ValidationError, "'Enter a valid date.'"): - f.clean('10/25/06') - - def test_datefield_4(self): - # Test whitespace stripping behavior (#5714) - f = DateField() - self.assertEqual(datetime.date(2006, 10, 25), f.clean(' 10/25/2006 ')) - self.assertEqual(datetime.date(2006, 10, 25), f.clean(' 10/25/06 ')) - self.assertEqual(datetime.date(2006, 10, 25), f.clean(' Oct 25 2006 ')) - self.assertEqual(datetime.date(2006, 10, 25), f.clean(' October 25 2006 ')) - self.assertEqual(datetime.date(2006, 10, 25), f.clean(' October 25, 2006 ')) - self.assertEqual(datetime.date(2006, 10, 25), f.clean(' 25 October 2006 ')) - with self.assertRaisesMessage(ValidationError, "'Enter a valid date.'"): - f.clean(' ') - - def test_datefield_5(self): - # Test null bytes (#18982) - f = DateField() - with self.assertRaisesMessage(ValidationError, "'Enter a valid date.'"): - f.clean('a\x00b') - - def test_datefield_changed(self): - format = '%d/%m/%Y' - f = DateField(input_formats=[format]) - d = datetime.date(2007, 9, 17) - self.assertFalse(f.has_changed(d, '17/09/2007')) - - def test_datefield_strptime(self): - """Test that field.strptime doesn't raise an UnicodeEncodeError (#16123)""" - f = DateField() - try: - f.strptime('31 мая 2011', '%d-%b-%y') - except Exception as e: - # assertIsInstance or assertRaises cannot be used because UnicodeEncodeError - # is a subclass of ValueError - self.assertEqual(e.__class__, ValueError) - - # TimeField ################################################################### - - def test_timefield_1(self): - f = TimeField() - self.assertEqual(datetime.time(14, 25), f.clean(datetime.time(14, 25))) - self.assertEqual(datetime.time(14, 25, 59), f.clean(datetime.time(14, 25, 59))) - self.assertEqual(datetime.time(14, 25), f.clean('14:25')) - self.assertEqual(datetime.time(14, 25, 59), f.clean('14:25:59')) - with self.assertRaisesMessage(ValidationError, "'Enter a valid time.'"): - f.clean('hello') - with self.assertRaisesMessage(ValidationError, "'Enter a valid time.'"): - f.clean('1:24 p.m.') - - def test_timefield_2(self): - f = TimeField(input_formats=['%I:%M %p']) - self.assertEqual(datetime.time(14, 25), f.clean(datetime.time(14, 25))) - self.assertEqual(datetime.time(14, 25, 59), f.clean(datetime.time(14, 25, 59))) - self.assertEqual(datetime.time(4, 25), f.clean('4:25 AM')) - self.assertEqual(datetime.time(16, 25), f.clean('4:25 PM')) - with self.assertRaisesMessage(ValidationError, "'Enter a valid time.'"): - f.clean('14:30:45') - - def test_timefield_3(self): - f = TimeField() - # Test whitespace stripping behavior (#5714) - self.assertEqual(datetime.time(14, 25), f.clean(' 14:25 ')) - self.assertEqual(datetime.time(14, 25, 59), f.clean(' 14:25:59 ')) - with self.assertRaisesMessage(ValidationError, "'Enter a valid time.'"): - f.clean(' ') - - def test_timefield_changed(self): - t1 = datetime.time(12, 51, 34, 482548) - t2 = datetime.time(12, 51) - f = TimeField(input_formats=['%H:%M', '%H:%M %p']) - self.assertTrue(f.has_changed(t1, '12:51')) - self.assertFalse(f.has_changed(t2, '12:51')) - self.assertFalse(f.has_changed(t2, '12:51 PM')) - - # DateTimeField ############################################################### - - def test_datetimefield_1(self): - f = DateTimeField() - self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean(datetime.date(2006, 10, 25))) - self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean(datetime.datetime(2006, 10, 25, 14, 30))) - self.assertEqual( - datetime.datetime(2006, 10, 25, 14, 30, 59), - f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59)) - ) - self.assertEqual( - datetime.datetime(2006, 10, 25, 14, 30, 59, 200), - f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59, 200)) - ) - self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45, 200), f.clean('2006-10-25 14:30:45.000200')) - self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45, 200), f.clean('2006-10-25 14:30:45.0002')) - self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45), f.clean('2006-10-25 14:30:45')) - self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('2006-10-25 14:30:00')) - self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('2006-10-25 14:30')) - self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean('2006-10-25')) - self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45, 200), f.clean('10/25/2006 14:30:45.000200')) - self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45), f.clean('10/25/2006 14:30:45')) - self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('10/25/2006 14:30:00')) - self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('10/25/2006 14:30')) - self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean('10/25/2006')) - self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45, 200), f.clean('10/25/06 14:30:45.000200')) - self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45), f.clean('10/25/06 14:30:45')) - self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('10/25/06 14:30:00')) - self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('10/25/06 14:30')) - self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean('10/25/06')) - with self.assertRaisesMessage(ValidationError, "'Enter a valid date/time.'"): - f.clean('hello') - with self.assertRaisesMessage(ValidationError, "'Enter a valid date/time.'"): - f.clean('2006-10-25 4:30 p.m.') - - def test_datetimefield_2(self): - f = DateTimeField(input_formats=['%Y %m %d %I:%M %p']) - self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean(datetime.date(2006, 10, 25))) - self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean(datetime.datetime(2006, 10, 25, 14, 30))) - self.assertEqual( - datetime.datetime(2006, 10, 25, 14, 30, 59), - f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59)) - ) - self.assertEqual( - datetime.datetime(2006, 10, 25, 14, 30, 59, 200), - f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59, 200)) - ) - self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean('2006 10 25 2:30 PM')) - with self.assertRaisesMessage(ValidationError, "'Enter a valid date/time.'"): - f.clean('2006-10-25 14:30:45') - - def test_datetimefield_3(self): - f = DateTimeField(required=False) - self.assertIsNone(f.clean(None)) - self.assertEqual('None', repr(f.clean(None))) - self.assertIsNone(f.clean('')) - self.assertEqual('None', repr(f.clean(''))) - - def test_datetimefield_4(self): - f = DateTimeField() - # Test whitespace stripping behavior (#5714) - self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45), f.clean(' 2006-10-25 14:30:45 ')) - self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean(' 2006-10-25 ')) - self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45), f.clean(' 10/25/2006 14:30:45 ')) - self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30), f.clean(' 10/25/2006 14:30 ')) - self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean(' 10/25/2006 ')) - self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45), f.clean(' 10/25/06 14:30:45 ')) - self.assertEqual(datetime.datetime(2006, 10, 25, 0, 0), f.clean(' 10/25/06 ')) - with self.assertRaisesMessage(ValidationError, "'Enter a valid date/time.'"): - f.clean(' ') - - def test_datetimefield_5(self): - f = DateTimeField(input_formats=['%Y.%m.%d %H:%M:%S.%f']) - self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45, 200), f.clean('2006.10.25 14:30:45.0002')) - - def test_datetimefield_changed(self): - format = '%Y %m %d %I:%M %p' - f = DateTimeField(input_formats=[format]) - d = datetime.datetime(2006, 9, 17, 14, 30, 0) - self.assertFalse(f.has_changed(d, '2006 09 17 2:30 PM')) - - # DurationField ########################################################### - - def test_durationfield_1(self): - f = DurationField() - self.assertEqual(datetime.timedelta(seconds=30), f.clean('30')) - self.assertEqual( - datetime.timedelta(minutes=15, seconds=30), - f.clean('15:30') - ) - self.assertEqual( - datetime.timedelta(hours=1, minutes=15, seconds=30), - f.clean('1:15:30') - ) - self.assertEqual( - datetime.timedelta( - days=1, hours=1, minutes=15, seconds=30, milliseconds=300), - f.clean('1 1:15:30.3') - ) - - def test_durationfield_2(self): - class DurationForm(Form): - duration = DurationField(initial=datetime.timedelta(hours=1)) - f = DurationForm() - self.assertHTMLEqual( - '<input id="id_duration" type="text" name="duration" value="01:00:00">', - str(f['duration']) - ) - - def test_durationfield_integer_value(self): - f = DurationField() - self.assertEqual(datetime.timedelta(0, 10800), f.clean(10800)) - - def test_durationfield_prepare_value(self): - field = DurationField() - td = datetime.timedelta(minutes=15, seconds=30) - self.assertEqual(field.prepare_value(td), duration_string(td)) - self.assertEqual(field.prepare_value('arbitrary'), 'arbitrary') - self.assertIsNone(field.prepare_value(None)) - - # RegexField ################################################################## - - def test_regexfield_1(self): - f = RegexField('^[0-9][A-F][0-9]$') - self.assertEqual('2A2', f.clean('2A2')) - self.assertEqual('3F3', f.clean('3F3')) - with self.assertRaisesMessage(ValidationError, "'Enter a valid value.'"): - f.clean('3G3') - with self.assertRaisesMessage(ValidationError, "'Enter a valid value.'"): - f.clean(' 2A2') - with self.assertRaisesMessage(ValidationError, "'Enter a valid value.'"): - f.clean('2A2 ') - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean('') - - def test_regexfield_2(self): - f = RegexField('^[0-9][A-F][0-9]$', required=False) - self.assertEqual('2A2', f.clean('2A2')) - self.assertEqual('3F3', f.clean('3F3')) - with self.assertRaisesMessage(ValidationError, "'Enter a valid value.'"): - f.clean('3G3') - self.assertEqual('', f.clean('')) - - def test_regexfield_3(self): - f = RegexField(re.compile('^[0-9][A-F][0-9]$')) - self.assertEqual('2A2', f.clean('2A2')) - self.assertEqual('3F3', f.clean('3F3')) - with self.assertRaisesMessage(ValidationError, "'Enter a valid value.'"): - f.clean('3G3') - with self.assertRaisesMessage(ValidationError, "'Enter a valid value.'"): - f.clean(' 2A2') - with self.assertRaisesMessage(ValidationError, "'Enter a valid value.'"): - f.clean('2A2 ') - - def test_regexfield_5(self): - f = RegexField('^[0-9]+$', min_length=5, max_length=10) - with self.assertRaisesMessage(ValidationError, "'Ensure this value has at least 5 characters (it has 3).'"): - f.clean('123') - six.assertRaisesRegex( - self, ValidationError, - "'Ensure this value has at least 5 characters \(it has 3\)\.'," - " u?'Enter a valid value\.'", - f.clean, 'abc' - ) - self.assertEqual('12345', f.clean('12345')) - self.assertEqual('1234567890', f.clean('1234567890')) - with self.assertRaisesMessage(ValidationError, "'Ensure this value has at most 10 characters (it has 11).'"): - f.clean('12345678901') - with self.assertRaisesMessage(ValidationError, "'Enter a valid value.'"): - f.clean('12345a') - - def test_regexfield_6(self): - """ - Ensure that it works with unicode characters. - Refs #. - """ - f = RegexField('^\w+$') - self.assertEqual('éèøçÎÎ你好', f.clean('éèøçÎÎ你好')) - - def test_change_regex_after_init(self): - f = RegexField('^[a-z]+$') - f.regex = '^[0-9]+$' - self.assertEqual('1234', f.clean('1234')) - with self.assertRaisesMessage(ValidationError, "'Enter a valid value.'"): - f.clean('abcd') - - # EmailField ################################################################## - # See also validators tests for validate_email specific tests - - def test_emailfield_1(self): - f = EmailField() - self.assertWidgetRendersTo(f, '<input type="email" name="f" id="id_f" />') - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean('') - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean(None) - self.assertEqual('person@example.com', f.clean('person@example.com')) - with self.assertRaisesMessage(ValidationError, "'Enter a valid email address.'"): - f.clean('foo') - self.assertEqual( - 'local@domain.with.idn.xyz\xe4\xf6\xfc\xdfabc.part.com', - f.clean('local@domain.with.idn.xyzäöüßabc.part.com') - ) - - def test_email_regexp_for_performance(self): - f = EmailField() - # Check for runaway regex security problem. This will take for-freeking-ever - # if the security fix isn't in place. - addr = 'viewx3dtextx26qx3d@yahoo.comx26latlngx3d15854521645943074058' - self.assertEqual(addr, f.clean(addr)) - - def test_emailfield_not_required(self): - f = EmailField(required=False) - self.assertEqual('', f.clean('')) - self.assertEqual('', f.clean(None)) - self.assertEqual('person@example.com', f.clean('person@example.com')) - self.assertEqual('example@example.com', f.clean(' example@example.com \t \t ')) - with self.assertRaisesMessage(ValidationError, "'Enter a valid email address.'"): - f.clean('foo') - - def test_emailfield_min_max_length(self): - f = EmailField(min_length=10, max_length=15) - self.assertWidgetRendersTo(f, '<input id="id_f" type="email" name="f" maxlength="15" />') - with self.assertRaisesMessage(ValidationError, "'Ensure this value has at least 10 characters (it has 9).'"): - f.clean('a@foo.com') - self.assertEqual('alf@foo.com', f.clean('alf@foo.com')) - with self.assertRaisesMessage(ValidationError, "'Ensure this value has at most 15 characters (it has 20).'"): - f.clean('alf123456788@foo.com') - - # FileField ################################################################## - - def test_filefield_1(self): - f = FileField() - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean('') - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean('', '') - self.assertEqual('files/test1.pdf', f.clean('', 'files/test1.pdf')) - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean(None) - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean(None, '') - self.assertEqual('files/test2.pdf', f.clean(None, 'files/test2.pdf')) - no_file_msg = "'No file was submitted. Check the encoding type on the form.'" - with self.assertRaisesMessage(ValidationError, no_file_msg): - f.clean(SimpleUploadedFile('', b'')) - with self.assertRaisesMessage(ValidationError, no_file_msg): - f.clean(SimpleUploadedFile('', b''), '') - self.assertEqual('files/test3.pdf', f.clean(None, 'files/test3.pdf')) - with self.assertRaisesMessage(ValidationError, no_file_msg): - f.clean('some content that is not a file') - with self.assertRaisesMessage(ValidationError, "'The submitted file is empty.'"): - f.clean(SimpleUploadedFile('name', None)) - with self.assertRaisesMessage(ValidationError, "'The submitted file is empty.'"): - f.clean(SimpleUploadedFile('name', b'')) - self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('name', b'Some File Content')))) - self.assertIsInstance( - f.clean(SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', 'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह'.encode('utf-8'))), - SimpleUploadedFile - ) - self.assertIsInstance( - f.clean(SimpleUploadedFile('name', b'Some File Content'), 'files/test4.pdf'), - SimpleUploadedFile - ) - - def test_filefield_2(self): - f = FileField(max_length=5) - with self.assertRaisesMessage(ValidationError, "'Ensure this filename has at most 5 characters (it has 18).'"): - f.clean(SimpleUploadedFile('test_maxlength.txt', b'hello world')) - self.assertEqual('files/test1.pdf', f.clean('', 'files/test1.pdf')) - self.assertEqual('files/test2.pdf', f.clean(None, 'files/test2.pdf')) - self.assertEqual(SimpleUploadedFile, type(f.clean(SimpleUploadedFile('name', b'Some File Content')))) - - def test_filefield_3(self): - f = FileField(allow_empty_file=True) - self.assertEqual(SimpleUploadedFile, - type(f.clean(SimpleUploadedFile('name', b'')))) - - def test_filefield_changed(self): - ''' - Test for the behavior of has_changed for FileField. The value of data will - more than likely come from request.FILES. The value of initial data will - likely be a filename stored in the database. Since its value is of no use to - a FileField it is ignored. - ''' - f = FileField() - - # No file was uploaded and no initial data. - self.assertFalse(f.has_changed('', None)) - - # A file was uploaded and no initial data. - self.assertTrue(f.has_changed('', {'filename': 'resume.txt', 'content': 'My resume'})) - - # A file was not uploaded, but there is initial data - self.assertFalse(f.has_changed('resume.txt', None)) - - # A file was uploaded and there is initial data (file identity is not dealt - # with here) - self.assertTrue(f.has_changed('resume.txt', {'filename': 'resume.txt', 'content': 'My resume'})) - - def test_file_picklable(self): - self.assertIsInstance(pickle.loads(pickle.dumps(FileField())), FileField) - - # ImageField ################################################################## - - @skipIf(Image is None, "Pillow is required to test ImageField") - def test_imagefield_annotate_with_image_after_clean(self): - f = ImageField() - - img_path = os.path.dirname(upath(__file__)) + '/filepath_test_files/1x1.png' - with open(img_path, 'rb') as img_file: - img_data = img_file.read() - - img_file = SimpleUploadedFile('1x1.png', img_data) - img_file.content_type = 'text/plain' - - uploaded_file = f.clean(img_file) - - self.assertEqual('PNG', uploaded_file.image.format) - self.assertEqual('image/png', uploaded_file.content_type) - - @skipIf(Image is None, "Pillow is required to test ImageField") - def test_imagefield_annotate_with_bitmap_image_after_clean(self): - """ - This also tests the situation when Pillow doesn't detect the MIME type - of the image (#24948). - """ - from PIL.BmpImagePlugin import BmpImageFile - try: - Image.register_mime(BmpImageFile.format, None) - f = ImageField() - img_path = os.path.dirname(upath(__file__)) + '/filepath_test_files/1x1.bmp' - with open(img_path, 'rb') as img_file: - img_data = img_file.read() - - img_file = SimpleUploadedFile('1x1.bmp', img_data) - img_file.content_type = 'text/plain' - - uploaded_file = f.clean(img_file) - - self.assertEqual('BMP', uploaded_file.image.format) - self.assertIsNone(uploaded_file.content_type) - finally: - Image.register_mime(BmpImageFile.format, 'image/bmp') - - # URLField ################################################################## - - def test_urlfield_1(self): - f = URLField() - self.assertWidgetRendersTo(f, '<input type="url" name="f" id="id_f" />') - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean('') - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean(None) - self.assertEqual('http://localhost', f.clean('http://localhost')) - self.assertEqual('http://example.com', f.clean('http://example.com')) - self.assertEqual('http://example.com.', f.clean('http://example.com.')) - self.assertEqual('http://www.example.com', f.clean('http://www.example.com')) - self.assertEqual('http://www.example.com:8000/test', f.clean('http://www.example.com:8000/test')) - self.assertEqual('http://valid-with-hyphens.com', f.clean('valid-with-hyphens.com')) - self.assertEqual('http://subdomain.domain.com', f.clean('subdomain.domain.com')) - self.assertEqual('http://200.8.9.10', f.clean('http://200.8.9.10')) - self.assertEqual('http://200.8.9.10:8000/test', f.clean('http://200.8.9.10:8000/test')) - with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): - f.clean('foo') - with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): - f.clean('http://') - with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): - f.clean('http://example') - with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): - f.clean('http://example.') - with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): - f.clean('com.') - with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): - f.clean('.') - with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): - f.clean('http://.com') - with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): - f.clean('http://invalid-.com') - with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): - f.clean('http://-invalid.com') - with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): - f.clean('http://inv-.alid-.com') - with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): - f.clean('http://inv-.-alid.com') - self.assertEqual('http://valid-----hyphens.com', f.clean('http://valid-----hyphens.com')) - self.assertEqual( - 'http://some.idn.xyz\xe4\xf6\xfc\xdfabc.domain.com:123/blah', - f.clean('http://some.idn.xyzäöüßabc.domain.com:123/blah') - ) - self.assertEqual( - 'http://www.example.com/s/http://code.djangoproject.com/ticket/13804', - f.clean('www.example.com/s/http://code.djangoproject.com/ticket/13804') - ) - with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): - f.clean('[a') - with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): - f.clean('http://[a') - - def test_url_regex_ticket11198(self): - f = URLField() - # hangs "forever" if catastrophic backtracking in ticket:#11198 not fixed - with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): - f.clean('http://%s' % ("X" * 200,)) - - # a second test, to make sure the problem is really addressed, even on - # domains that don't fail the domain label length check in the regex - with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): - f.clean('http://%s' % ("X" * 60,)) - - def test_urlfield_2(self): - f = URLField(required=False) - self.assertEqual('', f.clean('')) - self.assertEqual('', f.clean(None)) - self.assertEqual('http://example.com', f.clean('http://example.com')) - self.assertEqual('http://www.example.com', f.clean('http://www.example.com')) - with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): - f.clean('foo') - with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): - f.clean('http://') - with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): - f.clean('http://example') - with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): - f.clean('http://example.') - with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): - f.clean('http://.com') - - def test_urlfield_5(self): - f = URLField(min_length=15, max_length=20) - self.assertWidgetRendersTo(f, '<input id="id_f" type="url" name="f" maxlength="20" />') - with self.assertRaisesMessage(ValidationError, "'Ensure this value has at least 15 characters (it has 12).'"): - f.clean('http://f.com') - self.assertEqual('http://example.com', f.clean('http://example.com')) - with self.assertRaisesMessage(ValidationError, "'Ensure this value has at most 20 characters (it has 37).'"): - f.clean('http://abcdefghijklmnopqrstuvwxyz.com') - - def test_urlfield_6(self): - f = URLField(required=False) - self.assertEqual('http://example.com', f.clean('example.com')) - self.assertEqual('', f.clean('')) - self.assertEqual('https://example.com', f.clean('https://example.com')) - - def test_urlfield_7(self): - f = URLField() - self.assertEqual('http://example.com', f.clean('http://example.com')) - self.assertEqual('http://example.com/test', f.clean('http://example.com/test')) - self.assertEqual('http://example.com?some_param=some_value', - f.clean('http://example.com?some_param=some_value')) - - def test_urlfield_9(self): - f = URLField() - urls = ( - 'http://עברית.idn.icann.org/', - 'http://sãopaulo.com/', - 'http://sãopaulo.com.br/', - 'http://пример.испытание/', - 'http://مثال.إختبار/', - 'http://例子.测试/', - 'http://例子.測試/', - 'http://उदाहरण.परीक्षा/', - 'http://例え.テスト/', - 'http://مثال.آزمایشی/', - 'http://실례.테스트/', - 'http://العربية.idn.icann.org/', - ) - for url in urls: - # Valid IDN - self.assertEqual(url, f.clean(url)) - - def test_urlfield_10(self): - """Test URLField correctly validates IPv6 (#18779).""" - f = URLField() - urls = ( - 'http://[12:34::3a53]/', - 'http://[a34:9238::]:8080/', - ) - for url in urls: - self.assertEqual(url, f.clean(url)) - - def test_urlfield_not_string(self): - f = URLField(required=False) - with self.assertRaisesMessage(ValidationError, "'Enter a valid URL.'"): - f.clean(23) - - def test_urlfield_normalization(self): - f = URLField() - self.assertEqual(f.clean('http://example.com/ '), 'http://example.com/') - - # BooleanField ################################################################ - - def test_booleanfield_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.assertEqual(True, f.clean(True)) - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean(False) - self.assertEqual(True, f.clean(1)) - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean(0) - self.assertEqual(True, f.clean('Django rocks')) - self.assertEqual(True, f.clean('True')) - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean('False') - - def test_booleanfield_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')) - - # ChoiceField ################################################################# - - 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, - '<select id="id_f" name="f" disabled><option value="J">John</option>' - '<option value="P">Paul</option></select>' - ) - - # TypedChoiceField ############################################################ - # TypedChoiceField is just like ChoiceField, except that coerced types will - # be returned: - - def test_typedchoicefield_1(self): - f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int) - self.assertEqual(1, f.clean('1')) - msg = "'Select a valid choice. 2 is not one of the available choices.'" - with self.assertRaisesMessage(ValidationError, msg): - f.clean('2') - - def test_typedchoicefield_2(self): - # Different coercion, same validation. - f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=float) - self.assertEqual(1.0, f.clean('1')) - - def test_typedchoicefield_3(self): - # This can also cause weirdness: be careful (bool(-1) == True, remember) - f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=bool) - self.assertEqual(True, f.clean('-1')) - - def test_typedchoicefield_4(self): - # Even more weirdness: if you have a valid choice but your coercion function - # can't coerce, you'll still get a validation error. Don't do this! - f = TypedChoiceField(choices=[('A', 'A'), ('B', 'B')], coerce=int) - msg = "'Select a valid choice. B is not one of the available choices.'" - with self.assertRaisesMessage(ValidationError, msg): - f.clean('B') - # Required fields require values - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean('') - - def test_typedchoicefield_5(self): - # Non-required fields aren't required - f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=False) - self.assertEqual('', f.clean('')) - # If you want cleaning an empty value to return a different type, tell the field - - def test_typedchoicefield_6(self): - f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=False, empty_value=None) - self.assertIsNone(f.clean('')) - - def test_typedchoicefield_has_changed(self): - # has_changed should not trigger required validation - f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=True) - self.assertFalse(f.has_changed(None, '')) - self.assertFalse(f.has_changed(1, '1')) - self.assertFalse(f.has_changed('1', '1')) - - f = TypedChoiceField( - choices=[('', '---------'), ('a', "a"), ('b', "b")], coerce=six.text_type, - required=False, initial=None, empty_value=None, - ) - self.assertFalse(f.has_changed(None, '')) - self.assertTrue(f.has_changed('', 'a')) - self.assertFalse(f.has_changed('a', 'a')) - - def test_typedchoicefield_special_coerce(self): - """ - Test a coerce function which results in a value not present in choices. - Refs #21397. - """ - def coerce_func(val): - return Decimal('1.%s' % val) - - f = TypedChoiceField(choices=[(1, "1"), (2, "2")], coerce=coerce_func, required=True) - self.assertEqual(Decimal('1.2'), f.clean('2')) - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean('') - msg = "'Select a valid choice. 3 is not one of the available choices.'" - with self.assertRaisesMessage(ValidationError, msg): - f.clean('3') - - # NullBooleanField ############################################################ - - def test_nullbooleanfield_1(self): - f = NullBooleanField() - self.assertIsNone(f.clean('')) - self.assertEqual(True, f.clean(True)) - self.assertEqual(False, f.clean(False)) - self.assertIsNone(f.clean(None)) - self.assertEqual(False, f.clean('0')) - self.assertEqual(True, f.clean('1')) - self.assertIsNone(f.clean('2')) - self.assertIsNone(f.clean('3')) - self.assertIsNone(f.clean('hello')) - self.assertEqual(True, f.clean('true')) - self.assertEqual(False, f.clean('false')) - - def test_nullbooleanfield_2(self): - # Make sure that the internal value is preserved if using HiddenInput (#7753) - class HiddenNullBooleanForm(Form): - hidden_nullbool1 = NullBooleanField(widget=HiddenInput, initial=True) - hidden_nullbool2 = NullBooleanField(widget=HiddenInput, initial=False) - f = HiddenNullBooleanForm() - self.assertHTMLEqual( - '<input type="hidden" name="hidden_nullbool1" value="True" id="id_hidden_nullbool1" />' - '<input type="hidden" name="hidden_nullbool2" value="False" id="id_hidden_nullbool2" />', - str(f) - ) - - def test_nullbooleanfield_3(self): - class HiddenNullBooleanForm(Form): - hidden_nullbool1 = NullBooleanField(widget=HiddenInput, initial=True) - hidden_nullbool2 = NullBooleanField(widget=HiddenInput, initial=False) - f = HiddenNullBooleanForm({'hidden_nullbool1': 'True', 'hidden_nullbool2': 'False'}) - self.assertIsNone(f.full_clean()) - self.assertEqual(True, f.cleaned_data['hidden_nullbool1']) - self.assertEqual(False, f.cleaned_data['hidden_nullbool2']) - - def test_nullbooleanfield_4(self): - # Make sure we're compatible with MySQL, which uses 0 and 1 for its boolean - # values. (#9609) - NULLBOOL_CHOICES = (('1', 'Yes'), ('0', 'No'), ('', 'Unknown')) - - class MySQLNullBooleanForm(Form): - nullbool0 = NullBooleanField(widget=RadioSelect(choices=NULLBOOL_CHOICES)) - nullbool1 = NullBooleanField(widget=RadioSelect(choices=NULLBOOL_CHOICES)) - nullbool2 = NullBooleanField(widget=RadioSelect(choices=NULLBOOL_CHOICES)) - f = MySQLNullBooleanForm({'nullbool0': '1', 'nullbool1': '0', 'nullbool2': ''}) - self.assertIsNone(f.full_clean()) - self.assertEqual(True, f.cleaned_data['nullbool0']) - self.assertEqual(False, f.cleaned_data['nullbool1']) - self.assertIsNone(f.cleaned_data['nullbool2']) - - def test_nullbooleanfield_changed(self): - f = NullBooleanField() - self.assertTrue(f.has_changed(False, None)) - self.assertTrue(f.has_changed(None, False)) - self.assertFalse(f.has_changed(None, None)) - self.assertFalse(f.has_changed(False, False)) - self.assertTrue(f.has_changed(True, False)) - self.assertTrue(f.has_changed(True, None)) - self.assertTrue(f.has_changed(True, False)) - - # MultipleChoiceField ######################################################### - - def test_multiplechoicefield_1(self): - f = MultipleChoiceField(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'])) - self.assertEqual(['1', '2'], f.clean(['1', '2'])) - self.assertEqual(['1', '2'], f.clean([1, '2'])) - self.assertEqual(['1', '2'], f.clean((1, '2'))) - with self.assertRaisesMessage(ValidationError, "'Enter a list of values.'"): - f.clean('hello') - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean([]) - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean(()) - msg = "'Select a valid choice. 3 is not one of the available choices.'" - with self.assertRaisesMessage(ValidationError, msg): - f.clean(['3']) - - def test_multiplechoicefield_2(self): - f = MultipleChoiceField(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'])) - self.assertEqual(['1', '2'], f.clean(['1', '2'])) - self.assertEqual(['1', '2'], f.clean([1, '2'])) - self.assertEqual(['1', '2'], f.clean((1, '2'))) - with self.assertRaisesMessage(ValidationError, "'Enter a list of values.'"): - f.clean('hello') - self.assertEqual([], f.clean([])) - self.assertEqual([], f.clean(())) - msg = "'Select a valid choice. 3 is not one of the available choices.'" - with self.assertRaisesMessage(ValidationError, msg): - f.clean(['3']) - - def test_multiplechoicefield_3(self): - f = MultipleChoiceField( - 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(['1', '5'], f.clean([1, 5])) - self.assertEqual(['1', '5'], f.clean([1, '5'])) - self.assertEqual(['1', '5'], f.clean(['1', 5])) - self.assertEqual(['1', '5'], f.clean(['1', '5'])) - msg = "'Select a valid choice. 6 is not one of the available choices.'" - with self.assertRaisesMessage(ValidationError, msg): - f.clean(['6']) - msg = "'Select a valid choice. 6 is not one of the available choices.'" - with self.assertRaisesMessage(ValidationError, msg): - f.clean(['1', '6']) - - def test_multiplechoicefield_changed(self): - f = MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two'), ('3', 'Three')]) - self.assertFalse(f.has_changed(None, None)) - self.assertFalse(f.has_changed([], None)) - self.assertTrue(f.has_changed(None, ['1'])) - self.assertFalse(f.has_changed([1, 2], ['1', '2'])) - self.assertFalse(f.has_changed([2, 1], ['1', '2'])) - self.assertTrue(f.has_changed([1, 2], ['1'])) - self.assertTrue(f.has_changed([1, 2], ['1', '3'])) - - # TypedMultipleChoiceField ############################################################ - # TypedMultipleChoiceField is just like MultipleChoiceField, except that coerced types - # will be returned: - - def test_typedmultiplechoicefield_1(self): - f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int) - self.assertEqual([1], f.clean(['1'])) - msg = "'Select a valid choice. 2 is not one of the available choices.'" - with self.assertRaisesMessage(ValidationError, msg): - f.clean(['2']) - - def test_typedmultiplechoicefield_2(self): - # Different coercion, same validation. - f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=float) - self.assertEqual([1.0], f.clean(['1'])) - - def test_typedmultiplechoicefield_3(self): - # This can also cause weirdness: be careful (bool(-1) == True, remember) - f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=bool) - self.assertEqual([True], f.clean(['-1'])) - - def test_typedmultiplechoicefield_4(self): - f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int) - self.assertEqual([1, -1], f.clean(['1', '-1'])) - msg = "'Select a valid choice. 2 is not one of the available choices.'" - with self.assertRaisesMessage(ValidationError, msg): - f.clean(['1', '2']) - - def test_typedmultiplechoicefield_5(self): - # Even more weirdness: if you have a valid choice but your coercion function - # can't coerce, you'll still get a validation error. Don't do this! - f = TypedMultipleChoiceField(choices=[('A', 'A'), ('B', 'B')], coerce=int) - msg = "'Select a valid choice. B is not one of the available choices.'" - with self.assertRaisesMessage(ValidationError, msg): - f.clean(['B']) - # Required fields require values - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean([]) - - def test_typedmultiplechoicefield_6(self): - # Non-required fields aren't required - f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=False) - self.assertEqual([], f.clean([])) - - def test_typedmultiplechoicefield_7(self): - # If you want cleaning an empty value to return a different type, tell the field - f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=False, empty_value=None) - self.assertIsNone(f.clean([])) - - def test_typedmultiplechoicefield_has_changed(self): - # has_changed should not trigger required validation - f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=True) - self.assertFalse(f.has_changed(None, '')) - - def test_typedmultiplechoicefield_special_coerce(self): - """ - Test a coerce function which results in a value not present in choices. - Refs #21397. - """ - def coerce_func(val): - return Decimal('1.%s' % val) - - f = TypedMultipleChoiceField( - choices=[(1, "1"), (2, "2")], coerce=coerce_func, required=True) - self.assertEqual([Decimal('1.2')], f.clean(['2'])) - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean([]) - msg = "'Select a valid choice. 3 is not one of the available choices.'" - with self.assertRaisesMessage(ValidationError, msg): - f.clean(['3']) - - # ComboField ################################################################## - - 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)) - - # FilePathField ############################################################### - - def test_filepathfield_1(self): - path = os.path.abspath(upath(forms.__file__)) - path = os.path.dirname(path) + '/' - self.assertTrue(fix_os_paths(path).endswith('/django/forms/')) - - def test_filepathfield_2(self): - path = upath(forms.__file__) - path = os.path.dirname(os.path.abspath(path)) + '/' - f = FilePathField(path=path) - f.choices = [p for p in f.choices if p[0].endswith('.py')] - f.choices.sort() - expected = [ - ('/django/forms/__init__.py', '__init__.py'), - ('/django/forms/boundfield.py', 'boundfield.py'), - ('/django/forms/fields.py', 'fields.py'), - ('/django/forms/forms.py', 'forms.py'), - ('/django/forms/formsets.py', 'formsets.py'), - ('/django/forms/models.py', 'models.py'), - ('/django/forms/utils.py', 'utils.py'), - ('/django/forms/widgets.py', 'widgets.py') - ] - for exp, got in zip(expected, fix_os_paths(f.choices)): - self.assertEqual(exp[1], got[1]) - self.assertTrue(got[0].endswith(exp[0])) - msg = "'Select a valid choice. fields.py is not one of the available choices.'" - with self.assertRaisesMessage(ValidationError, msg): - f.clean('fields.py') - assert fix_os_paths(f.clean(path + 'fields.py')).endswith('/django/forms/fields.py') - - def test_filepathfield_3(self): - path = upath(forms.__file__) - path = os.path.dirname(os.path.abspath(path)) + '/' - f = FilePathField(path=path, match='^.*?\.py$') - f.choices.sort() - expected = [ - ('/django/forms/__init__.py', '__init__.py'), - ('/django/forms/boundfield.py', 'boundfield.py'), - ('/django/forms/fields.py', 'fields.py'), - ('/django/forms/forms.py', 'forms.py'), - ('/django/forms/formsets.py', 'formsets.py'), - ('/django/forms/models.py', 'models.py'), - ('/django/forms/utils.py', 'utils.py'), - ('/django/forms/widgets.py', 'widgets.py') - ] - for exp, got in zip(expected, fix_os_paths(f.choices)): - self.assertEqual(exp[1], got[1]) - self.assertTrue(got[0].endswith(exp[0])) - - def test_filepathfield_4(self): - path = os.path.abspath(upath(forms.__file__)) - path = os.path.dirname(path) + '/' - f = FilePathField(path=path, recursive=True, match='^.*?\.py$') - f.choices.sort() - expected = [ - ('/django/forms/__init__.py', '__init__.py'), - ('/django/forms/boundfield.py', 'boundfield.py'), - ('/django/forms/extras/__init__.py', 'extras/__init__.py'), - ('/django/forms/extras/widgets.py', 'extras/widgets.py'), - ('/django/forms/fields.py', 'fields.py'), - ('/django/forms/forms.py', 'forms.py'), - ('/django/forms/formsets.py', 'formsets.py'), - ('/django/forms/models.py', 'models.py'), - ('/django/forms/utils.py', 'utils.py'), - ('/django/forms/widgets.py', 'widgets.py') - ] - for exp, got in zip(expected, fix_os_paths(f.choices)): - self.assertEqual(exp[1], got[1]) - self.assertTrue(got[0].endswith(exp[0])) - - def test_filepathfield_folders(self): - path = os.path.dirname(upath(__file__)) + '/filepath_test_files/' - f = FilePathField(path=path, allow_folders=True, allow_files=False) - f.choices.sort() - expected = [ - ('/forms_tests/tests/filepath_test_files/directory', 'directory'), - ] - for exp, got in zip(expected, fix_os_paths(f.choices)): - self.assertEqual(exp[1], got[1]) - self.assertTrue(got[0].endswith(exp[0])) - - f = FilePathField(path=path, allow_folders=True, allow_files=True) - f.choices.sort() - expected = [ - ('/forms_tests/tests/filepath_test_files/.dot-file', '.dot-file'), - ('/forms_tests/tests/filepath_test_files/1x1.bmp', '1x1.bmp'), - ('/forms_tests/tests/filepath_test_files/1x1.png', '1x1.png'), - ('/forms_tests/tests/filepath_test_files/directory', 'directory'), - ('/forms_tests/tests/filepath_test_files/fake-image.jpg', 'fake-image.jpg'), - ('/forms_tests/tests/filepath_test_files/real-text-file.txt', 'real-text-file.txt'), - ] - - actual = fix_os_paths(f.choices) - self.assertEqual(len(expected), len(actual)) - for exp, got in zip(expected, actual): - self.assertEqual(exp[1], got[1]) - self.assertTrue(got[0].endswith(exp[0])) - - # SplitDateTimeField ########################################################## - - def test_splitdatetimefield_1(self): - from django.forms.widgets import SplitDateTimeWidget - f = SplitDateTimeField() - self.assertIsInstance(f.widget, SplitDateTimeWidget) - self.assertEqual( - datetime.datetime(2006, 1, 10, 7, 30), - f.clean([datetime.date(2006, 1, 10), datetime.time(7, 30)]) - ) - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean(None) - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean('') - with self.assertRaisesMessage(ValidationError, "'Enter a list of values.'"): - f.clean('hello') - six.assertRaisesRegex( - self, ValidationError, "'Enter a valid date\.', u?'Enter a valid time\.'", - f.clean, ['hello', 'there'] - ) - with self.assertRaisesMessage(ValidationError, "'Enter a valid time.'"): - f.clean(['2006-01-10', 'there']) - with self.assertRaisesMessage(ValidationError, "'Enter a valid date.'"): - f.clean(['hello', '07:30']) - - def test_splitdatetimefield_2(self): - f = SplitDateTimeField(required=False) - self.assertEqual( - datetime.datetime(2006, 1, 10, 7, 30), - f.clean([datetime.date(2006, 1, 10), datetime.time(7, 30)]) - ) - self.assertEqual(datetime.datetime(2006, 1, 10, 7, 30), f.clean(['2006-01-10', '07:30'])) - self.assertIsNone(f.clean(None)) - self.assertIsNone(f.clean('')) - self.assertIsNone(f.clean([''])) - self.assertIsNone(f.clean(['', ''])) - with self.assertRaisesMessage(ValidationError, "'Enter a list of values.'"): - f.clean('hello') - six.assertRaisesRegex( - self, ValidationError, "'Enter a valid date\.', u?'Enter a valid time\.'", - f.clean, ['hello', 'there'] - ) - with self.assertRaisesMessage(ValidationError, "'Enter a valid time.'"): - f.clean(['2006-01-10', 'there']) - with self.assertRaisesMessage(ValidationError, "'Enter a valid date.'"): - f.clean(['hello', '07:30']) - with self.assertRaisesMessage(ValidationError, "'Enter a valid time.'"): - f.clean(['2006-01-10', '']) - with self.assertRaisesMessage(ValidationError, "'Enter a valid time.'"): - f.clean(['2006-01-10']) - with self.assertRaisesMessage(ValidationError, "'Enter a valid date.'"): - f.clean(['', '07:30']) - - def test_splitdatetimefield_changed(self): - f = SplitDateTimeField(input_date_formats=['%d/%m/%Y']) - self.assertFalse(f.has_changed(['11/01/2012', '09:18:15'], ['11/01/2012', '09:18:15'])) - self.assertTrue(f.has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['2008-05-06', '12:40:00'])) - self.assertFalse(f.has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:40'])) - self.assertTrue(f.has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:41'])) - - # GenericIPAddressField ####################################################### - - def test_generic_ipaddress_invalid_arguments(self): - with self.assertRaises(ValueError): - GenericIPAddressField(protocol='hamster') - with self.assertRaises(ValueError): - GenericIPAddressField(protocol='ipv4', unpack_ipv4=True) - - def test_generic_ipaddress_as_generic(self): - # The edge cases of the IPv6 validation code are not deeply tested - # here, they are covered in the tests for django.utils.ipv6 - f = GenericIPAddressField() - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean('') - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean(None) - self.assertEqual(f.clean(' 127.0.0.1 '), '127.0.0.1') - with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 or IPv6 address.'"): - f.clean('foo') - with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 or IPv6 address.'"): - f.clean('127.0.0.') - with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 or IPv6 address.'"): - f.clean('1.2.3.4.5') - with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 or IPv6 address.'"): - f.clean('256.125.1.5') - self.assertEqual(f.clean(' fe80::223:6cff:fe8a:2e8a '), 'fe80::223:6cff:fe8a:2e8a') - self.assertEqual(f.clean(' 2a02::223:6cff:fe8a:2e8a '), '2a02::223:6cff:fe8a:2e8a') - with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): - f.clean('12345:2:3:4') - with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): - f.clean('1::2:3::4') - with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): - f.clean('foo::223:6cff:fe8a:2e8a') - with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): - f.clean('1::2:3:4:5:6:7:8') - with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): - f.clean('1:2') - - def test_generic_ipaddress_as_ipv4_only(self): - f = GenericIPAddressField(protocol="IPv4") - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean('') - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean(None) - self.assertEqual(f.clean(' 127.0.0.1 '), '127.0.0.1') - with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 address.'"): - f.clean('foo') - with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 address.'"): - f.clean('127.0.0.') - with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 address.'"): - f.clean('1.2.3.4.5') - with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 address.'"): - f.clean('256.125.1.5') - with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 address.'"): - f.clean('fe80::223:6cff:fe8a:2e8a') - with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 address.'"): - f.clean('2a02::223:6cff:fe8a:2e8a') - - def test_generic_ipaddress_as_ipv6_only(self): - f = GenericIPAddressField(protocol="IPv6") - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean('') - with self.assertRaisesMessage(ValidationError, "'This field is required.'"): - f.clean(None) - with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv6 address.'"): - f.clean('127.0.0.1') - with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv6 address.'"): - f.clean('foo') - with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv6 address.'"): - f.clean('127.0.0.') - with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv6 address.'"): - f.clean('1.2.3.4.5') - with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv6 address.'"): - f.clean('256.125.1.5') - self.assertEqual(f.clean(' fe80::223:6cff:fe8a:2e8a '), 'fe80::223:6cff:fe8a:2e8a') - self.assertEqual(f.clean(' 2a02::223:6cff:fe8a:2e8a '), '2a02::223:6cff:fe8a:2e8a') - with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): - f.clean('12345:2:3:4') - with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): - f.clean('1::2:3::4') - with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): - f.clean('foo::223:6cff:fe8a:2e8a') - with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): - f.clean('1::2:3:4:5:6:7:8') - with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): - f.clean('1:2') - - def test_generic_ipaddress_as_generic_not_required(self): - f = GenericIPAddressField(required=False) - self.assertEqual(f.clean(''), '') - self.assertEqual(f.clean(None), '') - self.assertEqual(f.clean('127.0.0.1'), '127.0.0.1') - with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 or IPv6 address.'"): - f.clean('foo') - with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 or IPv6 address.'"): - f.clean('127.0.0.') - with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 or IPv6 address.'"): - f.clean('1.2.3.4.5') - with self.assertRaisesMessage(ValidationError, "'Enter a valid IPv4 or IPv6 address.'"): - f.clean('256.125.1.5') - self.assertEqual(f.clean(' fe80::223:6cff:fe8a:2e8a '), 'fe80::223:6cff:fe8a:2e8a') - self.assertEqual(f.clean(' 2a02::223:6cff:fe8a:2e8a '), '2a02::223:6cff:fe8a:2e8a') - with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): - f.clean('12345:2:3:4') - with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): - f.clean('1::2:3::4') - with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): - f.clean('foo::223:6cff:fe8a:2e8a') - with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): - f.clean('1::2:3:4:5:6:7:8') - with self.assertRaisesMessage(ValidationError, "'This is not a valid IPv6 address.'"): - f.clean('1:2') - - def test_generic_ipaddress_normalization(self): - # Test the normalizing code - f = GenericIPAddressField() - self.assertEqual(f.clean(' ::ffff:0a0a:0a0a '), '::ffff:10.10.10.10') - self.assertEqual(f.clean(' ::ffff:10.10.10.10 '), '::ffff:10.10.10.10') - self.assertEqual(f.clean(' 2001:000:a:0000:0:fe:fe:beef '), '2001:0:a::fe:fe:beef') - self.assertEqual(f.clean(' 2001::a:0000:0:fe:fe:beef '), '2001:0:a::fe:fe:beef') - - f = GenericIPAddressField(unpack_ipv4=True) - self.assertEqual(f.clean(' ::ffff:0a0a:0a0a'), '10.10.10.10') - - # SlugField ################################################################### - - def test_slugfield_normalization(self): - f = SlugField() - self.assertEqual(f.clean(' aa-bb-cc '), 'aa-bb-cc') - - def test_slugfield_unicode_normalization(self): - f = SlugField(allow_unicode=True) - self.assertEqual(f.clean('a'), 'a') - self.assertEqual(f.clean('1'), '1') - self.assertEqual(f.clean('a1'), 'a1') - self.assertEqual(f.clean('你好'), '你好') - self.assertEqual(f.clean(' 你-好 '), '你-好') - self.assertEqual(f.clean('ıçğüş'), 'ıçğüş') - self.assertEqual(f.clean('foo-ıç-bar'), 'foo-ıç-bar') - - # UUIDField ################################################################### - - def test_uuidfield_1(self): - field = UUIDField() - value = field.clean('550e8400e29b41d4a716446655440000') - self.assertEqual(value, uuid.UUID('550e8400e29b41d4a716446655440000')) - - def test_uuidfield_2(self): - field = UUIDField(required=False) - value = field.clean('') - self.assertEqual(value, None) - - def test_uuidfield_3(self): - field = UUIDField() - with self.assertRaises(ValidationError) as cm: - field.clean('550e8400') - self.assertEqual(cm.exception.messages[0], 'Enter a valid UUID.') - - def test_uuidfield_4(self): - field = UUIDField() - value = field.prepare_value(uuid.UUID('550e8400e29b41d4a716446655440000')) - self.assertEqual(value, '550e8400e29b41d4a716446655440000') |
