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

Lines changed: 8 additions & 0 deletions
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

Lines changed: 25 additions & 2 deletions
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

Lines changed: 67 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy