mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-11-04 01:47:59 +03:00 
			
		
		
		
	Merge pull request #553 from maspwr/null-one-to-one
Handle ObjectDoesNotExist exceptions when serializing null reverse one-to-one
This commit is contained in:
		
						commit
						394a26f833
					
				| 
						 | 
				
			
			@ -21,6 +21,7 @@ Major version numbers (x.0.0) are reserved for project milestones.  No major poi
 | 
			
		|||
* Deprecate django.utils.simplejson in favor of Python 2.6's built-in json module.
 | 
			
		||||
* Bugfix: Validation errors instead of exceptions when serializers receive incorrect types.
 | 
			
		||||
* Bugfix: Validation errors instead of exceptions when related fields receive incorrect types.
 | 
			
		||||
* Bugfix: Handle ObjectDoesNotExist exception when serializing null reverse one-to-one
 | 
			
		||||
 | 
			
		||||
### 2.1.15
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -101,7 +101,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):
 | 
			
		||||
| 
						 | 
				
			
			@ -211,7 +214,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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -298,15 +298,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)):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -205,3 +205,14 @@ class NullableForeignKeySource(RESTFrameworkModel):
 | 
			
		|||
    name = models.CharField(max_length=100)
 | 
			
		||||
    target = models.ForeignKey(ForeignKeyTarget, null=True, blank=True,
 | 
			
		||||
                               related_name='nullable_sources')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# OneToOne
 | 
			
		||||
class OneToOneTarget(RESTFrameworkModel):
 | 
			
		||||
    name = models.CharField(max_length=100)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NullableOneToOneSource(RESTFrameworkModel):
 | 
			
		||||
    name = models.CharField(max_length=100)
 | 
			
		||||
    target = models.OneToOneField(OneToOneTarget, null=True, blank=True,
 | 
			
		||||
                                  related_name='nullable_source')
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,7 @@ from django.db import models
 | 
			
		|||
from django.test import TestCase
 | 
			
		||||
from rest_framework import serializers
 | 
			
		||||
from rest_framework.compat import patterns, url
 | 
			
		||||
from rest_framework.tests.models import ManyToManyTarget, ManyToManySource, ForeignKeyTarget, ForeignKeySource
 | 
			
		||||
from rest_framework.tests.models import ManyToManyTarget, ManyToManySource, ForeignKeyTarget, ForeignKeySource, NullableForeignKeySource, OneToOneTarget, NullableOneToOneSource
 | 
			
		||||
 | 
			
		||||
def dummy_view(request, pk):
 | 
			
		||||
    pass
 | 
			
		||||
