You need to enable JavaScript to run this app. Django博客实现用户投票后选中箭头变色及状态保存
优惠活动
大模型
产品
解决方案
定价
更多

Django博客实现用户投票后选中箭头变色及状态保存

投票功能实现方案

1. 新增关联投票模型(持久化核心)

原有Post模型仅存储总票数,无法记录单个用户对单篇帖子的历史投票状态,需要新增关联模型存储用户、帖子、投票类型的对应关系:

# models.py
from django.db import models
from django.contrib.auth import get_user_model
from .models import Post  # 替换为项目内Post模型的实际路径

User = get_user_model()

class UserPostVote(models.Model):
    VOTE_TYPE = (
        ('up', '顶'),
        ('down', '踩'),
    )
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="post_votes")
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="user_votes")
    vote_type = models.CharField(max_length=10, choices=VOTE_TYPE)

    class Meta:
        # *唯一约束*:同一用户对同一篇帖子仅能有一条投票记录
        unique_together = ("user", "post")

写完模型后依次执行命令生成迁移并同步数据库:

python manage.py makemigrations
python manage.py migrate

2. 投票视图逻辑改造

修改原有顶/踩视图,覆盖三种处理场景:用户未投票时新增记录、已投同类型票时取消投票、已投异类型票时切换投票类型,同时同步更新Post的总票数:

# views.py
from django.shortcuts import get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from .models import Post, UserPostVote

# 顶票视图
@login_required
def post_vote_up(request, id):
    post = get_object_or_404(Post, id=id)
    # 查询当前用户对该帖子的已有投票
    user_vote = UserPostVote.objects.filter(user=request.user, post=post).first()

    if not user_vote:
        # 无历史投票,新增顶票
        UserPostVote.objects.create(user=request.user, post=post, vote_type="up")
        post.votes += 1
    elif user_vote.vote_type == "up":
        # 已投顶票,取消
        user_vote.delete()
        post.votes -= 1
    else:
        # 原是踩票,切换为顶票
        user_vote.vote_type = "up"
        user_vote.save()
        post.votes += 2
    post.save()
    # 投票完成后返回原页面
    return redirect(request.META.get("HTTP_REFERER", "home"))

# 踩票视图
@login_required
def post_vote_down(request, id):
    post = get_object_or_404(Post, id=id)
    user_vote = UserPostVote.objects.filter(user=request.user, post=post).first()

    if not user_vote:
        UserPostVote.objects.create(user=request.user, post=post, vote_type="down")
        post.votes -= 1
    elif user_vote.vote_type == "down":
        user_vote.delete()
        post.votes += 1
    else:
        user_vote.vote_type = "down"
        user_vote.save()
        post.votes -= 2
    post.save()
    return redirect(request.META.get("HTTP_REFERER", "home"))

3. 模板与样式改造

首先修正原有HTML的语法错误(踩票的<a>标签缺失闭合括号),再新增投票选中态的样式类,根据用户历史投票状态给箭头绑定对应class:

  • 新增样式(写入项目的静态CSS文件):
/* 顶票选中态:蓝色 */
.vote-up-active {
  color: #165DFF !important;
}
/* 踩票选中态:红色 */
.vote-down-active {
  color: #F53F3F !important;
}

选中态颜色可根据项目UI规范自行调整。

  • 改造后的模板代码(以帖子循环渲染为例):
{% for post in posts %}
<!-- 临时获取当前用户对该帖子的投票状态 -->
{% with user_vote=post.user_votes.filter(user=request.user).first %}
<a href="{% url 'home:post-vote-up' id=post.id %}" 
   class="{% if user_vote.vote_type == 'up' %}vote-up-active{% endif %}">
  <i class="fas fa-arrow-up"></i>
</a>
<p class="lead">{{ post.votes }}</p>
<a href="{% url 'home:post-vote-down' id=post.id %}"
   class="{% if user_vote.vote_type == 'down' %}vote-down-active{% endif %}">
  <i class="fas fa-arrow-down"></i>  
</a>
{% endwith %}
{% endfor %}

4. 性能优化点

如果帖子列表数据量大,不要在模板层循环时反复查询用户投票状态,可以在视图层查询帖子时用annotate预关联用户的投票类型,减少数据库查询次数:

# views.py
from django.db.models import OuterRef, Subquery

def post_list(request):
    # 预查询当前用户对每篇帖子的投票类型,直接绑定到帖子对象上
    posts = Post.objects.all().annotate(
        user_vote_type=Subquery(
            UserPostVote.objects.filter(
                user=request.user, post=OuterRef("pk")
            ).values("vote_type")[:1]
        )
    )
    return render(request, "home/post_list.html", {"posts": posts})

优化后模板可以简化判断逻辑,直接用post.user_vote_type判断是否给箭头加选中类即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.26 19:36:07