From 213981cef394c6f7603c24b9a51096ffb56f6024 Mon Sep 17 00:00:00 2001 From: Mark Aaron Shirley Date: Fri, 4 Jan 2013 21:11:03 +0100 Subject: [PATCH] Handle ObjectDoesNotExist exceptions when serializing null reverse one-to-one --- rest_framework/relations.py | 10 ++++++++-- rest_framework/serializers.py | 17 ++++++++++------- rest_framework/tests/models.py | 5 +++++ rest_framework/tests/relations_nested.py | 22 +++++++++++++++++++++- 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 686dcf04e..6c1d4f5ba 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -100,7 +100,10 @@ class RelatedField(WritableField): ### Regular serializer stuff... def field_to_native(self, obj, field_name): - value = getattr(obj, self.source or field_name) + try: + value = getattr(obj, self.source or field_name) + except ObjectDoesNotExist: + return None return self.to_native(value) def field_from_native(self, data, files, field_name, into): @@ -202,7 +205,10 @@ class PrimaryKeyRelatedField(RelatedField): pk = obj.serializable_value(self.source or field_name) except AttributeError: # RelatedObject (reverse relationship) - obj = getattr(obj, self.source or field_name) + try: + obj = getattr(obj, self.source or field_name) + except ObjectDoesNotExist: + return None return self.to_native(obj.pk) # Forward relationship return self.to_native(pk) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index bd54db4ca..3391a262d 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -293,15 +293,18 @@ class BaseSerializer(Field): Override default so that we can apply ModelSerializer as a nested field to relationships. """ - if self.source: - for component in self.source.split('.'): - obj = getattr(obj, component) + try: + if self.source: + for component in self.source.split('.'): + obj = getattr(obj, component) + if is_simple_callable(obj): + obj = obj() + else: + obj = getattr(obj, field_name) if is_simple_callable(obj): obj = obj() - else: - obj = getattr(obj, field_name) - if is_simple_callable(obj): - obj = value() + except ObjectDoesNotExist: + return None # If the object has an "all" method, assume it's a relationship if is_simple_callable(getattr(obj, 'all', None)): diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py index 59c350740..34cdbff31 100644 --- a/rest_framework/tests/models.py +++ b/rest_framework/tests/models.py @@ -205,3 +205,8 @@ class NullableForeignKeySource(RESTFrameworkModel): name = models.CharField(max_length=100) target = models.ForeignKey(ForeignKeyTarget, null=True, blank=True, related_name='nullable_sources') + +class NullableOneToOneSource(RESTFrameworkModel): + name = models.CharField(max_length=100) + target = models.OneToOneField(ForeignKeyTarget, null=True, blank=True, + related_name='nullable_source') diff --git a/rest_framework/tests/relations_nested.py b/rest_framework/tests/relations_nested.py index 5710c1ef4..808399f74 100644 --- a/rest_framework/tests/relations_nested.py +++ b/rest_framework/tests/relations_nested.py @@ -1,7 +1,7 @@ from django.db import models from django.test import TestCase from rest_framework import serializers -from rest_framework.tests.models import ForeignKeyTarget, ForeignKeySource, NullableForeignKeySource +from rest_framework.tests.models import ForeignKeyTarget, ForeignKeySource, NullableForeignKeySource, NullableOneToOneSource class ForeignKeySourceSerializer(serializers.ModelSerializer): @@ -28,6 +28,13 @@ class NullableForeignKeySourceSerializer(serializers.ModelSerializer): model = NullableForeignKeySource +class NullableForeignKeyTargetSerializer(serializers.ModelSerializer): + nullable_source = serializers.PrimaryKeyRelatedField() + + class Meta: + model = ForeignKeyTarget + + class ReverseForeignKeyTests(TestCase): def setUp(self): target = ForeignKeyTarget(name='target-1') @@ -67,6 +74,10 @@ class NestedNullableForeignKeyTests(TestCase): def setUp(self): target = ForeignKeyTarget(name='target-1') target.save() + new_target = ForeignKeyTarget(name='target-2') + new_target.save() + one_source = NullableOneToOneSource(name='one-source-1', target=target) + one_source.save() for idx in range(1, 4): if idx == 3: target = None @@ -82,3 +93,12 @@ class NestedNullableForeignKeyTests(TestCase): {'id': 3, 'name': u'source-3', 'target': None}, ] self.assertEquals(serializer.data, expected) + + def test_reverse_foreign_key_retrieve_with_null(self): + queryset = ForeignKeyTarget.objects.all() + serializer = NullableForeignKeyTargetSerializer(queryset) + expected = [ + {'id': 1, 'name': u'target-1', 'nullable_source': 1}, + {'id': 2, 'name': u'target-2', 'nullable_source': None}, + ] + self.assertEquals(serializer.data, expected)