Django 休息框架嵌套的自引用对象

我有一个这样的模型:

class Category(models.Model):
parentCategory = models.ForeignKey('self', blank=True, null=True, related_name='subcategories')
name = models.CharField(max_length=200)
description = models.CharField(max_length=500)

我设法使用序列化程序获得所有类别的扁平 json 表示:

class CategorySerializer(serializers.HyperlinkedModelSerializer):
parentCategory = serializers.PrimaryKeyRelatedField()
subcategories = serializers.ManyRelatedField()


class Meta:
model = Category
fields = ('parentCategory', 'name', 'description', 'subcategories')

现在我要做的是让子类别列表使用内联 json 表示子类别而不是它们的 id。如何使用 django-rest-Framework 实现这一点呢?我试图在文件中找到它,但似乎不完整。

65940 次浏览

不要使用 ManyRelatedField,而是使用嵌套的序列化程序作为字段:

class SubCategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('name', 'description')


class CategorySerializer(serializers.ModelSerializer):
parentCategory = serializers.PrimaryKeyRelatedField()
subcategories = serializers.SubCategorySerializer()


class Meta:
model = Category
fields = ('parentCategory', 'name', 'description', 'subcategories')

If you want to deal with arbitrarily nested fields you should take a look at the 自定义默认字段 part of the docs. You can't currently directly declare a serializer as a field on itself, but you can use these methods to override what fields are used by default.

class CategorySerializer(serializers.ModelSerializer):
parentCategory = serializers.PrimaryKeyRelatedField()


class Meta:
model = Category
fields = ('parentCategory', 'name', 'description', 'subcategories')


def get_related_field(self, model_field):
# Handles initializing the `subcategories` field
return CategorySerializer()

事实上,正如你所注意到的,上面的内容并不完全正确。 这有点像一种技巧,但是您可以尝试在序列化程序已经声明之后添加字段。

class CategorySerializer(serializers.ModelSerializer):
parentCategory = serializers.PrimaryKeyRelatedField()


class Meta:
model = Category
fields = ('parentCategory', 'name', 'description', 'subcategories')


CategorySerializer.base_fields['subcategories'] = CategorySerializer()

需要添加一种声明递归关系的机制。


编辑 : 注意,现在有一个第三方软件包专门处理这种用例。

我最近也遇到了同样的问题,并且想出了一个迄今为止似乎行之有效的解决方案,即使对于任意的深度也是如此。 The solution is a small modification of the one from Tom Christie:

class CategorySerializer(serializers.ModelSerializer):
parentCategory = serializers.PrimaryKeyRelatedField()


def convert_object(self, obj):
#Add any self-referencing fields here (if not already done)
if not self.fields.has_key('subcategories'):
self.fields['subcategories'] = CategorySerializer()
return super(CategorySerializer,self).convert_object(obj)


class Meta:
model = Category
#do NOT include self-referencing fields here
#fields = ('parentCategory', 'name', 'description', 'subcategories')
fields = ('parentCategory', 'name', 'description')
#This is not needed
#CategorySerializer.base_fields['subcategories'] = CategorySerializer()

我不确定它是否能在 任何的情况下可靠地工作,虽然..。

Another option would be to recurse in the view that serializes your model. Here's an example:

class DepartmentSerializer(ModelSerializer):
class Meta:
model = models.Department




class DepartmentViewSet(ModelViewSet):
model = models.Department
serializer_class = DepartmentSerializer


def serialize_tree(self, queryset):
for obj in queryset:
data = self.get_serializer(obj).data
data['children'] = self.serialize_tree(obj.children.all())
yield data


def list(self, request):
queryset = self.get_queryset().filter(level=0)
data = self.serialize_tree(queryset)
return Response(data)


def retrieve(self, request, pk=None):
self.object = self.get_object()
data = self.serialize_tree([self.object])
return Response(data)

比赛已经迟到了,但我的解决办法是。假设我正在连载一个 Blah,其中包含多个 Blah 类型的子节点。

    class RecursiveField(serializers.Serializer):
def to_native(self, value):
return self.parent.to_native(value)

使用这个字段,我可以序列化具有许多子对象的递归定义的对象

    class BlahSerializer(serializers.Serializer):
name = serializers.Field()
child_blahs = RecursiveField(many=True)

我为 DRF3.0编写了一个递归字段,并将其打包为 pip Https://pypi.python.org/pypi/djangorestframework-recursive/

在升级到 Django REST 框架3.0.0之前,@wjin 的解决方案对我来说非常有用,但是它弃用了 to_native。这是我的 DRF 3.0解决方案,稍作修改。

假设您有一个具有自引用字段的模型,例如,在一个称为“答复”的属性中有线程注释。您拥有此注释线程的树表示形式,并且希望序列化该树

首先,定义可重用的 RecursiveField 类

class RecursiveField(serializers.Serializer):
def to_representation(self, value):
serializer = self.parent.parent.__class__(value, context=self.context)
return serializer.data

然后,对于序列化器,使用 RecursiveField 序列化“答复”的值

class CommentSerializer(serializers.Serializer):
replies = RecursiveField(many=True)


