From 25f21bd2376603c8e233a0a0e5a726a0fdfdd33e Mon Sep 17 00:00:00 2001 From: Diederik van der Boor Date: Thu, 13 Jul 2017 13:55:23 +0200 Subject: [PATCH] Fixed #28393 -- Added helpful error messages for invalid AutoField/FloatField/IntegerField values. Co-authored-by: Diederik van der Boor Co-authored-by: Nick Pope --- django/db/models/fields/__init__.py | 21 +++++++++++++--- tests/model_fields/models.py | 12 ++++++++++ tests/model_fields/test_autofield.py | 32 +++++++++++++++++++++++++ tests/model_fields/test_floatfield.py | 17 +++++++++++++ tests/model_fields/test_integerfield.py | 17 +++++++++++++ tests/queries/tests.py | 2 +- 6 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 tests/model_fields/test_autofield.py diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index ff686e4f62..94effe437f 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -964,7 +964,12 @@ class AutoField(Field): value = super().get_prep_value(value) if value is None or isinstance(value, OuterRef): return value - return int(value) + try: + return int(value) + except (TypeError, ValueError) as e: + raise e.__class__( + "Field '%s' expected a number but got %r." % (self.name, value), + ) from e def contribute_to_class(self, cls, name, **kwargs): assert not cls._meta.auto_field, "Model %s can't have more than one AutoField." % cls._meta.label @@ -1745,7 +1750,12 @@ class FloatField(Field): value = super().get_prep_value(value) if value is None: return None - return float(value) + try: + return float(value) + except (TypeError, ValueError) as e: + raise e.__class__( + "Field '%s' expected a number but got %r." % (self.name, value), + ) from e def get_internal_type(self): return "FloatField" @@ -1827,7 +1837,12 @@ class IntegerField(Field): value = super().get_prep_value(value) if value is None: return None - return int(value) + try: + return int(value) + except (TypeError, ValueError) as e: + raise e.__class__( + "Field '%s' expected a number but got %r." % (self.name, value), + ) from e def get_internal_type(self): return "IntegerField" diff --git a/tests/model_fields/models.py b/tests/model_fields/models.py index cbf3ec26ea..98b32d0c1e 100644 --- a/tests/model_fields/models.py +++ b/tests/model_fields/models.py @@ -92,6 +92,18 @@ class UnicodeSlugField(models.Model): s = models.SlugField(max_length=255, allow_unicode=True) +class AutoModel(models.Model): + value = models.AutoField(primary_key=True) + + +class BigAutoModel(models.Model): + value = models.BigAutoField(primary_key=True) + + +class SmallAutoModel(models.Model): + value = models.SmallAutoField(primary_key=True) + + class SmallIntegerModel(models.Model): value = models.SmallIntegerField() diff --git a/tests/model_fields/test_autofield.py b/tests/model_fields/test_autofield.py new file mode 100644 index 0000000000..73220ae62b --- /dev/null +++ b/tests/model_fields/test_autofield.py @@ -0,0 +1,32 @@ +from django.test import TestCase + +from .models import AutoModel, BigAutoModel, SmallAutoModel + + +class AutoFieldTests(TestCase): + model = AutoModel + + def test_invalid_value(self): + tests = [ + (TypeError, ()), + (TypeError, []), + (TypeError, {}), + (TypeError, set()), + (TypeError, object()), + (TypeError, complex()), + (ValueError, 'non-numeric string'), + (ValueError, b'non-numeric byte-string'), + ] + for exception, value in tests: + with self.subTest(value=value): + msg = "Field 'value' expected a number but got %r." % (value,) + with self.assertRaisesMessage(exception, msg): + self.model.objects.create(value=value) + + +class BigAutoFieldTests(AutoFieldTests): + model = BigAutoModel + + +class SmallAutoFieldTests(AutoFieldTests): + model = SmallAutoModel diff --git a/tests/model_fields/test_floatfield.py b/tests/model_fields/test_floatfield.py index 481925cf11..856197cd6e 100644 --- a/tests/model_fields/test_floatfield.py +++ b/tests/model_fields/test_floatfield.py @@ -31,3 +31,20 @@ class TestFloatField(TestCase): obj.size = obj with self.assertRaisesMessage(TypeError, msg): obj.save() + + def test_invalid_value(self): + tests = [ + (TypeError, ()), + (TypeError, []), + (TypeError, {}), + (TypeError, set()), + (TypeError, object()), + (TypeError, complex()), + (ValueError, 'non-numeric string'), + (ValueError, b'non-numeric byte-string'), + ] + for exception, value in tests: + with self.subTest(value): + msg = "Field 'size' expected a number but got %r." % (value,) + with self.assertRaisesMessage(exception, msg): + FloatModel.objects.create(size=value) diff --git a/tests/model_fields/test_integerfield.py b/tests/model_fields/test_integerfield.py index 626b67b00b..c72cb48015 100644 --- a/tests/model_fields/test_integerfield.py +++ b/tests/model_fields/test_integerfield.py @@ -137,6 +137,23 @@ class IntegerFieldTests(TestCase): instance = self.model.objects.get(value='10') self.assertEqual(instance.value, 10) + def test_invalid_value(self): + tests = [ + (TypeError, ()), + (TypeError, []), + (TypeError, {}), + (TypeError, set()), + (TypeError, object()), + (TypeError, complex()), + (ValueError, 'non-numeric string'), + (ValueError, b'non-numeric byte-string'), + ] + for exception, value in tests: + with self.subTest(value): + msg = "Field 'value' expected a number but got %r." % (value,) + with self.assertRaisesMessage(exception, msg): + self.model.objects.create(value=value) + class SmallIntegerFieldTests(IntegerFieldTests): model = SmallIntegerModel diff --git a/tests/queries/tests.py b/tests/queries/tests.py index 6d3dcbdb1f..8339c52a02 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -3853,7 +3853,7 @@ class TestTicket24279(TestCase): class TestInvalidValuesRelation(SimpleTestCase): def test_invalid_values(self): - msg = "invalid literal for int() with base 10: 'abc'" + msg = "Field 'id' expected a number but got 'abc'." with self.assertRaisesMessage(ValueError, msg): Annotation.objects.filter(tag='abc') with self.assertRaisesMessage(ValueError, msg):