1. from django.core import validators
    
  2. from django.core.exceptions import ValidationError
    
  3. from django.db import IntegrityError, connection, models
    
  4. from django.test import SimpleTestCase, TestCase
    
  5. 
    
  6. from .models import (
    
  7.     BigIntegerModel,
    
  8.     IntegerModel,
    
  9.     PositiveBigIntegerModel,
    
  10.     PositiveIntegerModel,
    
  11.     PositiveSmallIntegerModel,
    
  12.     SmallIntegerModel,
    
  13. )
    
  14. 
    
  15. 
    
  16. class IntegerFieldTests(TestCase):
    
  17.     model = IntegerModel
    
  18.     documented_range = (-2147483648, 2147483647)
    
  19.     rel_db_type_class = models.IntegerField
    
  20. 
    
  21.     @property
    
  22.     def backend_range(self):
    
  23.         field = self.model._meta.get_field("value")
    
  24.         internal_type = field.get_internal_type()
    
  25.         return connection.ops.integer_field_range(internal_type)
    
  26. 
    
  27.     def test_documented_range(self):
    
  28.         """
    
  29.         Values within the documented safe range pass validation, and can be
    
  30.         saved and retrieved without corruption.
    
  31.         """
    
  32.         min_value, max_value = self.documented_range
    
  33. 
    
  34.         instance = self.model(value=min_value)
    
  35.         instance.full_clean()
    
  36.         instance.save()
    
  37.         qs = self.model.objects.filter(value__lte=min_value)
    
  38.         self.assertEqual(qs.count(), 1)
    
  39.         self.assertEqual(qs[0].value, min_value)
    
  40. 
    
  41.         instance = self.model(value=max_value)
    
  42.         instance.full_clean()
    
  43.         instance.save()
    
  44.         qs = self.model.objects.filter(value__gte=max_value)
    
  45.         self.assertEqual(qs.count(), 1)
    
  46.         self.assertEqual(qs[0].value, max_value)
    
  47. 
    
  48.     def test_backend_range_save(self):
    
  49.         """
    
  50.         Backend specific ranges can be saved without corruption.
    
  51.         """
    
  52.         min_value, max_value = self.backend_range
    
  53. 
    
  54.         if min_value is not None:
    
  55.             instance = self.model(value=min_value)
    
  56.             instance.full_clean()
    
  57.             instance.save()
    
  58.             qs = self.model.objects.filter(value__lte=min_value)
    
  59.             self.assertEqual(qs.count(), 1)
    
  60.             self.assertEqual(qs[0].value, min_value)
    
  61. 
    
  62.         if max_value is not None:
    
  63.             instance = self.model(value=max_value)
    
  64.             instance.full_clean()
    
  65.             instance.save()
    
  66.             qs = self.model.objects.filter(value__gte=max_value)
    
  67.             self.assertEqual(qs.count(), 1)
    
  68.             self.assertEqual(qs[0].value, max_value)
    
  69. 
    
  70.     def test_backend_range_validation(self):
    
  71.         """
    
  72.         Backend specific ranges are enforced at the model validation level
    
  73.         (#12030).
    
  74.         """
    
  75.         min_value, max_value = self.backend_range
    
  76. 
    
  77.         if min_value is not None:
    
  78.             instance = self.model(value=min_value - 1)
    
  79.             expected_message = validators.MinValueValidator.message % {
    
  80.                 "limit_value": min_value,
    
  81.             }
    
  82.             with self.assertRaisesMessage(ValidationError, expected_message):
    
  83.                 instance.full_clean()
    
  84.             instance.value = min_value
    
  85.             instance.full_clean()
    
  86. 
    
  87.         if max_value is not None:
    
  88.             instance = self.model(value=max_value + 1)
    
  89.             expected_message = validators.MaxValueValidator.message % {
    
  90.                 "limit_value": max_value,
    
  91.             }
    
  92.             with self.assertRaisesMessage(ValidationError, expected_message):
    
  93.                 instance.full_clean()
    
  94.             instance.value = max_value
    
  95.             instance.full_clean()
    
  96. 
    
  97.     def test_redundant_backend_range_validators(self):
    
  98.         """
    
  99.         If there are stricter validators than the ones from the database
    
  100.         backend then the backend validators aren't added.
    
  101.         """
    
  102.         min_backend_value, max_backend_value = self.backend_range
    
  103. 
    
  104.         for callable_limit in (True, False):
    
  105.             with self.subTest(callable_limit=callable_limit):
    
  106.                 if min_backend_value is not None:
    
  107.                     min_custom_value = min_backend_value + 1
    
  108.                     limit_value = (
    
  109.                         (lambda: min_custom_value)
    
  110.                         if callable_limit
    
  111.                         else min_custom_value
    
  112.                     )
    
  113.                     ranged_value_field = self.model._meta.get_field("value").__class__(
    
  114.                         validators=[validators.MinValueValidator(limit_value)]
    
  115.                     )
    
  116.                     field_range_message = validators.MinValueValidator.message % {
    
  117.                         "limit_value": min_custom_value,
    
  118.                     }
    
  119.                     with self.assertRaisesMessage(
    
  120.                         ValidationError, "[%r]" % field_range_message
    
  121.                     ):
    
  122.                         ranged_value_field.run_validators(min_backend_value - 1)
    
  123. 
    
  124.                 if max_backend_value is not None:
    
  125.                     max_custom_value = max_backend_value - 1
    
  126.                     limit_value = (
    
  127.                         (lambda: max_custom_value)
    
  128.                         if callable_limit
    
  129.                         else max_custom_value
    
  130.                     )
    
  131.                     ranged_value_field = self.model._meta.get_field("value").__class__(
    
  132.                         validators=[validators.MaxValueValidator(limit_value)]
    
  133.                     )
    
  134.                     field_range_message = validators.MaxValueValidator.message % {
    
  135.                         "limit_value": max_custom_value,
    
  136.                     }
    
  137.                     with self.assertRaisesMessage(
    
  138.                         ValidationError, "[%r]" % field_range_message
    
  139.                     ):
    
  140.                         ranged_value_field.run_validators(max_backend_value + 1)
    
  141. 
    
  142.     def test_types(self):
    
  143.         instance = self.model(value=1)
    
  144.         self.assertIsInstance(instance.value, int)
    
  145.         instance.save()
    
  146.         self.assertIsInstance(instance.value, int)
    
  147.         instance = self.model.objects.get()
    
  148.         self.assertIsInstance(instance.value, int)
    
  149. 
    
  150.     def test_coercing(self):
    
  151.         self.model.objects.create(value="10")
    
  152.         instance = self.model.objects.get(value="10")
    
  153.         self.assertEqual(instance.value, 10)
    
  154. 
    
  155.     def test_invalid_value(self):
    
  156.         tests = [
    
  157.             (TypeError, ()),
    
  158.             (TypeError, []),
    
  159.             (TypeError, {}),
    
  160.             (TypeError, set()),
    
  161.             (TypeError, object()),
    
  162.             (TypeError, complex()),
    
  163.             (ValueError, "non-numeric string"),
    
  164.             (ValueError, b"non-numeric byte-string"),
    
  165.         ]
    
  166.         for exception, value in tests:
    
  167.             with self.subTest(value):
    
  168.                 msg = "Field 'value' expected a number but got %r." % (value,)
    
  169.                 with self.assertRaisesMessage(exception, msg):
    
  170.                     self.model.objects.create(value=value)
    
  171. 
    
  172.     def test_rel_db_type(self):
    
  173.         field = self.model._meta.get_field("value")
    
  174.         rel_db_type = field.rel_db_type(connection)
    
  175.         self.assertEqual(rel_db_type, self.rel_db_type_class().db_type(connection))
    
  176. 
    
  177. 
    
  178. class SmallIntegerFieldTests(IntegerFieldTests):
    
  179.     model = SmallIntegerModel
    
  180.     documented_range = (-32768, 32767)
    
  181.     rel_db_type_class = models.SmallIntegerField
    
  182. 
    
  183. 
    
  184. class BigIntegerFieldTests(IntegerFieldTests):
    
  185.     model = BigIntegerModel
    
  186.     documented_range = (-9223372036854775808, 9223372036854775807)
    
  187.     rel_db_type_class = models.BigIntegerField
    
  188. 
    
  189. 
    
  190. class PositiveSmallIntegerFieldTests(IntegerFieldTests):
    
  191.     model = PositiveSmallIntegerModel
    
  192.     documented_range = (0, 32767)
    
  193.     rel_db_type_class = (
    
  194.         models.PositiveSmallIntegerField
    
  195.         if connection.features.related_fields_match_type
    
  196.         else models.SmallIntegerField
    
  197.     )
    
  198. 
    
  199. 
    
  200. class PositiveIntegerFieldTests(IntegerFieldTests):
    
  201.     model = PositiveIntegerModel
    
  202.     documented_range = (0, 2147483647)
    
  203.     rel_db_type_class = (
    
  204.         models.PositiveIntegerField
    
  205.         if connection.features.related_fields_match_type
    
  206.         else models.IntegerField
    
  207.     )
    
  208. 
    
  209.     def test_negative_values(self):
    
  210.         p = PositiveIntegerModel.objects.create(value=0)
    
  211.         p.value = models.F("value") - 1
    
  212.         with self.assertRaises(IntegrityError):
    
  213.             p.save()
    
  214. 
    
  215. 
    
  216. class PositiveBigIntegerFieldTests(IntegerFieldTests):
    
  217.     model = PositiveBigIntegerModel
    
  218.     documented_range = (0, 9223372036854775807)
    
  219.     rel_db_type_class = (
    
  220.         models.PositiveBigIntegerField
    
  221.         if connection.features.related_fields_match_type
    
  222.         else models.BigIntegerField
    
  223.     )
    
  224. 
    
  225. 
    
  226. class ValidationTests(SimpleTestCase):
    
  227.     class Choices(models.IntegerChoices):
    
  228.         A = 1
    
  229. 
    
  230.     def test_integerfield_cleans_valid_string(self):
    
  231.         f = models.IntegerField()
    
  232.         self.assertEqual(f.clean("2", None), 2)
    
  233. 
    
  234.     def test_integerfield_raises_error_on_invalid_intput(self):
    
  235.         f = models.IntegerField()
    
  236.         with self.assertRaises(ValidationError):
    
  237.             f.clean("a", None)
    
  238. 
    
  239.     def test_choices_validation_supports_named_groups(self):
    
  240.         f = models.IntegerField(choices=(("group", ((10, "A"), (20, "B"))), (30, "C")))
    
  241.         self.assertEqual(10, f.clean(10, None))
    
  242. 
    
  243.     def test_nullable_integerfield_raises_error_with_blank_false(self):
    
  244.         f = models.IntegerField(null=True, blank=False)
    
  245.         with self.assertRaises(ValidationError):
    
  246.             f.clean(None, None)
    
  247. 
    
  248.     def test_nullable_integerfield_cleans_none_on_null_and_blank_true(self):
    
  249.         f = models.IntegerField(null=True, blank=True)
    
  250.         self.assertIsNone(f.clean(None, None))
    
  251. 
    
  252.     def test_integerfield_raises_error_on_empty_input(self):
    
  253.         f = models.IntegerField(null=False)
    
  254.         with self.assertRaises(ValidationError):
    
  255.             f.clean(None, None)
    
  256.         with self.assertRaises(ValidationError):
    
  257.             f.clean("", None)
    
  258. 
    
  259.     def test_integerfield_validates_zero_against_choices(self):
    
  260.         f = models.IntegerField(choices=((1, 1),))
    
  261.         with self.assertRaises(ValidationError):
    
  262.             f.clean("0", None)
    
  263. 
    
  264.     def test_enum_choices_cleans_valid_string(self):
    
  265.         f = models.IntegerField(choices=self.Choices.choices)
    
  266.         self.assertEqual(f.clean("1", None), 1)
    
  267. 
    
  268.     def test_enum_choices_invalid_input(self):
    
  269.         f = models.IntegerField(choices=self.Choices.choices)
    
  270.         with self.assertRaises(ValidationError):
    
  271.             f.clean("A", None)
    
  272.         with self.assertRaises(ValidationError):
    
  273.             f.clean("3", None)