Pytest中插入ForeignKey ID未触发IntegrityError问题咨询
解决Django外键关联不存在对象却未触发IntegrityError的问题
问题根源
你的ChildModel外键字段设置了null=True和blank=True,且直接通过_id后缀字段赋值时,Django不会自动校验关联的ParentModel是否存在——它仅将传入的symbol值存入字段。如果测试数据库(比如SQLite)未强制开启外键约束,数据库层面也不会拦截无效关联,因此不会抛出IntegrityError,导致无效关联被保存。
可行解决方案
1. 调整外键约束(业务允许的前提下)
如果业务逻辑要求这两个外键必须关联有效的ParentModel,移除null=True和blank=True:
class ChildModel(models.Model): parent_instrument = models.ForeignKey( to=ParentModel, on_delete=models.SET_NULL, # 移除null、blank参数 ) instrument = models.ForeignKey( to=ParentModel, on_delete=models.SET_NULL, # 移除null、blank参数 )
修改后,Django会在保存时自动校验关联对象是否存在,不存在则抛出ValidationError,数据库层面也会触发IntegrityError。
2. 手动校验关联对象存在性
在执行update_or_create前,先确保对应的ParentModel对象存在:
# 批量创建不存在的ParentModel对象 for symbol in [symbol1, symbol2]: ParentModel.objects.get_or_create( symbol=symbol, defaults={"name": f"默认名称_{symbol}"} # 根据实际需求设置默认值 ) # 再执行关联操作 ChildModel.objects.update_or_create( parent_instrument_id=symbol1, instrument_id=symbol2, )
这种方式适合业务允许外键为null,但当前场景需要确保关联对象存在的情况。
3. 开启测试数据库的外键约束
若使用SQLite作为测试数据库,需手动开启外键约束。在settings.py中配置测试数据库:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', 'TEST': { 'NAME': BASE_DIR / 'test_db.sqlite3', }, } }
然后在测试用例的初始化方法中开启外键:
from django.db import connection def setUp(self): with connection.cursor() as cursor: cursor.execute("PRAGMA foreign_keys = ON;")
开启后,数据库会拦截无效的外键关联,抛出IntegrityError。
4. 调用full_clean()触发模型验证
如果不想修改模型或数据库配置,可以在保存前调用full_clean()触发Django的模型验证,它会检查外键关联的对象是否存在:
from django.core.exceptions import ValidationError try: obj, created = ChildModel.objects.update_or_create( parent_instrument_id=symbol1, instrument_id=symbol2, defaults={} # 按需添加字段默认值 ) obj.full_clean() # 触发完整验证 obj.save() except (ValidationError, IntegrityError): self._create_instrument(symbol1, symbol2)
注意full_clean()会抛出ValidationError,需要同时捕获该异常。
内容的提问来源于stack exchange,提问作者Headmaster
相关产品推荐
相关产品推荐