class Meta:
model = Comment
fields = ('replies, ....)

很简单,而且您只需要4行代码就可以得到一个可重用的解决方案。

注意: 如果您的数据结构比树更复杂,比如 有向无环图(FANCY!)然后你可以试试@wjin 的软件包——看看他的解决方案。但是对于这个基于 MPTTModel 的树的解决方案,我没有遇到任何问题。

This is an adaptation from the caipirginka solution that works on drf 3.0.5 and django 2.7.4:

class CategorySerializer(serializers.ModelSerializer):


def to_representation(self, obj):
#Add any self-referencing fields here (if not already done)
if 'branches' not in self.fields:
self.fields['subcategories'] = CategorySerializer(obj, many=True)
return super(CategorySerializer, self).to_representation(obj)


class Meta:
model = Category
fields = ('id', 'description', 'parentCategory')

请注意,第6行中的 Category orySerializer 是使用 object 和 many = True 属性调用的。

我想我也来凑凑热闹!

通过 WjinMark Chackerian,我创建了一个更通用的解决方案,它适用于直接的树状模型和具有通过模型的树结构。我不确定这是否属于它自己的答案,但我想我还是把它放在某个地方吧。我包含了一个 max _ deep 选项,它可以防止无限递归,在最深层次上,子元素被表示为 URLS (如果您希望它不是一个 url,那么这是最后一个 else 子句)。

from rest_framework.reverse import reverse
from rest_framework import serializers


class RecursiveField(serializers.Serializer):
"""
Can be used as a field within another serializer,
to produce nested-recursive relationships. Works with
through models, and limited and/or arbitrarily deep trees.
"""
def __init__(self, **kwargs):
self._recurse_through = kwargs.pop('through_serializer', None)
self._recurse_max = kwargs.pop('max_depth', None)
self._recurse_view = kwargs.pop('reverse_name', None)
self._recurse_attr = kwargs.pop('reverse_attr', None)
self._recurse_many = kwargs.pop('many', False)


super(RecursiveField, self).__init__(**kwargs)


def to_representation(self, value):
parent = self.parent
if isinstance(parent, serializers.ListSerializer):
parent = parent.parent


lvl = getattr(parent, '_recurse_lvl', 1)
max_lvl = self._recurse_max or getattr(parent, '_recurse_max', None)


# Defined within RecursiveField(through_serializer=A)
serializer_class = self._recurse_through
is_through = has_through = True


# Informed by previous serializer (for through m2m)
if not serializer_class:
is_through = False
serializer_class = getattr(parent, '_recurse_next', None)


# Introspected for cases without through models.
if not serializer_class:
has_through = False
serializer_class = parent.__class__


if is_through or not max_lvl or lvl <= max_lvl:
serializer = serializer_class(
value, many=self._recurse_many, context=self.context)


# Propagate hereditary attributes.
serializer._recurse_lvl = lvl + is_through or not has_through
serializer._recurse_max = max_lvl


if is_through:
# Delay using parent serializer till next lvl.
serializer._recurse_next = parent.__class__


return serializer.data
else:
view = self._recurse_view or self.context['request'].resolver_match.url_name
attr = self._recurse_attr or 'id'
return reverse(view, args=[getattr(value, attr)],
request=self.context['request'])

在 Django REST 框架3.3.1中,我需要以下代码来将子类别添加到类别中:

Models.py

class Category(models.Model):


id = models.AutoField(
primary_key=True
)


name = models.CharField(
max_length=45,
blank=False,
null=False
)


parentid = models.ForeignKey(
'self',
related_name='subcategories',
blank=True,
null=True
)


class Meta:
db_table = 'Categories'

序列化器 py

class SubcategorySerializer(serializers.ModelSerializer):


class Meta:
model = Category
fields = ('id', 'name', 'parentid')




class CategorySerializer(serializers.ModelSerializer):
subcategories = SubcategorySerializer(many=True, read_only=True)


class Meta:
model = Category
fields = ('id', 'name', 'parentid', 'subcategories')

使用 Django REST Framework 3.3.2的另一个选项是:

class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('id', 'name', 'parentid', 'subcategories')


def get_fields(self):
fields = super(CategorySerializer, self).get_fields()
fields['subcategories'] = CategorySerializer(many=True)
return fields

我能够使用 serializers.SerializerMethodField实现这个结果。我不知道这是不是最好的办法,但对我来说很有效:

class CategorySerializer(serializers.ModelSerializer):


subcategories = serializers.SerializerMethodField(
read_only=True, method_name="get_child_categories")


class Meta:
model = Category
fields = [
'name',
'category_id',
'subcategories',
]


def get_child_categories(self, obj):
""" self referral field """
serializer = CategorySerializer(
instance=obj.subcategories_set.all(),
many=True
)
return serializer.data

这个解决方案与这里提到的其他解决方案几乎相似,但是在根级别的子重复问题方面略有不同(如果您认为这是一个问题的话)。举个例子

class RecursiveSerializer(serializers.Serializer):
def to_representation(self, value):
serializer = self.parent.parent.__class__(value, context=self.context)
return serializer.data


class CategoryListSerializer(ModelSerializer):
sub_category = RecursiveSerializer(many=True, read_only=True)


class Meta:
model = Category
fields = (
'name',
'slug',
'parent',
'sub_category'
)

如果你有这样的风景

class CategoryListAPIView(ListAPIView):
queryset = Category.objects.all()
serializer_class = CategoryListSerializer

这将产生以下结果,

[
{
"name": "parent category",
"slug": "parent-category",
"parent": null,
"sub_category": [
{
"name": "child category",
"slug": "child-category",
"parent": 20,
"sub_category": []
}
]
},
{
"name": "child category",
"slug": "child-category",
"parent": 20,
"sub_category": []
}
]

Here the parent category has a child category and the json representation is exactly what we want it to be represent.

但你可以看到 child category在根层次上的重复。

正如一些人在评论部分问上述张贴的答案,我们怎样才能从根本上阻止这种孩子的重复,只是过滤您的查询集与 parent=None,如下所示

class CategoryListAPIView(ListAPIView):
queryset = Category.objects.filter(parent=None)
serializer_class = CategoryListSerializer

就能解决问题。

注意: 这个答案可能与这个问题没有直接关系,但是这个问题在某种程度上是相关的。而且这种使用 RecursiveSerializer的方法代价很高。如果您使用其他性能易受影响的选项,效果会更好。