Django传入合法外键值仍触发Foreign Key IntegrityError问题排查
Django外键约束报错问题解决方案
核心原因
你遇到的FOREIGN KEY constraint failed报错是外键字段类型不匹配导致的,具体逻辑如下:
- 你的
GameBoard模型使用Django默认的自增int类型主键,Game表的board外键字段类型为int,二者完全匹配,所以关联不会报错。 - 你大概率是先按默认写法定义了
Player模型(未指定username为主键),生成了第一次迁移,此时Game表的player1、player2外键字段会被设为int类型,用来匹配Player默认的int类型id主键。 - 后续你修改
Player模型将username设为字符串类型主键后,新生成的迁移只会修改Player表的主键,不会自动更新Game表中已存在的player1、player2字段类型,导致这两个字段仍然是int类型,无法匹配Player现在的字符串主键值(Alice、Bob),因此触发外键约束失败。 - 传入
None保存游戏时不需要校验外键值匹配,所以可以正常执行。
解决方法
开发阶段(无需要保留的生产数据)
直接清理历史迁移和数据库,重新生成表结构即可:
- 删除app下
migrations文件夹中除了__init__.py之外的所有文件 - 删除项目根目录下的sqlite数据库文件(默认是
db.sqlite3) - 重新执行迁移命令:
python manage.py makemigrations python manage.py migrate
重新生成的Game表player1、player2字段会自动调整为和Player.username一致的varchar(10)类型,后续测试代码就可以正常运行。
有需要保留的生产数据
手动生成迁移修改Game表的player1、player2字段类型:
执行命令创建空迁移:
python manage.py makemigrations --empty your_app_name
在生成的空迁移文件中添加字段类型修改逻辑,将两个字段从int改为varchar(10),之后执行migrate即可。
额外优化建议
- 实例化Django模型时优先使用关键字参数,避免字段顺序调整导致的赋值错误,比如创建
Player时建议写成:
p1 = Player(username='Alice', password='password1')
related_name是反向查询时的属性名,当前的命名语义不够清晰,建议修改为:
player1 = models.ForeignKey(Player,related_name='games_as_p1',blank = True,null=True,on_delete=models.SET_NULL) player2 = models.ForeignKey(Player,related_name='games_as_p2',blank = True,null=True,on_delete=models.SET_NULL)
后续查询玩家作为房主的游戏时可以直接用player.games_as_p1.all(),可读性更高。
- 不要明文存储用户密码,建议使用Django自带的
django.contrib.auth.hashers工具做密码的加密存储和校验。
内容的提问来源于stack exchange,提问作者Cristian
相关产品推荐
相关产品推荐

