Adding more verbose validation error messages in case of both "min_value" and "max_value" limits are set.

This commit is contained in:
Sergey Piskunov 2021-03-05 11:04:36 +02:00
parent ec29ff8a80
commit 3b4f617a2e
2 changed files with 253 additions and 37 deletions

View File

@ -931,6 +931,7 @@ class IntegerField(Field):
'invalid': _('A valid integer is required.'),
'max_value': _('Ensure this value is less than or equal to {max_value}.'),
'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
'range': _('Ensure this value is in the range between {min_value} and {max_value}.'),
'max_string_length': _('String value too large.')
}
MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs.
@ -940,14 +941,22 @@ class IntegerField(Field):
self.max_value = kwargs.pop('max_value', None)
self.min_value = kwargs.pop('min_value', None)
super().__init__(**kwargs)
if self.min_value is not None and self.max_value is not None:
message = lazy_format(
self.error_messages['range'], min_value=self.min_value, max_value=self.max_value)
else:
message = None
if self.max_value is not None:
message = lazy_format(self.error_messages['max_value'], max_value=self.max_value)
self.validators.append(
MaxValueValidator(self.max_value, message=message))
MaxValueValidator(self.max_value, message=message or lazy_format(
self.error_messages['max_value'], max_value=self.max_value)))
if self.min_value is not None:
message = lazy_format(self.error_messages['min_value'], min_value=self.min_value)
self.validators.append(
MinValueValidator(self.min_value, message=message))
MinValueValidator(self.min_value, message=message or lazy_format(
self.error_messages['min_value'], min_value=self.min_value)))
def to_internal_value(self, data):
if isinstance(data, str) and len(data) > self.MAX_STRING_LENGTH:
@ -968,6 +977,7 @@ class FloatField(Field):
'invalid': _('A valid number is required.'),
'max_value': _('Ensure this value is less than or equal to {max_value}.'),
'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
'range': _('Ensure this value is in the range between {min_value} and {max_value}.'),
'max_string_length': _('String value too large.')
}
MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs.
@ -976,14 +986,22 @@ class FloatField(Field):
self.max_value = kwargs.pop('max_value', None)
self.min_value = kwargs.pop('min_value', None)
super().__init__(**kwargs)
if self.min_value is not None and self.max_value is not None:
message = lazy_format(
self.error_messages['range'], min_value=self.min_value, max_value=self.max_value)
else:
message = None
if self.max_value is not None:
message = lazy_format(self.error_messages['max_value'], max_value=self.max_value)
self.validators.append(
MaxValueValidator(self.max_value, message=message))
MaxValueValidator(self.max_value, message=message or lazy_format(
self.error_messages['max_value'], max_value=self.max_value)))
if self.min_value is not None:
message = lazy_format(self.error_messages['min_value'], min_value=self.min_value)
self.validators.append(
MinValueValidator(self.min_value, message=message))
MinValueValidator(self.min_value, message=message or lazy_format(
self.error_messages['min_value'], min_value=self.min_value)))
def to_internal_value(self, data):
@ -1004,6 +1022,7 @@ class DecimalField(Field):
'invalid': _('A valid number is required.'),
'max_value': _('Ensure this value is less than or equal to {max_value}.'),
'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
'range': _('Ensure this value is in the range between {min_value} and {max_value}.'),
'max_digits': _('Ensure that there are no more than {max_digits} digits in total.'),
'max_decimal_places': _('Ensure that there are no more than {max_decimal_places} decimal places.'),
'max_whole_digits': _('Ensure that there are no more than {max_whole_digits} digits before the decimal point.'),
@ -1031,14 +1050,21 @@ class DecimalField(Field):
super().__init__(**kwargs)
if self.min_value is not None and self.max_value is not None:
message = lazy_format(
self.error_messages['range'], min_value=self.min_value, max_value=self.max_value)
else:
message = None
if self.max_value is not None:
message = lazy_format(self.error_messages['max_value'], max_value=self.max_value)
self.validators.append(
MaxValueValidator(self.max_value, message=message))
MaxValueValidator(self.max_value, message=message or lazy_format(
self.error_messages['max_value'], max_value=self.max_value)))
if self.min_value is not None:
message = lazy_format(self.error_messages['min_value'], min_value=self.min_value)
self.validators.append(
MinValueValidator(self.min_value, message=message))
MinValueValidator(self.min_value, message=message or lazy_format(
self.error_messages['min_value'], min_value=self.min_value)))
if rounding is not None:
valid_roundings = [v for k, v in vars(decimal).items() if k.startswith('ROUND_')]
@ -1365,20 +1391,29 @@ class DurationField(Field):
'invalid': _('Duration has wrong format. Use one of these formats instead: {format}.'),
'max_value': _('Ensure this value is less than or equal to {max_value}.'),
'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
'range': _('Ensure this value is in the range between {min_value} and {max_value}.'),
}
def __init__(self, **kwargs):
self.max_value = kwargs.pop('max_value', None)
self.min_value = kwargs.pop('min_value', None)
super().__init__(**kwargs)
if self.min_value is not None and self.max_value is not None:
message = lazy_format(
self.error_messages['range'], min_value=self.min_value, max_value=self.max_value)
else:
message = None
if self.max_value is not None:
message = lazy_format(self.error_messages['max_value'], max_value=self.max_value)
self.validators.append(
MaxValueValidator(self.max_value, message=message))
MaxValueValidator(self.max_value, message=message or lazy_format(
self.error_messages['max_value'], max_value=self.max_value)))
if self.min_value is not None:
message = lazy_format(self.error_messages['min_value'], min_value=self.min_value)
self.validators.append(
MinValueValidator(self.min_value, message=message))
MinValueValidator(self.min_value, message=message or lazy_format(
self.error_messages['min_value'], min_value=self.min_value)))
def to_internal_value(self, value):
if isinstance(value, datetime.timedelta):
@ -1603,7 +1638,8 @@ class ListField(Field):
'not_a_list': _('Expected a list of items but got type "{input_type}".'),
'empty': _('This list may not be empty.'),
'min_length': _('Ensure this field has at least {min_length} elements.'),
'max_length': _('Ensure this field has no more than {max_length} elements.')
'max_length': _('Ensure this field has no more than {max_length} elements.'),
'range': _('Ensure this field has at least {min_length} and no more than {max_length} elements.'),
}
def __init__(self, *args, **kwargs):
@ -1620,12 +1656,22 @@ class ListField(Field):
super().__init__(*args, **kwargs)
self.child.bind(field_name='', parent=self)
if self.min_length is not None and self.max_length is not None:
message = lazy_format(
self.error_messages['range'], min_length=self.min_length, max_length=self.max_length)
else:
message = None
if self.max_length is not None:
message = lazy_format(self.error_messages['max_length'], max_length=self.max_length)
self.validators.append(MaxLengthValidator(self.max_length, message=message))
self.validators.append(
MaxLengthValidator(self.max_length, message=message or lazy_format(
self.error_messages['max_length'], max_length=self.max_length)))
if self.min_length is not None:
message = lazy_format(self.error_messages['min_length'], min_length=self.min_length)
self.validators.append(MinLengthValidator(self.min_length, message=message))
self.validators.append(
MinLengthValidator(self.min_length, message=message or lazy_format(
self.error_messages['min_length'], min_length=self.min_length)))
def get_value(self, dictionary):
if self.field_name not in dictionary:

