mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-07-29 01:20:02 +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].
|
||||
|
||||
|
||||
## 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
|
||||
|
|
|
@ -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):
|
||||
|
|
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