diff options
| author | Tom Christie | 2013-05-18 03:38:36 -0700 | 
|---|---|---|
| committer | Tom Christie | 2013-05-18 03:38:36 -0700 | 
| commit | 9fe6a103ec8d43eded7089f7d0c75a49ca45bb3b (patch) | |
| tree | c146c9d9f5204e6548a16cb7dd7cffc248f56b75 | |
| parent | a73c16b85f79aeb9139734a64623b49bc169fce9 (diff) | |
| parent | c992b600f7b0aefb156cddb5e27b438ccc316b39 (diff) | |
| download | django-rest-framework-9fe6a103ec8d43eded7089f7d0c75a49ca45bb3b.tar.bz2 | |
Merge pull request #694 from craigds/master
fix function names and dotted lookups for use in PrimaryKeyRelatedField
| -rw-r--r-- | rest_framework/relations.py | 16 | ||||
| -rw-r--r-- | rest_framework/tests/relations.py | 55 | ||||
| -rw-r--r-- | rest_framework/tests/relations_hyperlink.py | 71 | ||||
| -rw-r--r-- | rest_framework/tests/relations_pk.py | 57 | ||||
| -rw-r--r-- | rest_framework/tests/serializer.py | 17 | 
5 files changed, 193 insertions, 23 deletions
diff --git a/rest_framework/relations.py b/rest_framework/relations.py index c4b790d4..884b954c 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -221,12 +221,20 @@ class PrimaryKeyRelatedField(RelatedField):      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()] diff --git a/rest_framework/tests/relations.py b/rest_framework/tests/relations.py index cbf93c65..d19219c9 100644 --- a/rest_framework/tests/relations.py +++ b/rest_framework/tests/relations.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals  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): @@ -33,7 +34,7 @@ class FieldTests(TestCase):          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 @@ -45,3 +46,55 @@ class TestManyRelateMixin(TestCase):          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']) diff --git a/rest_framework/tests/relations_hyperlink.py b/rest_framework/tests/relations_hyperlink.py index b1eed9a7..b3efbf52 100644 --- a/rest_framework/tests/relations_hyperlink.py +++ b/rest_framework/tests/relations_hyperlink.py @@ -4,6 +4,7 @@ from django.test.client import RequestFactory  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  ) @@ -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'), @@ -451,3 +453,72 @@ class HyperlinkedNullableOneToOneTests(TestCase):              {'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/']) diff --git a/rest_framework/tests/relations_pk.py b/rest_framework/tests/relations_pk.py index 5ce8b567..0f8c5247 100644 --- a/rest_framework/tests/relations_pk.py +++ b/rest_framework/tests/relations_pk.py @@ -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 @@ -421,3 +424,55 @@ class PKNullableOneToOneTests(TestCase):              {'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]) diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index db3881f9..34acbaab 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -871,23 +871,6 @@ class RelatedTraversalTest(TestCase):          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):  | 
