mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			274 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			274 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from django.core import validators
 | |
| from django.core.exceptions import ValidationError
 | |
| from django.db import IntegrityError, connection, models
 | |
| from django.test import SimpleTestCase, TestCase
 | |
| 
 | |
| from .models import (
 | |
|     BigIntegerModel,
 | |
|     IntegerModel,
 | |
|     PositiveBigIntegerModel,
 | |
|     PositiveIntegerModel,
 | |
|     PositiveSmallIntegerModel,
 | |
|     SmallIntegerModel,
 | |
| )
 | |
| 
 | |
| 
 | |
| class IntegerFieldTests(TestCase):
 | |
|     model = IntegerModel
 | |
|     documented_range = (-2147483648, 2147483647)
 | |
|     rel_db_type_class = models.IntegerField
 | |
| 
 | |
|     @property
 | |
|     def backend_range(self):
 | |
|         field = self.model._meta.get_field("value")
 | |
|         internal_type = field.get_internal_type()
 | |
|         return connection.ops.integer_field_range(internal_type)
 | |
| 
 | |
|     def test_documented_range(self):
 | |
|         """
 | |
|         Values within the documented safe range pass validation, and can be
 | |
|         saved and retrieved without corruption.
 | |
|         """
 | |
|         min_value, max_value = self.documented_range
 | |
| 
 | |
|         instance = self.model(value=min_value)
 | |
|         instance.full_clean()
 | |
|         instance.save()
 | |
|         qs = self.model.objects.filter(value__lte=min_value)
 | |
|         self.assertEqual(qs.count(), 1)
 | |
|         self.assertEqual(qs[0].value, min_value)
 | |
| 
 | |
|         instance = self.model(value=max_value)
 | |
|         instance.full_clean()
 | |
|         instance.save()
 | |
|         qs = self.model.objects.filter(value__gte=max_value)
 | |
|         self.assertEqual(qs.count(), 1)
 | |
|         self.assertEqual(qs[0].value, max_value)
 | |
| 
 | |
|     def test_backend_range_save(self):
 | |
|         """
 | |
|         Backend specific ranges can be saved without corruption.
 | |
|         """
 | |
|         min_value, max_value = self.backend_range
 | |
| 
 | |
|         if min_value is not None:
 | |
|             instance = self.model(value=min_value)
 | |
|             instance.full_clean()
 | |
|             instance.save()
 | |
|             qs = self.model.objects.filter(value__lte=min_value)
 | |
|             self.assertEqual(qs.count(), 1)
 | |
|             self.assertEqual(qs[0].value, min_value)
 | |
| 
 | |
|         if max_value is not None:
 | |
|             instance = self.model(value=max_value)
 | |
|             instance.full_clean()
 | |
|             instance.save()
 | |
|             qs = self.model.objects.filter(value__gte=max_value)
 | |
|             self.assertEqual(qs.count(), 1)
 | |
|             self.assertEqual(qs[0].value, max_value)
 | |
| 
 | |
|     def test_backend_range_validation(self):
 | |
|         """
 | |
|         Backend specific ranges are enforced at the model validation level
 | |
|         (#12030).
 | |
|         """
 | |
|         min_value, max_value = self.backend_range
 | |
| 
 | |
|         if min_value is not None:
 | |
|             instance = self.model(value=min_value - 1)
 | |
|             expected_message = validators.MinValueValidator.message % {
 | |
|                 "limit_value": min_value,
 | |
|             }
 | |
|             with self.assertRaisesMessage(ValidationError, expected_message):
 | |
|                 instance.full_clean()
 | |
|             instance.value = min_value
 | |
|             instance.full_clean()
 | |
| 
 | |
|         if max_value is not None:
 | |
|             instance = self.model(value=max_value + 1)
 | |
|             expected_message = validators.MaxValueValidator.message % {
 | |
|                 "limit_value": max_value,
 | |
|             }
 | |
