django_models_關系多對多


多對多關系

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模型中所需要的所有信息; 而簡單的addcreate 和賦值語句是做不到這一點的。 所以它們不能在使用中介模型的多對多關系中使用。 此時,唯一的辦法就是創建中介模型的實例。

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.'

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM