Django实现单用户单影片单条评论限制及评论跨影片显示问题排查
问题根因排查
- 评论查询未过滤对应电影:
views.py中movies_detail函数里的all_reviews = Review.objects.all().order_by('movie')是查询了全站所有评论,没有过滤当前页面的电影,哪怕模板做判断也效率极低且容易出错。 - 模板变量引用错误:模板判断用的
movie.title根本没有在视图传递的上下文里,上下文当前电影变量叫articles,判断条件永远不生效,导致所有评论都渲染出来。 check_user_review函数逻辑错误:现有循环只要第一个评论不是当前用户的就直接返回True,逻辑完全错误,无法正确判断用户是否已经评过当前电影。- 缺少数据库层约束:没有在
Review模型加联合唯一索引,极端情况下仍可能出现同一用户同一电影多条评论的问题。
解决方案
第一步:修改models.py,添加联合唯一约束
from django.db import models from django.contrib.auth.models import User # 确保你自己定义了CATEGORY_CHOICE,这里不影响现有逻辑 CATEGORY_CHOICE = ( # 你的分类定义 ) class Play(models.Model): title = models.CharField(max_length=150) plot = models.TextField(max_length=900) slug = models.SlugField(unique=True, null=True) cast = models.CharField(max_length=500, default=0) category = models.CharField(choices=CATEGORY_CHOICE, max_length=10) image = models.ImageField(upload_to='movie') year = models.DateTimeField() author = models.ForeignKey(User, default=None, on_delete=models.CASCADE) def __str__(self): return self.title class Review(models.Model): user = models.ForeignKey(User, default=None, on_delete=models.CASCADE) # 添加related_name方便反向查询当前电影的所有评论 movie = models.ForeignKey(Play, blank=True, null=True, on_delete=models.CASCADE, related_name='reviews') text = models.TextField() # 补全你用到的点赞字段 likes = models.ManyToManyField(User, related_name='review_likes', blank=True) class Meta: # 数据库层面兜底:同一用户对同一电影只能发一条评论 unique_together = ('user', 'movie') # 补全点赞计数方法 def count_likes(self): return self.likes.count()
第二步:修改views.py,优化查询和校验逻辑
from django.shortcuts import render, redirect, reverse from .models import Play, Review from django.contrib.auth.decorators import login_required from . import forms from django.http import HttpResponseRedirect def movies_list(request): article = Play.objects.all().order_by('year') return render(request, "articles/movies_list.html", {"articles":article}) def movies_detail(request, slug): current_movie = Play.objects.get(slug=slug) # 仅查询当前电影的评论,无需查询全站评论 all_reviews = current_movie.reviews.all().order_by('-id') isReviewAllowed = check_user_review(request, current_movie) # 修改变量名避免歧义 return render(request, "articles/movies_detail.html", {"current_movie": current_movie,'all_reviews': all_reviews, 'isReviewAllowed': isReviewAllowed}) @login_required(login_url="/accounts/login/") def article_create(request): if request.method == "POST": form = forms.CreatePlay(request.POST, request.FILES) if form.is_valid(): instance = form.save(commit=False) instance.author = request.user instance.save() return redirect("movies:home") else: form = forms.CreatePlay() return render(request, "articles/article_create.html", {"form":form}) @login_required(login_url="/accounts/login/") def sub_review(request, slug): current_movie = Play.objects.get(slug=slug) # 提交前先校验,避免用户绕过前端直接重复提交评论 if Review.objects.filter(user=request.user, movie=current_movie).exists(): return redirect("movies:detail", slug=slug) if request.method == "POST": form = forms.CreateReview(request.POST) if form.is_valid(): instance = form.save(commit=False) instance.user = request.user instance.movie = current_movie instance.save() return redirect("movies:detail", slug=slug) else: form = forms.CreateReview() return render(request, "articles/review.html", {"form":form, "slug":slug}) # 重写校验逻辑,无需循环全表,查询效率大幅提升 def check_user_review(request, current_movie): if not request.user.is_authenticated: return False # 直接查询是否存在当前用户对当前电影的评论 return not Review.objects.filter(user=request.user, movie=current_movie).exists() def like_view(request, slug): review_id = request.POST.get('review_id') if review_id: try: like = Review.objects.get(id=review_id) like.likes.add(request.user) except Review.DoesNotExist: pass return HttpResponseRedirect(reverse('movies:detail', args=[slug]))
第三步:修改movies_detail.html,修正变量引用,删除冗余判断
{% extends 'base.html' %} {% block content %} <img src="{{ current_movie.image.url }}" width="200px" height="300" style="display: inline-block;" > <div id="movie_plot"> <h2>{{current_movie.title}}</h2> <p>{{current_movie.plot}}</p> {{current_movie.category}} <p>{{current_movie.cast}}</p> </div> {% if isReviewAllowed %} <div class="isReviewAllowed"> <div class="nav_button"><a class="new_movie" href="{% url 'movies:review' slug=current_movie.slug %}">Create Review</a></div> </div> {% endif %} {% for review in all_reviews %} <div class="all_reviews"> <div class="review"> <p><span style="font-weight: normal;">Reviewed by: </span> {{review.user}} </p> <p><span style="font-weight: normal;">Description: </span>{{review.text}}</p> <form style="text-align: center;" action="{% url 'movies:like' slug=current_movie.slug %}" method="post"> {% csrf_token %} <button style="color: white; background: #104e89;" type="submit" name="review_id" value="{{ review.id }}" class="btn btn-primary btn-sm">Like</button> {{review.count_likes}} </form> </div> </div> {% endfor %} {% endblock %}
第四步:执行数据库迁移
修改模型后执行以下命令,将联合唯一约束同步到数据库:
python manage.py makemigrations python manage.py migrate
内容的提问来源于stack exchange,提问作者Hagai Buachi
相关产品推荐
相关产品推荐

