mirror of
https://github.com/django/django.git
synced 2024-12-01 15:42:04 +01:00
72ebe85a26
These classes can serve as a base class for user enums, supporting translatable human-readable names, or names automatically inferred from the enum member name. Additional properties make it easy to access the list of names, values and display labels. Thanks to the following for ideas and reviews: Carlton Gibson, Fran Hrženjak, Ian Foote, Mariusz Felisiak, Shai Berger. Co-authored-by: Shai Berger <shai@platonix.com> Co-authored-by: Nick Pope <nick.pope@flightdataservices.com> Co-authored-by: Mariusz Felisiak <felisiak.mariusz@gmail.com>
234 lines
8.7 KiB
Python
234 lines
8.7 KiB
Python
import unittest
|
|
|
|
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, PositiveIntegerModel,
|
|
PositiveSmallIntegerModel, SmallIntegerModel,
|
|
)
|
|
|
|
|
|
class IntegerFieldTests(TestCase):
|
|
model = IntegerModel
|
|
documented_range = (-2147483648, 2147483647)
|
|
|
|
@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)
|
|
|
|
|
|
class SmallIntegerFieldTests(IntegerFieldTests):
|
|
model = SmallIntegerModel
|
|
documented_range = (-32768, 32767)
|
|
|
|
|
|
class BigIntegerFieldTests(IntegerFieldTests):
|
|
model = BigIntegerModel
|
|
documented_range = (-9223372036854775808, 9223372036854775807)
|
|
|
|
|
|
class PositiveSmallIntegerFieldTests(IntegerFieldTests):
|
|
model = PositiveSmallIntegerModel
|
|
documented_range = (0, 32767)
|
|
|
|
|
|
class PositiveIntegerFieldTests(IntegerFieldTests):
|
|
model = PositiveIntegerModel
|
|
documented_range = (0, 2147483647)
|
|
|
|
@unittest.skipIf(connection.vendor == 'sqlite', "SQLite doesn't have a constraint.")
|
|
def test_negative_values(self):
|
|
p = PositiveIntegerModel.objects.create(value=0)
|
|
p.value = models.F('value') - 1
|
|
with self.assertRaises(IntegrityError):
|
|
p.save()
|
|
|
|
|
|
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)
|