python - Django MPTT 使用 DRF 高效序列化关系数据

标签 python django serialization django-rest-framework django-mptt

我有一个类别模型,它是一个 MPTT 模型。它是 Group 的 m2m,我需要用相关计数序列化树,想象一下我的类别树是这样的:

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: []
        }]
    }]
}

这是我目前 super 低效的实现:

型号

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')

这行得通,但也会通过大量查询访问数据库,而且还有其他关系,而不仅仅是组。有没有办法提高效率?如何编写自己的序列化程序?

最佳答案

您肯定遇到了 N+1 查询问题,我已经介绍了 in detail in another Stack Overflow answer .我建议阅读有关在 Django 中优化查询的内容,因为这是一个非常常见的问题。

现在,就 N+1 查询而言,Django MPTT 也有一些您需要解决的问题。 self.get_ancestorsself.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 情况。

关于python - Django MPTT 使用 DRF 高效序列化关系数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27073115/

相关文章:

django - 使用 Django 逐行(或特定行 #)解析 textarea

django - 外键在对象上不可用

.net - 为什么 SerializableAttribute 不包含在 Silverlight 版本的 FCL 中?

控制台中出现 Python 错误,但文件 : unexpected character after line continuation character 中没有

python - 一组动态成员的自然命名方案

python - 如何生成一个数字的所有可能的除数积?

python - 凯拉斯 |类型错误 : __init__() missing 1 required positional argument: 'nb_col'

django - DRF PrimaryRelatedField 写入时和 NestedSerializer 读取时?

java - GZIP Streams 的序列化/反序列化不一致

c# - 在 C# 中以顺序方式使用 BinaryFormatter