Django更新M2M模型记录触发ValueError:无主键无法强制更新
如何更新带有联合唯一约束的Django多对多中间模型字段?
问题场景
我们使用Django定义了多对多中间模型Occurrence,其中attendee和training字段有联合唯一约束,模型及关联模型代码如下:
模型定义
# Occurrence 中间模型 class Occurrence(models.Model): attendee = models.ForeignKey(Attendee, on_delete=models.CASCADE) training = models.ForeignKey(Training, on_delete=models.CASCADE) attended_date = models.DateField(default=date(1900, 12, 31)) class Meta: constraints = [ models.UniqueConstraint(fields=['attendee', 'training'], name='unique_attendee_training') ] # Attendee 模型 class Attendee(models.Model): first_name = models.CharField('First Name', max_length=30, blank=False) last_name = models.CharField('Last Name', max_length=30, blank=False) email = models.EmailField("Email Address", max_length=75, blank=False, primary_key=True) # Training 模型 class Training(models.Model): long_name = models.CharField('Training Name', max_length=80, blank=False, null=False) short_name = models.CharField('Abbreviation', max_length=10, blank=False, null=False, primary_key=True) description = models.TextField('Descriprion', blank=True) alumni = models.ManyToManyField(Attendee, through='Occurrence')
当数据库中已存在某用户的培训记录时,正常查询记录没问题:
from trainings.models import Attendee, Training, Occurrence from datetime import date, datetime attendee = Attendee.objects.get(pk='john@gmail.com') training = Training.objects.get(pk='python310') occurrence = Occurrence.objects.get(attendee=attendee, training=training) print(occurrence.attended_date) # 输出:datetime.date(2021, 11, 4)
但尝试通过新建Occurrence实例并调用save()更新字段时,触发错误:
occurrence = Occurrence(attendee=attendee, training=training, attended_date=date(2021, 11, 5)) occurrence.save(update_fields=["attendee", "training", "attended_date"])
错误信息:
ValueError: Cannot force an update in save() with no primary key.
错误原因
新建的Occurrence实例没有主键值,Django无法识别这是一条已存在的记录;而update_fields参数会强制Django执行更新操作,没有主键就找不到要更新的目标记录,因此报错。
正确更新方法
方法1:修改已查询到的实例后保存
直接使用查询得到的occurrence实例,修改字段后调用save(),还可以指定update_fields只更新需要修改的字段,提升性能:
attendee = Attendee.objects.get(pk='john@gmail.com') training = Training.objects.get(pk='python310') occurrence = Occurrence.objects.get(attendee=attendee, training=training) occurrence.attended_date = date(2021, 11, 5) # 仅更新attended_date字段,避免不必要的字段写入 occurrence.save(update_fields=["attended_date"])
方法2:使用QuerySet的update()直接更新
无需实例化模型,通过QuerySet直接执行更新操作,性能更优(不会触发模型的save()信号):
attendee = Attendee.objects.get(pk='john@gmail.com') training = Training.objects.get(pk='python310') Occurrence.objects.filter(attendee=attendee, training=training).update(attended_date=date(2021, 11, 5))
方法3:用get_or_create()兼容记录存在/不存在场景
如果不确定记录是否已存在,可以使用get_or_create()获取实例,再判断是否需要更新:
attendee = Attendee.objects.get(pk='john@gmail.com') training = Training.objects.get(pk='python310') occurrence, created = Occurrence.objects.get_or_create(attendee=attendee, training=training) if not created: occurrence.attended_date = date(2021, 11, 5) occurrence.save(update_fields=["attended_date"])
内容的提问来源于stack exchange,提问作者Domenico Spidy Tamburro
相关产品推荐
相关产品推荐

