Skip to content

fix function names and dotted lookups for use in PrimaryKeyRelatedField #694

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 18, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions rest_framework/relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,12 +221,20 @@ def from_native(self, data):
def field_to_native(self, obj, field_name):
if self.many:
# To-many relationship
try:

queryset = None
if not self.source:
# Prefer obj.serializable_value for performance reasons
queryset = obj.serializable_value(self.source or field_name)
except AttributeError:
try:
queryset = obj.serializable_value(field_name)
except AttributeError:
pass
if queryset is None:
# RelatedManager (reverse relationship)
queryset = getattr(obj, self.source or field_name)
source = self.source or field_name
queryset = obj
for component in source.split('.'):
queryset = get_component(queryset, component)

# Forward relationship
return [self.to_native(item.pk) for item in queryset.all()]
Expand Down
55 changes: 54 additions & 1 deletion rest_framework/tests/relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from django.db import models
from django.test import TestCase
from rest_framework import serializers
from rest_framework.tests.models import BlogPost


class NullModel(models.Model):
Expand Down Expand Up @@ -33,7 +34,7 @@ def test_slug_related_field_with_empty_string(self):
self.assertRaises(serializers.ValidationError, field.from_native, [])


class TestManyRelateMixin(TestCase):
class TestManyRelatedMixin(TestCase):
def test_missing_many_to_many_related_field(self):
'''
Regression test for #632
Expand All @@ -45,3 +46,55 @@ def test_missing_many_to_many_related_field(self):
into = {}
field.field_from_native({}, None, 'field_name', into)
self.assertEqual(into['field_name'], [])


# Regression tests for #694 (`source` attribute on related fields)

class RelatedFieldSourceTests(TestCase):
def test_related_manager_source(self):
"""
Relational fields should be able to use manager-returning methods as their source.
"""
BlogPost.objects.create(title='blah')
field = serializers.RelatedField(many=True, source='get_blogposts_manager')

class ClassWithManagerMethod(object):
def get_blogposts_manager(self):
return BlogPost.objects

obj = ClassWithManagerMethod()
value = field.field_to_native(obj, 'field_name')
self.assertEqual(value, ['BlogPost object'])

def test_related_queryset_source(self):
"""
Relational fields should be able to use queryset-returning methods as their source.
"""
BlogPost.objects.create(title='blah')
field = serializers.RelatedField(many=True, source='get_blogposts_queryset')

class ClassWithQuerysetMethod(object):
def get_blogposts_queryset(self):
return BlogPost.objects.all()

obj = ClassWithQuerysetMethod()
value = field.field_to_native(obj, 'field_name')
self.assertEqual(value, ['BlogPost object'])

def test_dotted_source(self):
"""
Source argument should support dotted.source notation.
"""
BlogPost.objects.create(title='blah')
field = serializers.RelatedField(many=True, source='a.b.c')

class ClassWithQuerysetMethod(object):
a = {
'b': {
'c': BlogPost.objects.all()
}
}

obj = ClassWithQuerysetMethod()
value = field.field_to_native(obj, 'field_name')
self.assertEqual(value, ['BlogPost object'])
71 changes: 71 additions & 0 deletions rest_framework/tests/relations_hyperlink.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from rest_framework import serializers
from rest_framework.compat import patterns, url
from rest_framework.tests.models import (
BlogPost,
ManyToManyTarget, ManyToManySource, ForeignKeyTarget, ForeignKeySource,
NullableForeignKeySource, OneToOneTarget, NullableOneToOneSource
)
Expand All @@ -16,6 +17,7 @@ def dummy_view(request, pk):
pass

urlpatterns = patterns('',
url(r'^dummyurl/(?P<pk>[0-9]+)/$', dummy_view, name='dummy-url'),
url(r'^manytomanysource/(?P<pk>[0-9]+)/$', dummy_view, name='manytomanysource-detail'),
url(r'^manytomanytarget/(?P<pk>[0-9]+)/$', dummy_view, name='manytomanytarget-detail'),
url(r'^foreignkeysource/(?P<pk>[0-9]+)/$', dummy_view, name='foreignkeysource-detail'),
Expand Down Expand Up @@ -451,3 +453,72 @@ def test_reverse_foreign_key_retrieve_with_null(self):
{'url': 'http://testserver/onetoonetarget/2/', 'name': 'target-2', 'nullable_source': None},
]
self.assertEqual(serializer.data, expected)


# Regression tests for #694 (`source` attribute on related fields)

class HyperlinkedRelatedFieldSourceTests(TestCase):
urls = 'rest_framework.tests.relations_hyperlink'

def test_related_manager_source(self):
"""
Relational fields should be able to use manager-returning methods as their source.
"""
BlogPost.objects.create(title='blah')
field = serializers.HyperlinkedRelatedField(
many=True,
source='get_blogposts_manager',
view_name='dummy-url',
)
field.context = {'request': request}

class ClassWithManagerMethod(object):
def get_blogposts_manager(self):
return BlogPost.objects

obj = ClassWithManagerMethod()
value = field.field_to_native(obj, 'field_name')
self.assertEqual(value, ['http://testserver/dummyurl/1/'])

def test_related_queryset_source(self):
"""
Relational fields should be able to use queryset-returning methods as their source.
"""
BlogPost.objects.create(title='blah')
field = serializers.HyperlinkedRelatedField(
many=True,
source='get_blogposts_queryset',
view_name='dummy-url',
)
field.context = {'request': request}

class ClassWithQuerysetMethod(object):
def get_blogposts_queryset(self):
return BlogPost.objects.all()

obj = ClassWithQuerysetMethod()
value = field.field_to_native(obj, 'field_name')
self.assertEqual(value, ['http://testserver/dummyurl/1/'])

def test_dotted_source(self):
"""
Source argument should support dotted.source notation.
"""
BlogPost.objects.create(title='blah')
field = serializers.HyperlinkedRelatedField(
many=True,
source='a.b.c',
view_name='dummy-url',
)
field.context = {'request': request}

class ClassWithQuerysetMethod(object):
a = {
'b': {
'c': BlogPost.objects.all()
}
}

obj = ClassWithQuerysetMethod()
value = field.field_to_native(obj, 'field_name')
self.assertEqual(value, ['http://testserver/dummyurl/1/'])
57 changes: 56 additions & 1 deletion rest_framework/tests/relations_pk.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from __future__ import unicode_literals
from django.test import TestCase
from rest_framework import serializers
from rest_framework.tests.models import ManyToManyTarget, ManyToManySource, ForeignKeyTarget, ForeignKeySource, NullableForeignKeySource, OneToOneTarget, NullableOneToOneSource
from rest_framework.tests.models import (
BlogPost, ManyToManyTarget, ManyToManySource, ForeignKeyTarget, ForeignKeySource,
NullableForeignKeySource, OneToOneTarget, NullableOneToOneSource,
)
from rest_framework.compat import six


Expand Down Expand Up @@ -421,3 +424,55 @@ def test_reverse_foreign_key_retrieve_with_null(self):
{'id': 2, 'name': 'target-2', 'nullable_source': 1},
]
self.assertEqual(serializer.data, expected)


# Regression tests for #694 (`source` attribute on related fields)

class PrimaryKeyRelatedFieldSourceTests(TestCase):
def test_related_manager_source(self):
"""
Relational fields should be able to use manager-returning methods as their source.
"""
BlogPost.objects.create(title='blah')
field = serializers.PrimaryKeyRelatedField(many=True, source='get_blogposts_manager')

class ClassWithManagerMethod(object):
def get_blogposts_manager(self):
return BlogPost.objects

obj = ClassWithManagerMethod()
value = field.field_to_native(obj, 'field_name')
self.assertEqual(value, [1])

def test_related_queryset_source(self):
"""
Relational fields should be able to use queryset-returning methods as their source.
"""
BlogPost.objects.create(title='blah')
field = serializers.PrimaryKeyRelatedField(many=True, source='get_blogposts_queryset')

class ClassWithQuerysetMethod(object):
def get_blogposts_queryset(self):
return BlogPost.objects.all()

obj = ClassWithQuerysetMethod()
value = field.field_to_native(obj, 'field_name')
self.assertEqual(value, [1])

def test_dotted_source(self):
"""
Source argument should support dotted.source notation.
"""
BlogPost.objects.create(title='blah')
field = serializers.PrimaryKeyRelatedField(many=True, source='a.b.c')

class ClassWithQuerysetMethod(object):
a = {
'b': {
'c': BlogPost.objects.all()
}
}

obj = ClassWithQuerysetMethod()
value = field.field_to_native(obj, 'field_name')
self.assertEqual(value, [1])
17 changes: 0 additions & 17 deletions rest_framework/tests/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -871,23 +871,6 @@ class NullableSourceSerializer(serializers.Serializer):

self.assertEqual(serializer.data, expected)

def test_queryset_nested_traversal(self):
"""
Relational fields should be able to use methods as their source.
"""
BlogPost.objects.create(title='blah')

class QuerysetMethodSerializer(serializers.Serializer):
blogposts = serializers.RelatedField(many=True, source='get_all_blogposts')

class ClassWithQuerysetMethod(object):
def get_all_blogposts(self):
return BlogPost.objects

obj = ClassWithQuerysetMethod()
serializer = QuerysetMethodSerializer(obj)
self.assertEqual(serializer.data, {'blogposts': ['BlogPost object']})


class SerializerMethodFieldTests(TestCase):
def setUp(self):
Expand Down