3

我有一个类别模型是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与查询的疯狂数量,也有更多的关系,不只是集团。有没有办法让这个效率更高?我如何编写我自己的序列化程序?

回答

1

你肯定会遇到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,该项重用父对象以生成名称。像下面的内容可能工作:

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情况下最好地获取所需数据。

+0

谢谢详细的序列化方法!我希望那些制作MPTT的巫师在N + 1问题上有一个解决方案来计算:( – WBC 2014-11-24 17:41:30

相关问题