diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 992db81b344..a210ffc0b7e 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -207,6 +207,7 @@ class Field(RegisterLookupMixin): errors = [] errors.extend(self._check_field_name()) errors.extend(self._check_choices()) + errors.extend(self._check_default()) errors.extend(self._check_db_index()) errors.extend(self._check_null_allowed_for_primary_keys()) errors.extend(self._check_backend_specific_checks(**kwargs)) @@ -283,6 +284,22 @@ class Field(RegisterLookupMixin): else: return [] + def _check_default(self): + if self.has_default(): + default = self.get_default() + try: + self.clean(default, None) + except exceptions.ValidationError as messages: + return [ + checks.Error( + "Invalid 'default' value: %s" % message, + hint=None, + obj=self, + id='fields.E008', + ) for message in messages + ] + return [] + def _check_db_index(self): if self.db_index not in (None, True, False): return [ diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt index 31f9a297cd2..39f3a06089b 100644 --- a/docs/ref/checks.txt +++ b/docs/ref/checks.txt @@ -140,6 +140,7 @@ 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**: Invalid ``default`` value. * **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/docs/releases/1.9.txt b/docs/releases/1.9.txt index 810d6192619..30f812c4d97 100644 --- a/docs/releases/1.9.txt +++ b/docs/releases/1.9.txt @@ -512,6 +512,9 @@ Models * ``connection.queries`` shows queries with substituted parameters on SQLite. +* Added a new model field check that makes sure + :attr:`~django.db.models.Field.default` is a valid value. + CSRF ^^^^ diff --git a/tests/invalid_models_tests/test_ordinary_fields.py b/tests/invalid_models_tests/test_ordinary_fields.py index 033970aae4b..9bde621ed4d 100644 --- a/tests/invalid_models_tests/test_ordinary_fields.py +++ b/tests/invalid_models_tests/test_ordinary_fields.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- from __future__ import unicode_literals +import decimal import unittest from django.core.checks import Error, Warning as DjangoWarning @@ -64,6 +65,22 @@ class BooleanFieldTests(IsolatedModelsTestCase): ] self.assertEqual(errors, expected) + def test_invalid_default(self): + class Model(models.Model): + field = models.BooleanField(default='invalid') + + field = Model._meta.get_field('field') + errors = field.check() + expected = [ + Error( + "Invalid 'default' value: 'invalid' value must be either True or False.", + hint=None, + obj=field, + id='fields.E008', + ), + ] + self.assertEqual(errors, expected) + class CharFieldTests(IsolatedModelsTestCase, TestCase): @@ -215,6 +232,22 @@ class CharFieldTests(IsolatedModelsTestCase, TestCase): ] self.assertEqual(errors, expected) + def test_invalid_default(self): + class Model(models.Model): + field = models.CharField(max_length=10, default=None) + + field = Model._meta.get_field('field') + errors = field.check() + expected = [ + Error( + "Invalid 'default' value: This field cannot be null.", + hint=None, + obj=field, + id='fields.E008', + ), + ] + self.assertEqual(errors, expected) + class DateFieldTests(IsolatedModelsTestCase, TestCase): @@ -281,6 +314,25 @@ class DateFieldTests(IsolatedModelsTestCase, TestCase): def test_fix_default_value_tz(self): self.test_fix_default_value() + def test_invalid_default(self): + class Model(models.Model): + field = models.DateField(default='invalid') + + field = Model._meta.get_field('field') + errors = field.check() + message = ( + "Invalid 'default' value: 'invalid' value has an invalid date format. It must be in YYYY-MM-DD format." + ) + expected = [ + Error( + message, + hint=None, + obj=field, + id='fields.E008', + ), + ] + self.assertEqual(errors, expected) + class DateTimeFieldTests(IsolatedModelsTestCase, TestCase): @@ -419,6 +471,22 @@ class DecimalFieldTests(IsolatedModelsTestCase): expected = [] self.assertEqual(errors, expected) + def test_invalid_default(self): + class Model(models.Model): + field = models.DecimalField(max_digits=3, decimal_places=2, default=decimal.Decimal('20.00')) + + field = Model._meta.get_field('field') + errors = field.check() + expected = [ + Error( + "Invalid 'default' value: Ensure that there are no more than 3 digits in total.", + hint=None, + obj=field, + id='fields.E008', + ), + ] + self.assertEqual(errors, expected) + class FileFieldTests(IsolatedModelsTestCase):