From cf5d401a0e60948ed0b3ad384c3f76fc30c3e222 Mon Sep 17 00:00:00 2001 From: Tibo Beijen Date: Tue, 7 Mar 2017 14:19:19 +0100 Subject: [PATCH] Allow required false and default (#4692) * Default value will now be used when serializing if key or attribute is missing. --- docs/api-guide/fields.md | 2 ++ rest_framework/fields.py | 4 ++- tests/test_serializer.py | 56 +++++++++++++++++++++++----------------- 3 files changed, 37 insertions(+), 25 deletions(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 677598205..28d06f25c 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -55,6 +55,8 @@ The `default` is not applied during partial update operations. In the partial up May be set to a function or other callable, in which case the value will be evaluated each time it is used. When called, it will receive no arguments. If the callable has a `set_context` method, that will be called each time before getting the value with the field instance as only argument. This works the same way as for [validators](validators.md#using-set_context). +When serializing the instance, default will be used if the the object attribute or dictionary key is not present in the instance. + Note that setting a `default` value implies that the field is not required. Including both the `default` and `required` keyword arguments is invalid and will raise an error. ### `source` diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 2efe89610..5a881c772 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -443,7 +443,9 @@ class Field(object): try: return get_attribute(instance, self.source_attrs) except (KeyError, AttributeError) as exc: - if not self.required and self.default is empty: + if self.default is not empty: + return self.get_default() + if not self.required: raise SkipField() msg = ( 'Got {exc_type} when attempting to get a value for field ' diff --git a/tests/test_serializer.py b/tests/test_serializer.py index cd82ba3df..f76cec9c3 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -372,36 +372,44 @@ class TestNotRequiredOutput: serializer.save() assert serializer.data == {'included': 'abc'} - def test_default_required_output_for_dict(self): - """ - 'default="something"' should require dictionary key. - We need to handle this as the field will have an implicit - 'required=False', but it should still have a value. - """ +class TestDefaultOutput: + def setup(self): class ExampleSerializer(serializers.Serializer): - omitted = serializers.CharField(default='abc') - included = serializers.CharField() + has_default = serializers.CharField(default='x') + has_default_callable = serializers.CharField(default=lambda: 'y') + no_default = serializers.CharField() + self.Serializer = ExampleSerializer - serializer = ExampleSerializer({'included': 'abc'}) - with pytest.raises(KeyError): - serializer.data - - def test_default_required_output_for_object(self): + def test_default_used_for_dict(self): """ - 'default="something"' should require object attribute. - - We need to handle this as the field will have an implicit - 'required=False', but it should still have a value. + 'default="something"' should be used if dictionary key is missing from input. """ - class ExampleSerializer(serializers.Serializer): - omitted = serializers.CharField(default='abc') - included = serializers.CharField() + serializer = self.Serializer({'no_default': 'abc'}) + assert serializer.data == {'has_default': 'x', 'has_default_callable': 'y', 'no_default': 'abc'} - instance = MockObject(included='abc') - serializer = ExampleSerializer(instance) - with pytest.raises(AttributeError): - serializer.data + def test_default_used_for_object(self): + """ + 'default="something"' should be used if object attribute is missing from input. + """ + instance = MockObject(no_default='abc') + serializer = self.Serializer(instance) + assert serializer.data == {'has_default': 'x', 'has_default_callable': 'y', 'no_default': 'abc'} + + def test_default_not_used_when_in_dict(self): + """ + 'default="something"' should not be used if dictionary key is present in input. + """ + serializer = self.Serializer({'has_default': 'def', 'has_default_callable': 'ghi', 'no_default': 'abc'}) + assert serializer.data == {'has_default': 'def', 'has_default_callable': 'ghi', 'no_default': 'abc'} + + def test_default_not_used_when_in_object(self): + """ + 'default="something"' should not be used if object attribute is present in input. + """ + instance = MockObject(has_default='def', has_default_callable='ghi', no_default='abc') + serializer = self.Serializer(instance) + assert serializer.data == {'has_default': 'def', 'has_default_callable': 'ghi', 'no_default': 'abc'} class TestCacheSerializerData: