Repository: encode/django-rest-framework

Issue:
ModelSerializer does not traverse through related object using '.' notation
In follow up to this thread:
https://groups.google.com/forum/?fromgroups=#!topic/django-rest-framework/H5C758KGy0I

Fixing this should allow more flexibility when defining nested serializers.

Going to try to submit a fix + test for this.



Discussion:
Great, thanks Pavel.

On 29 Nov 2012, at 18:03, Pavel Savchenko notifications@github.com wrote:

> In follow up to this thread:
> https://groups.google.com/forum/?fromgroups=#!topic/django-rest-framework/H5C758KGy0I
> 
> Fixing this should allow more flexibility when defining nested serializers.
> 
> Going to try to submit a fix + test for this.
> 
> —
> Reply to this email directly or view it on GitHub.


Resolving patch:
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -272,10 +272,18 @@ def field_to_native(self, obj, field_name):
         Override default so that we can apply ModelSerializer as a nested
         field to relationships.
         """
-        obj = getattr(obj, self.source or field_name)
 
-        if is_simple_callable(obj):
-            obj = obj()
+        if self.source:
+            value = obj
+            for component in self.source.split('.'):
+                value = getattr(value, component)
+                if is_simple_callable(value):
+                    value = value()
+            obj = value
+        else:
+            value = getattr(obj, field_name)
+            if is_simple_callable(value):
+                obj = value()
 
         # If the object has an "all" method, assume it's a relationship
         if is_simple_callable(getattr(obj, 'all', None)):


Test patch:
diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py
--- a/rest_framework/tests/models.py
+++ b/rest_framework/tests/models.py
@@ -124,8 +124,21 @@ class ActionItem(RESTFrameworkModel):
 
 
 # Models for reverse relations
+class Person(RESTFrameworkModel):
+    name = models.CharField(max_length=10)
+    age = models.IntegerField(null=True, blank=True)
+
+    @property
+    def info(self):
+        return {
+            'name': self.name,
+            'age': self.age,
+        }
+
+
 class BlogPost(RESTFrameworkModel):
     title = models.CharField(max_length=100)
+    writer = models.ForeignKey(Person, null=True, blank=True)
 
     def get_first_comment(self):
         return self.blogpostcomment_set.all()[0]
@@ -145,18 +158,6 @@ class Photo(RESTFrameworkModel):
     album = models.ForeignKey(Album)
 
 
-class Person(RESTFrameworkModel):
-    name = models.CharField(max_length=10)
-    age = models.IntegerField(null=True, blank=True)
-
-    @property
-    def info(self):
-        return {
-            'name': self.name,
-            'age': self.age,
-        }
-
-
 # Model for issue #324
 class BlankFieldModel(RESTFrameworkModel):
     title = models.CharField(max_length=100, blank=True)
diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -560,6 +560,47 @@ class BlogPostSerializer(serializers.Serializer):
         self.assertEqual(serializer.data, expected)
 
 
+class RelatedTraversalTest(TestCase):
+    def test_nested_traversal(self):
+        user = Person.objects.create(name="django")
+        post = BlogPost.objects.create(title="Test blog post", writer=user)
+        post.blogpostcomment_set.create(text="I love this blog post")
+
+        from rest_framework.tests.models import BlogPostComment
+
+        class PersonSerializer(serializers.ModelSerializer):
+            class Meta:
+                model = Person
+                fields = ("name", "age")
+
+        class BlogPostCommentSerializer(serializers.ModelSerializer):
+            class Meta:
+                model = BlogPostComment
+                fields = ("text", "post_owner")
+
+            text = serializers.CharField()
+            post_owner = PersonSerializer(source='blog_post.writer')
+
+        class BlogPostSerializer(serializers.Serializer):
+            title = serializers.CharField()
+            comments = BlogPostCommentSerializer(source='blogpostcomment_set')
+
+        serializer = BlogPostSerializer(instance=post)
+
+        expected = {
+            'title': u'Test blog post',
+            'comments': [{
+                'text': u'I love this blog post',
+                'post_owner': {
+                    "name": u"django",
+                    "age": None
+                }
+            }]
+        }
+
+        self.assertEqual(serializer.data, expected)
+
+
 class SerializerMethodFieldTests(TestCase):
     def setUp(self):