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
相关产品推荐
相关产品推荐

