轉:多對多中間表詳解

申明:本文轉載自柳江博客
原鏈接:https://www.cnblogs.com/feixuelove1009/p/8417714.html

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

一、默認中間表
首先,模型是這樣的:

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爲我們創建了三張數據表,其中的app1是應用名。
在這裏插入圖片描述
然後我在數據庫中添加了下面的Person對象:
在這裏插入圖片描述
再添加下面的Group對象:
在這裏插入圖片描述
讓我們來看看,中間表是個什麼樣子的:
在這裏插入圖片描述
首先有一列id,這是Django默認添加的,沒什麼好說的。然後是Group和Person的id列,這是默認情況下,Django關聯兩張表的方式。如果你要設置關聯的列,可以使用to_field參數。

可見在中間表中,並不是將兩張表的數據都保存在一起,而是通過id的關聯進行映射。

二、自定義中間表
一般情況,普通的多對多已經夠用,無需自己創建第三張關係表。但是某些情況可能更復雜一點,比如如果你想保存某個人加入某個分組的時間呢?想保存進組的原因呢?

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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章