Skip to content

Commit f4a59fd

Browse files
gaetano-guerrieroterencehonles
authored andcommitted
Dict field allow empty (#6583)
* dict field: support allow_empty option * document ListField allow_empty option * document HStoreField allow_empty parameter
1 parent 0fe63e3 commit f4a59fd

File tree

3 files changed

+23
-4
lines changed

3 files changed

+23
-4
lines changed

docs/api-guide/fields.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -448,9 +448,10 @@ Requires either the `Pillow` package or `PIL` package. The `Pillow` package is
448448

449449
A field class that validates a list of objects.
450450

451-
**Signature**: `ListField(child=<A_FIELD_INSTANCE>, min_length=None, max_length=None)`
451+
**Signature**: `ListField(child=<A_FIELD_INSTANCE>, allow_empty=True, min_length=None, max_length=None)`
452452

453453
- `child` - A field instance that should be used for validating the objects in the list. If this argument is not provided then objects in the list will not be validated.
454+
- `allow_empty` - Designates if empty lists are allowed.
454455
- `min_length` - Validates that the list contains no fewer than this number of elements.
455456
- `max_length` - Validates that the list contains no more than this number of elements.
456457

@@ -471,9 +472,10 @@ We can now reuse our custom `StringListField` class throughout our application,
471472

472473
A field class that validates a dictionary of objects. The keys in `DictField` are always assumed to be string values.
473474

474-
**Signature**: `DictField(child=<A_FIELD_INSTANCE>)`
475+
**Signature**: `DictField(child=<A_FIELD_INSTANCE>, allow_empty=True)`
475476

476477
- `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.
478+
- `allow_empty` - Designates if empty dictionaries are allowed.
477479

478480
For example, to create a field that validates a mapping of strings to strings, you would write something like this:
479481

@@ -488,9 +490,10 @@ You can also use the declarative style, as with `ListField`. For example:
488490

489491
A preconfigured `DictField` that is compatible with Django's postgres `HStoreField`.
490492

491-
**Signature**: `HStoreField(child=<A_FIELD_INSTANCE>)`
493+
**Signature**: `HStoreField(child=<A_FIELD_INSTANCE>, allow_empty=True)`
492494

493495
- `child` - A field instance that is used for validating the values in the dictionary. The default child field accepts both empty strings and null values.
496+
- `allow_empty` - Designates if empty dictionaries are allowed.
494497

495498
Note that the child field **must** be an instance of `CharField`, as the hstore extension stores values as strings.
496499

rest_framework/fields.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1663,11 +1663,13 @@ class DictField(Field):
16631663
child = _UnvalidatedField()
16641664
initial = {}
16651665
default_error_messages = {
1666-
'not_a_dict': _('Expected a dictionary of items but got type "{input_type}".')
1666+
'not_a_dict': _('Expected a dictionary of items but got type "{input_type}".'),
1667+
'empty': _('This dictionary may not be empty.'),
16671668
}
16681669

16691670
def __init__(self, *args, **kwargs):
16701671
self.child = kwargs.pop('child', copy.deepcopy(self.child))
1672+
self.allow_empty = kwargs.pop('allow_empty', True)
16711673

16721674
assert not inspect.isclass(self.child), '`child` has not been instantiated.'
16731675
assert self.child.source is None, (
@@ -1693,6 +1695,9 @@ def to_internal_value(self, data):
16931695
data = html.parse_html_dict(data)
16941696
if not isinstance(data, dict):
16951697
self.fail('not_a_dict', input_type=type(data).__name__)
1698+
if not self.allow_empty and len(data) == 0:
1699+
self.fail('empty')
1700+
16961701
return self.run_child_validation(data)
16971702

16981703
def to_representation(self, value):

tests/test_fields.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1982,6 +1982,7 @@ class TestDictField(FieldValues):
19821982
"""
19831983
valid_inputs = [
19841984
({'a': 1, 'b': '2', 3: 3}, {'a': '1', 'b': '2', '3': '3'}),
1985+
({}, {}),
19851986
]
19861987
invalid_inputs = [
19871988
({'a': 1, 'b': None, 'c': None}, {'b': ['This field may not be null.'], 'c': ['This field may not be null.']}),
@@ -2009,6 +2010,16 @@ def test_allow_null(self):
20092010
output = field.run_validation(None)
20102011
assert output is None
20112012

2013+
def test_allow_empty_disallowed(self):
2014+
"""
2015+
If allow_empty is False then an empty dict is not a valid input.
2016+
"""
2017+
field = serializers.DictField(allow_empty=False)
2018+
with pytest.raises(serializers.ValidationError) as exc_info:
2019+
field.run_validation({})
2020+
2021+
assert exc_info.value.detail == ['This dictionary may not be empty.']
2022+
20122023

20132024
class TestNestedDictField(FieldValues):
20142025
"""

0 commit comments

Comments
 (0)