Fixed issue #5926: Ensure that html forms (multipart form data) respect optional fields

This commit is contained in:
Christian Kreuzberger 2018-04-09 11:20:15 +02:00
parent 0178d3063d
commit 950e0567cd
4 changed files with 47 additions and 5 deletions

View File

@ -1614,7 +1614,8 @@ class ListField(Field):
if len(val) > 0: if len(val) > 0:
# Support QueryDict lists in HTML input. # Support QueryDict lists in HTML input.
return val 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) return dictionary.get(self.field_name, empty)
def to_internal_value(self, data): def to_internal_value(self, data):

View File

@ -607,7 +607,7 @@ class ListSerializer(BaseSerializer):
# We override the default field access in order to support # We override the default field access in order to support
# lists in HTML forms. # lists in HTML forms.
if html.is_html_input(dictionary): 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) return dictionary.get(self.field_name, empty)
def run_validation(self, data=empty): def run_validation(self, data=empty):
@ -635,7 +635,7 @@ class ListSerializer(BaseSerializer):
List of dicts of native values <- List of dicts of primitive datatypes. List of dicts of native values <- List of dicts of primitive datatypes.
""" """
if html.is_html_input(data): if html.is_html_input(data):
data = html.parse_html_list(data) data = html.parse_html_list(data, default=empty)
if not isinstance(data, list): if not isinstance(data, list):
message = self.error_messages['not_a_list'].format( message = self.error_messages['not_a_list'].format(

View File

@ -12,7 +12,7 @@ def is_html_input(dictionary):
return hasattr(dictionary, 'getlist') return hasattr(dictionary, 'getlist')
def parse_html_list(dictionary, prefix=''): def parse_html_list(dictionary, prefix='', default=[]):
""" """
Used to support list values in HTML forms. Used to support list values in HTML forms.
Supports lists of primitives and/or dictionaries. Supports lists of primitives and/or dictionaries.
@ -44,6 +44,8 @@ def parse_html_list(dictionary, prefix=''):
{'foo': 'abc', 'bar': 'def'}, {'foo': 'abc', 'bar': 'def'},
{'foo': 'hij', 'bar': 'klm'} {'foo': 'hij', 'bar': 'klm'}
] ]
:returns a list of objects, or the value specified in ``default`` if the list is empty
""" """
ret = {} ret = {}
regex = re.compile(r'^%s\[([0-9]+)\](.*)$' % re.escape(prefix)) regex = re.compile(r'^%s\[([0-9]+)\](.*)$' % re.escape(prefix))
@ -59,7 +61,7 @@ def parse_html_list(dictionary, prefix=''):
ret[index][key] = value ret[index][key] = value
else: else:
ret[index] = MultiValueDict({key: [value]}) ret[index] = MultiValueDict({key: [value]})
return [ret[item] for item in sorted(ret)] return [ret[item] for item in sorted(ret)] if len(ret.keys()) > 0 else default
def parse_html_dict(dictionary, prefix=''): def parse_html_dict(dictionary, prefix=''):

View File

@ -202,3 +202,42 @@ class TestNestedSerializerWithList:
assert serializer.is_valid() assert serializer.is_valid()
assert serializer.validated_data['nested']['example'] == {1, 2} 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