From 5cd5372e9033002e1331de52ab028f03b1935fc4 Mon Sep 17 00:00:00 2001 From: "b.khasanov" Date: Mon, 23 May 2016 02:16:48 +0300 Subject: [PATCH] rename parameter name to serializer_class, add tests --- docs/api-guide/relations.md | 6 +- rest_framework/relations.py | 15 +- tests/test_relations_serializable.py | 590 +++++++++++++++++++++++++++ 3 files changed, 602 insertions(+), 9 deletions(-) create mode 100644 tests/test_relations_serializable.py diff --git a/docs/api-guide/relations.md b/docs/api-guide/relations.md index d968d6b1e..7d50cf7f0 100644 --- a/docs/api-guide/relations.md +++ b/docs/api-guide/relations.md @@ -126,7 +126,7 @@ By default this field is read-write, although you can change this behavior using For example, if we pass `TrackSerializer` the following serializer: class AlbumSerializer(serializers.ModelSerializer): - tracks = serializers.SerializableRelatedField(many=True, read_only=True) + tracks = serializers.SerializableRelatedField(many=True, serializer_class=TrackSerializer) class Meta: model = Album @@ -152,14 +152,14 @@ Would serialize to a representation like this: ] } -By default this field take queryset from passed `serializer`. +By default this field take queryset from passed `serializer_class`. **Arguments**: * `queryset` - The queryset used for model instance lookups when validating the field input. Relationships must either set a queryset explicitly, or set `read_only=True`. * `many` - If applied to a to-many relationship, you should set this argument to `True`. * `allow_null` - If set to `True`, the field will accept values of `None` or the empty string for nullable relationships. Defaults to `False`. -* `serializer` - Required field, serializer to represent the target of the relationship +* `serializer_class` - serializer class to represent the target of the relationship, required field * `serializer_params` - Parameters, passed to serializer diff --git a/rest_framework/relations.py b/rest_framework/relations.py index dfd69db8f..47f147ba8 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -239,13 +239,14 @@ class SerializableRelatedField(RelatedField): } def __init__(self, **kwargs): - self.serializer = kwargs.pop('serializer', None) - assert self.serializer is not None, ( - 'SerializableRelatedField field must provide a `serializer` argument' + self.serializer_class = kwargs.pop('serializer_class', None) + assert self.serializer_class is not None, ( + 'SerializableRelatedField field must provide a `serializer_class` argument' ) self.serializer_params = kwargs.pop('serializer_params', dict()) - if 'queryset' not in kwargs: - kwargs['queryset'] = self.serializer.Meta.model.objects.all() + from rest_framework.serializers import ModelSerializer + if 'queryset' not in kwargs and issubclass(self.serializer_class, ModelSerializer): + kwargs['queryset'] = self.serializer_class.Meta.model.objects.all() kwargs['style'] = {'base_template': 'input.html', 'input_type': 'numeric'} super(SerializableRelatedField, self).__init__(**kwargs) @@ -258,7 +259,9 @@ class SerializableRelatedField(RelatedField): self.fail('incorrect_type', data_type=type(data).__name__) def to_representation(self, value): - return self.serializer(instance=value, context=self.context, **self.serializer_params).data + return (self + .serializer_class(instance=value, context=self.context, **self.serializer_params) + .data) class HyperlinkedRelatedField(RelatedField): diff --git a/tests/test_relations_serializable.py b/tests/test_relations_serializable.py new file mode 100644 index 000000000..456baf780 --- /dev/null +++ b/tests/test_relations_serializable.py @@ -0,0 +1,590 @@ +from __future__ import unicode_literals + +from django.test import TestCase +from django.utils import six + +from rest_framework import serializers +from tests.models import ( + ForeignKeySource, ForeignKeyTarget, ManyToManySource, ManyToManyTarget, + NullableForeignKeySource, NullableOneToOneSource, OneToOneTarget +) + + +# ManyToMany +class ManyToManyTargetRepresentationSerializer(serializers.ModelSerializer): + class Meta: + model = ManyToManyTarget + fields = ('id', 'name') + + +class ManyToManySourceRepresentationSerializer(serializers.ModelSerializer): + class Meta: + model = ManyToManySource + fields = ('id', 'name') + + +class ManyToManyTargetSerializer(serializers.ModelSerializer): + sources = serializers.SerializableRelatedField( + serializer_class=ManyToManySourceRepresentationSerializer, + many=True + ) + + class Meta: + model = ManyToManyTarget + fields = ('id', 'name', 'sources') + + +class ManyToManySourceSerializer(serializers.ModelSerializer): + targets = serializers.SerializableRelatedField( + serializer_class=ManyToManyTargetRepresentationSerializer, + many=True + ) + + class Meta: + model = ManyToManySource + fields = ('id', 'name', 'targets') + + +# ForeignKey +class ForeignKeySourceRepresentationSerializer(serializers.ModelSerializer): + class Meta: + model = ForeignKeySource + fields = ('id', 'name') + + +class ForeignKeyTargetRepresentationSerializer(serializers.ModelSerializer): + class Meta: + model = ForeignKeyTarget + fields = ('id', 'name') + + +class ForeignKeyTargetSerializer(serializers.ModelSerializer): + sources = serializers.SerializableRelatedField( + serializer_class=ForeignKeySourceRepresentationSerializer, + many=True + ) + + class Meta: + model = ForeignKeyTarget + fields = ('id', 'name', 'sources') + + +class ForeignKeySourceSerializer(serializers.ModelSerializer): + target = serializers.SerializableRelatedField( + serializer_class=ForeignKeyTargetRepresentationSerializer + ) + + class Meta: + model = ForeignKeySource + fields = ('id', 'name', 'target') + + +# Nullable ForeignKey +class NullableForeignKeySourceSerializer(serializers.ModelSerializer): + target = serializers.SerializableRelatedField( + serializer_class=ForeignKeyTargetRepresentationSerializer, + allow_null=True + ) + + class Meta: + model = NullableForeignKeySource + fields = ('id', 'name', 'target') + + +# Nullable OneToOne +class NullableOneToOneTargetSerializer(serializers.ModelSerializer): + nullable_source = serializers.SerializableRelatedField( + serializer_class=ForeignKeyTargetRepresentationSerializer, + allow_null=True + ) + + class Meta: + model = OneToOneTarget + fields = ('id', 'name', 'nullable_source') + + +# TODO: Add test that .data cannot be accessed prior to .is_valid + +class SerializableManyToManyTests(TestCase): + def setUp(self): + for idx in range(1, 4): + target = ManyToManyTarget(name='target-%d' % idx) + target.save() + source = ManyToManySource(name='source-%d' % idx) + source.save() + for target in ManyToManyTarget.objects.all(): + source.targets.add(target) + + def test_many_to_many_retrieve(self): + queryset = ManyToManySource.objects.all() + serializer = ManyToManySourceSerializer(queryset, many=True) + expected = [ + { + 'id': 1, + 'name': 'source-1', + 'targets': [ + {'id': 1, 'name': 'target-1'} + ] + }, + { + 'id': 2, + 'name': 'source-2', + 'targets': [ + {'id': 1, 'name': 'target-1'}, + {'id': 2, 'name': 'target-2'} + ] + }, + { + 'id': 3, + 'name': 'source-3', + 'targets': [ + {'id': 1, 'name': 'target-1'}, + {'id': 2, 'name': 'target-2'}, + {'id': 3, 'name': 'target-3'} + ] + } + ] + with self.assertNumQueries(4): + self.assertEqual(serializer.data, expected) + + def test_many_to_many_retrieve_prefetch_related(self): + queryset = ManyToManySource.objects.all().prefetch_related('targets') + serializer = ManyToManySourceSerializer(queryset, many=True) + with self.assertNumQueries(2): + serializer.data + + def test_reverse_many_to_many_retrieve(self): + queryset = ManyToManyTarget.objects.all() + serializer = ManyToManyTargetSerializer(queryset, many=True) + expected = [ + { + 'id': 1, + 'name': 'target-1', + 'sources': [ + {'id': 1, 'name': 'source-1'}, + {'id': 2, 'name': 'source-2'}, + {'id': 3, 'name': 'source-3'} + ] + }, + { + 'id': 2, + 'name': 'target-2', + 'sources': [ + {'id': 2, 'name': 'source-2'}, + {'id': 3, 'name': 'source-3'} + ] + }, + { + 'id': 3, + 'name': 'target-3', + 'sources': [ + {'id': 3, 'name': 'source-3'}, + + ] + } + ] + with self.assertNumQueries(4): + self.assertEqual(serializer.data, expected) + + def test_many_to_many_update(self): + data = {'id': 1, 'name': 'source-1', 'targets': [1, 2, 3]} + instance = ManyToManySource.objects.get(pk=1) + serializer = ManyToManySourceSerializer(instance, data=data) + self.assertTrue(serializer.is_valid()) + serializer.save() + expected = {'id': 1, 'name': 'source-1', 'targets': [{'id':1, 'name': 'target-1'}, {'id': 2, 'name': 'target-2'}, {'id': 3, 'name': 'target-3'}]} + self.assertEqual(serializer.data, expected) + + # Ensure source 1 is updated, and everything else is as expected + queryset = ManyToManySource.objects.all() + serializer = ManyToManySourceSerializer(queryset, many=True) + expected = [ + {'id': 1, 'name': 'source-1', 'targets': [{'id': 1, 'name': 'target-1'}, {'id': 2, 'name': 'target-2'}, {'id': 3, 'name': 'target-3'}]}, + {'id': 2, 'name': 'source-2', 'targets': [{'id': 1, 'name': 'target-1'}, {'id': 2, 'name': 'target-2'}]}, + {'id': 3, 'name': 'source-3', 'targets': [{'id': 1, 'name': 'target-1'}, {'id': 2, 'name': 'target-2'}, {'id': 3, 'name': 'target-3'}]} + ] + self.assertEqual(serializer.data, expected) + + def test_reverse_many_to_many_update(self): + data = {'id': 1, 'name': 'target-1', 'sources': [1]} + instance = ManyToManyTarget.objects.get(pk=1) + serializer = ManyToManyTargetSerializer(instance, data=data) + self.assertTrue(serializer.is_valid()) + serializer.save() + expected = {'id': 1, 'name': 'target-1', 'sources': [{'id': 1, 'name': 'source-1'}]} + self.assertEqual(serializer.data, expected) + + # Ensure target 1 is updated, and everything else is as expected + queryset = ManyToManyTarget.objects.all() + serializer = ManyToManyTargetSerializer(queryset, many=True) + expected = [ + {'id': 1, 'name': 'target-1', 'sources': [{'id': 1, 'name': 'source-1'}]}, + {'id': 2, 'name': 'target-2', 'sources': [{'id': 2, 'name': 'source-2'}, {'id': 3, 'name': 'source-3'}]}, + {'id': 3, 'name': 'target-3', 'sources': [{'id': 3, 'name': 'source-3'}]} + ] + self.assertEqual(serializer.data, expected) + + def test_many_to_many_create(self): + data = {'id': 4, 'name': 'source-4', 'targets': [1, 3]} + serializer = ManyToManySourceSerializer(data=data) + serializer.is_valid() + self.assertTrue(serializer.is_valid()) + obj = serializer.save() + expected = {'id': 4, 'name': 'source-4', 'targets': [{'id': 1, 'name': 'target-1'}, {'id': 3, 'name': 'target-3'}]} + self.assertEqual(serializer.data, expected) + self.assertEqual(obj.name, 'source-4') + + # Ensure source 4 is added, and everything else is as expected + queryset = ManyToManySource.objects.all() + serializer = ManyToManySourceSerializer(queryset, many=True) + expected = [ + {'id': 1, 'name': 'source-1', 'targets': [{'id': 1, 'name': 'target-1'}]}, + {'id': 2, 'name': 'source-2', 'targets': [{'id': 1, 'name': 'target-1'}, {'id': 2, 'name': 'target-2'}]}, + {'id': 3, 'name': 'source-3', 'targets': [{'id': 1, 'name': 'target-1'}, {'id': 2, 'name': 'target-2'}, {'id': 3, 'name': 'target-3'}]}, + {'id': 4, 'name': 'source-4', 'targets': [{'id': 1, 'name': 'target-1'}, {'id': 3, 'name': 'target-3'}]}, + ] + self.assertEqual(serializer.data, expected) + + def test_many_to_many_unsaved(self): + source = ManyToManySource(name='source-unsaved') + + serializer = ManyToManySourceSerializer(source) + + expected = {'id': None, 'name': 'source-unsaved', 'targets': []} + # no query if source hasn't been created yet + with self.assertNumQueries(0): + self.assertEqual(serializer.data, expected) + + def test_reverse_many_to_many_create(self): + data = {'id': 4, 'name': 'target-4', 'sources': [1, 3]} + serializer = ManyToManyTargetSerializer(data=data) + self.assertTrue(serializer.is_valid()) + obj = serializer.save() + expected = {'id': 4, 'name': 'target-4', 'sources': [{'id': 1, 'name': 'source-1'}, {'id': 3, 'name': 'source-3'}]} + self.assertEqual(serializer.data, expected) + self.assertEqual(obj.name, 'target-4') + + # Ensure target 4 is added, and everything else is as expected + queryset = ManyToManyTarget.objects.all() + serializer = ManyToManyTargetSerializer(queryset, many=True) + expected = [ + {'id': 1, 'name': 'target-1', 'sources': [{'id': 1, 'name': 'source-1'}, {'id': 2, 'name': 'source-2'}, {'id': 3, 'name': 'source-3'}]}, + {'id': 2, 'name': 'target-2', 'sources': [{'id': 2, 'name': 'source-2'}, {'id': 3, 'name': 'source-3'}]}, + {'id': 3, 'name': 'target-3', 'sources': [{'id': 3, 'name': 'source-3'}]}, + {'id': 4, 'name': 'target-4', 'sources': [{'id': 1, 'name': 'source-1'}, {'id': 3, 'name': 'source-3'}]} + ] + self.assertEqual(serializer.data, expected) + + +class SerializableForeignKeyTests(TestCase): + def setUp(self): + target = ForeignKeyTarget(name='target-1') + target.save() + new_target = ForeignKeyTarget(name='target-2') + new_target.save() + for idx in range(1, 4): + source = ForeignKeySource(name='source-%d' % idx, target=target) + source.save() + + def test_foreign_key_retrieve(self): + queryset = ForeignKeySource.objects.all() + serializer = ForeignKeySourceSerializer(queryset, many=True) + expected = [ + {'id': 1, 'name': 'source-1', 'target': {'id': 1, 'name': 'target-1'}}, + {'id': 2, 'name': 'source-2', 'target': {'id': 1, 'name': 'target-1'}}, + {'id': 3, 'name': 'source-3', 'target': {'id': 1, 'name': 'target-1'}} + ] + with self.assertNumQueries(4): + self.assertEqual(serializer.data, expected) + + def test_reverse_foreign_key_retrieve(self): + queryset = ForeignKeyTarget.objects.all() + serializer = ForeignKeyTargetSerializer(queryset, many=True) + expected = [ + {'id': 1, 'name': 'target-1', 'sources': [ + {'id': 1, 'name': 'source-1'}, + {'id': 2, 'name': 'source-2'}, + {'id': 3, 'name': 'source-3'} + ]}, + {'id': 2, 'name': 'target-2', 'sources': []}, + ] + with self.assertNumQueries(3): + self.assertEqual(serializer.data, expected) + + def test_reverse_foreign_key_retrieve_prefetch_related(self): + queryset = ForeignKeyTarget.objects.all().prefetch_related('sources') + serializer = ForeignKeyTargetSerializer(queryset, many=True) + with self.assertNumQueries(2): + serializer.data + + def test_foreign_key_update(self): + data = {'id': 1, 'name': 'source-1', 'target': 2} + instance = ForeignKeySource.objects.get(pk=1) + serializer = ForeignKeySourceSerializer(instance, data=data) + self.assertTrue(serializer.is_valid()) + serializer.save() + expected = {'id': 1, 'name': 'source-1', 'target': {'id': 2, 'name': 'target-2'}} + self.assertEqual(serializer.data, expected) + + # Ensure source 1 is updated, and everything else is as expected + queryset = ForeignKeySource.objects.all() + serializer = ForeignKeySourceSerializer(queryset, many=True) + expected = [ + {'id': 1, 'name': 'source-1', 'target': {'id': 2, 'name': 'target-2'}}, + {'id': 2, 'name': 'source-2', 'target': {'id': 1, 'name': 'target-1'}}, + {'id': 3, 'name': 'source-3', 'target': {'id': 1, 'name': 'target-1'}} + ] + self.assertEqual(serializer.data, expected) + + def test_foreign_key_update_incorrect_type(self): + data = {'id': 1, 'name': 'source-1', 'target': 'foo'} + instance = ForeignKeySource.objects.get(pk=1) + serializer = ForeignKeySourceSerializer(instance, data=data) + self.assertFalse(serializer.is_valid()) + self.assertEqual(serializer.errors, { + 'target': ['Incorrect type. Expected pk value, received %s.' % six.text_type.__name__]}) + + def test_reverse_foreign_key_update(self): + data = {'id': 2, 'name': 'target-2', 'sources': [1, 3]} + instance = ForeignKeyTarget.objects.get(pk=2) + serializer = ForeignKeyTargetSerializer(instance, data=data) + self.assertTrue(serializer.is_valid()) + # We shouldn't have saved anything to the db yet since save + # hasn't been called. + queryset = ForeignKeyTarget.objects.all() + new_serializer = ForeignKeyTargetSerializer(queryset, many=True) + expected = [ + { + 'id': 1, + 'name': 'target-1', + 'sources': [ + {'id': 1, 'name': 'source-1'}, + {'id': 2, 'name': 'source-2'}, + {'id': 3, 'name': 'source-3'} + ] + }, + {'id': 2, 'name': 'target-2', 'sources': []}, + ] + self.assertEqual(new_serializer.data, expected) + + serializer.save() + expected = { + 'id': 2, + 'name': 'target-2', + 'sources': [ + {'id': 1, 'name': 'source-1'}, + {'id': 3, 'name': 'source-3'} + ] + } + self.assertEqual(serializer.data, expected) + + # Ensure target 2 is update, and everything else is as expected + queryset = ForeignKeyTarget.objects.all() + serializer = ForeignKeyTargetSerializer(queryset, many=True) + expected = [ + {'id': 1, 'name': 'target-1', 'sources': [{'id': 2, 'name': 'source-2'}]}, + { + 'id': 2, + 'name': 'target-2', + 'sources': [{'id': 1, 'name': 'source-1'}, {'id': 3, 'name': 'source-3'}] + }, + ] + self.assertEqual(serializer.data, expected) + + def test_foreign_key_create(self): + data = {'id': 4, 'name': 'source-4', 'target': 2} + serializer = ForeignKeySourceSerializer(data=data) + self.assertTrue(serializer.is_valid()) + obj = serializer.save() + expected = {'id': 4, 'name': 'source-4', 'target': {'id': 2, 'name': 'target-2'}} + self.assertEqual(serializer.data, expected) + self.assertEqual(obj.name, 'source-4') + + # Ensure source 4 is added, and everything else is as expected + queryset = ForeignKeySource.objects.all() + serializer = ForeignKeySourceSerializer(queryset, many=True) + expected = [ + {'id': 1, 'name': 'source-1', 'target': {'id': 1, 'name': 'target-1'}}, + {'id': 2, 'name': 'source-2', 'target': {'id': 1, 'name': 'target-1'}}, + {'id': 3, 'name': 'source-3', 'target': {'id': 1, 'name': 'target-1'}}, + {'id': 4, 'name': 'source-4', 'target': {'id': 2, 'name': 'target-2'}}, + ] + self.assertEqual(serializer.data, expected) + + def test_reverse_foreign_key_create(self): + data = { + 'id': 3, + 'name': 'target-3', + 'sources': [1, 3] + } + serializer = ForeignKeyTargetSerializer(data=data) + self.assertTrue(serializer.is_valid()) + obj = serializer.save() + expected = { + 'id': 3, + 'name': 'target-3', + 'sources': [{'id': 1, 'name': 'source-1'}, {'id': 3, 'name': 'source-3'}] + } + self.assertEqual(serializer.data, expected) + self.assertEqual(obj.name, 'target-3') + + # Ensure target 3 is added, and everything else is as expected + queryset = ForeignKeyTarget.objects.all() + serializer = ForeignKeyTargetSerializer(queryset, many=True) + expected = [ + {'id': 1, 'name': 'target-1', 'sources': [{'id': 2, 'name': 'source-2'}]}, + {'id': 2, 'name': 'target-2', 'sources': []}, + {'id': 3, 'name': 'target-3', + 'sources': [{'id': 1, 'name': 'source-1'}, {'id': 3, 'name': 'source-3'}]}, + ] + self.assertEqual(serializer.data, expected) + + def test_foreign_key_update_with_invalid_null(self): + data = {'id': 1, 'name': 'source-1', 'target': None} + instance = ForeignKeySource.objects.get(pk=1) + serializer = ForeignKeySourceSerializer(instance, data=data) + self.assertFalse(serializer.is_valid()) + self.assertEqual(serializer.errors, {'target': ['This field may not be null.']}) + + def test_foreign_key_with_unsaved(self): + source = ForeignKeySource(name='source-unsaved') + expected = {'id': None, 'name': 'source-unsaved', 'target': None} + + serializer = ForeignKeySourceSerializer(source) + + # no query if source hasn't been created yet + with self.assertNumQueries(0): + self.assertEqual(serializer.data, expected) + + def test_foreign_key_with_empty(self): + """ + Regression test for #1072 + + https://github.com/tomchristie/django-rest-framework/issues/1072 + """ + serializer = NullableForeignKeySourceSerializer() + self.assertEqual(serializer.data['target'], None) + + +class SerializableNullableForeignKeyTests(TestCase): + def setUp(self): + target = ForeignKeyTarget(name='target-1') + target.save() + for idx in range(1, 4): + if idx == 3: + target = None + source = NullableForeignKeySource(name='source-%d' % idx, target=target) + source.save() + + def test_foreign_key_retrieve_with_null(self): + queryset = NullableForeignKeySource.objects.all() + serializer = NullableForeignKeySourceSerializer(queryset, many=True) + expected = [ + {'id': 1, 'name': 'source-1', 'target': {'id': 1, 'name': 'target-1'}}, + {'id': 2, 'name': 'source-2', 'target': {'id': 1, 'name': 'target-1'}}, + {'id': 3, 'name': 'source-3', 'target': None}, + ] + self.assertEqual(serializer.data, expected) + + def test_foreign_key_create_with_valid_null(self): + data = {'id': 4, 'name': 'source-4', 'target': None} + serializer = NullableForeignKeySourceSerializer(data=data) + self.assertTrue(serializer.is_valid()) + obj = serializer.save() + self.assertEqual(serializer.data, data) + self.assertEqual(obj.name, 'source-4') + + # Ensure source 4 is created, and everything else is as expected + queryset = NullableForeignKeySource.objects.all() + serializer = NullableForeignKeySourceSerializer(queryset, many=True) + expected = [ + {'id': 1, 'name': 'source-1', 'target': {'id': 1, 'name': 'target-1'}}, + {'id': 2, 'name': 'source-2', 'target': {'id': 1, 'name': 'target-1'}}, + {'id': 3, 'name': 'source-3', 'target': None}, + {'id': 4, 'name': 'source-4', 'target': None} + ] + self.assertEqual(serializer.data, expected) + + def test_foreign_key_create_with_valid_emptystring(self): + """ + The emptystring should be interpreted as null in the context + of relationships. + """ + data = {'id': 4, 'name': 'source-4', 'target': ''} + expected_data = {'id': 4, 'name': 'source-4', 'target': None} + serializer = NullableForeignKeySourceSerializer(data=data) + self.assertTrue(serializer.is_valid()) + obj = serializer.save() + self.assertEqual(serializer.data, expected_data) + self.assertEqual(obj.name, 'source-4') + + # Ensure source 4 is created, and everything else is as expected + queryset = NullableForeignKeySource.objects.all() + serializer = NullableForeignKeySourceSerializer(queryset, many=True) + expected = [ + {'id': 1, 'name': 'source-1', 'target': {'id': 1, 'name': 'target-1'}}, + {'id': 2, 'name': 'source-2', 'target': {'id': 1, 'name': 'target-1'}}, + {'id': 3, 'name': 'source-3', 'target': None}, + {'id': 4, 'name': 'source-4', 'target': None} + ] + self.assertEqual(serializer.data, expected) + + def test_foreign_key_update_with_valid_null(self): + data = {'id': 1, 'name': 'source-1', 'target': None} + instance = NullableForeignKeySource.objects.get(pk=1) + serializer = NullableForeignKeySourceSerializer(instance, data=data) + self.assertTrue(serializer.is_valid()) + serializer.save() + self.assertEqual(serializer.data, data) + + # Ensure source 1 is updated, and everything else is as expected + queryset = NullableForeignKeySource.objects.all() + serializer = NullableForeignKeySourceSerializer(queryset, many=True) + expected = [ + {'id': 1, 'name': 'source-1', 'target': None}, + {'id': 2, 'name': 'source-2', 'target': {'id': 1, 'name': 'target-1'}}, + {'id': 3, 'name': 'source-3', 'target': None} + ] + self.assertEqual(serializer.data, expected) + + def test_foreign_key_update_with_valid_emptystring(self): + """ + The emptystring should be interpreted as null in the context + of relationships. + """ + data = {'id': 1, 'name': 'source-1', 'target': ''} + expected_data = {'id': 1, 'name': 'source-1', 'target': None} + instance = NullableForeignKeySource.objects.get(pk=1) + serializer = NullableForeignKeySourceSerializer(instance, data=data) + self.assertTrue(serializer.is_valid()) + serializer.save() + self.assertEqual(serializer.data, expected_data) + + # Ensure source 1 is updated, and everything else is as expected + queryset = NullableForeignKeySource.objects.all() + serializer = NullableForeignKeySourceSerializer(queryset, many=True) + expected = [ + {'id': 1, 'name': 'source-1', 'target': None}, + {'id': 2, 'name': 'source-2', 'target': {'id': 1, 'name': 'target-1'}}, + {'id': 3, 'name': 'source-3', 'target': None} + ] + self.assertEqual(serializer.data, expected) + + +class SerializableNullableOneToOneTests(TestCase): + def setUp(self): + target = OneToOneTarget(name='target-1') + target.save() + new_target = OneToOneTarget(name='target-2') + new_target.save() + source = NullableOneToOneSource(name='source-1', target=new_target) + source.save() + + def test_reverse_foreign_key_retrieve_with_null(self): + queryset = OneToOneTarget.objects.all() + serializer = NullableOneToOneTargetSerializer(queryset, many=True) + expected = [ + {'id': 1, 'name': 'target-1', 'nullable_source': None}, + {'id': 2, 'name': 'target-2', 'nullable_source': {'id': 1, 'name': 'source-1'}}, + ] + self.assertEqual(serializer.data, expected)