diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 79a983b8fa..d9e2bac348 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -204,6 +204,7 @@ class Field(RegisterLookupMixin): errors.extend(self._check_db_index()) errors.extend(self._check_null_allowed_for_primary_keys()) errors.extend(self._check_backend_specific_checks(**kwargs)) + errors.extend(self._check_validators()) errors.extend(self._check_deprecation_details()) return errors @@ -302,6 +303,25 @@ class Field(RegisterLookupMixin): return connections[db].validation.check_field(self, **kwargs) return [] + def _check_validators(self): + errors = [] + for i, validator in enumerate(self.validators): + if not callable(validator): + errors.append( + checks.Error( + "All 'validators' must be callable.", + hint=( + "validators[{i}] ({repr}) isn't a function or " + "instance of a validator class.".format( + i=i, repr=repr(validator), + ) + ), + obj=self, + id='fields.E008', + ) + ) + return errors + def _check_deprecation_details(self): if self.system_check_removed_details is not None: return [ diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt index d71219f5be..a2066bf216 100644 --- a/docs/ref/checks.txt +++ b/docs/ref/checks.txt @@ -154,6 +154,7 @@ Model fields human readable name)`` tuples. * **fields.E006**: ``db_index`` must be ``None``, ``True`` or ``False``. * **fields.E007**: Primary keys must not have ``null=True``. +* **fields.E008**: All ``validators`` must be callable. * **fields.E100**: ``AutoField``\s must set primary_key=True. * **fields.E110**: ``BooleanField``\s do not accept null values. * **fields.E120**: ``CharField``\s must define a ``max_length`` attribute. diff --git a/tests/invalid_models_tests/test_ordinary_fields.py b/tests/invalid_models_tests/test_ordinary_fields.py index f1f1f07766..9b5b57834d 100644 --- a/tests/invalid_models_tests/test_ordinary_fields.py +++ b/tests/invalid_models_tests/test_ordinary_fields.py @@ -205,6 +205,23 @@ class CharFieldTests(TestCase): ] self.assertEqual(errors, expected) + def test_bad_validators(self): + class Model(models.Model): + field = models.CharField(max_length=10, validators=[True]) + + field = Model._meta.get_field('field') + self.assertEqual(field.check(), [ + Error( + "All 'validators' must be callable.", + hint=( + "validators[0] (True) isn't a function or instance of a " + "validator class." + ), + obj=field, + id='fields.E008', + ), + ]) + @unittest.skipUnless(connection.vendor == 'mysql', "Test valid only for MySQL") def test_too_long_char_field_under_mysql(self): diff --git a/tests/invalid_models_tests/test_relative_fields.py b/tests/invalid_models_tests/test_relative_fields.py index db677bd670..023e201a8a 100644 --- a/tests/invalid_models_tests/test_relative_fields.py +++ b/tests/invalid_models_tests/test_relative_fields.py @@ -102,7 +102,7 @@ class RelativeFieldTests(SimpleTestCase): m2m = models.ManyToManyField( Model, null=True, - validators=[''], + validators=[lambda x: x], limit_choices_to={'name': 'test_name'}, through='ThroughModel', through_fields=('modelm2m', 'model'),