mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-07-29 17:39:48 +03:00
Merge 9c95d15cf2
into 80bec363ef
This commit is contained in:
commit
beb0955286
|
@ -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].
|
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
|
## Deprecated APIs
|
||||||
|
|
|
@ -478,6 +478,48 @@ class HyperlinkedIdentityField(Field):
|
||||||
raise Exception('Could not resolve URL for field using view name "%s"' % view_name)
|
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
|
### Old-style many classes for backwards compat
|
||||||
|
|
||||||
class ManyRelatedField(RelatedField):
|
class ManyRelatedField(RelatedField):
|
||||||
|
|
151
rest_framework/tests/relations_recursive.py
Normal file
151
rest_framework/tests/relations_recursive.py
Normal 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)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user