Django4.0 執(zhí)行查詢-關聯(lián)對象

2022-03-16 17:33 更新

當你在模型中定義了關聯(lián)關系(如 ?ForeignKey?, ?OneToOneField? 或 ?ManyToManyField?),該模型的實例將會自動獲取一套 API,能快捷地訪問關聯(lián)對象。

拿本文開始的模型做例子,一個 ?Entry ?對象 ?e ?通過 ?blog ?屬性獲取其關聯(lián)的 ?Blog ?對象: ?e.blog?。

Django 也提供了從關聯(lián)關系 另一邊 訪問的 API —— 從被關聯(lián)模型到定義關聯(lián)關系的模型的連接。例如,一個 ?Blog ?對象 ?b ?能通過 ?entry_set ?屬性 ?b.entry_set.all()? 訪問包含所有關聯(lián) ?Entry ?對象的列表。

本章節(jié)中的所有例子都是用了本頁開頭定義的 ?Blog?, ?Author ?和 ?Entry ?模型。

一對多關聯(lián)

正向訪問

若模型有個 ?ForeignKey?,該模型的實例能通過其屬性訪問關聯(lián)(外部的)對象。例如:

>>> e = Entry.objects.get(id=2)
>>> e.blog # Returns the related Blog object.

你可以通過 ?foreign-key? 屬性獲取和設置值。對外鍵的修改直到你調用 ?save()? 后才會被存入數(shù)據(jù)庫。例如:

>>> e = Entry.objects.get(id=2)
>>> e.blog = some_blog
>>> e.save()

若 ?ForeignKey ?字段配置了 ?null=True? (即其允許 ?NULL ?值),你可以指定值為 ?None ?移除關聯(lián)。例如:

>>> e = Entry.objects.get(id=2)
>>> e.blog = None
>>> e.save() # "UPDATE blog_entry SET blog_id = NULL ...;"

首次通過正向一對多關聯(lián)訪問關聯(lián)對象時會緩存關聯(lián)關系。后續(xù)在同一對象上通過外鍵的訪問也會被緩存。例如:

>>> e = Entry.objects.get(id=2)
>>> print(e.blog)  # Hits the database to retrieve the associated Blog.
>>> print(e.blog)  # Doesn't hit the database; uses cached version.

注意:?select_related() QuerySet? 方法會預先用所有一對多關聯(lián)對象填充緩存。例如:

>>> e = Entry.objects.select_related().get(id=2)
>>> print(e.blog)  # Doesn't hit the database; uses cached version.
>>> print(e.blog)  # Doesn't hit the database; uses cached version.

“反向”關聯(lián)

若模型有 ?ForeignKey?,外鍵關聯(lián)的模型實例將能訪問 ?Manager?,后者會返回第一個模型的所有實例。默認情況下,該 ?Manager ?名為 ?FOO_set?, ?FOO ?即源模型名的小寫形式。 ?Manager ?返回 ?QuerySets?,后者能以 “檢索對象” 章節(jié)介紹的方式進行篩選和操作。例如:

>>> b = Blog.objects.get(id=1)
>>> b.entry_set.all() # Returns all Entry objects related to Blog.

# b.entry_set is a Manager that returns QuerySets.
>>> b.entry_set.filter(headline__contains='Lennon')
>>> b.entry_set.count()
    

你可以在定義 ?ForeignKey時設置 ?related_name參數(shù)重寫這個 ?FOO_set? 名。例如,若修改 ?Entry ?模型為 ?blog = ForeignKey(Blog, on_delete=models.CASCADE, related_name='entries')?,前文示例代碼會看起來像這樣:

>>> b = Blog.objects.get(id=1)
>>> b.entries.all() # Returns all Entry objects related to Blog.

# b.entries is a Manager that returns QuerySets.
>>> b.entries.filter(headline__contains='Lennon')
>>> b.entries.count()

使用自定義反向管理器

?RelatedManager ?反向關聯(lián)的默認實現(xiàn)是該模型默認管理器 一個實例。若你想為某個查詢指定一個不同的管理器,可以使用如下語法:

from django.db import models

class Entry(models.Model):
    #...
    objects = models.Manager()  # Default Manager
    entries = EntryManager()    # Custom Manager

b = Blog.objects.get(id=1)
b.entry_set(manager='entries').all()

若 ?EntryManager ?在其 ?get_queryset()? 方法執(zhí)行了默認過濾行為,該行為會應用到 對?all()? 的調用中。
指定一個自定義反向管理也允許你調用模型自定義方法:

b.entry_set(manager='entries').is_published()

管理關聯(lián)對象的額外方法

