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

如何解决Django的django.db.utils.IntegrityError: UNIQUE约束失败问题

Django IntegrityError 解决:UNIQUE约束冲突问题

错误详情

触发错误的代码:

Comment(comment=form.cleaned_data["comment"],user=request.user,stars=form.cleaned_data["stars"],tv_or_movie=tv_or_movie_object).save()

错误提示:

django.db.utils.IntegrityError: UNIQUE constraint failed: Movies_comment.user_id, Movies_comment.tv_or_movie_id

问题原因

从models.py的Comment模型定义可以看到,Meta类里设置了:

unique_together = ("user", "tv_or_movie")

这意味着同一个用户对同一个影视条目只能发表一条评论。当用户已经存在该影视的评论时,再次执行创建新Comment的save()操作,就会触发数据库的唯一约束校验,抛出上述错误。

而现有代码中,views.py的POST处理逻辑里,当action为create时,直接创建新Comment对象并保存,没有检查用户是否已经有该影视的评论——但实际上代码已经通过get_user_post_comment_for_tv_or_movie()获取了用户的现有评论(存在则返回对象,不存在返回None),只是创建逻辑没利用这个对象。

解决方案

方案1:统一表单处理(推荐)

直接利用已获取的mycomment_obj,不管是创建新评论还是更新已有评论,都通过表单的save()方法处理。因为当instance参数为None时,表单会自动创建新对象;当instance存在时,表单会更新该对象。这样可以彻底避免重复创建的问题,同时简化代码:

修改views.py中的POST处理部分:

if request.method == "POST":
    if request.POST.get("action") == "delete":
        mycomment_obj.delete()
        return redirect("view_tv_and_movie_detail", type=type_movie_or_tv, id=id)
    else:
        form = CommentCreateForm(request.POST, instance=mycomment_obj)
        if form.is_valid():
            # 无需区分create/update,表单自动处理
            form.save()
            return redirect("view_tv_and_movie_detail", type=type_movie_or_tv, id=id)

方案2:保留action区分的处理

如果一定要保留create和update的action区分,那么在create分支先检查用户是否已有评论,有则执行更新逻辑,无则创建:

修改views.py中的create分支:

elif form.is_valid() and request.POST.get("action") == "create":
    # 检查用户是否已有该影视的评论
    if not mycomment_obj:
        Comment(
            comment=form.cleaned_data["comment"],
            user=request.user,
            stars=form.cleaned_data["stars"],
            tv_or_movie=tv_or_movie_object,
        ).save()
    else:
        # 已有评论,执行更新
        form.save()
    return redirect("view_tv_and_movie_detail", type=type_movie_or_tv, id=id)

相关代码

views.py

def view_tv_and_movie_detail(request, type_movie_or_tv, id):
    tv_or_movie_object, _ = TVAndMovie.objects.get_or_create(tmdb_id=id, judge_tv_or_movie=type_movie_or_tv)
    detail_tv_or_movie = TvAndMovieDetailhelp(request, tv_or_movie_object, 3)
    mycomment_obj = detail_tv_or_movie.get_user_post_comment_for_tv_or_movie()
    if request.method == "POST":
        if request.POST.get("action") == "delete":
            mycomment_obj.delete()
            return redirect("view_tv_and_movie_detail", type=type_movie_or_tv, id=id)
        else:
            form = CommentCreateForm(request.POST, instance=mycomment_obj)
            if form.is_valid() and request.POST.get("action") == "update":
                form.save()
                return redirect("view_tv_and_movie_detail", type=type_movie_or_tv, id=id)
            elif form.is_valid() and request.POST.get("action") == "create":
                Comment(
                    comment=form.cleaned_data["comment"],
                    user=request.user,
                    stars=form.cleaned_data["stars"],
                    tv_or_movie=tv_or_movie_object,
                ).save()
                return redirect("view_tv_and_movie_detail", type=type_movie_or_tv, id=id)
    else:
        form = CommentCreateForm(instance=mycomment_obj)
    data = detail_tv_or_movie.get_object_tv_or_movie_data()
    recommendations = detail_tv_or_movie.get_recommendations_tmdb_data()
    pages = detail_tv_or_movie.get_page_comment()
    average = tv_or_movie_object.average_stars()
    context = {
        "data": data,
        "recommendations": recommendations,
        "type": "movie",
        "mycomment": mycomment_obj,
        "average": average,
        "form": form,
        "pages": pages
    }
    return render(request, "Movie/movie_detail.html", context)

models.py

class TVAndMovie(models.Model):
    tmdb_id = models.CharField(
        validators=[alphanumeric], max_length=9999
    )
    judge_tv_or_movie = models.CharField(
        blank=False, null=False, default="movie", max_length=20
    )
    stars = models.FloatField(
        blank=False,
        null=False,
        default=0,
        validators=[MinValueValidator(0.0), MaxValueValidator(10.0)],
    )

    def get_judge_tv_or_movie(self) -> str:
        return self.judge_tv_or_movie

    def get_comments(self) -> object:
        return Comment.objects.prefetch_related("tv_or_movie").filter(
            tv_or_movie_id=self.id
        )

    def average_stars(self) -> float:
        comments = self.get_comments()
        n_comments = comments.count()

        if n_comments:
            self.stars = round(
                sum([comment.stars for comment in comments]) / n_comments, 3
            )
        else:
            self.stars = 0
        self.save()
        return self.stars


class Comment(models.Model):

    comment = models.TextField(max_length=1000)
    stars = models.FloatField(
        blank=False,
        null=False,
        default=0,
        validators=[MinValueValidator(0.0), MaxValueValidator(10.0)],
    )
    user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
    tv_or_movie = models.ForeignKey(TVAndMovie, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        unique_together = ("user", "tv_or_movie")
        indexes = [models.Index(fields=["user", "tv_or_movie"])]
        
    def __str__(self) -> str:
        return self.comment[:20]

helper_views.py

class TvAndMovieDetailhelp:
    def __init__(self, request, obj_tv_or_movie: object, page_num: int) -> None:
        self.request = request
        self.obj_tv_or_movie: object = obj_tv_or_movie
        self.page_num: int = page_num

    def get_object_tv_or_movie_data(self) -> dict:
        tv_or_movie_url: str = (
            "https://api.themoviedb.org/3/" +
            self.obj_tv_or_movie.judge_tv_or_movie +
            "/" +
            str(self.obj_tv_or_movie.tmdb_id) +
            "?api_key=" +
            TMDB_API_KEY +
            "&language=en-US"
        )
        return (requests.get(tv_or_movie_url)).json()

    def get_recommendations_tmdb_data(self) -> dict:
        recommendations_url: str = (
            "https://api.themoviedb.org/3/" +
            self.obj_tv_or_movie.judge_tv_or_movie +
            "/" +
            str(self.obj_tv_or_movie.tmdb_id) +
            "/recommendations?api_key=" +
            TMDB_API_KEY +
            "&language=en-US"
        )
        return (requests.get(recommendations_url)).json()

    def get_comment_for_tv_or_movie(self) -> object:
        if self.request.user.id is not None:
            return (
                Comment.objects.filter(tv_or_movie=self.obj_tv_or_movie.id)
                .exclude(user=self.request.user)
                .order_by("-updated_at")
            )
        else:
            return Comment.objects.filter(tv_or_movie=self.obj_tv_or_movie.id).order_by(
                "-updated_at"
            )

    def get_user_post_comment_for_tv_or_movie(self) -> object:
        if self.request.user.id is not None:
            try:
                return Comment.objects.get(
                    user=self.request.user, tv_or_movie=self.obj_tv_or_movie
                )
            except Comment.DoesNotExist:
                return None
        else:
            return None

    def get_page_comment(self):
        other_comments = self.get_comment_for_tv_or_movie()
        paginator = Paginator(other_comments, self.page_num)
        page = self.request.GET.get("page", 1)
        try:
            pages = paginator.page(page)
        except PageNotAnInteger:
            pages = paginator.page(1)
        except EmptyPage:
            pages = paginator.page(1)
        return pages

    def send_contexts_detail(self,form) -> dict:
        data_for_tvor_movie = self.get_object_tv_or_movie_data()
        recommendations_data = self.get_recommendations_tmdb_data()
        for_movie_or_tv = self.obj_tv_or_movie.judge_tv_or_movie
        mycomment_obj = self.get_user_post_comment_for_tv_or_movie()
        average = self.obj_tv_or_movie.getaverage_stars()
        pages = self.get_page_comment()
        context = {
            "data": data_for_tvor_movie,
            "recommendations": recommendations_data,
            "type": for_movie_or_tv,
            "mycomment": mycomment_obj,
            "average": average,
            "form": form,
            "pages": pages,  # NOTE add the comment to context
        }
        return context

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 13:45:32