Browse Source

Fixed #27904 -- Added a system check that Field.validators are callable.

Adam Chainz 8 years ago
parent
commit
a452dddb25

+ 20 - 0
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 [

+ 1 - 0
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.

+ 17 - 0
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):

+ 1 - 1
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'),