?ForeignKey Manager ?還有方法能處理關聯(lián)對象集合。除了上面的 “檢索對象” 中定義的 ?QuerySet ?方法以外,以下是每項的簡要介紹:

  • ?add(obj1, obj2, ...)?:將特定的模型對象加入關聯(lián)對象集合。
  • ?create(**kwargs)?:創(chuàng)建一個新對象,保存,并將其放入關聯(lián)對象集合中。返回新創(chuàng)建的對象。
  • ?remove(obj1, obj2, ...)?:從關聯(lián)對象集合刪除指定模型對象。
  • ?clear()?:從關聯(lián)對象集合刪除所有對象。
  • ?set(objs)?:替換關聯(lián)對象集合

要指定關聯(lián)集合的成員,調用 ?set()? 方法,并傳入可迭代的對象實例集合。例如,若 ?e1 ?和 ?e2 ?都是 ?Entry ?實例:

b = Blog.objects.get(id=1)
b.entry_set.set([e1, e2])

若能使用 ?clear() ?方法, ?entry_set ?中所有舊對象會在將可迭代集合(本例中是個列表)中的對象加入其中之前被刪除。若 不能 使用 ?clear()? 方法,添加新對象時不會刪除舊對象。

多對多關聯(lián)

多對多關聯(lián)的兩端均自動獲取訪問另一端的 API。該 API 的工作方式類似上面的 “反向” 一對多關聯(lián)。
不同點在為屬性命名上:定義了 ?ManyToManyField ?的模型使用字段名作為屬性名,而 “反向” 模型使用源模型名的小寫形式,加上? '_set'? (就像反向一對多關聯(lián)一樣)。
例如:

e = Entry.objects.get(id=3)
e.authors.all() # Returns all Author objects for this Entry.
e.authors.count()
e.authors.filter(name__contains='John')

a = Author.objects.get(id=5)
a.entry_set.all() # Returns all Entry objects for this Author.

和 ?ForeignKey ?一樣, ?ManyToManyField ?能指定 ?related_name?。在上面的例子中,若 ?Entry ?中的 ?ManyToManyField ?已指定了 ?related_name='entries'?,隨后每個 ?Author ?實例會擁有一個 ?entries ?屬性,而不是 ?entry_set?。
另一個與一對多關聯(lián)不同的地方是,除了模型實例以外,多對多關聯(lián)中的? add()?, ?set()? 和 ?remove()? 方法能接收主鍵值。例如,若 ?e ?和 ?e2 ?是 ?Entry ?的實例,以下兩種 ?set()? 調用結果一致:

a = Author.objects.get(id=5)
a.entry_set.set([e1, e2])
a.entry_set.set([e1.pk, e2.pk])

一對一關聯(lián)

一對一關聯(lián)與多對一關聯(lián)非常類似。若在模型中定義了 ?OneToOneField?,該模型的實例只需通過其屬性就能訪問關聯(lián)對象。

例如:

class EntryDetail(models.Model):
    entry = models.OneToOneField(Entry, on_delete=models.CASCADE)
    details = models.TextField()

ed = EntryDetail.objects.get(id=2)
ed.entry # Returns the related Entry object.

不同點在于 “反向” 查詢。一對一關聯(lián)所關聯(lián)的對象也能訪問 ?Manager ?對象,但這個 ?Manager ?僅代表一個對象,而不是對象的集合:

e = Entry.objects.get(id=2)
e.entrydetail # returns the related EntryDetail object

若未為關聯(lián)關系指定對象,Django 會拋出 ?DoesNotExist? 異常。
實例能通過為正向關聯(lián)指定關聯(lián)對象一樣的方式指定給反向關聯(lián):

e.entrydetail = ed

反向關聯(lián)的實現(xiàn)

其它對象關聯(lián)映射實現(xiàn)要求你在兩邊都定義關聯(lián)關系。而 Django 開發(fā)者堅信這違反了 ?DRY ?原則(不要自我重復),故 Django 僅要求你在一端定義關聯(lián)關系。
但這是如何實現(xiàn)的呢,給你一個模型類,模型類并不知道是否有其它模型類關聯(lián)它,直到其它模型類被加載?
答案在于 應用注冊。 Django 啟動時,它會導入 ?INSTALLED_APPS ?列出的每個應用,和每個應用中的 ?model ?模塊。無論何時創(chuàng)建了一個新模型類,Django 為每個關聯(lián)模型添加反向關聯(lián)。若被關聯(lián)的模型未被導入,Django 會持續(xù)追蹤這些關聯(lián),并在關聯(lián)模型被導入時添加關聯(lián)關系。
出于這個原因,包含你所使用的所有模型的應用必須列在 ?INSTALLED_APPS ?中。否則,反向關聯(lián)可能不會正常工作。

查詢關聯(lián)對象

涉及關聯(lián)對象的查詢與涉及普通字段的查詢遵守同樣的規(guī)則。未查詢條件指定值時,你可以使用對象實例,或該實例的主鍵。
例如,若有個博客對象 ?b?,其 ?id=5?,以下三種查詢是一樣的:

Entry.objects.filter(blog=b) # Query using object instance
Entry.objects.filter(blog=b.id) # Query using id from instance
Entry.objects.filter(blog=5) # Query using id directly


以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號