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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.21 09:49:30