WBC*_*WBC 7 python django serialization django-mptt django-rest-framework
我有一个 MPTT 模型的类别模型。它是 m2m 到 Group,我需要用相关计数序列化树,想象我的类别树是这样的:
Root (related to 1 group)
- Branch (related to 2 groups)
- Leaf (related to 3 groups)
...
Run Code Online (Sandbox Code Playgroud)
所以序列化的输出看起来像这样:
{
id: 1,
name: 'root1',
full_name: 'root1',
group_count: 6,
children: [
{
id: 2,
name: 'branch1',
full_name: 'root1 - branch1',
group_count: 5,
children: [
{
id: 3,
name: 'leaf1',
full_name: 'root1 - branch1 - leaf1',
group_count: 3,
children: []
}]
}]
}
Run Code Online (Sandbox Code Playgroud)
这是我目前的超级低效实现:
模型
class Category(MPTTModel):
name = ...
parent = ... (related_name='children')
def get_full_name(self):
names = self.get_ancestors(include_self=True).values('name')
full_name = ' - '.join(map(lambda x: x['name'], names))
return full_name
def get_group_count(self):
cats = self.get_descendants(include_self=True)
return Group.objects.filter(categories__in=cats).count()
Run Code Online (Sandbox Code Playgroud)
看法
class CategoryViewSet(ModelViewSet):
def list(self, request):
tree = cache_tree_children(Category.objects.filter(level=0))
serializer = CategorySerializer(tree, many=True)
return Response(serializer.data)
Run Code Online (Sandbox Code Playgroud)
序列化器
class RecursiveField(serializers.Serializer):
def to_native(self, value):
return self.parent.to_native(value)
class CategorySerializer(serializers.ModelSerializer):
children = RecursiveField(many=True, required=False)
full_name = serializers.Field(source='get_full_name')
group_count = serializers.Field(source='get_group_count')
class Meta:
model = Category
fields = ('id', 'name', 'children', 'full_name', 'group_count')
Run Code Online (Sandbox Code Playgroud)
这有效,但也会通过大量查询访问数据库,还有其他关系,而不仅仅是 Group。有没有办法使这个有效?如何编写自己的序列化程序?
您肯定遇到了 N+1 查询问题,我在另一个 Stack Overflow 答案中详细介绍了该问题。我建议阅读 Django 中的优化查询,因为这是一个非常常见的问题。
现在,Django MPTT 还存在一些问题,您需要在 N+1 查询方面解决这些问题。和方法都会创建一个新self.get_ancestors的self.get_descendants查询集,在您的情况下,这会发生在您正在序列化的每个对象上。您可能想寻找一种更好的方法来避免这些问题,我在下面描述了可能的改进。
在您的get_full_name方法中,您调用是self.get_ancestors为了生成正在使用的链。考虑到在生成输出时始终拥有父对象,因此将其移动到SerializerMethodField重用父对象来生成名称可能会受益。像下面这样的东西可能会起作用:
class RecursiveField(serializers.Serializer):
def to_native(self, value):
return CategorySerializer(value, context={"parent": self.parent.object, "parent_serializer": self.parent})
class CategorySerializer(serializers.ModelSerializer):
children = RecursiveField(many=True, required=False)
full_name = SerializerMethodField("get_full_name")
group_count = serializers.Field(source='get_group_count')
class Meta:
model = Category
fields = ('id', 'name', 'children', 'full_name', 'group_count')
def get_full_name(self, obj):
name = obj.name
if "parent" in self.context:
parent = self.context["parent"]
parent_name = self.context["parent_serializer"].get_full_name(parent)
name = "%s - %s" % (parent_name, name, )
return name
Run Code Online (Sandbox Code Playgroud)
您可能需要稍微编辑此代码,但总体思路是您并不总是需要获取祖先,因为您已经拥有祖先链。
这并不能解决Group查询,您可能无法优化查询,但它至少应该减少查询。递归查询非常难以优化,并且通常需要进行大量规划才能弄清楚如何最好地获取所需数据,而不会退回到 N+1 情况。
| 归档时间: |
|
| 查看次数: |
4367 次 |
| 最近记录: |