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

Django中NOT NULL约束失败(Movies_comment.movie_id)问题修复求助

Django评论功能修复:NOT NULL与外键约束失败问题

问题背景

在Django项目中实现Movie模型的评论及评分平均值计算功能时,出现两个关联错误:

  1. NOT NULL constraint failed: Movies_comment.movie_id
  2. 后续触发FOREIGN KEY constraint failed

关联代码

模型代码

class Movie(models.Model):
    
    def get_comments(self):
        return Comment.objects.filter(movie_id=self.id)
    
    def average_stars(self):
        comments = self.get_comments()
        n_comments = comments.count()
        if n_comments == 0:
            return 0
        else:
            return sum([comment.stars for comment in comments]) / n_comments
    

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.OneToOneField(CustomUser, on_delete=models.CASCADE,)
    movie       = models.ForeignKey(Movie, on_delete=models.CASCADE)
    created_at  = models.DateTimeField(default=datetime.now)
    updated_at  = models.DateTimeField(auto_now=True)

视图代码

def view_movie_detail(request, movie_id):
    if request.method == "POST":
        user = request.user  
        comment = request.POST.get("comment")
        stars = request.POST.get("stars")
        if not request.user.is_authenticated:
            user = User.objects.get(id=1)

        Comment(comment=comment, stars = stars, user=user,movie_id=movie_id).save()

        return redirect(f"/movie/{movie_id}/")
    else:
        data = requests.get(f"https://api.themoviedb.org/3/movie/{movie_id}?api_key={TMDB_API_KEY}&language=en-US")
        recommendations = requests.get(f"https://api.themoviedb.org/3/movie/{movie_id}/recommendations?api_key={TMDB_API_KEY}&language=en-US")
        comments = reversed(Movie().get_comments())
        average = Movie().average_stars()
        return render(request, "Movie/movie_detail.html", {
            "data": data.json(),
            "recommendations": recommendations.json(),
            "type": "movie",
            "comments": comments,
            "average" : average,
        })

翻译后的错误回溯

Traceback (most recent call last):
File "C:\\Users\\81804\\AppData\\Local\\Programs\\Python\\Python38\\lib\\site-packages\\django\\db\\utils.py", line 98, in inner
return func(*args, **kwargs)

