Skip to content

Commit 713333d

Browse files
committed
Merge branch 'master' of github.com:tomchristie/django-rest-framework
2 parents 806b0b3 + 5c264c0 commit 713333d

File tree

4 files changed

+69
-6
lines changed

4 files changed

+69
-6
lines changed

rest_framework/fields.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1141,10 +1141,15 @@ def to_representation(self, value):
11411141
class MultipleChoiceField(ChoiceField):
11421142
default_error_messages = {
11431143
'invalid_choice': _('"{input}" is not a valid choice.'),
1144-
'not_a_list': _('Expected a list of items but got type "{input_type}".')
1144+
'not_a_list': _('Expected a list of items but got type "{input_type}".'),
1145+
'empty': _('This selection may not be empty.')
11451146
}
11461147
default_empty_html = []
11471148

1149+
def __init__(self, *args, **kwargs):
1150+
self.allow_empty = kwargs.pop('allow_empty', True)
1151+
super(MultipleChoiceField, self).__init__(*args, **kwargs)
1152+
11481153
def get_value(self, dictionary):
11491154
# We override the default field access in order to support
11501155
# lists in HTML forms.
@@ -1159,6 +1164,8 @@ def get_value(self, dictionary):
11591164
def to_internal_value(self, data):
11601165
if isinstance(data, type('')) or not hasattr(data, '__iter__'):
11611166
self.fail('not_a_list', input_type=type(data).__name__)
1167+
if not self.allow_empty and len(data) == 0:
1168+
self.fail('empty')
11621169

