首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Django MPTT高效地使用DRF序列化关系数据

Django MPTT高效地使用DRF序列化关系数据
EN

Stack Overflow用户
提问于 2014-11-22 01:39:49
回答 2查看 5.6K关注 0票数 7

我有一个类别模型,也就是MPTT模型。它是m2m to Group,我需要用相关计数序列化树,假设我的分类树是这样的:

代码语言:javascript
运行
复制
Root (related to 1 group)
 - Branch (related to 2 groups) 
    - Leaf (related to 3 groups)
...

因此,序列化的输出如下所示:

代码语言:javascript
运行
复制
{ 
    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: []
        }]
    }]
}

这是我目前效率极低的实现:

模型

代码语言:javascript
运行
复制
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()

视图

代码语言:javascript
运行
复制
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)

串行化器

代码语言:javascript
运行
复制
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,而且还有额外的关系,而不仅仅是Group。有什么办法让这件事更有效率吗?如何编写自己的序列化程序?

EN

回答 2

Stack Overflow用户

发布于 2014-11-22 02:27:35

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

现在,Django MPTT还存在一些问题,您需要在N+1查询中解决这些问题。self.get_ancestorsself.get_descendants方法都会创建一个新的查询集,在您的情况下,这种情况会发生在您正在序列化的每个对象上。您可能想研究一种更好的方法来避免这些,下面我已经描述了可能的改进。

get_full_name方法中,调用self.get_ancestors是为了生成正在使用的链。考虑到在生成输出时始终有父对象,将其移到重用父对象以生成名称的SerializerMethodField中可能会有好处。类似于以下几点可能会奏效:

代码语言:javascript
运行
复制
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的情况。

票数 4
EN

Stack Overflow用户

发布于 2019-05-08 21:56:34

我已经找到了计算的解决办法。由于django-mptt的函数get_cached_trees,您可以执行以下操作:

代码语言:javascript
运行
复制
from django.db.models import Count


class CategorySerializer(serializers.ModelSerializer):
    def get_group_count(self, obj, field=field):
        return obj.group_count

    class Meta:
        model = Category
        fields = [
            'name',
            'slug',
            'children',
            'group_count',
        ]

CategorySerializer._declared_fields['children'] = CategorySerializer(
    many=True,
    source='get_children',
)

class CategoryViewSet(ModelViewSet):
    serializer_class = CategorySerializer

    def get_queryset(self, queryset=None):
        queryset = Category.tree.annotate('group_count': Count('group')})
        queryset = queryset.get_cached_trees()
        return queryset

其中树是mpttTreeManager,在django-categories中使用,为此我为PR:https://github.com/callowayproject/django-categories/pull/145/files编写了稍微复杂一些的代码。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/27073115

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档