This commit is contained in:
Lukas Bünger 2013-04-11 08:28:21 -07:00
commit beb0955286
3 changed files with 226 additions and 0 deletions

View File

@ -394,6 +394,39 @@ Note that reverse generic keys, expressed using the `GenericRelation` field, can
For more information see [the Django documentation on generic relations][generic-relations].
## Recursive relationships
If you want to serialize recursive relationships, you can use the `RecursiveRelatedField`.
For example, given the following model that has a self-referencing foreign key to establish a tree-like structure:
class TreeModel(models.Model):
name = models.CharField(max_length=127)
parent = models.ForeignKey('self', null=True, related_name='children')
def __unicode__(self):
return self.name
You could have the child objects nested recursively with the following serializer:
class TreeSerializer(serializers.ModelSerializer):
children = RecursiveRelatedField(many=True)
class Meta:
model = TreeModel
exclude = ('id', )
By default, the number of recursions is made until no further objects are found (`max_depth=-1`).
However, you can restrict the number of recursions by passing the number of levels as `max_depth` argument:
children = RecursiveRelatedField(many=True, max_depth=1)
Note that as for now the the `RecursiveRelatedField` is read only.
---
## Deprecated APIs

View File

@ -478,6 +478,48 @@ class HyperlinkedIdentityField(Field):
raise Exception('Could not resolve URL for field using view name "%s"' % view_name)
### Recursive relationships
class RecursiveRelatedField(RelatedField):
def __init__(self, max_depth=-1, *args, **kwargs):
super(RecursiveRelatedField, self).__init__(*args, **kwargs)
# Forced read only.
self.max_depth = max_depth
self.read_only = True
def field_to_native(self, obj, field_name):
serializer_class = self.parent.__class__
serializer = serializer_class()
serializer.initialize(self.parent, field_name)
if self.max_depth > -1:
if self.max_depth > 0:
serializer.fields[field_name].max_depth = self.max_depth - 1
else:
return [] if self.many else None
if self.many:
related_manager = getattr(obj, self.source or field_name)
if not obj.__class__ == related_manager.model:
raise Exception('`RecursiveRelatedField` must point at a self-referencing relation.')
queryset = related_manager.all()
return [serializer.to_native(item) for item in queryset]
try:
queryset = getattr(obj, self.source or field_name)
if not obj.__class__ == queryset.__class__:
raise Exception('`RecursiveRelatedField` must point at a self-referencing relation.')
except ObjectDoesNotExist:
return None
return serializer.to_native(queryset)
def to_native(self, value):
# Override to prevent simplifying process as present in `WritableField`.
return value
### Old-style many classes for backwards compat
class ManyRelatedField(RelatedField):

View File

@ -0,0 +1,151 @@
from __future__ import unicode_literals
from django.test import TestCase
from django.db import models
from rest_framework import serializers
from rest_framework.relations import RecursiveRelatedField
class TreeModel(models.Model):
name = models.CharField(max_length=127)
parent = models.ForeignKey('self', null=True, related_name='children')
def __unicode__(self):
return self.name
class TreeSerializer(serializers.ModelSerializer):
children = RecursiveRelatedField(many=True)
class Meta:
model = TreeModel
exclude = ('id', )
class MaxDepthTreeSerializer(serializers.ModelSerializer):
children = RecursiveRelatedField(many=True, max_depth=1)
class Meta:
model = TreeModel
exclude = ('id', )
class ChainModel(models.Model):
name = models.CharField(max_length=127)
previous = models.OneToOneField('self', null=True, related_name='next')
def __unicode__(self):
return self.name
class ChainSerializer(serializers.ModelSerializer):
next = RecursiveRelatedField(many=False)
class Meta:
model = ChainModel
exclude = ('id', )
class TestRecursiveRelatedField(TestCase):
def setUp(self):
self.tree_root = TreeModel.objects.create(name='Tree Root')
tree_depth_1_children = []
for x in range(0, 3):
tree_depth_1_children.append(TreeModel.objects.create(name='Child 1:%d' % x, parent=self.tree_root))
for x in range(0, 2):
TreeModel.objects.create(name='Child 2:%d' % x, parent=tree_depth_1_children[1])
self.chain_root = ChainModel.objects.create(name='Chain Root')
current = self.chain_root
for x in range(0, 3):
chain_link = ChainModel.objects.create(name='Chain link %d' % x, previous=current)
current = chain_link
def test_many(self):
serializer = TreeSerializer(self.tree_root)
expected = {
'children': [
{
'children': [],
'name': 'Child 1:0',
'parent': 1
},
{
'children': [
{
'children': [],
'name': 'Child 2:0',
'parent': 3
},
{
'children': [],
'name': 'Child 2:1',
'parent': 3
}
],
'name': 'Child 1:1',
'parent': 1
},
{
'children': [],
'name': 'Child 1:2',
'parent': 1
}
],
'name': 'Tree Root',
'parent': None
}
self.assertEqual(serializer.data, expected)
def test_one(self):
serializer = ChainSerializer(self.chain_root)
expected = {
'next':
{
'next':
{'next':
{'next': None,
'name': 'Chain link 2',
'previous': 3},
'name': 'Chain link 1',
'previous': 2},
'name': 'Chain link 0',
'previous': 1},
'name': 'Chain Root',
'previous': None
}
self.assertEqual(serializer.data, expected)
def test_max_depth(self):
serializer = MaxDepthTreeSerializer(self.tree_root)
expected = {
'children': [
{
'children': [],
'name': 'Child 1:0',
'parent': 1
},
{
'children': [],
'name': 'Child 1:1',
'parent': 1
},
{
'children': [],
'name': 'Child 1:2',
'parent': 1
}
],
'name': 'Tree Root',
'parent': None
}
self.assertEqual(serializer.data, expected)