dict field: support allow_empty option

This commit is contained in:
Gaetano Guerriero 2019-04-11 23:33:39 +02:00 committed by Gaetano Guerriero
parent 564faddb0f
commit 315dedeebf
3 changed files with 19 additions and 2 deletions

View File

@ -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. A field class that validates a dictionary of objects. The keys in `DictField` are always assumed to be string values.
**Signature**: `DictField(child=<A_FIELD_INSTANCE>)` **Signature**: `DictField(child=<A_FIELD_INSTANCE>, 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. - `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: For example, to create a field that validates a mapping of strings to strings, you would write something like this:

View File

@ -1663,11 +1663,13 @@ class DictField(Field):
child = _UnvalidatedField() child = _UnvalidatedField()
initial = {} initial = {}
default_error_messages = { 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): def __init__(self, *args, **kwargs):
self.child = kwargs.pop('child', copy.deepcopy(self.child)) 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 not inspect.isclass(self.child), '`child` has not been instantiated.'
assert self.child.source is None, ( assert self.child.source is None, (
@ -1693,6 +1695,9 @@ class DictField(Field):
data = html.parse_html_dict(data) data = html.parse_html_dict(data)
if not isinstance(data, dict): if not isinstance(data, dict):
self.fail('not_a_dict', input_type=type(data).__name__) 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) return self.run_child_validation(data)
def to_representation(self, value): def to_representation(self, value):

View File

@ -1982,6 +1982,7 @@ class TestDictField(FieldValues):
""" """
valid_inputs = [ valid_inputs = [
({'a': 1, 'b': '2', 3: 3}, {'a': '1', 'b': '2', '3': '3'}), ({'a': 1, 'b': '2', 3: 3}, {'a': '1', 'b': '2', '3': '3'}),
({}, {}),
] ]
invalid_inputs = [ invalid_inputs = [
({'a': 1, 'b': None, 'c': None}, {'b': ['This field may not be null.'], 'c': ['This field may not be null.']}), ({'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) output = field.run_validation(None)
assert output is 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): class TestNestedDictField(FieldValues):
""" """