Skip to content

Fixed issue #5926: Ensure that html forms (multipart form data) respe… #5927

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions rest_framework/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -1614,15 +1614,16 @@ 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):
"""
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:
Expand Down
4 changes: 2 additions & 2 deletions rest_framework/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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(
Expand Down
8 changes: 6 additions & 2 deletions rest_framework/utils/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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))
Expand All @@ -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=''):
Expand Down
49 changes: 49 additions & 0 deletions tests/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
30 changes: 30 additions & 0 deletions tests/test_serializer_lists.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.http import QueryDict
from django.utils.datastructures import MultiValueDict

from rest_framework import serializers
Expand Down Expand Up @@ -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 == []
39 changes: 39 additions & 0 deletions tests/test_serializer_nested.py
Original file line number Diff line number Diff line change
Expand Up @@ -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