View File

@ -1019,15 +1019,51 @@ class TestMinMaxIntegerField(FieldValues):
3: 3,
}
invalid_inputs = {
0: ['Ensure this value is greater than or equal to 1.'],
4: ['Ensure this value is less than or equal to 3.'],
'0': ['Ensure this value is greater than or equal to 1.'],
'4': ['Ensure this value is less than or equal to 3.'],
0: ['Ensure this value is in the range between 1 and 3.'],
4: ['Ensure this value is in the range between 1 and 3.'],
'0': ['Ensure this value is in the range between 1 and 3.'],
'4': ['Ensure this value is in the range between 1 and 3.'],
}
outputs = {}
field = serializers.IntegerField(min_value=1, max_value=3)
class TestMinIntegerField(FieldValues):
"""
Valid and invalid values for `IntegerField` with min limit.
"""
valid_inputs = {
'1': 1,
'3': 3,
1: 1,
3: 3,
}
invalid_inputs = {
0: ['Ensure this value is greater than or equal to 1.'],
'0': ['Ensure this value is greater than or equal to 1.'],
}
outputs = {}
field = serializers.IntegerField(min_value=1)
class TestMaxIntegerField(FieldValues):
"""
Valid and invalid values for `IntegerField` with max limit.
"""
valid_inputs = {
'1': 1,
'3': 3,
1: 1,
3: 3,
}
invalid_inputs = {
4: ['Ensure this value is less than or equal to 3.'],
'4': ['Ensure this value is less than or equal to 3.'],
}
outputs = {}
field = serializers.IntegerField(max_value=3)
class TestFloatField(FieldValues):
"""
Valid and invalid values for `FloatField`.
@ -1067,15 +1103,55 @@ class TestMinMaxFloatField(FieldValues):
3.0: 3.0,
}
invalid_inputs = {
0.9: ['Ensure this value is greater than or equal to 1.'],
3.1: ['Ensure this value is less than or equal to 3.'],
'0.0': ['Ensure this value is greater than or equal to 1.'],
'3.1': ['Ensure this value is less than or equal to 3.'],
0.9: ['Ensure this value is in the range between 1 and 3.'],
3.1: ['Ensure this value is in the range between 1 and 3.'],
'0.0': ['Ensure this value is in the range between 1 and 3.'],
'3.1': ['Ensure this value is in the range between 1 and 3.'],
}
outputs = {}
field = serializers.FloatField(min_value=1, max_value=3)
class TestMinFloatField(FieldValues):
"""
Valid and invalid values for `FloatField` with min limit.
"""
valid_inputs = {
'1': 1,
'3': 3,
1: 1,
3: 3,
1.0: 1.0,
3.0: 3.0,
}
invalid_inputs = {
0.9: ['Ensure this value is greater than or equal to 1.'],
'0.0': ['Ensure this value is greater than or equal to 1.'],
}
outputs = {}
field = serializers.FloatField(min_value=1)
class TestMaxFloatField(FieldValues):
"""
Valid and invalid values for `FloatField` with max limit.
"""
valid_inputs = {
'1': 1,
'3': 3,
1: 1,
3: 3,
1.0: 1.0,
3.0: 3.0,
}
invalid_inputs = {
3.1: ['Ensure this value is less than or equal to 3.'],
'3.1': ['Ensure this value is less than or equal to 3.'],
}
outputs = {}
field = serializers.FloatField(max_value=3)
class TestDecimalField(FieldValues):
"""
Valid and invalid values for `DecimalField`.
@ -1124,8 +1200,8 @@ class TestMinMaxDecimalField(FieldValues):
'20.0': Decimal('20.0'),
}
invalid_inputs = {
'9.9': ['Ensure this value is greater than or equal to 10.'],
'20.1': ['Ensure this value is less than or equal to 20.'],
'9.9': ['Ensure this value is in the range between 10 and 20.'],
'20.1': ['Ensure this value is in the range between 10 and 20.'],
}
outputs = {}
field = serializers.DecimalField(
@ -1134,6 +1210,42 @@ class TestMinMaxDecimalField(FieldValues):
)
class TestMinDecimalField(FieldValues):
"""
Valid and invalid values for `DecimalField` with min limit.
"""
valid_inputs = {
'10.0': Decimal('10.0'),
'20.0': Decimal('20.0'),
}
invalid_inputs = {
'9.9': ['Ensure this value is greater than or equal to 10.'],
}
outputs = {}
field = serializers.DecimalField(
max_digits=3, decimal_places=1,
min_value=10
)
class TestMaxDecimalField(FieldValues):
"""
Valid and invalid values for `DecimalField` with max limit.
"""
valid_inputs = {
'10.0': Decimal('10.0'),
'20.0': Decimal('20.0'),
}
invalid_inputs = {
'20.1': ['Ensure this value is less than or equal to 20.'],
}
outputs = {}
field = serializers.DecimalField(
max_digits=3, decimal_places=1,
max_value=20
)
class TestNoMaxDigitsDecimalField(FieldValues):
field = serializers.DecimalField(
max_value=100, min_value=0,
@ -1536,14 +1648,45 @@ class TestMinMaxDurationField(FieldValues):
86401: datetime.timedelta(days=1, seconds=1),
}
invalid_inputs = {
3600: ['Ensure this value is greater than or equal to 1 day, 0:00:00.'],
'4 08:32:01.000123': ['Ensure this value is less than or equal to 4 days, 0:00:00.'],
'3600': ['Ensure this value is greater than or equal to 1 day, 0:00:00.'],
3600: ['Ensure this value is in the range between 1 day, 0:00:00 and 4 days, 0:00:00.'],
'4 08:32:01.000123': ['Ensure this value is in the range between 1 day, 0:00:00 and 4 days, 0:00:00.'],
'3600': ['Ensure this value is in the range between 1 day, 0:00:00 and 4 days, 0:00:00.'],
}
outputs = {}
field = serializers.DurationField(min_value=datetime.timedelta(days=1), max_value=datetime.timedelta(days=4))
class TestMinDurationField(FieldValues):
"""
Valid and invalid values for `DurationField` with min limit.
"""
valid_inputs = {
'3 08:32:01.000123': datetime.timedelta(days=3, hours=8, minutes=32, seconds=1, microseconds=123),
86401: datetime.timedelta(days=1, seconds=1),
}
invalid_inputs = {
3600: ['Ensure this value is greater than or equal to 1 day, 0:00:00.'],
'3600': ['Ensure this value is greater than or equal to 1 day, 0:00:00.'],
}
outputs = {}
field = serializers.DurationField(min_value=datetime.timedelta(days=1))
class TestMaxDurationField(FieldValues):
"""
Valid and invalid values for `DurationField` with max limit.
"""
valid_inputs = {
'3 08:32:01.000123': datetime.timedelta(days=3, hours=8, minutes=32, seconds=1, microseconds=123),
86401: datetime.timedelta(days=1, seconds=1),
}
invalid_inputs = {
'4 08:32:01.000123': ['Ensure this value is less than or equal to 4 days, 0:00:00.'],
}
outputs = {}
field = serializers.DurationField(max_value=datetime.timedelta(days=4))
class TestDurationField(FieldValues):
"""
Valid and invalid values for `DurationField`.
@ -1989,16 +2132,43 @@ class TestEmptyListField(FieldValues):
field = serializers.ListField(child=serializers.IntegerField(), allow_empty=False)
class TestListFieldLengthLimit(FieldValues):
class TestListFieldMinMaxLengthLimit(FieldValues):
"""
Valid and invalid values for `ListField` with min and max limits.
"""
valid_inputs = ()
invalid_inputs = [
((0, 1), ['Ensure this field has at least 3 elements.']),
((0, 1, 2, 3, 4, 5), ['Ensure this field has no more than 4 elements.']),
((0, 1), ['Ensure this field has at least 3 and no more than 4 elements.']),
((0, 1, 2, 3, 4, 5), ['Ensure this field has at least 3 and no more than 4 elements.']),
]
outputs = ()
field = serializers.ListField(child=serializers.IntegerField(), min_length=3, max_length=4)
class TestListFieldMinLengthLimit(FieldValues):
"""
Valid and invalid values for `ListField` with min limit.
"""
valid_inputs = ()
invalid_inputs = [
((0, 1), ['Ensure this field has at least 3 elements.']),
]
outputs = ()
field = serializers.ListField(child=serializers.IntegerField(), min_length=3)
class TestListFieldMaxLengthLimit(FieldValues):
"""
Valid and invalid values for `ListField` with max limit.
"""
valid_inputs = ()
invalid_inputs = [
((0, 1, 2, 3, 4, 5), ['Ensure this field has no more than 4 elements.']),
]
outputs = ()
field = serializers.ListField(child=serializers.IntegerField(), max_length=4)
class TestUnvalidatedListField(FieldValues):
"""
Values for `ListField` with no `child` argument.