Skip to content

Commit

Permalink
Add max_length and min_length options to ListSerializer (#8165)
Browse files Browse the repository at this point in the history
  • Loading branch information
TheBlusky authored Sep 14, 2021
1 parent 761f56e commit f0a5b95
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 2 deletions.
8 changes: 8 additions & 0 deletions docs/api-guide/serializers.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

### `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

There *are* a few use cases when you might want to customize the `ListSerializer` behavior. For example:
Expand Down
27 changes: 25 additions & 2 deletions rest_framework/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@
LIST_SERIALIZER_KWARGS = (
'read_only', 'write_only', 'required', 'default', 'initial', 'source',
'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__'
Expand Down Expand Up @@ -143,12 +144,18 @@ def many_init(cls, *args, **kwargs):
return CustomListSerializer(*args, **kwargs)
"""
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)
list_kwargs = {
'child': child_serializer,
}
if allow_empty is not None:
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({
key: value for key, value in kwargs.items()
if key in LIST_SERIALIZER_KWARGS
Expand Down Expand Up @@ -568,12 +575,16 @@ class ListSerializer(BaseSerializer):

default_error_messages = {
'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):
self.child = kwargs.pop('child', copy.deepcopy(self.child))
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 not inspect.isclass(self.child), '`child` has not been instantiated.'
super().__init__(*args, **kwargs)
Expand Down Expand Up @@ -635,6 +646,18 @@ def to_internal_value(self, data):
api_settings.NON_FIELD_ERRORS_KEY: [message]
}, 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 = []
errors = []

Expand Down
67 changes: 67 additions & 0 deletions tests/test_serializer_lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -616,3 +616,70 @@ def test_nested_serializer_with_list_multipart(self):

assert serializer.is_valid()
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()

0 comments on commit f0a5b95

Please sign in to comment.