上述异常(NOT NULL约束失败)直接引发了以下异常:
File "C:\\Users\\81804\\AppData\\Local\\Programs\\Python\\Python38\\lib\\site-packages\\django\\core\\handlers\\exception.py", line 55, in inner
response = get_response(request)
File "C:\\Users\\81804\\AppData\\Local\\Programs\\Python\\Python38\\lib\\site-packages\\django\\core\\handlers\\base.py", line 197, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\\Users\\81804\\enviroment\\MovieReview\\MovieReview\\Movies\\views.py", line 74, in view_movie_detail
Comment(comment=comment, stars = stars, user=user,movie_id=movie_id).save()
File "C:\\Users\\81804\\AppData\\Local\\Programs\\Python\\Python38\\lib\\site-packages\\django\\db\\models\\base.py", line 806, in save
self.save_base(
File "C:\\Users\\81804\\AppData\\Local\\Programs\\Python\\Python38\\lib\\site-packages\\django\\db\\models\\base.py", line 857, in save_base
updated = self._save_table(
File "C:\\Users\\81804\\AppData\\Local\\Programs\\Python\\Python38\\lib\\site-packages\\django\\db\\models\\base.py", line 1000, in _save_table
results = self._do_insert(
File "C:\\Users\\81804\\AppData\\Local\\Programs\\Python\\Python38\\lib\\site-packages\\django\\db\\models\\base.py", line 1041, in _do_insert
return manager._insert(
File "C:\\Users\\81804\\AppData\\Local\\Programs\\Python\\Python38\\lib\\site-packages\\django\\db\\models\\manager.py", line 85, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "C:\\Users\\81804\\AppData\\Local\\Programs\\Python\\Python38\\lib\\site-packages\\django\\db\\models\\query.py", line 1434, in _insert
return query.get_compiler(using=using).execute_sql(returning_fields)
File "C:\\Users\\81804\\AppData\\Local\\Programs\\Python\\Python38\\lib\\site-packages\\django\\db\\models\\sql\\compiler.py", line 1632, in execute_sql
self.connection.ops.fetch_returned_insert_columns(
File "C:\\Users\\81804\\AppData\\Local\\Programs\\Python\\Python38\\lib\\site-packages\\django\\db\\backends\\base\\operations.py", line 205, in fetch_returned_insert_columns
return cursor.fetchone()
File "C:\\Users\\81804\\AppData\\Local\\Programs\\Python\\Python38\\lib\\site-packages\\django\\db\\utils.py", line 98, in inner
return func(*args, **kwargs)
File "C:\\Users\\81804\\AppData\\Local\\Programs\\Python\\Python38\\lib\\site-packages\\django\\db\\utils.py", line 91, in __exit__
raise dj_exc_value.with_traceback(traceback) from exc_value
File "C:\\Users\\81804\\AppData\\Local\\Programs\\Python\\Python38\\lib\\site-packages\\django\\db\\utils.py", line 98, in inner
return func(*args, **kwargs)

异常类型:IntegrityError at /movie/616037/
异常信息:外键约束失败

错误原因

  1. 外键关联失效:创建Comment时传入的movie_id对应的Movie实例未在本地数据库中创建——仅通过TMDB API获取数据,但未同步到本地Movie表,导致外键约束校验失败。
  2. Movie实例调用错误:GET请求中用Movie().get_comments()创建空实例,会查询所有评论的平均值,而非当前电影的评论数据。
  3. 用户字段设计不合理:Comment的user字段为OneToOneField,限制一个用户只能发一条评论,不符合业务逻辑。

修复方案

1. 修正Comment的user字段类型

将OneToOneField改为ForeignKey,允许用户发表多条评论:

class Comment(models.Model):
    # 其他字段保持不变
    user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
    # 其他字段保持不变

2. 同步本地Movie记录(核心修复)

处理请求时先检查本地是否存在对应Movie实例,不存在则创建;创建Comment时关联已存在的Movie实例:

def view_movie_detail(request, movie_id):
    # 获取或创建本地Movie实例
    movie, created = Movie.objects.get_or_create(id=movie_id)
    
    if request.method == "POST":
        user = request.user  
        comment = request.POST.get("comment")
        stars = request.POST.get("stars")
        if not request.user.is_authenticated:
            # 改用匿名用户避免固定ID冲突
            user = CustomUser.objects.get_or_create(username='anonymous')[0]
        
        # 关联已存在的movie实例,而非直接传movie_id
        Comment.objects.create(
            comment=comment, 
            stars=float(stars),  # 转换为float类型匹配字段
            user=user,
            movie=movie
        )

        return redirect(f"/movie/{movie_id}/")
    else:
        data = requests.get(f"https://api.themoviedb.org/3/movie/{movie_id}?api_key={TMDB_API_KEY}&language=en-US")
        recommendations = requests.get(f"https://api.themoviedb.org/3/movie/{movie_id}/recommendations?api_key={TMDB_API_KEY}&language=en-US")
        # 使用当前movie实例的关联方法
        comments = reversed(movie.get_comments())
        average = movie.average_stars()
        return render(request, "Movie/movie_detail.html", {
            "data": data.json(),
            "recommendations": recommendations.json(),
            "type": "movie",
            "comments": comments,
            "average" : average,
        })

3. 优化Movie模型方法(可选)

用Django聚合函数简化平均值计算,提升性能:

from django.db.models import Avg

class Movie(models.Model):
    def get_comments(self):
        return self.comment_set.all()  # 反向关联更简洁
    
    def average_stars(self):
        avg = self.comment_set.aggregate(Avg('stars'))['stars__avg']
        return avg if avg is not None else 0.0

4. 执行数据库迁移

修改模型后,运行迁移命令更新数据库:

python manage.py makemigrations
python manage.py migrate

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 08:54:22