diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 58e28ed4ce..39050ff878 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1614,7 +1614,8 @@ def get_value(self, dictionary): if len(val) > 0: # Support QueryDict lists in HTML input. return val - return html.parse_html_list(dictionary, prefix=self.field_name) + return html.parse_html_list(dictionary, prefix=self.field_name, default=empty) + return dictionary.get(self.field_name, empty) def to_internal_value(self, data): @@ -1622,7 +1623,7 @@ def to_internal_value(self, data): List of dicts of native values <- List of dicts of primitive datatypes. """ if html.is_html_input(data): - data = html.parse_html_list(data) + data = html.parse_html_list(data, default=[]) if isinstance(data, type('')) or isinstance(data, collections.Mapping) or not hasattr(data, '__iter__'): self.fail('not_a_list', input_type=type(data).__name__) if not self.allow_empty and len(data) == 0: diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 7e84372def..43c7972a4c 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -607,7 +607,7 @@ def get_value(self, dictionary): # We override the default field access in order to support # lists in HTML forms. if html.is_html_input(dictionary): - return html.parse_html_list(dictionary, prefix=self.field_name) + return html.parse_html_list(dictionary, prefix=self.field_name, default=empty) return dictionary.get(self.field_name, empty) def run_validation(self, data=empty): @@ -635,7 +635,7 @@ def to_internal_value(self, data): List of dicts of native values <- List of dicts of primitive datatypes. """ if html.is_html_input(data): - data = html.parse_html_list(data) + data = html.parse_html_list(data, default=[]) if not isinstance(data, list): message = self.error_messages['not_a_list'].format( diff --git a/rest_framework/utils/html.py b/rest_framework/utils/html.py index 77167e4708..c7ede78035 100644 --- a/rest_framework/utils/html.py +++ b/rest_framework/utils/html.py @@ -12,7 +12,7 @@ def is_html_input(dictionary): return hasattr(dictionary, 'getlist') -def parse_html_list(dictionary, prefix=''): +def parse_html_list(dictionary, prefix='', default=None): """ Used to support list values in HTML forms. Supports lists of primitives and/or dictionaries. @@ -44,6 +44,8 @@ def parse_html_list(dictionary, prefix=''): {'foo': 'abc', 'bar': 'def'}, {'foo': 'hij', 'bar': 'klm'} ] + + :returns a list of objects, or the value specified in ``default`` if the list is empty """ ret = {} regex = re.compile(r'^%s\[([0-9]+)\](.*)$' % re.escape(prefix)) @@ -59,7 +61,9 @@ def parse_html_list(dictionary, prefix=''): ret[index][key] = value else: ret[index] = MultiValueDict({key: [value]}) - return [ret[item] for item in sorted(ret)] + + # return the items of the ``ret`` dict, sorted by key, or ``default`` if the dict is empty + return [ret[item] for item in sorted(ret)] if ret else default def parse_html_dict(dictionary, prefix=''): diff --git a/tests/test_fields.py b/tests/test_fields.py index eee794eaab..ca4e4ecc52 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -466,6 +466,55 @@ class TestSerializer(serializers.Serializer): assert serializer.is_valid() assert serializer.validated_data == {'scores': [1]} + def test_querydict_list_input_no_values_uses_default(self): + """ + When there are no values passed in, and default is set + The field should return the default value + """ + class TestSerializer(serializers.Serializer): + a = serializers.IntegerField(required=True) + scores = serializers.ListField(default=lambda: [1, 3]) + + serializer = TestSerializer(data=QueryDict('a=1&')) + assert serializer.is_valid() + assert serializer.validated_data == {'a': 1, 'scores': [1, 3]} + + def test_querydict_list_input_supports_indexed_keys(self): + """ + When data is passed in the format `scores[0]=1&scores[1]=3` + The field should return the correct list, ignoring the default + """ + class TestSerializer(serializers.Serializer): + scores = serializers.ListField(default=lambda: [1, 3]) + + serializer = TestSerializer(data=QueryDict("scores[0]=5&scores[1]=6")) + assert serializer.is_valid() + assert serializer.validated_data == {'scores': ['5', '6']} + + def test_querydict_list_input_no_values_no_default_and_not_required(self): + """ + When there are no keys passed, there is no default, and required=False + The field should be skipped + """ + class TestSerializer(serializers.Serializer): + scores = serializers.ListField(required=False) + + serializer = TestSerializer(data=QueryDict('')) + assert serializer.is_valid() + assert serializer.validated_data == {} + + def test_querydict_list_input_posts_key_but_no_values(self): + """ + When there are no keys passed, there is no default, and required=False + The field should return an array of 1 item, blank + """ + class TestSerializer(serializers.Serializer): + scores = serializers.ListField(required=False) + + serializer = TestSerializer(data=QueryDict('scores=&')) + assert serializer.is_valid() + assert serializer.validated_data == {'scores': ['']} + class TestCreateOnlyDefault: def setup(self): diff --git a/tests/test_serializer_lists.py b/tests/test_serializer_lists.py index 34c3d100b4..12ed78b84a 100644 --- a/tests/test_serializer_lists.py +++ b/tests/test_serializer_lists.py @@ -1,3 +1,4 @@ +from django.http import QueryDict from django.utils.datastructures import MultiValueDict from rest_framework import serializers @@ -532,3 +533,32 @@ class Serializer(serializers.Serializer): assert value == updated_data_list[index][key] assert serializer.errors == {} + + +class TestEmptyListSerializer: + """ + Tests the behaviour of ListSerializers when there is no data passed to it + """ + + def setup(self): + class ExampleListSerializer(serializers.ListSerializer): + child = serializers.IntegerField() + + self.Serializer = ExampleListSerializer + + def test_nested_serializer_with_list_json(self): + # pass an empty array to the serializer + input_data = [] + + serializer = self.Serializer(data=input_data) + + assert serializer.is_valid() + assert serializer.validated_data == [] + + def test_nested_serializer_with_list_multipart(self): + # pass an "empty" QueryDict to the serializer (should be the same as an empty array) + input_data = QueryDict('') + serializer = self.Serializer(data=input_data) + + assert serializer.is_valid() + assert serializer.validated_data == [] diff --git a/tests/test_serializer_nested.py b/tests/test_serializer_nested.py index 09b8dd1052..1cd0caf854 100644 --- a/tests/test_serializer_nested.py +++ b/tests/test_serializer_nested.py @@ -202,3 +202,42 @@ def test_nested_serializer_with_list_multipart(self): assert serializer.is_valid() assert serializer.validated_data['nested']['example'] == {1, 2} + + +class TestNotRequiredNestedSerializerWithMany: + def setup(self): + class NestedSerializer(serializers.Serializer): + one = serializers.IntegerField(max_value=10) + + class TestSerializer(serializers.Serializer): + nested = NestedSerializer(required=False, many=True) + + self.Serializer = TestSerializer + + def test_json_validate(self): + input_data = {} + serializer = self.Serializer(data=input_data) + + # request is empty, therefor 'nested' should not be in serializer.data + assert serializer.is_valid() + assert 'nested' not in serializer.validated_data + + input_data = {'nested': [{'one': '1'}, {'one': 2}]} + serializer = self.Serializer(data=input_data) + assert serializer.is_valid() + assert 'nested' in serializer.validated_data + + def test_multipart_validate(self): + # leave querydict empty + input_data = QueryDict('') + serializer = self.Serializer(data=input_data) + + # the querydict is empty, therefor 'nested' should not be in serializer.data + assert serializer.is_valid() + assert 'nested' not in serializer.validated_data + + input_data = QueryDict('nested[0]one=1&nested[1]one=2') + + serializer = self.Serializer(data=input_data) + assert serializer.is_valid() + assert 'nested' in serializer.validated_data 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