多對多中間表詳解 -- Django從入門到精通系列教程

該系列教程系我的原創,並完整發布在我的官網劉江的博客和教程

全部轉載本文者,需在頂部顯著位置註明原做者及www.liujiangblog.com官網地址。


咱們都知道對於ManyToMany字段,Django採用的是第三張中間表的方式。經過這第三張表,來關聯ManyToMany的雙方。下面咱們根據一個具體的例子,詳細解說中間表的使用。python

1、默認中間表

首先,模型是這樣的:數據庫

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模型創建了關係。django

讓咱們到數據庫內看一下實際的內容,Django爲咱們建立了三張數據表,其中的app1是應用名。app

image.png-6.4kB

而後我在數據庫中添加了下面的Person對象:code

image.png-16.7kB

再添加下面的Group對象:orm

image.png-13.9kB

讓咱們來看看,中間表是個什麼樣子的:對象

image.png-23.7kB

首先有一列id,這是Django默認添加的,沒什麼好說的。而後是Group和Person的id列,這是默認狀況下,Django關聯兩張表的方式。若是你要設置關聯的列,可使用to_field參數。blog

可見在中間表中,並非將兩張表的數據都保存在一塊兒,而是經過id的關聯進行映射。教程

2、自定義中間表

通常狀況,普通的多對多已經夠用,無需本身建立第三張關係表。可是某些狀況可能更復雜一點,好比若是你想保存某我的加入某個分組的時間呢?想保存進組的緣由呢?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):

image.png-3.8kB

注意中間表的名字已經變成「app2_membership」了。

image.png-16.5kB

image.png-13.8kB

Person和Group沒有變化。

image.png-42.6kB

可是中間表就大相徑庭了!它完美的保存了咱們須要的內容。

3、使用中間表

針對上面的中間表,下面是一些使用例子(以歐洲著名的甲殼蟲樂隊成員爲例):

>>> 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)
相關文章
相關標籤/搜索