11631170
return set([
11641171
super(MultipleChoiceField, self).to_internal_value(item)
@@ -1263,11 +1270,13 @@ class ListField(Field):
12631270
child = _UnvalidatedField()
12641271
initial = []
12651272
default_error_messages = {
1266-
'not_a_list': _('Expected a list of items but got type "{input_type}".')
1273+
'not_a_list': _('Expected a list of items but got type "{input_type}".'),
1274+
'empty': _('This list may not be empty.')
12671275
}
12681276

12691277
def __init__(self, *args, **kwargs):
12701278
self.child = kwargs.pop('child', copy.deepcopy(self.child))
1279+
self.allow_empty = kwargs.pop('allow_empty', True)
12711280
assert not inspect.isclass(self.child), '`child` has not been instantiated.'
12721281
super(ListField, self).__init__(*args, **kwargs)
12731282
self.child.bind(field_name='', parent=self)
@@ -1287,6 +1296,8 @@ def to_internal_value(self, data):
12871296
data = html.parse_html_list(data)
12881297
if isinstance(data, type('')) or not hasattr(data, '__iter__'):
12891298
self.fail('not_a_list', input_type=type(data).__name__)
1299+
if not self.allow_empty and len(data) == 0:
1300+
self.fail('empty')
12901301
return [self.child.run_validation(item) for item in data]
12911302

12921303
def to_representation(self, data):

rest_framework/relations.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def __init__(self, pk):
3232
# rather than the parent serializer.
3333
MANY_RELATION_KWARGS = (
3434
'read_only', 'write_only', 'required', 'default', 'initial', 'source',
35-
'label', 'help_text', 'style', 'error_messages'
35+
'label', 'help_text', 'style', 'error_messages', 'allow_empty'
3636
)
3737

3838

@@ -366,9 +366,14 @@ class ManyRelatedField(Field):
366366
"""
367367
initial = []
368368
default_empty_html = []
369+
default_error_messages = {
370+
'not_a_list': _('Expected a list of items but got type "{input_type}".'),
371+
'empty': _('This list may not be empty.')
372+
}
369373

370374
def __init__(self, child_relation=None, *args, **kwargs):
371375
self.child_relation = child_relation
376+
self.allow_empty = kwargs.pop('allow_empty', True)
372377
assert child_relation is not None, '`child_relation` is a required argument.'
373378
super(ManyRelatedField, self).__init__(*args, **kwargs)
374379
self.child_relation.bind(field_name='', parent=self)
@@ -386,6 +391,11 @@ def get_value(self, dictionary):
386391
return dictionary.get(self.field_name, empty)
387392

388393
def to_internal_value(self, data):
394+
if isinstance(data, type('')) or not hasattr(data, '__iter__'):
395+
self.fail('not_a_list', input_type=type(data).__name__)
396+
if not self.allow_empty and len(data) == 0:
397+
self.fail('empty')
398+
389399
return [
390400
self.child_relation.to_internal_value(item)
391401
for item in data

rest_framework/serializers.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
# rather than the parent serializer.
5050
LIST_SERIALIZER_KWARGS = (
5151
'read_only', 'write_only', 'required', 'default', 'initial', 'source',
52-
'label', 'help_text', 'style', 'error_messages',
52+
'label', 'help_text', 'style', 'error_messages', 'allow_empty',
5353
'instance', 'data', 'partial', 'context'
5454
)
5555

@@ -493,11 +493,13 @@ class ListSerializer(BaseSerializer):
493493
many = True
494494

495495
default_error_messages = {
496-
'not_a_list': _('Expected a list of items but got type "{input_type}".')
496+
'not_a_list': _('Expected a list of items but got type "{input_type}".'),
497+
'empty': _('This list may not be empty.')
497498
}
498499

499500
def __init__(self, *args, **kwargs):
500501
self.child = kwargs.pop('child', copy.deepcopy(self.child))
502+
self.allow_empty = kwargs.pop('allow_empty', True)
501503
assert self.child is not None, '`child` is a required argument.'
502504
assert not inspect.isclass(self.child), '`child` has not been instantiated.'
503505
super(ListSerializer, self).__init__(*args, **kwargs)
@@ -553,6 +555,12 @@ def to_internal_value(self, data):
553555
api_settings.NON_FIELD_ERRORS_KEY: [message]
554556
})
555557

558+
if not self.allow_empty and len(data) == 0:
559+
message = self.error_messages['empty']
560+
raise ValidationError({
561+
api_settings.NON_FIELD_ERRORS_KEY: [message]
562+
})
563+
556564
ret = []
557565
errors = []
558566

tests/test_fields.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,27 @@ def test_against_partial_and_full_updates(self):
11401140
assert field.get_value(QueryDict({})) == rest_framework.fields.empty
11411141

11421142

1143+
class TestEmptyMultipleChoiceField(FieldValues):
1144+
"""
1145+
Invalid values for `MultipleChoiceField(allow_empty=False)`.
1146+
"""
1147+
valid_inputs = {
1148+
}
1149+
invalid_inputs = (
1150+
([], ['This selection may not be empty.']),
1151+
)
1152+
outputs = [
1153+
]
1154+
field = serializers.MultipleChoiceField(
1155+
choices=[
1156+
('consistency', 'Consistency'),
1157+
('availability', 'Availability'),
1158+
('partition', 'Partition tolerance'),
1159+
],
1160+
allow_empty=False
1161+
)
1162+
1163+
11431164
# File serializers...
11441165

11451166
class MockFile:
@@ -1233,7 +1254,8 @@ class TestListField(FieldValues):
12331254
"""
12341255
valid_inputs = [
12351256
([1, 2, 3], [1, 2, 3]),
1236-
(['1', '2', '3'], [1, 2, 3])
1257+
(['1', '2', '3'], [1, 2, 3]),
1258+
([], [])
12371259
]
12381260
invalid_inputs = [
12391261
('not a list', ['Expected a list of items but got type "str".']),
@@ -1246,6 +1268,18 @@ class TestListField(FieldValues):
12461268
field = serializers.ListField(child=serializers.IntegerField())
12471269

12481270

1271+
class TestEmptyListField(FieldValues):
1272+
"""
1273+
Values for `ListField` with allow_empty=False flag.
1274+
"""
1275+
valid_inputs = {}
1276+
invalid_inputs = [
1277+
([], ['This list may not be empty.'])
1278+
]
1279+
outputs = {}
1280+
field = serializers.ListField(child=serializers.IntegerField(), allow_empty=False)
1281+
1282+
12491283
class TestUnvalidatedListField(FieldValues):
12501284
"""
12511285
Values for `ListField` with no `child` argument.

0 commit comments

Comments
 (0)