mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-11 04:07:39 +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