Django MPTT高效地将关系数据序列化为DRF
问题描述:
我有一个类别模型是MPTT模型。这是M2M,以集团和我需要序列树与相关罪名,想象我的类别树是这样的:Django MPTT高效地将关系数据序列化为DRF
Root (related to 1 group)
- Branch (related to 2 groups)
- Leaf (related to 3 groups)
...
所以串行输出应该是这样的:
{
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: []
}]
}]
}
这是我现在的超级低效的实现:
型号
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()
查看
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)
串行
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')
这工作,但也碰到DB与查询的疯狂数量,也有更多的关系,不只是集团。有没有办法让这个效率更高?我如何编写我自己的序列化程序?
答
你肯定会遇到N + 1查询问题,我已经涵盖了in detail in another Stack Overflow answer。我建议阅读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
您可能需要稍微修改这个代码,但总的想法是,你并不总是需要得到祖先,因为你将有祖先链了。
这并不能解决您可能无法优化的Group
查询,但它至少应能减少您的查询。递归查询非常难以优化,他们通常需要大量的计划来确定如何在不返回N + 1情况下最好地获取所需数据。
谢谢详细的序列化方法!我希望那些制作MPTT的巫师在N + 1问题上有一个解决方案来计算:( – WBC 2014-11-24 17:41:30