使用DRF序列化时如何减少递归MPTT树查询

时间:2019-04-29 10:48:13

标签: django django-rest-framework django-mptt drf-queryset

从事电子商务项目。项目具有相关的模型。类别模型具有MPTT继承。它使用Django Rest Framework在API之间进行通信。国外服务最近希望我将完整的Category路径放入XML响应这一边。但是此请求导致了很高的数据库查询。我需要减少查询,但是我不知道如何在DRF序列化中执行此操作。我尝试了几种方法。我的最后一种方法是在下面使用模型视图和序列化。

class Category(MPTTModel):
    parent = TreeForeignKey('self', blank=True, null=True, related_name='children')
    root = TreeForeignKey('self', blank=True, null=True, related_name='leaf')
    name = models.CharField(max_length=100)

class ProductMeta(models.Model):
    ...
    category = models.ForeignKey('Category', null=True, blank=True, db_index=True, related_name='category')
    ...

class Product(models.Model):
    ...
    meta = models.ForeignKey(ProductMeta, related_name='product')
    ...

一些DRF视图集将模型数据呈现为XML

class ProductMetaBaseViewSet(viewsets.ModelViewSet):
    def get_serializer_class(self):
        return ProductMetaSerializer

    def get_queryset(self):
        queryset = ProductMeta.objects.all().prefetch_related('products', 'category__root')
        return self.paginate_queryset(queryset)

    def list(self, request):
        serializer = ProductMetaSerializer(self.get_queryset(), many=True)
        return Response(serializer.data)


class ProductMetaXMLViewSet(ProductMetaBaseViewSet, viewsets.ModelViewSet):
    parser_classes = (XMLParser,)
    renderer_classes = (XMLRenderer,)

这是获取数据的序列化器:

class RootCategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ('id', 'name')


class CategorySerializer(serializers.ModelSerializer):
    root = RootCategorySerializer()
    full_category_path = serializers.SerializerMethodField()

    class Meta:
        model = Category
        fields = ('name', 'root', 'category_path')

    def get_full_category_path(self, obj):
        related_ancestor_name_list = []
        related_ancestor_list = []

        next_rel_name = ""
        next_rel = None
        cat_level = obj.get_level()
        for i in range(cat_level):
            if i <= 0 and not next_rel_name:
                next_rel_name = 'name'
                next_rel = "parent"
            else:
                next_rel_name = "{}__{}".format("parent", next_rel_name)
                next_rel = "{}__parent".format(next_rel)

            related_ancestor_name_list.append(next_rel_name)
            if next_rel is not None:
                related_ancestor_list.append(next_rel)

        print(related_ancestor_name_list, related_ancestor_list)
        cobj = Category.objects.filter(pk=obj.pk).select_related(*related_ancestor_list).prefetch_related(*related_ancestor_list).values_list(*related_ancestor_name_list[::-1]).first()
        return ' > '.join(cobj)


class ProductSerializer(serializers.ModelSerializer):

    class Meta:
        model = Product
        fields = ('price', 'stock')


class ProductMetaSerializer(serializers.ModelSerializer):
    products = ProductSerializer(many=True, read_only=True)
    category = CategorySerializer(read_only=True)

    class Meta:
        model = ProductMeta
        fields = ('name', 'category', 'products')

在我的测试数据库上,如果我不使用get_full_category_path方法,则会有20条查询记录到记录器中。当我需要使用该方法来获取完整类别路径时,查询数量将超过100。

我发布了最近的尝试。我也尝试使用MPTT的get_ancestors方法,但它不受数据库命中数的影响。无论哪种方式,在每个产品对象上序列化都会产生大量的类别模型查询。

PS:我知道最好的选择是缓存树,但是我真的很想知道在进行递归MPTT查询时是否有减少DB命中的方法。

0 个答案:

没有答案