带through类的Django ManyToManyField:add()/set()/clear()方法使用指南
这个报错太典型了——你的多对多关系依赖了带必填额外字段的中间表Bank_customer,直接调用bank.customers.add(customer)时,Django没办法自动填充city这个非空外键,自然就触发数据库约束错误了。下面给你一步步讲解决办法,以及add()、set()、clear()的正确用法:
一、先解决当前的关联报错问题
有两种可靠的方式来关联Bank和Customer实例:
方式1:直接创建中间表实例(最直观)
既然中间表需要city字段,那就手动创建Bank_customer对象来完成关联:
# 假设你已经有了bank、customer、city的实例 Bank_customer.objects.create( bank=bank, customer=customer, city=city_instance # 必须指定这个必填字段 )
方式2:用add()方法配合through_defaults参数
Django 1.11及以上版本支持通过through_defaults给中间表的额外字段传值,这样就能直接用add()了:
bank.customers.add( customer, through_defaults={'city': city_instance} )
如果要批量添加多个Customer,且它们对应的city相同,还可以一次性传多个实例:
bank.customers.add( customer1, customer2, customer3, through_defaults={'city': same_city_instance} )
要是每个Customer对应的city不同,那就得分开调用add(),或者用方式1逐个创建中间表实例。
二、ManyRelatedManager的add()、set()、clear()方法详解
这三个方法的用法分**普通多对多(无额外中间表字段)和带额外字段的多对多(through指定中间表)**两种情况:
1. 普通多对多关系(无中间表额外字段)
比如你定义的多对多如果是customers = models.ManyToManyField(Customer)(没有through参数),用法就很简单:
add():添加一个/多个关联对象,重复添加会自动忽略(不会创建重复的中间表记录)。可以传实例,也可以传主键:bank.customers.add(customer1, customer2) # 或者传主键 bank.customers.add(1, 2)set():替换当前的关联集合,默认会先清空现有关联,再添加新的对象。可以传实例列表或主键列表:# 替换为customer1和customer3 bank.customers.set([customer1, customer3]) # 如果不想清空旧关联,只添加新的(可能产生重复),可以设置clear=False bank.customers.set([customer4], clear=False)clear():清除当前对象的所有关联记录,不会删除Customer或Bank实例本身,只是删除中间表的关联行:bank.customers.clear()
2. 带额外字段的多对多关系(through指定中间表)
这种情况因为中间表有额外必填字段,所以调用add()和set()时必须指定这些字段的值:
add():必须配合through_defaults参数传递中间表的额外字段值,格式是字典,键为中间表的字段名:# 单个对象 bank.customers.add(customer, through_defaults={'city': city_obj}) # 多个对象共用同一中间表字段值 bank.customers.add(customer1, customer2, through_defaults={'city': city_obj})set():同样需要through_defaults参数,默认会先清除旧的关联记录,再添加新的,新记录都会使用through_defaults里的值:
要是每个关联对象需要不同的中间表字段值,建议先调用bank.customers.set([customer1, customer3], through_defaults={'city': city_obj}) # 如果需要保留旧关联,添加新的,设置clear=False(但要注意重复问题) bank.customers.set([customer4], through_defaults={'city': another_city}, clear=False)clear(),再逐个创建Bank_customer实例,这样更灵活。clear():和普通多对多用法完全一样,直接清除所有关联的中间表记录,不需要额外参数:bank.customers.clear()
注意事项
- 如果中间表的额外字段有默认值,那
through_defaults可以省略,Django会自动用默认值填充; - 中间表的外键字段(比如
bank和customer)不需要在through_defaults里指定,Django会自动关联当前的Bank实例和你传入的Customer实例; - 确保你使用的Django版本支持
through_defaults(1.11+),如果是旧版本,只能用方式1创建中间表实例。
内容的提问来源于stack exchange,提问作者Vikramark
相关产品推荐
相关产品推荐

