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

