mirror of
https://github.com/encode/django-rest-framework.git
synced 2026-02-18 05:00:33 +03:00
Fix DictField returning empty dict instead of empty for missing HTML input (#6234)
When using DictField with HTML form (multipart/form-data) input, parse_html_dict always returned an empty MultiValueDict when no matching keys were found. This made it impossible to distinguish between an unspecified field and an empty input, causing issues with required/default field handling. This aligns parse_html_dict with parse_html_list by adding a default parameter that is returned when no matching keys are found. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1b63dce808
commit
34065b70e7
|
|
@ -1749,7 +1749,7 @@ class DictField(Field):
|
|||
# We override the default field access in order to support
|
||||
# dictionaries in HTML forms.
|
||||
if html.is_html_input(dictionary):
|
||||
return html.parse_html_dict(dictionary, prefix=self.field_name)
|
||||
return html.parse_html_dict(dictionary, prefix=self.field_name, default=empty)
|
||||
return dictionary.get(self.field_name, empty)
|
||||
|
||||
def to_internal_value(self, data):
|
||||
|
|
@ -1757,7 +1757,7 @@ class DictField(Field):
|
|||
Dicts of native values <- Dicts of primitive datatypes.
|
||||
"""
|
||||
if html.is_html_input(data):
|
||||
data = html.parse_html_dict(data)
|
||||
data = html.parse_html_dict(data, default=data)
|
||||
if not isinstance(data, dict):
|
||||
self.fail('not_a_dict', input_type=type(data).__name__)
|
||||
if not self.allow_empty and len(data) == 0:
|
||||
|
|
|
|||
|
|
@ -428,7 +428,7 @@ class Serializer(BaseSerializer, metaclass=SerializerMetaclass):
|
|||
# We override the default field access in order to support
|
||||
# nested HTML forms.
|
||||
if html.is_html_input(dictionary):
|
||||
return html.parse_html_dict(dictionary, prefix=self.field_name) or empty
|
||||
return html.parse_html_dict(dictionary, prefix=self.field_name, default=empty)
|
||||
return dictionary.get(self.field_name, empty)
|
||||
|
||||
def run_validation(self, data=empty):
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ def parse_html_list(dictionary, prefix='', default=None):
|
|||
return [ret[item] for item in sorted(ret)] if ret else default
|
||||
|
||||
|
||||
def parse_html_dict(dictionary, prefix=''):
|
||||
def parse_html_dict(dictionary, prefix='', default=None):
|
||||
"""
|
||||
Used to support dictionary values in HTML forms.
|
||||
|
||||
|
|
@ -81,6 +81,9 @@ def parse_html_dict(dictionary, prefix=''):
|
|||
'email': 'example@example.com'
|
||||
}
|
||||
}
|
||||
|
||||
:returns a MultiValueDict of the parsed data, or the value specified in
|
||||
``default`` if the dict field was not present in the input
|
||||
"""
|
||||
ret = MultiValueDict()
|
||||
regex = re.compile(r'^%s\.(.+)$' % re.escape(prefix))
|
||||
|
|
@ -92,4 +95,4 @@ def parse_html_dict(dictionary, prefix=''):
|
|||
value = dictionary.getlist(field)
|
||||
ret.setlist(key, value)
|
||||
|
||||
return ret
|
||||
return ret if ret else default
|
||||
|
|
|
|||
|
|
@ -2496,6 +2496,56 @@ class TestDictField(FieldValues):
|
|||
|
||||
assert exc_info.value.detail == ['This dictionary may not be empty.']
|
||||
|
||||
def test_querydict_dict_input(self):
|
||||
"""
|
||||
DictField should correctly parse HTML form (QueryDict) input
|
||||
with dot-separated keys.
|
||||
"""
|
||||
class TestSerializer(serializers.Serializer):
|
||||
data = serializers.DictField(child=serializers.CharField())
|
||||
|
||||
serializer = TestSerializer(data=QueryDict('data.a=1&data.b=2'))
|
||||
assert serializer.is_valid()
|
||||
assert serializer.validated_data == {'data': {'a': '1', 'b': '2'}}
|
||||
|
||||
def test_querydict_dict_input_no_values_uses_default(self):
|
||||
"""
|
||||
When no matching keys are present in the QueryDict and a default
|
||||
is set, the field should return the default value.
|
||||
"""
|
||||
class TestSerializer(serializers.Serializer):
|
||||
a = serializers.IntegerField(required=True)
|
||||
data = serializers.DictField(default=lambda: {'x': 'y'})
|
||||
|
||||
serializer = TestSerializer(data=QueryDict('a=1'))
|
||||
assert serializer.is_valid()
|
||||
assert serializer.validated_data == {'a': 1, 'data': {'x': 'y'}}
|
||||
|
||||
def test_querydict_dict_input_no_values_no_default_and_not_required(self):
|
||||
"""
|
||||
When no matching keys are present in the QueryDict, there is no
|
||||
default, and the field is not required, the field should be
|
||||
skipped entirely from validated_data.
|
||||
"""
|
||||
class TestSerializer(serializers.Serializer):
|
||||
data = serializers.DictField(required=False)
|
||||
|
||||
serializer = TestSerializer(data=QueryDict(''))
|
||||
assert serializer.is_valid()
|
||||
assert serializer.validated_data == {}
|
||||
|
||||
def test_querydict_dict_input_no_values_required(self):
|
||||
"""
|
||||
When no matching keys are present in the QueryDict and the field
|
||||
is required, validation should fail.
|
||||
"""
|
||||
class TestSerializer(serializers.Serializer):
|
||||
data = serializers.DictField(required=True)
|
||||
|
||||
serializer = TestSerializer(data=QueryDict(''))
|
||||
assert not serializer.is_valid()
|
||||
assert 'data' in serializer.errors
|
||||
|
||||
|
||||
class TestNestedDictField(FieldValues):
|
||||
"""
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user