Skip to content

Commit f0a5b95

Browse files
authored
Add max_length and min_length options to ListSerializer (#8165)
1 parent 761f56e commit f0a5b95

File tree

3 files changed

+100
-2
lines changed

3 files changed

+100
-2
lines changed

docs/api-guide/serializers.md

+8
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,14 @@ The following argument can also be passed to a `ListSerializer` field or a seria
755755

756756
This is `True` by default, but can be set to `False` if you want to disallow empty lists as valid input.
757757

758+
### `max_length`
759+
760+
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.
761+
762+
### `min_length`
763+
764+
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.
765+
758766
### Customizing `ListSerializer` behavior
759767

760768
There *are* a few use cases when you might want to customize the `ListSerializer` behavior. For example:

rest_framework/serializers.py

+25-2
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@
7171
LIST_SERIALIZER_KWARGS = (
7272
'read_only', 'write_only', 'required', 'default', 'initial', 'source',
7373
'label', 'help_text', 'style', 'error_messages', 'allow_empty',
74-
'instance', 'data', 'partial', 'context', 'allow_null'
74+
'instance', 'data', 'partial', 'context', 'allow_null',
75+
'max_length', 'min_length'
7576
)
7677

7778
ALL_FIELDS = '__all__'
@@ -143,12 +144,18 @@ def many_init(cls, *args, **kwargs):
143144
return CustomListSerializer(*args, **kwargs)
144145
"""
145146
allow_empty = kwargs.pop('allow_empty', None)
147+
max_length = kwargs.pop('max_length', None)
148+
min_length = kwargs.pop('min_length', None)
146149
child_serializer = cls(*args, **kwargs)
147150
list_kwargs = {
148151
'child': child_serializer,
149152
}
150153
if allow_empty is not None:
151154
list_kwargs['allow_empty'] = allow_empty
155+
if max_length is not None:
156+
list_kwargs['max_length'] = max_length
157+
if min_length is not None:
158+
list_kwargs['min_length'] = min_length
152159
list_kwargs.update({
153160
key: value for key, value in kwargs.items()
154161
if key in LIST_SERIALIZER_KWARGS
@@ -568,12 +575,16 @@ class ListSerializer(BaseSerializer):
568575

569576
default_error_messages = {
570577
'not_a_list': _('Expected a list of items but got type "{input_type}".'),
571-
'empty': _('This list may not be empty.')
578+
'empty': _('This list may not be empty.'),
579+
'max_length': _('Ensure this field has no more than {max_length} elements.'),
580+
'min_length': _('Ensure this field has at least {min_length} elements.')
572581
}
573582

574583
def __init__(self, *args, **kwargs):
575584
self.child = kwargs.pop('child', copy.deepcopy(self.child))
576585
self.allow_empty = kwargs.pop('allow_empty', True)
586+
self.max_length = kwargs.pop('max_length', None)
587+
self.min_length = kwargs.pop('min_length', None)
577588
assert self.child is not None, '`child` is a required argument.'
578589
assert not inspect.isclass(self.child), '`child` has not been instantiated.'
579590
super().__init__(*args, **kwargs)
@@ -635,6 +646,18 @@ def to_internal_value(self, data):
635646
api_settings.NON_FIELD_ERRORS_KEY: [message]
636647
}, code='empty')
637648

649+
if self.max_length is not None and len(data) > self.max_length:
650+
message = self.error_messages['max_length'].format(max_length=self.max_length)
651+
raise ValidationError({
652+
api_settings.NON_FIELD_ERRORS_KEY: [message]
653+
}, code='max_length')
654+
655+
if self.min_length is not None and len(data) < self.min_length:
656+
message = self.error_messages['min_length'].format(min_length=self.min_length)
657+
raise ValidationError({
658+
api_settings.NON_FIELD_ERRORS_KEY: [message]
659+
}, code='min_length')
660+
638661
ret = []
639662
errors = []
640663

tests/test_serializer_lists.py

+67
Original file line numberDiff line numberDiff line change
@@ -616,3 +616,70 @@ def test_nested_serializer_with_list_multipart(self):
616616

617617
assert serializer.is_valid()
618618
assert serializer.validated_data == []
619+
620+
621+
class TestMaxMinLengthListSerializer:
622+
"""
623+
Tests the behaviour of ListSerializers when max_length and min_length are used
624+
"""
625+
626+
def setup(self):
627+
class IntegerSerializer(serializers.Serializer):
628+
some_int = serializers.IntegerField()
629+
630+
class MaxLengthSerializer(serializers.Serializer):
631+
many_int = IntegerSerializer(many=True, max_length=5)
632+
633+
class MinLengthSerializer(serializers.Serializer):
634+
many_int = IntegerSerializer(many=True, min_length=3)
635+
636+
class MaxMinLengthSerializer(serializers.Serializer):
637+
many_int = IntegerSerializer(many=True, min_length=3, max_length=5)
638+
639+
self.MaxLengthSerializer = MaxLengthSerializer
640+
self.MinLengthSerializer = MinLengthSerializer
641+
self.MaxMinLengthSerializer = MaxMinLengthSerializer
642+
643+
def test_min_max_length_two_items(self):
644+
input_data = {'many_int': [{'some_int': i} for i in range(2)]}
645+
646+
max_serializer = self.MaxLengthSerializer(data=input_data)
647+
min_serializer = self.MinLengthSerializer(data=input_data)
648+
max_min_serializer = self.MaxMinLengthSerializer(data=input_data)
649+
650+
assert max_serializer.is_valid()
651+
assert max_serializer.validated_data == input_data
652+
653+
assert not min_serializer.is_valid()
654+
655+
assert not max_min_serializer.is_valid()
656+
657+
def test_min_max_length_four_items(self):
658+
input_data = {'many_int': [{'some_int': i} for i in range(4)]}
659+
660+
max_serializer = self.MaxLengthSerializer(data=input_data)
661+
min_serializer = self.MinLengthSerializer(data=input_data)
662+
max_min_serializer = self.MaxMinLengthSerializer(data=input_data)
663+
664+
assert max_serializer.is_valid()
665+
assert max_serializer.validated_data == input_data
666+
667+
assert min_serializer.is_valid()
668+
assert min_serializer.validated_data == input_data
669+
670+
assert max_min_serializer.is_valid()
671+
assert min_serializer.validated_data == input_data
672+
673+
def test_min_max_length_six_items(self):
674+
input_data = {'many_int': [{'some_int': i} for i in range(6)]}
675+
676+
max_serializer = self.MaxLengthSerializer(data=input_data)
677+
min_serializer = self.MinLengthSerializer(data=input_data)
678+
max_min_serializer = self.MaxMinLengthSerializer(data=input_data)
679+
680+
assert not max_serializer.is_valid()
681+
682+
assert min_serializer.is_valid()
683+
assert min_serializer.validated_data == input_data
684+
685+
assert not max_min_serializer.is_valid()

0 commit comments

Comments
 (0)