多對多關系
ManyToManyField 用來定義多對多關系, 和使用其它Field類型一樣:在模型當中把它做為一個類屬性包含進來。
ManyToManyField 需要一個位置參數:和該模型關聯的類。
例如,一個Pizza可以有多種Topping 即一種Topping 也可以位於多個Pizza上,而且每個Pizza有多個topping,下面是如何表示這個關系:
from django.db import models class Topping(models.Model): # ... pass class Pizza(models.Model): # ... toppings = models.ManyToManyField(Topping)
與ForeignKey一樣,你還可以創建遞歸關聯關系(與其自身具有多對多關系的對象)和與尚未定義的模型的關聯關系。
建議你以被關聯模型名稱的復數形式做為ManyToManyField 的名字(例如上例中的toppings)。
在哪個模型中設置 ManyToManyField 並不重要,在兩個模型中任選一個即可 —— 不要兩個模型都設置。
注意:
一般來說,ManyToManyField 實例應該放在要在表單中被編輯的對象中。 在上面的例子中,Topping 位於Pizza 中(而不是在 toppings 里面設置pizzas 的ManyToManyField 字段),因為設想一個Pizza 有多種Topping 比一個Topping 位於多個Pizza 上要更加自然。 按照上面的方式,在Pizza 的表單中將允許用戶選擇不同的Toppings。
提示:
完整的示例參見多對多關聯關系模型示例。
ManyToManyField 字段還接受別的參數,在模型字段參考中有詳細介紹。 這些選項有助於確定關系如何工作;都是可選的。
多對多關系的額外字段¶
處理類似搭配 pizza 和 topping 這樣簡單的多對多關系時,使用標准的ManyToManyField 就可以了。 但是,有時你可能需要關聯數據到兩個模型之間的關系上。
例如,有這樣一個應用,它記錄音樂家所屬的音樂小組。 我們可以用一個ManyToManyField 表示小組和成員之間的多對多關系。 但是,有時你可能想知道更多成員關系的細節,比如成員是何時加入小組的。
對於這些情況,Django 允許你指定一個中介模型來定義多對多關系。 你可以將其他字段放在中介模型里面。 源模型的ManyToManyField 字段將使用through 參數指向中介模型。 對於上面的音樂小組的例子,代碼如下:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self): # __unicode__ on Python 2
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self): # __unicode__ on Python 2
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)
在設置中介模型時,要顯式地指定外鍵並關聯到多對多關系涉及的模型。 這個顯式聲明定義兩個模型之間是如何關聯的。
中介模型有一些限制:
- 中介模型必須有且只有一個外鍵到源模型(上面例子中的
Group),或者你必須使用ManyToManyField.through_fields顯式指定Django 應該在關系中使用的外鍵。 如果你的模型中存在不止一個外鍵,並且through_fields沒有指定,將會觸發一個無效的錯誤。 對目標模型的外鍵有相同的限制(上面例子中的Person)。 - 對於通過中介模型與自己進行多對多關聯的模型,允許存在到同一個模型的兩個外鍵,但它們將被當做多對多關聯中一個關系的兩邊。 如果有超過兩個外鍵,同樣你必須像上面一樣指定
through_fields,否則將引發一個驗證錯誤。 - 使用中介模型定義與自身的多對多關系時,你必須設置
symmetrical=False(詳見模型字段參考)。
既然你已經設置好ManyToManyField 來使用中介模型(在這個例子中就是Membership),接下來你要開始創建多對多關系。 你要做的就是創建中介模型的實例:
>>> 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()或set()創建關系:
>>> # 下列語句都是無法工作的 >>> beatles.members.add(john) >>> beatles.members.create(name="George Harrison") >>> beatles.members.set([john, paul, ringo, george])
為什么不能這樣做? 這是因為你不能只創建 Person和 Group之間的關聯關系,你還要指定 Membership模型中所需要的所有信息; 而簡單的add、create 和賦值語句是做不到這一點的。 所以它們不能在使用中介模型的多對多關系中使用。 此時,唯一的辦法就是創建中介模型的實例。
remove()方法被禁用也是出於同樣的原因。 例如,如果通過中介模型定義的表沒有在(model1, model2)對上強制執行唯一性,則remove()調用將不能提供足夠的信息,說明應該刪除哪個中介模型實例:
>>> Membership.objects.create(person=ringo, group=beatles, ... date_joined=date(1968, 9, 4), ... invite_reason="You've been gone for a month and we miss you.") >>> beatles.members.all() <QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>, <Person: Ringo Starr>]> >>> # This will not work because it cannot tell which membership to remove >>> beatles.members.remove(ringo)
但是clear() 方法卻是可用的。它可以清空某個實例所有的多對多關系: >>> # Beatles have broken up >>> beatles.members.clear() >>> # Note that this deletes the intermediate model instances >>> Membership.objects.all()
通過創建中介模型的實例來建立對多對多關系后,你就可以執行查詢了。 和普通的多對多字段一樣,你可以直接使用被關聯模型的屬性進行查詢: # Find all the groups with a member whose name starts with 'Paul' >>> Group.objects.filter(members__name__startswith='Paul') <QuerySet [<Group: The Beatles>]>
如果你使用了中介模型,你也可以利用中介模型的屬性進行查詢: # Find all the members of the Beatles that joined after 1 Jan 1961 >>> Person.objects.filter( ... group__name='The Beatles', ... membership__date_joined__gt=date(1961,1,1)) <QuerySet [<Person: Ringo Starr]>
如果你需要訪問一個成員的信息,你可以直接獲取Membership模型: >>> 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.'
另一種獲取相同信息的方法是,在Person對象上查詢多對多反向關系: >>> 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.'