| 
						 | 
				
			
			@ -13,6 +13,8 @@ urlpatterns = patterns('',
 | 
			
		|||
    url(r'^foreignkeysource/(?P<pk>[0-9]+)/$', dummy_view, name='foreignkeysource-detail'),
 | 
			
		||||
    url(r'^foreignkeytarget/(?P<pk>[0-9]+)/$', dummy_view, name='foreignkeytarget-detail'),
 | 
			
		||||
    url(r'^nullableforeignkeysource/(?P<pk>[0-9]+)/$', dummy_view, name='nullableforeignkeysource-detail'),
 | 
			
		||||
    url(r'^onetoonetarget/(?P<pk>[0-9]+)/$', dummy_view, name='onetoonetarget-detail'),
 | 
			
		||||
    url(r'^nullableonetoonesource/(?P<pk>[0-9]+)/$', dummy_view, name='nullableonetoonesource-detail'),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
class ManyToManyTargetSerializer(serializers.HyperlinkedModelSerializer):
 | 
			
		||||
| 
						 | 
				
			
			@ -40,18 +42,19 @@ class ForeignKeySourceSerializer(serializers.HyperlinkedModelSerializer):
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
# Nullable ForeignKey
 | 
			
		||||
 | 
			
		||||
class NullableForeignKeySource(models.Model):
 | 
			
		||||
    name = models.CharField(max_length=100)
 | 
			
		||||
    target = models.ForeignKey(ForeignKeyTarget, null=True, blank=True,
 | 
			
		||||
                               related_name='nullable_sources')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NullableForeignKeySourceSerializer(serializers.HyperlinkedModelSerializer):
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = NullableForeignKeySource
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# OneToOne
 | 
			
		||||
class NullableOneToOneTargetSerializer(serializers.HyperlinkedModelSerializer):
 | 
			
		||||
    nullable_source = serializers.HyperlinkedRelatedField(view_name='nullableonetoonesource-detail')
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = OneToOneTarget
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# TODO: Add test that .data cannot be accessed prior to .is_valid
 | 
			
		||||
 | 
			
		||||
class HyperlinkedManyToManyTests(TestCase):
 | 
			
		||||
| 
						 | 
				
			
			@ -409,3 +412,24 @@ class HyperlinkedNullableForeignKeyTests(TestCase):
 | 
			
		|||
    #         {'id': 2, 'name': u'target-2', 'sources': []},
 | 
			
		||||
    #     ]
 | 
			
		||||
    #     self.assertEquals(serializer.data, expected)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HyperlinkedNullableOneToOneTests(TestCase):
 | 
			
		||||
    urls = 'rest_framework.tests.relations_hyperlink'
 | 
			
		||||
 | 
			
		||||
    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=target)
 | 
			
		||||
        source.save()
 | 
			
		||||
 | 
			
		||||
    def test_reverse_foreign_key_retrieve_with_null(self):
 | 
			
		||||
        queryset = OneToOneTarget.objects.all()
 | 
			
		||||
        serializer = NullableOneToOneTargetSerializer(queryset)
 | 
			
		||||
        expected = [
 | 
			
		||||
            {'url': '/onetoonetarget/1/', 'name': u'target-1', 'nullable_source': '/nullableonetoonesource/1/'},
 | 
			
		||||
            {'url': '/onetoonetarget/2/', 'name': u'target-2', 'nullable_source': None},
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEquals(serializer.data, expected)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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, OneToOneTarget, NullableOneToOneSource
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ForeignKeySourceSerializer(serializers.ModelSerializer):
 | 
			
		||||
| 
						 | 
				
			
			@ -28,6 +28,18 @@ class NullableForeignKeySourceSerializer(serializers.ModelSerializer):
 | 
			
		|||
        model = NullableForeignKeySource
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NullableOneToOneSourceSerializer(serializers.ModelSerializer):
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = NullableOneToOneSource
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NullableOneToOneTargetSerializer(serializers.ModelSerializer):
 | 
			
		||||
    nullable_source = NullableOneToOneSourceSerializer()
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = OneToOneTarget
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ReverseForeignKeyTests(TestCase):
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        target = ForeignKeyTarget(name='target-1')
 | 
			
		||||
| 
						 | 
				
			
			@ -82,3 +94,22 @@ class NestedNullableForeignKeyTests(TestCase):
 | 
			
		|||
            {'id': 3, 'name': u'source-3', 'target': None},
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEquals(serializer.data, expected)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NestedNullableOneToOneTests(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=target)
 | 
			
		||||
        source.save()
 | 
			
		||||
 | 
			
		||||
    def test_reverse_foreign_key_retrieve_with_null(self):
 | 
			
		||||
        queryset = OneToOneTarget.objects.all()
 | 
			
		||||
        serializer = NullableOneToOneTargetSerializer(queryset)
 | 
			
		||||
        expected = [
 | 
			
		||||
            {'id': 1, 'name': u'target-1', 'nullable_source': {'id': 1, 'name': u'source-1', 'target': 1}},
 | 
			
		||||
            {'id': 2, 'name': u'target-2', 'nullable_source': None},
 | 
			
		||||
        ]
 | 
			
		||||
        self.assertEquals(serializer.data, expected)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 ManyToManyTarget, ManyToManySource, ForeignKeyTarget, ForeignKeySource, NullableForeignKeySource
 | 
			
		||||
from rest_framework.tests.models import ManyToManyTarget, ManyToManySource, ForeignKeyTarget, ForeignKeySource, NullableForeignKeySource, OneToOneTarget, NullableOneToOneSource
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ManyToManyTargetSerializer(serializers.ModelSerializer):
 | 
			
		||||
| 
						 | 
				
			
			@ -33,6 +33,14 @@ class NullableForeignKeySourceSerializer(serializers.ModelSerializer):
 | 
			
		|||
        model = NullableForeignKeySource
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# OneToOne
 | 
			
		||||
class NullableOneToOneTargetSerializer(serializers.ModelSerializer):
 | 
			
		||||
    nullable_source = serializers.PrimaryKeyRelatedField()
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = OneToOneTarget
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# TODO: Add test that .data cannot be accessed prior to .is_valid
 | 
			
		||||
 | 
			
		||||
class PKManyToManyTests(TestCase):
 | 
			
		||||
| 
						 | 
				
			
			@ -383,3 +391,22 @@ class PKNullableForeignKeyTests(TestCase):
 | 
			
		|||
    #         {'id': 2, 'name': u'target-2', 'sources': []},
 | 
			
		||||
    #     ]
 | 
			
		||||
    #     self.assertEquals(serializer.data, expected)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PKNullableOneToOneTests(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=target)
 | 
			
		||||
        source.save()
 | 
			
		||||
 | 
			
		||||
    def test_reverse_foreign_key_retrieve_with_null(self):
 | 
			
		||||
        queryset = OneToOneTarget.objects.all()
 | 
			
		||||
        serializer = NullableOneToOneTargetSerializer(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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user