关于Django模型ManyToMany关系的技术疑问
我在阅读Django模型文档时,对通过中间模型创建多对多(ManyToMany)关系的描述产生了兴趣,文档中关于中间模型的限制描述如下:
中间模型存在一些限制:
你的中间模型必须包含且仅包含一个指向源模型(source model,示例中的Group)的外键,否则必须通过ManyToManyField.through_fields明确指定Django用于该关系的外键。如果存在多个外键且未指定through_fields,将引发验证错误。指向目标模型(target model,示例中的Person)的外键也有类似限制。
对于通过中间模型实现自关联多对多关系的模型,允许存在两个指向同一模型的外键,但它们会被视为多对多关系的两个不同端。不过如果存在两个以上的外键,必须像上文一样指定through_fields,否则将引发验证错误。
针对上述描述,整理出以下疑问及解答:
1. 什么是源模型(source model)和目标模型(target model)?
源模型就是定义ManyToManyField字段的模型,目标模型则是该字段所关联的模型。举个实际例子:
class Group(models.Model): name = models.CharField(max_length=100) members = models.ManyToManyField('Person', through='Membership') class Person(models.Model): name = models.CharField(max_length=100) class Membership(models.Model): group = models.ForeignKey(Group, on_delete=models.CASCADE) person = models.ForeignKey(Person, on_delete=models.CASCADE) join_date = models.DateField()
这里Group是源模型(因为它定义了members这个多对多字段),Person是目标模型(被members字段关联)。
2. 为什么中间模型只能包含一个指向源模型或目标模型的外键?
Django需要明确识别中间模型中对应多对多关系两端的外键,若存在多个指向同一模型的外键,会产生歧义——Django无法自动判断哪一个外键是用来构建核心多对多关联的。
比如如果中间模型Membership里有两个指向Group的外键:group和secondary_group,Django就不知道该用哪个外键来关联Group和Person的多对多关系,因此默认要求每个模型只能对应一个外键;如果确实需要多个外键,就必须手动通过through_fields指定具体用哪两个。
3. 如何使用through_fields指定自关联多对多关系的两个外键?
自关联多对多关系是指模型关联自身,此时中间模型会有两个指向该模型的外键,需要用through_fields明确指定哪两个外键对应关系的两端。
举个具体实现示例:
class Person(models.Model): name = models.CharField(max_length=100) # 自关联多对多,指定中间模型和through_fields friends = models.ManyToManyField( 'self', through='Friendship', symmetrical=False, through_fields=('from_person', 'to_person') ) class Friendship(models.Model): from_person = models.ForeignKey(Person, on_delete=models.CASCADE, related_name='friendships_initiated') to_person = models.ForeignKey(Person, on_delete=models.CASCADE, related_name='friendships_received') become_friends_date = models.DateField()
这里through_fields是一个元组,第一个元素是源模型对应的外键名(也就是定义ManyToManyField的Person自身,对应from_person),第二个元素是目标模型对应的外键名(对应to_person)。通过这个配置,Django就能明确用这两个外键来构建自关联的多对多关系。
内容的提问来源于stack exchange,提问作者user571102

