From 315dedeebf4b3a2bf387123c7fdbd199102dc4ee Mon Sep 17 00:00:00 2001 From: Gaetano Guerriero Date: Thu, 11 Apr 2019 23:33:39 +0200 Subject: [PATCH] dict field: support allow_empty option --- docs/api-guide/fields.md | 3 ++- rest_framework/fields.py | 7 ++++++- tests/test_fields.py | 11 +++++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index d371bb8fd..30484195e 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -471,9 +471,10 @@ We can now reuse our custom `StringListField` class throughout our application, A field class that validates a dictionary of objects. The keys in `DictField` are always assumed to be string values. -**Signature**: `DictField(child=)` +**Signature**: `DictField(child=, allow_empty=True)` - `child` - A field instance that should be used for validating the values in the dictionary. If this argument is not provided then values in the mapping will not be validated. +- `allow_empty` - Designates if empty dictionaries are allowed. For example, to create a field that validates a mapping of strings to strings, you would write something like this: diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 5e3132074..e2fb8cdc6 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1663,11 +1663,13 @@ class DictField(Field): child = _UnvalidatedField() initial = {} default_error_messages = { - 'not_a_dict': _('Expected a dictionary of items but got type "{input_type}".') + 'not_a_dict': _('Expected a dictionary of items but got type "{input_type}".'), + 'empty': _('This dictionary may not be empty.'), } def __init__(self, *args, **kwargs): self.child = kwargs.pop('child', copy.deepcopy(self.child)) + self.allow_empty = kwargs.pop('allow_empty', True) assert not inspect.isclass(self.child), '`child` has not been instantiated.' assert self.child.source is None, ( @@ -1693,6 +1695,9 @@ class DictField(Field): data = html.parse_html_dict(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: + self.fail('empty') + return self.run_child_validation(data) def to_representation(self, value): diff --git a/tests/test_fields.py b/tests/test_fields.py index 41d08bd5e..e7f16c178 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1982,6 +1982,7 @@ class TestDictField(FieldValues): """ valid_inputs = [ ({'a': 1, 'b': '2', 3: 3}, {'a': '1', 'b': '2', '3': '3'}), + ({}, {}), ] invalid_inputs = [ ({'a': 1, 'b': None, 'c': None}, {'b': ['This field may not be null.'], 'c': ['This field may not be null.']}), @@ -2009,6 +2010,16 @@ class TestDictField(FieldValues): output = field.run_validation(None) assert output is None + def test_allow_empty_disallowed(self): + """ + If allow_empty is False then an empty dict is not a valid input. + """ + field = serializers.DictField(allow_empty=False) + with pytest.raises(serializers.ValidationError) as exc_info: + field.run_validation({}) + + assert exc_info.value.detail == ['This dictionary may not be empty.'] + class TestNestedDictField(FieldValues): """