diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index a4018dd6e92..1e04858344a 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -5,6 +5,7 @@ import collections import copy import datetime import decimal +import itertools import uuid import warnings from base64 import b64decode, b64encode @@ -531,9 +532,11 @@ class Field(RegisterLookupMixin): @cached_property def validators(self): - # Some validators can't be created at field initialization time. - # This method provides a way to delay their creation until required. - return self.default_validators + self._validators + """ + Some validators can't be created at field initialization time. + This method provides a way to delay their creation until required. + """ + return list(itertools.chain(self.default_validators, self._validators)) def run_validators(self, value): if value in self.empty_values: diff --git a/django/forms/fields.py b/django/forms/fields.py index 9af85c86e35..f3f8756e391 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import copy import datetime +import itertools import os import re import sys @@ -119,7 +120,8 @@ class Field(object): messages.update(error_messages or {}) self.error_messages = messages - self.validators = self.default_validators + validators + self.validators = list(itertools.chain(self.default_validators, validators)) + super(Field, self).__init__() def prepare_value(self, value): diff --git a/tests/forms_tests/tests/test_validators.py b/tests/forms_tests/tests/test_validators.py index 8c348d600d5..e0afd621016 100644 --- a/tests/forms_tests/tests/test_validators.py +++ b/tests/forms_tests/tests/test_validators.py @@ -52,3 +52,17 @@ class TestFieldWithValidators(TestCase): self.assertFalse(form.is_valid()) self.assertEqual(form.errors['string'], ["Letters only."]) self.assertEqual(form.errors['string'], ["Letters only."]) + + def test_field_validators_can_be_any_iterable(self): + class UserForm(forms.Form): + full_name = forms.CharField( + max_length=50, + validators=( + validators.validate_integer, + validators.validate_email, + ) + ) + + form = UserForm({'full_name': 'not int nor mail'}) + self.assertFalse(form.is_valid()) + self.assertEqual(form.errors['full_name'], ['Enter a valid integer.', 'Enter a valid email address.']) diff --git a/tests/validation/models.py b/tests/validation/models.py index 83b998c38f3..2e2a144cb6e 100644 --- a/tests/validation/models.py +++ b/tests/validation/models.py @@ -31,6 +31,8 @@ class ModelToValidate(models.Model): ) url = models.URLField(blank=True) f_with_custom_validator = models.IntegerField(blank=True, null=True, validators=[validate_answer_to_universe]) + f_with_iterable_of_validators = models.IntegerField(blank=True, null=True, + validators=(validate_answer_to_universe,)) slug = models.SlugField(blank=True) def clean(self): diff --git a/tests/validation/test_validators.py b/tests/validation/test_validators.py index d06605eeca0..4a3c99f08b1 100644 --- a/tests/validation/test_validators.py +++ b/tests/validation/test_validators.py @@ -6,14 +6,26 @@ from .models import ModelToValidate class TestModelsWithValidators(ValidationTestCase): def test_custom_validator_passes_for_correct_value(self): - mtv = ModelToValidate(number=10, name='Some Name', f_with_custom_validator=42) + mtv = ModelToValidate(number=10, name='Some Name', f_with_custom_validator=42, + f_with_iterable_of_validators=42) self.assertIsNone(mtv.full_clean()) def test_custom_validator_raises_error_for_incorrect_value(self): - mtv = ModelToValidate(number=10, name='Some Name', f_with_custom_validator=12) + mtv = ModelToValidate(number=10, name='Some Name', f_with_custom_validator=12, + f_with_iterable_of_validators=42) self.assertFailsValidation(mtv.full_clean, ['f_with_custom_validator']) self.assertFieldFailsValidationWithMessage( mtv.full_clean, 'f_with_custom_validator', ['This is not the answer to life, universe and everything!'] ) + + def test_field_validators_can_be_any_iterable(self): + mtv = ModelToValidate(number=10, name='Some Name', f_with_custom_validator=42, + f_with_iterable_of_validators=12) + self.assertFailsValidation(mtv.full_clean, ['f_with_iterable_of_validators']) + self.assertFieldFailsValidationWithMessage( + mtv.full_clean, + 'f_with_iterable_of_validators', + ['This is not the answer to life, universe and everything!'] + )