咱们都知道对于ManyToMany字段,Django采用的是第三张中间表的方式。经过这第三张表,来关联ManyToMany的双方。下面咱们根据一个具体的例子,详细解说中间表的使用。数据库
首先,模型是这样的:django
class Person(models.Model): name = models.CharField(max_length=128) def __str__(self): return self.name class Group(models.Model): name = models.CharField(max_length=128) members = models.ManyToManyField(Person) def __str__(self): return self.name
在Group模型中,经过members字段,以ManyToMany方式与Person模型创建了关系。app
让咱们到数据库内看一下实际的内容,Django为咱们建立了三张数据表,其中的app1是应用名。spa
而后我在数据库中添加了下面的Person对象:3d
再添加下面的Group对象:code
让咱们来看看,中间表是个什么样子的:orm
首先有一列id,这是Django默认添加的,没什么好说的。而后是Group和Person的id列,这是默认状况下,Django关联两张表的方式。若是你要设置关联的列,可使用to_field参数。对象
可见在中间表中,并非将两张表的数据都保存在一块儿,而是经过id的关联进行映射。blog
通常状况,普通的多对多已经够用,无需本身建立第三张关系表。可是某些状况可能更复杂一点,好比若是你想保存某我的加入某个分组的时间呢?想保存进组的缘由呢?ip
Django提供了一个through
参数,用于指定中间模型,你能够将相似进组时间,邀请缘由等其余字段放在这个中间模型内。例子以下:
from django.db import models class Person(models.Model): name = models.CharField(max_length=128) def __str__(self): return self.name class Group(models.Model): name = models.CharField(max_length=128) members = models.ManyToManyField(Person, through='Membership') def __str__(self): return self.name class Membership(models.Model): person = models.ForeignKey(Person, on_delete=models.CASCADE) group = models.ForeignKey(Group, on_delete=models.CASCADE) date_joined = models.DateField() # 进组时间 invite_reason = models.CharField(max_length=64) # 邀请缘由
在中间表中,咱们至少要编写两个外键字段,分别指向关联的两个模型。在本例中就是‘Person’和‘group’。 这里,咱们额外增长了‘date_joined’字段,用于保存人员进组的时间,‘invite_reason’字段用于保存邀请进组的缘由。
下面咱们依然在数据库中实际查看一下(应用名为app2):
注意中间表的名字已经变成“app2_membership”了。
Person和Group没有变化。
可是中间表就大相径庭了!它完美的保存了咱们须要的内容。
针对上面的中间表,下面是一些使用例子(以欧洲著名的甲壳虫乐队成员为例):
>>> ringo = Person.objects.create(name="Ringo Starr") >>> paul = Person.objects.create(name="Paul McCartney") >>> beatles = Group.objects.create(name="The Beatles") >>> m1 = Membership(person=ringo, group=beatles, ... date_joined=date(1962, 8, 16), ... invite_reason="Needed a new drummer.") >>> m1.save() >>> beatles.members.all() <QuerySet [<Person: Ringo Starr>]> >>> ringo.group_set.all() <QuerySet [<Group: The Beatles>]> >>> m2 = Membership.objects.create(person=paul, group=beatles, ... date_joined=date(1960, 8, 1), ... invite_reason="Wanted to form a band.") >>> beatles.members.all() <QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>]>
与普通的多对多不同,使用自定义中间表的多对多不能使用add(), create(),remove(),和set()方法来建立、删除关系,看下面:
>>> # 无效 >>> beatles.members.add(john) >>> # 无效 >>> beatles.members.create(name="George Harrison") >>> # 无效 >>> beatles.members.set([john, paul, ringo, george])
为何?由于上面的方法没法提供加入时间、邀请缘由等中间模型须要的字段内容。惟一的办法只能是经过建立中间模型的实例来建立这种类型的多对多关联。可是,clear()方法是有效的,它能清空全部的多对多关系。
>>> # 甲壳虫乐队解散了 >>> beatles.members.clear() >>> # 删除了中间模型的对象 >>> Membership.objects.all() <QuerySet []>
一旦你经过建立中间模型实例的方法创建了多对多的关联,你马上就能够像普通的多对多那样进行查询操做:
# 查找组内有Paul这我的的全部的组(以Paul开头的名字) >>> Group.objects.filter(members__name__startswith='Paul') <QuerySet [<Group: The Beatles>]>
可使用中间模型的属性进行查询:
# 查找甲壳虫乐队中加入日期在1961年1月1日以后的成员 >>> Person.objects.filter( ... group__name='The Beatles', ... membership__date_joined__gt=date(1961,1,1)) <QuerySet [<Person: Ringo Starr]>
能够像普通模型同样使用中间模型:
>>> ringos_membership = Membership.objects.get(group=beatles, person=ringo) >>> ringos_membership.date_joined datetime.date(1962, 8, 16) >>> ringos_membership.invite_reason 'Needed a new drummer.' >>> ringos_membership = ringo.membership_set.get(group=beatles) >>> ringos_membership.date_joined datetime.date(1962, 8, 16) >>> ringos_membership.invite_reason 'Needed a new drummer.'
这一部份内容,须要结合后面的模型query,若是暂时看不懂,没有关系。
对于中间表,有一点要注意(在前面章节已经介绍过,再次重申一下),默认状况下,中间模型只能包含一个指向源模型的外键关系,上面例子中,也就是在Membership中只能有Person和Group外键关系各一个,不能多。不然,你必须显式的经过ManyToManyField.through_fields
参数指定关联的对象。参考下面的例子:
from django.db import models class Person(models.Model): name = models.CharField(max_length=50) class Group(models.Model): name = models.CharField(max_length=128) members = models.ManyToManyField( Person, through='Membership', through_fields=('group', 'person'), ) class Membership(models.Model): group = models.ForeignKey(Group, on_delete=models.CASCADE) person = models.ForeignKey(Person, on_delete=models.CASCADE) inviter = models.ForeignKey( Person, on_delete=models.CASCADE, related_name="membership_invites", ) invite_reason = models.CharField(max_length=64)