Skip to content

Commit

Permalink
required=False allows omission of value for output. Closes #2342
Browse files Browse the repository at this point in the history
  • Loading branch information
tomchristie committed Jan 5, 2015
1 parent 8cf3744 commit b6ca724
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 1 deletion.
2 changes: 2 additions & 0 deletions docs/api-guide/fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
2 changes: 2 additions & 0 deletions rest_framework/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 '
Expand Down
8 changes: 7 additions & 1 deletion rest_framework/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
62 changes: 62 additions & 0 deletions tests/test_serializer.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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

0 comments on commit b6ca724

Please sign in to comment.