You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django Serializer:提取重复average_rating字段,避免冗余查询

问题描述

当前使用的ReviewSerializer存在两个问题:average_rating字段会在每个评论实例中重复返回,且每次序列化都会触发额外查询。现有代码如下:

class ReviewSerializer(serializers.ModelSerializer):
    average_rating = serializers.SerializerMethodField(read_only=True)
    reviewer = serializers.SerializerMethodField(read_only=True)

    book = serializers.SlugRelatedField(slug_field='title', read_only=True)

    def get_average_rating(self, review):
        book_id = self.context['book_id']
        return Review.objects.filter(book_id=book_id).aggregate(average_rating=Avg('rating'))


    class Meta:
        model = Review
        fields = ["id", "book", "reviewer", "description", 'rating', 'average_rating', ]

当前返回的响应格式:

[
    {
        "id": 1,
        "book": "Clean Code: A Handbook of Agile Software Craftsmanship",
        "reviewer": "user1",
        "description": "aaaa",
        "rating": 5,
        "average_rating": {
            "average_rating": 4.5
        }
    },
    {
        "id": 2,
        "book": "Clean Code: A Handbook of Agile Software Craftsmanship",
        "reviewer": "user2",
        "description": "bbbb",
        "rating": 5,
        "average_rating": {
            "average_rating": 4.5
        }
    }
]

需要调整为合法的JSON结构,将average_rating提取到顶层,避免重复,同时减少查询次数:

{
    "average_rating": 4.5,
    "reviews": [
        {
            "id": 1,
            "book": "Clean Code: A Handbook of Agile Software Craftsmanship",
            "reviewer": "user1",
            "description": "aaaa",
            "rating": 5
        },
        {
            "id": 2,
            "book": "Clean Code: A Handbook of Agile Software Craftsmanship",
            "reviewer": "user2",
            "description": "bbbb",
            "rating": 5
        }
    ]
}
解决方案

方法一:视图层直接处理(最简单高效)

在视图中一次性计算平均评分,再序列化评论列表,最后组合成响应数据,仅触发一次平均评分查询。

首先修改ReviewSerializer,移除average_rating相关代码:

class ReviewSerializer(serializers.ModelSerializer):
    reviewer = serializers.SerializerMethodField(read_only=True)
    book = serializers.SlugRelatedField(slug_field='title', read_only=True)

    def get_reviewer(self, review):
        return review.reviewer.username  # 根据实际业务逻辑调整

    class Meta:
        model = Review
        fields = ["id", "book", "reviewer", "description", 'rating']

然后编写视图:

from django.db.models import Avg
from rest_framework.response import Response
from rest_framework.views import APIView

class BookReviewListView(APIView):
    def get(self, request, book_id):
        # 仅执行一次平均评分查询
        average_rating = Review.objects.filter(book_id=book_id).aggregate(average_rating=Avg('rating'))['average_rating']
        # 查询当前书籍的所有评论
        reviews = Review.objects.filter(book_id=book_id)
        # 序列化评论列表
        serializer = ReviewSerializer(reviews, many=True)
        # 构造最终响应
        return Response({
            'average_rating': average_rating,
            'reviews': serializer.data
        })

方法二:自定义ListSerializer封装结果

如果希望序列化逻辑更内聚,可以自定义ListSerializer来包含平均评分,视图层代码更简洁。

先定义基础的ReviewSerializer(同方法一),再自定义ListSerializer:

class BookReviewListSerializer(serializers.ListSerializer):
    def to_representation(self, data):
        book_id = self.context['book_id']
        # 一次性计算平均评分
        average_rating = Review.objects.filter(book_id=book_id).aggregate(average_rating=Avg('rating'))['average_rating']
        # 调用父类方法序列化评论列表
        reviews = super().to_representation(data)
        # 返回组合结果
        return {
            'average_rating': average_rating,
            'reviews': reviews
        }

在ReviewSerializer的Meta类中指定该ListSerializer:

class ReviewSerializer(serializers.ModelSerializer):
    reviewer = serializers.SerializerMethodField(read_only=True)
    book = serializers.SlugRelatedField(slug_field='title', read_only=True)

    def get_reviewer(self, review):
        return review.reviewer.username

    class Meta:
        model = Review
        fields = ["id", "book", "reviewer", "description", 'rating']
        list_serializer_class = BookReviewListSerializer

视图层代码:

class BookReviewListView(APIView):
    def get(self, request, book_id):
        reviews = Review.objects.filter(book_id=book_id)
        serializer = ReviewSerializer(reviews, many=True, context={'book_id': book_id})
        return Response(serializer.data)

优化说明

  • 两种方案都只执行一次平均评分查询,彻底解决原代码的N+1查询问题。
  • 修正了原需求中的JSON语法错误,确保响应符合标准格式。

内容的提问来源于stack exchange,提问作者Ryan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 06:24:16