Django新手求助:如何在序列化器中计算并返回Answer模型的总票数
Django新手求助:如何在序列化器中计算并返回Answer模型的总票数
嘿,看了你贴的代码,我发现你已经摸到门道了——用SerializerMethodField来计算总票数,但现有代码里有个小坑,还有几个更优雅的实现方式,我一步步帮你理清楚:
首先,修正你当前的get_votes方法
你现在的代码里加了if obj.upVotes and obj.downVotes的判断,但这两个字段默认值都是0,只要其中一个为0,这个条件就不成立,会导致get_votes返回None,这显然不是你想要的。直接去掉判断,直接计算就好:
def get_votes(self, obj): # 如果你要的是**总投票次数**(不管是赞还是踩): return obj.upVotes + obj.downVotes # 如果你要的是**净票数**(赞数减踩数),就换成下面这行: # return obj.upVotes - obj.downVotes
(可选)把票数计算逻辑移到模型里,更内聚
如果想让这个总票数不仅在序列化器里能用,在其他地方(比如视图、模板)也能直接调用,可以给Answer模型加一个属性:
class Answer(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name='answers') answer = models.CharField(max_length=1000, default="", blank=False) author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) createdAt = models.DateTimeField(auto_now_add=True) upVotes = models.IntegerField(default=0) downVotes = models.IntegerField(default=0) def __str__(self): return str(self.answer) # 新增属性:计算总票数 @property def total_votes(self): return self.upVotes + self.downVotes # 或者净票数:self.upVotes - self.downVotes
然后序列化器里就不用写SerializerMethodField了,直接引用这个属性就行,更简洁:
class AnswerSerializer(serializers.ModelSerializer): # 用source直接关联模型的total_votes属性 votes = serializers.IntegerField(source='total_votes', read_only=True) class Meta: model = Answer fields = ('id', 'question', 'answer', 'author', 'createdAt', 'votes')
(进阶优化)避免手动维护票数,用关联模型+注解提升性能和一致性
你现在手动维护upVotes和downVotes字段,容易出现数据不一致的问题(比如用户取消投票时忘记更新字段)。可以改成用一个Vote模型记录每个用户的投票行为,然后通过查询注解来计算票数:
第一步:创建Vote模型
class Vote(models.Model): # 区分投票类型 VOTE_TYPES = ( ('UP', '点赞'), ('DOWN', '点踩'), ) answer = models.ForeignKey(Answer, on_delete=models.CASCADE, related_name='votes') author = models.ForeignKey(User, on_delete=models.CASCADE) vote_type = models.CharField(max_length=4, choices=VOTE_TYPES) # 确保一个用户只能给同一个回答投一次票 class Meta: unique_together = ('answer', 'author')
第二步:修改Answer模型,去掉手动维护的票数字段
class Answer(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name='answers') answer = models.CharField(max_length=1000, default="", blank=False) author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) createdAt = models.DateTimeField(auto_now_add=True) def __str__(self): return str(self.answer)
第三步:在查询时用注解预计算票数
在获取Answer列表或单个对象的视图里,用annotate来预计算点赞、点踩和总票数,避免N+1查询问题:
from django.db.models import Count, Case, When, IntegerField # 单个回答的查询 answer = Answer.objects.annotate( upVotes=Count(Case(When(votes__vote_type='UP', then=1), output_field=IntegerField())), downVotes=Count(Case(When(votes__vote_type='DOWN', then=1), output_field=IntegerField())), total_votes=Count('votes') # 总投票次数 ).get(id=pk) # 回答列表的查询 answers = Answer.objects.annotate( upVotes=Count(Case(When(votes__vote_type='UP', then=1), output_field=IntegerField())), downVotes=Count(Case(When(votes__vote_type='DOWN', then=1), output_field=IntegerField())), total_votes=Count('votes') ).all()
第四步:序列化器直接使用注解字段
class AnswerSerializer(serializers.ModelSerializer): # 直接用注解出来的字段,无需额外计算 votes = serializers.IntegerField(source='total_votes', read_only=True) # 也可以把upVotes和downVotes一起返回 # upVotes = serializers.IntegerField(read_only=True) # downVotes = serializers.IntegerField(read_only=True) class Meta: model = Answer fields = ('id', 'question', 'answer', 'author', 'createdAt', 'votes')
这样一来,票数计算完全由数据库查询保证,不会出现手动维护导致的不一致,性能也更好。
最后说下你的upvote视图
你现在的视图逻辑是对的,但如果改成用上面的Vote模型,需要调整一下:
@api_view(['POST']) @permission_classes([IsAuthenticated]) def upvote(request, pk): answer = get_object_or_404(Answer, id=pk) author = request.user # 检查用户是否已经投过票(不管是赞还是踩) existing_vote = Vote.objects.filter(author=author, answer=answer).first() if existing_vote: if existing_vote.vote_type == 'UP': return Response({'details':'你已经点过赞了'}) else: # 如果之前是踩,改成赞 existing_vote.vote_type = 'UP' existing_vote.save() return Response({'details':'已从点踩改为点赞'}) else: Vote.objects.create(answer=answer, author=author, vote_type='UP') return Response({'details':'点赞成功'})
这样就不用手动更新upVotes字段了,票数会通过注解自动计算。
备注:内容来源于stack exchange,提问作者Rohit Gajula
相关产品推荐
相关产品推荐

