From b6ca7248ebcf95a95e1911aa0b130f653b8bf690 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 5 Jan 2015 14:32:12 +0000 Subject: [PATCH] required=False allows omission of value for output. Closes #2342 --- docs/api-guide/fields.md | 2 ++ rest_framework/fields.py | 2 ++ rest_framework/serializers.py | 8 ++++- tests/test_serializer.py | 62 +++++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 1 deletion(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 946e355da8..b3d274ddb7 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -41,6 +41,8 @@ Defaults to `False` Normally an error will be raised if a field is not supplied during deserialization. Set to false if this field is not required to be present during deserialization. +Setting this to `False` also allows the object attribute or dictionary key to be omitted from output when serializing the instance. If the key is not present it will simply not be included in the output representation. + Defaults to `True`. ### `allow_null` diff --git a/rest_framework/fields.py b/rest_framework/fields.py index aab80982af..cc9410aa7e 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -288,6 +288,8 @@ def get_attribute(self, instance): try: return get_attribute(instance, self.source_attrs) except (KeyError, AttributeError) as exc: + if not self.required and self.default is empty: + raise SkipField() msg = ( 'Got {exc_type} when attempting to get a value for field ' '`{field}` on serializer `{serializer}`.\nThe serializer ' diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 6f89df0db9..53f092d7a9 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -419,8 +419,14 @@ def to_representation(self, instance): fields = [field for field in self.fields.values() if not field.write_only] for field in fields: - attribute = field.get_attribute(instance) + try: + attribute = field.get_attribute(instance) + except SkipField: + continue + if attribute is None: + # We skip `to_representation` for `None` values so that + # fields do not have to explicitly deal with that case. ret[field.field_name] = None else: ret[field.field_name] = field.to_representation(attribute) diff --git a/tests/test_serializer.py b/tests/test_serializer.py index c17b6d8c52..68bbbe9831 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -1,5 +1,6 @@ # coding: utf-8 from __future__ import unicode_literals +from .utils import MockObject from rest_framework import serializers from rest_framework.compat import unicode_repr import pytest @@ -216,3 +217,64 @@ def __repr__(self): instance = ExampleObject() serializer = ExampleSerializer(instance) repr(serializer) # Should not error. + + +class TestNotRequiredOutput: + def test_not_required_output_for_dict(self): + """ + 'required=False' should allow a dictionary key to be missing in output. + """ + class ExampleSerializer(serializers.Serializer): + omitted = serializers.CharField(required=False) + included = serializers.CharField() + + serializer = ExampleSerializer(data={'included': 'abc'}) + serializer.is_valid() + assert serializer.data == {'included': 'abc'} + + def test_not_required_output_for_object(self): + """ + 'required=False' should allow an object attribute to be missing in output. + """ + class ExampleSerializer(serializers.Serializer): + omitted = serializers.CharField(required=False) + included = serializers.CharField() + + def create(self, validated_data): + return MockObject(**validated_data) + + serializer = ExampleSerializer(data={'included': 'abc'}) + serializer.is_valid() + 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 ExampleSerializer(serializers.Serializer): + omitted = serializers.CharField(default='abc') + included = serializers.CharField() + + serializer = ExampleSerializer({'included': 'abc'}) + with pytest.raises(KeyError): + serializer.data + + def test_default_required_output_for_object(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. + """ + class ExampleSerializer(serializers.Serializer): + omitted = serializers.CharField(default='abc') + included = serializers.CharField() + + instance = MockObject(included='abc') + serializer = ExampleSerializer(instance) + with pytest.raises(AttributeError): + serializer.data