|             with self.assertRaisesMessage(ValidationError, expected_message):
 | |
|                 instance.full_clean()
 | |
|             instance.value = max_value
 | |
|             instance.full_clean()
 | |
| 
 | |
|     def test_redundant_backend_range_validators(self):
 | |
|         """
 | |
|         If there are stricter validators than the ones from the database
 | |
|         backend then the backend validators aren't added.
 | |
|         """
 | |
|         min_backend_value, max_backend_value = self.backend_range
 | |
| 
 | |
|         for callable_limit in (True, False):
 | |
|             with self.subTest(callable_limit=callable_limit):
 | |
|                 if min_backend_value is not None:
 | |
|                     min_custom_value = min_backend_value + 1
 | |
|                     limit_value = (
 | |
|                         (lambda: min_custom_value)
 | |
|                         if callable_limit
 | |
|                         else min_custom_value
 | |
|                     )
 | |
|                     ranged_value_field = self.model._meta.get_field("value").__class__(
 | |
|                         validators=[validators.MinValueValidator(limit_value)]
 | |
|                     )
 | |
|                     field_range_message = validators.MinValueValidator.message % {
 | |
|                         "limit_value": min_custom_value,
 | |
|                     }
 | |
|                     with self.assertRaisesMessage(
 | |
|                         ValidationError, "[%r]" % field_range_message
 | |
|                     ):
 | |
|                         ranged_value_field.run_validators(min_backend_value - 1)
 | |
| 
 | |
|                 if max_backend_value is not None:
 | |
|                     max_custom_value = max_backend_value - 1
 | |
|                     limit_value = (
 | |
|                         (lambda: max_custom_value)
 | |
|                         if callable_limit
 | |
|                         else max_custom_value
 | |
|                     )
 | |
|                     ranged_value_field = self.model._meta.get_field("value").__class__(
 | |
|                         validators=[validators.MaxValueValidator(limit_value)]
 | |
|                     )
 | |
|                     field_range_message = validators.MaxValueValidator.message % {
 | |
|                         "limit_value": max_custom_value,
 | |
|                     }
 | |
|                     with self.assertRaisesMessage(
 | |
|                         ValidationError, "[%r]" % field_range_message
 | |
|                     ):
 | |
|                         ranged_value_field.run_validators(max_backend_value + 1)
 | |
| 
 | |
|     def test_types(self):
 | |
|         instance = self.model(value=1)
 | |
|         self.assertIsInstance(instance.value, int)
 | |
|         instance.save()
 | |
|         self.assertIsInstance(instance.value, int)
 | |
|         instance = self.model.objects.get()
 | |
|         self.assertIsInstance(instance.value, int)
 | |
| 
 | |
|     def test_coercing(self):
 | |
|         self.model.objects.create(value="10")
 | |
|         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)
 | |
| 
 | |
|     def test_rel_db_type(self):
 | |
|         field = self.model._meta.get_field("value")
 | |
|         rel_db_type = field.rel_db_type(connection)
 | |
|         self.assertEqual(rel_db_type, self.rel_db_type_class().db_type(connection))
 | |
| 
 | |
| 
 | |
| class SmallIntegerFieldTests(IntegerFieldTests):
 | |
|     model = SmallIntegerModel
 | |
|     documented_range = (-32768, 32767)
 | |
|     rel_db_type_class = models.SmallIntegerField
 | |
| 
 | |
| 
 | |
| class BigIntegerFieldTests(IntegerFieldTests):
 | |
|     model = BigIntegerModel
 | |
|     documented_range = (-9223372036854775808, 9223372036854775807)
 | |
|     rel_db_type_class = models.BigIntegerField
 | |
| 
 | |
| 
 | |
| class PositiveSmallIntegerFieldTests(IntegerFieldTests):
 | |
|     model = PositiveSmallIntegerModel
 | |
|     documented_range = (0, 32767)
 | |
|     rel_db_type_class = (
 | |
|         models.PositiveSmallIntegerField
 | |
|         if connection.features.related_fields_match_type
 | |
|         else models.SmallIntegerField
 | |
|     )
 | |
| 
 | |
| 
 | |
| class PositiveIntegerFieldTests(IntegerFieldTests):
 | |
|     model = PositiveIntegerModel
 | |
|     documented_range = (0, 2147483647)
 | |
|     rel_db_type_class = (
 | |
|         models.PositiveIntegerField
 | |
|         if connection.features.related_fields_match_type
 | |
|         else models.IntegerField
 | |
|     )
 | |
| 
 | |
|     def test_negative_values(self):
 | |
|         p = PositiveIntegerModel.objects.create(value=0)
 | |
|         p.value = models.F("value") - 1
 | |
|         with self.assertRaises(IntegrityError):
 | |
|             p.save()
 | |
| 
 | |
| 
 | |
| class PositiveBigIntegerFieldTests(IntegerFieldTests):
 | |
|     model = PositiveBigIntegerModel
 | |
|     documented_range = (0, 9223372036854775807)
 | |
|     rel_db_type_class = (
 | |
|         models.PositiveBigIntegerField
 | |
|         if connection.features.related_fields_match_type
 | |
|         else models.BigIntegerField
 | |
|     )
 | |
| 
 | |
| 
 | |
| class ValidationTests(SimpleTestCase):
 | |
|     class Choices(models.IntegerChoices):
 | |
|         A = 1
 | |
| 
 | |
|     def test_integerfield_cleans_valid_string(self):
 | |
|         f = models.IntegerField()
 | |
|         self.assertEqual(f.clean("2", None), 2)
 | |
| 
 | |
|     def test_integerfield_raises_error_on_invalid_intput(self):
 | |
|         f = models.IntegerField()
 | |
|         with self.assertRaises(ValidationError):
 | |
|             f.clean("a", None)
 | |
| 
 | |
|     def test_choices_validation_supports_named_groups(self):
 | |
|         f = models.IntegerField(choices=(("group", ((10, "A"), (20, "B"))), (30, "C")))
 | |
|         self.assertEqual(10, f.clean(10, None))
 | |
| 
 | |
|     def test_nullable_integerfield_raises_error_with_blank_false(self):
 | |
|         f = models.IntegerField(null=True, blank=False)
 | |
|         with self.assertRaises(ValidationError):
 | |
|             f.clean(None, None)
 | |
| 
 | |
|     def test_nullable_integerfield_cleans_none_on_null_and_blank_true(self):
 | |
|         f = models.IntegerField(null=True, blank=True)
 | |
|         self.assertIsNone(f.clean(None, None))
 | |
| 
 | |
|     def test_integerfield_raises_error_on_empty_input(self):
 | |
|         f = models.IntegerField(null=False)
 | |
|         with self.assertRaises(ValidationError):
 | |
|             f.clean(None, None)
 | |
|         with self.assertRaises(ValidationError):
 | |
|             f.clean("", None)
 | |
| 
 | |
|     def test_integerfield_validates_zero_against_choices(self):
 | |
|         f = models.IntegerField(choices=((1, 1),))
 | |
|         with self.assertRaises(ValidationError):
 | |
|             f.clean("0", None)
 | |
| 
 | |
|     def test_enum_choices_cleans_valid_string(self):
 | |
|         f = models.IntegerField(choices=self.Choices.choices)
 | |
|         self.assertEqual(f.clean("1", None), 1)
 | |
| 
 | |
|     def test_enum_choices_invalid_input(self):
 | |
|         f = models.IntegerField(choices=self.Choices.choices)
 | |
|         with self.assertRaises(ValidationError):
 | |
|             f.clean("A", None)
 | |
|         with self.assertRaises(ValidationError):
 | |
|             f.clean("3", None)
 |