Add max_length and min_length options to ListSerializer (#8165)

This commit is contained in:
Dan Lousqui 2021-09-14 14:45:55 +02:00 committed by GitHub
parent 761f56ef40
commit f0a5b958a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 100 additions and 2 deletions

View File

@ -755,6 +755,14 @@ The following argument can also be passed to a `ListSerializer` field or a seria
This is `True` by default, but can be set to `False` if you want to disallow empty lists as valid input. This is `True` by default, but can be set to `False` if you want to disallow empty lists as valid input.
### `max_length`
This is `None` by default, but can be set to a positive integer if you want to validates that the list contains no more than this number of elements.
### `min_length`
This is `None` by default, but can be set to a positive integer if you want to validates that the list contains no fewer than this number of elements.
### Customizing `ListSerializer` behavior ### Customizing `ListSerializer` behavior
There *are* a few use cases when you might want to customize the `ListSerializer` behavior. For example: There *are* a few use cases when you might want to customize the `ListSerializer` behavior. For example:

View File

@ -71,7 +71,8 @@ from rest_framework.relations import Hyperlink, PKOnlyObject # NOQA # isort:ski
LIST_SERIALIZER_KWARGS = ( LIST_SERIALIZER_KWARGS = (
'read_only', 'write_only', 'required', 'default', 'initial', 'source', 'read_only', 'write_only', 'required', 'default', 'initial', 'source',
'label', 'help_text', 'style', 'error_messages', 'allow_empty', 'label', 'help_text', 'style', 'error_messages', 'allow_empty',
'instance', 'data', 'partial', 'context', 'allow_null' 'instance', 'data', 'partial', 'context', 'allow_null',
'max_length', 'min_length'
) )
ALL_FIELDS = '__all__' ALL_FIELDS = '__all__'
@ -143,12 +144,18 @@ class BaseSerializer(Field):
return CustomListSerializer(*args, **kwargs) return CustomListSerializer(*args, **kwargs)
""" """
allow_empty = kwargs.pop('allow_empty', None) allow_empty = kwargs.pop('allow_empty', None)
max_length = kwargs.pop('max_length', None)
min_length = kwargs.pop('min_length', None)
child_serializer = cls(*args, **kwargs) child_serializer = cls(*args, **kwargs)
list_kwargs = { list_kwargs = {
'child': child_serializer, 'child': child_serializer,
} }
if allow_empty is not None: if allow_empty is not None:
list_kwargs['allow_empty'] = allow_empty list_kwargs['allow_empty'] = allow_empty
if max_length is not None:
list_kwargs['max_length'] = max_length
if min_length is not None:
list_kwargs['min_length'] = min_length
list_kwargs.update({ list_kwargs.update({
key: value for key, value in kwargs.items() key: value for key, value in kwargs.items()
if key in LIST_SERIALIZER_KWARGS if key in LIST_SERIALIZER_KWARGS
@ -568,12 +575,16 @@ class ListSerializer(BaseSerializer):
default_error_messages = { default_error_messages = {
'not_a_list': _('Expected a list of items but got type "{input_type}".'), 'not_a_list': _('Expected a list of items but got type "{input_type}".'),
'empty': _('This list may not be empty.') 'empty': _('This list may not be empty.'),
'max_length': _('Ensure this field has no more than {max_length} elements.'),
'min_length': _('Ensure this field has at least {min_length} elements.')
} }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.child = kwargs.pop('child', copy.deepcopy(self.child)) self.child = kwargs.pop('child', copy.deepcopy(self.child))
self.allow_empty = kwargs.pop('allow_empty', True) self.allow_empty = kwargs.pop('allow_empty', True)
self.max_length = kwargs.pop('max_length', None)
self.min_length = kwargs.pop('min_length', None)
assert self.child is not None, '`child` is a required argument.' assert self.child is not None, '`child` is a required argument.'
assert not inspect.isclass(self.child), '`child` has not been instantiated.' assert not inspect.isclass(self.child), '`child` has not been instantiated.'
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -635,6 +646,18 @@ class ListSerializer(BaseSerializer):
api_settings.NON_FIELD_ERRORS_KEY: [message] api_settings.NON_FIELD_ERRORS_KEY: [message]
}, code='empty') }, code='empty')
if self.max_length is not None and len(data) > self.max_length:
message = self.error_messages['max_length'].format(max_length=self.max_length)
raise ValidationError({
api_settings.NON_FIELD_ERRORS_KEY: [message]
}, code='max_length')
if self.min_length is not None and len(data) < self.min_length:
message = self.error_messages['min_length'].format(min_length=self.min_length)
raise ValidationError({
api_settings.NON_FIELD_ERRORS_KEY: [message]
}, code='min_length')
ret = [] ret = []
errors = [] errors = []

View File

@ -616,3 +616,70 @@ class TestEmptyListSerializer:
assert serializer.is_valid() assert serializer.is_valid()
assert serializer.validated_data == [] assert serializer.validated_data == []
class TestMaxMinLengthListSerializer:
"""
Tests the behaviour of ListSerializers when max_length and min_length are used
"""
def setup(self):
class IntegerSerializer(serializers.Serializer):
some_int = serializers.IntegerField()
class MaxLengthSerializer(serializers.Serializer):
many_int = IntegerSerializer(many=True, max_length=5)
class MinLengthSerializer(serializers.Serializer):
many_int = IntegerSerializer(many=True, min_length=3)
class MaxMinLengthSerializer(serializers.Serializer):
many_int = IntegerSerializer(many=True, min_length=3, max_length=5)
self.MaxLengthSerializer = MaxLengthSerializer
self.MinLengthSerializer = MinLengthSerializer
self.MaxMinLengthSerializer = MaxMinLengthSerializer
def test_min_max_length_two_items(self):
input_data = {'many_int': [{'some_int': i} for i in range(2)]}
max_serializer = self.MaxLengthSerializer(data=input_data)
min_serializer = self.MinLengthSerializer(data=input_data)
max_min_serializer = self.MaxMinLengthSerializer(data=input_data)
assert max_serializer.is_valid()
assert max_serializer.validated_data == input_data
assert not min_serializer.is_valid()
assert not max_min_serializer.is_valid()
def test_min_max_length_four_items(self):
input_data = {'many_int': [{'some_int': i} for i in range(4)]}
max_serializer = self.MaxLengthSerializer(data=input_data)
min_serializer = self.MinLengthSerializer(data=input_data)
max_min_serializer = self.MaxMinLengthSerializer(data=input_data)
assert max_serializer.is_valid()
assert max_serializer.validated_data == input_data
assert min_serializer.is_valid()
assert min_serializer.validated_data == input_data
assert max_min_serializer.is_valid()
assert min_serializer.validated_data == input_data
def test_min_max_length_six_items(self):
input_data = {'many_int': [{'some_int': i} for i in range(6)]}
max_serializer = self.MaxLengthSerializer(data=input_data)
min_serializer = self.MinLengthSerializer(data=input_data)
max_min_serializer = self.MaxMinLengthSerializer(data=input_data)
assert not max_serializer.is_valid()
assert min_serializer.is_valid()
assert min_serializer.validated_data == input_data
assert not max_min_serializer.is_valid()