1. 程式人生 > >django 1.8 官方文件翻譯: 2-2-1 執行查詢

django 1.8 官方文件翻譯: 2-2-1 執行查詢

執行查詢

一旦你建立好資料模型之後,django會自動生成一套資料庫抽象的API,可以讓你執行增刪改查的操作。這篇文件闡述瞭如何使用這些API。關於所有模型檢索選項的詳細內容,請見。

在整個文件(以及參考)中,我們會大量使用下面的模型,它構成了一個部落格應用。

from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def __str__(self):              # __unicode__ on Python 2
return self.name class Author(models.Model): name = models.CharField(max_length=50) email = models.EmailField() def __str__(self): # __unicode__ on Python 2 return self.name class Entry(models.Model): blog = models.ForeignKey(Blog) headline = models.CharField(max_length=255
) body_text = models.TextField() pub_date = models.DateField() mod_date = models.DateField() authors = models.ManyToManyField(Author) n_comments = models.IntegerField() n_pingbacks = models.IntegerField() rating = models.IntegerField() def __str__(self): # __unicode__ on Python 2
return self.headline

建立物件

為了把資料庫表中的資料表示成python物件,django使用一種直觀的方式:一個模型類代表資料庫的一個表,一個模型的例項代表資料庫表中的一條特定的記錄。

使用關鍵詞引數例項化一個物件來建立它,然後呼叫save()把它儲存到資料庫中。

假設模型存放於檔案mysite/blog/models.py中,下面是一個例子:

>>> from blog.models import Blog
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
>>> b.save()

上面的程式碼在背後執行了sql的INSERT操作。在你顯式呼叫save()之前,django不會訪問資料庫。

save()方法沒有返回值。

請參見

save()方法帶有一些高階選項,它們沒有在這裡給出,完整的細節請見save()文件。

如果你想只用一條語句建立並儲存一個物件,使用create()方法。

儲存物件的改動

呼叫save()方法,來儲存已經存在於資料庫中的物件的改動。

假設一個Blog的例項b5已經被儲存在資料庫中,這個例子更改了它的名字,並且在資料庫中更新它的記錄:

>>> b5.name = 'New name'
>>> b5.save()

上面的程式碼在背後執行了sql的UPDATE操作。在你顯式呼叫save()之前,django不會訪問資料庫。

儲存ForeignKeyManyToManyField欄位

更新ForeignKey欄位的方式和儲存普通欄位相同–只是簡單地把一個型別正確的物件賦值到欄位中。下面的例子更新了Entry類的例項entryblog屬性,假設Entry的一個合適的例項以及Blog已經儲存在資料庫中(我們可以像下面那樣獲取他們):

>>> from blog.models import Entry
>>> entry = Entry.objects.get(pk=1)
>>> cheese_blog = Blog.objects.get(name="Cheddar Talk")
>>> entry.blog = cheese_blog
>>> entry.save()

更新ManyToManyField的方式有一些不同–使用欄位的add()方法來增加關係的記錄。這個例子向entry物件新增Author類的例項joe

>>> from blog.models import Author
>>> joe = Author.objects.create(name="Joe")
>>> entry.authors.add(joe)

為了在一條語句中,向ManyToManyField新增多條記錄,可以在呼叫add()方法時傳入多個引數,像這樣:

>>> john = Author.objects.create(name="John")
>>> paul = Author.objects.create(name="Paul")
>>> george = Author.objects.create(name="George")
>>> ringo = Author.objects.create(name="Ringo")
>>> entry.authors.add(john, paul, george, ringo)

Django將會在你新增錯誤型別的物件時丟擲異常。

獲取物件

通過模型中的Manager構造一個QuertSet,來從你的資料庫中獲取物件。

QuerySet表示你資料庫中取出來的一個物件的集合。它可以含有零個、一個或者多個過濾器,過濾器根據所給的引數限制查詢結果的範圍。在sql的角度,QuerySetSELECT命令等價,過濾器是像WHERELIMIT一樣的限制子句。

你可以從模型的Manager那裡取得QuerySet。每個模型都至少有一個Manager,它通常命名為objects。通過模型類直接訪問它,像這樣:

>>> Blog.objects
<django.db.models.manager.Manager object at ...>
>>> b = Blog(name='Foo', tagline='Bar')
>>> b.objects
Traceback:
    ...
AttributeError: "Manager isn't accessible via Blog instances."

注意

管理器通常只可以通過模型類來訪問,不可以通過模型例項來訪問。這是為了強制區分表級別和記錄級別的操作。

對於一個模型來說,ManagerQuerySet的主要來源。例如,** Blog.objects.all() 會返回持有資料庫中所有**Blog物件的一個QuerySet

獲取所有物件

獲取一個表中所有物件的最簡單的方式是全部獲取。使用Managerall()方法:

>>> all_entries = Entry.objects.all()

all()方法返回包含資料庫中所有物件的QuerySet

使用過濾器獲取特定物件

all()方法返回的結果集中包含全部物件,但是更普遍的情況是你需要獲取完整集合的一個子集。

要建立這樣一個子集,需要精煉上面的結果集,增加一些過濾器作為條件。兩個最普遍的途徑是:

filter(**kwargs)
返回一個包含物件的集合,它們滿足引數中所給的條件。

exclude(**kwargs)
返回一個包含物件的集合,它們滿足引數中所給的條件。

查詢引數(上面函式定義中的**kwargs)需要滿足特定的格式,欄位檢索一節中會提到。

舉個例子,要獲取年份為2006的所有文章的結果集,可以這樣使用filter()方法:

Entry.objects.filter(pub_date__year=2006)

在預設的管理器類中,它相當於:

Entry.objects.all().filter(pub_date__year=2006)

鏈式過濾

QuerySet的精煉結果還是QuerySet,所以你可以把精煉用的語句組合到一起,像這樣:

>>> Entry.objects.filter(
...     headline__startswith='What'
... ).exclude(
...     pub_date__gte=datetime.date.today()
... ).filter(
...     pub_date__gte=datetime(2005, 1, 30)
... )

最開始的QuerySet包含資料庫中的所有物件,之後增加一個過濾器去掉一部分,在之後又是另外一個過濾器。最後的結果的一個QuerySet,包含所有標題以”word“開頭的記錄,並且日期是2005年一月,日為當天的值。

過濾後的結果集是獨立的

每次你篩選一個結果集,得到的都是全新的另一個結果集,它和之前的結果集之間沒有任何繫結關係。每次篩選都會建立一個獨立的結果集,可以被儲存及反覆使用。

例如:

>>> q1 = Entry.objects.filter(headline__startswith="What")
>>> q2 = q1.exclude(pub_date__gte=datetime.date.today())
>>> q3 = q1.filter(pub_date__gte=datetime.date.today())

這三個 QuerySets 是不同的。 第一個 QuerySet 包含大標題以”What”開頭的所有記錄。第二個則是第一個的子集,用一個附加的條件排除了出版日期 pub_date 是今天的記錄。 第三個也是第一個的子集,它只保留出版日期 pub_date 是今天的記錄。 最初的 QuerySet (q1) 沒有受到篩選的影響。

查詢集是延遲的

QuerySets 是惰性的 – 建立 QuerySet 的動作不涉及任何資料庫操作。你可以一直新增過濾器,在這個過程中,Django 不會執行任何資料庫查詢,除非 QuerySet 被執行. 看看下面這個例子:

>>> q = Entry.objects.filter(headline__startswith="What")
>>> q = q.filter(pub_date__lte=datetime.now())
>>> q = q.exclude(body_text__icontains="food")
>>> print q

雖然上面的程式碼看上去象是三個資料庫操作,但實際上只在最後一行 (print q) 執行了一次資料庫操作,。一般情況下, QuerySet 不能從資料庫中主動地獲得資料,得被動地由你來請求。對 QuerySet 求值就意味著 Django 會訪問資料庫。想了解對查詢集何時求值,請檢視 何時對查詢集求值 (When QuerySets are evaluated).

其他查詢集方法

大多數情況使用 all(), filter() 和 exclude() 就足夠了。 但也有一些不常用的;請檢視 查詢API參考 (QuerySet API Reference) 中完整的 QuerySet 方法列表。

限制查詢集範圍

可以用 python 的陣列切片語法來限制你的 QuerySet 以得到一部分結果。它等價於SQL中的 LIMIT 和 OFFSET 。

例如,下面的這個例子返回前五個物件 (LIMIT 5):

>>> Entry.objects.all()[:5]

這個例子返回第六到第十之間的物件 (OFFSET 5 LIMIT 5):

>>> Entry.objects.all()[5:10]

Django 不支援對查詢集做負數索引 (例如 Entry.objects.all()[-1]) 。

一般來說,對 QuerySet 切片會返回新的 QuerySet – 這個過程中不會對執行查詢。不過也有例外,如果你在切片時使用了 “step” 引數,查詢集就會被求值,就在資料庫中執行查詢。舉個例子,使用下面這個這個查詢集返回前十個物件中的偶數次物件,就會執行資料庫查詢:

>>> Entry.objects.all()[:10:2]

要檢索單獨的物件,而非列表 (比如 SELECT foo FROM bar LIMIT 1),可以直接使用索引來代替切片。舉個例子,下面這段程式碼將返回大標題排序後的第一條記錄 Entry:

>>> Entry.objects.order_by('headline')[0]

大約等價於:

>>> Entry.objects.order_by('headline')[0:1].get()

要注意的是:如果找不到符合條件的物件,第一種方法會丟擲 IndexError ,而第二種方法會丟擲 DoesNotExist。 詳看 get() 。

欄位篩選條件

欄位篩選條件就是 SQL 語句中的 WHERE 從句。就是 Django 中的 QuerySet 的 filter(), exclude() 和 get() 方法中的關鍵字引數。

篩選條件的形式是 field__lookuptype=value 。 (注意:這裡是雙下劃線)。例如:

>>> Entry.objects.filter(pub_date__lte='2006-01-01')

大體可以翻譯為如下的 SQL 語句:

SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';

這是怎麼辦到的?

Python 允許函式接受任意多 name-value 形式的引數,並在執行時才確定name和value的值。詳情請參閱官方Python教程中的 關鍵字引數(Keyword Arguments)。

如果你傳遞了一個無效的關鍵字引數,會丟擲 TypeError 導常。

資料庫 API 支援24種查詢型別;可以在 欄位篩選參考(field lookup reference) 檢視詳細的列表。為了給您一個直觀的認識,這裡我們列出一些常用的查詢型別:

exact

“exact” 匹配。例如:

>>> Entry.objects.get(headline__exact="Man bites dog")

會生成如下的 SQL 語句:

SELECT ... WHERE headline = 'Man bites dog';

如果你沒有提供查詢型別 – 也就是說關鍵字引數中沒有雙下劃線,那麼查詢型別就會被指定為 exact。

舉個例子,這兩個語句是相等的:

>>> Blog.objects.get(id__exact=14)  # Explicit form
>>> Blog.objects.get(id=14)         # __exact is implied

這樣做很方便,因為 exact 是最常用的。

iexact

忽略大小寫的匹配。所以下面的這個查詢:

>>> Blog.objects.get(name__iexact="beatles blog")

會匹配標題是 “Beatles Blog”, “beatles blog”, 甚至 “BeAtlES blOG” 的 Blog

contains

大小寫敏感的模糊匹配。 例如:

Entry.objects.get(headline__contains='Lennon')

大體可以翻譯為如下的 SQL:

SELECT ... WHERE headline LIKE '%Lennon%';

要注意這段程式碼匹配大標題 ‘Today Lennon honored’ ,而不能匹配 ‘today lennon honored’。

它也有一個忽略大小寫的版本,就是 icontains。

startswith, endswith

分別匹配開頭和結尾,同樣也有忽略大小寫的版本 istartswith 和 iendswith。
再強調一次,這僅僅是簡短介紹。完整的參考請參見 欄位篩選條件參考(field lookup reference)。

跨關係查詢

Django 提供了一種直觀而高效的方式在查詢(lookups)中表示關聯關係,它能自動確認 SQL JOIN 聯絡。要做跨關係查詢,就使用兩個下劃線來連結模型(model)間關聯欄位的名稱,直到最終連結到你想要的 model 為止。

這個例子檢索所有關聯 Blog 的 name 值為 ‘Beatles Blog’ 的所有 Entry 物件:

>>> Entry.objects.filter(blog__name__exact='Beatles Blog')

跨關係的篩選條件可以一直延展。

關係也是可逆的。可以在目標 model 上使用源 model 名稱的小寫形式得到反向關聯。

下面這個例子檢索至少關聯一個 Entry 且大標題 headline 包含 ‘Lennon’ 的所有 Blog 物件:

>>> Blog.objects.filter(entry__headline__contains='Lennon')

如果在某個關聯 model 中找不到符合過濾條件的物件,Django 將視它為一個空的 (所有的值都是 NULL), 但是可用的物件。這意味著不會有異常丟擲,在這個例子中:

Blog.objects.filter(entry__author__name='Lennon')

(假設關聯到 Author 類), 如果沒有哪個 author 與 entry 相關聯,Django 會認為它沒有 name 屬性,而不會因為不存在 author 丟擲異常。通常來說,這正是你所希望的機制。唯一的例外是使用 isnull 的情況。如下:

Blog.objects.filter(entry__author__name__isnull=True)

這段程式碼會得到 author 的 name 為空的 Blog 或 entry 的 author為空的 Blog。 如果不嫌麻煩,可以這樣寫:

Blog.objects.filter (entry__author__isnull=False,
        entry__author__name__isnull=True)

跨一對多/多對多關係(Spanning multi-valued relationships)

這部分是Django 1.0中新增的: 請檢視版本記錄
如果你的過濾是基於 ManyToManyField 或是逆向 ForeignKeyField 的,你可能會對下面這兩種情況感興趣。回顧 Blog/Entry 的關係(Blog 到 Entry 是一對多關係),如果要查詢這樣的 blog:它關聯一個大標題包含”Lennon”,且在2008年出版的 entry ;或者要查詢這樣的 blogs:它關聯一個大標題包含”Lennon”的 entry ,同時它又關聯另外一個在2008年出版的 entry 。因為一個 Blog 會關聯多個的Entry,所以上述兩種情況在現實應用中是很有可能出現的。

同樣的情形也出現在 ManyToManyField 上。例如,如果 Entry 有一個 ManyToManyField 欄位,名字是 tags,我們想得到 tags 是”music”和”bands”的 entries,或者我們想得到包含名為”music” 的標籤而狀態是”public”的 entry。

針對這兩種情況,Django 用一種很方便的方式來使用 filter() 和 exclude()。對於包含在同一個 filter() 中的篩選條件,查詢集要同時滿足所有篩選條件。而對於連續的 filter() ,查詢集的範圍是依次限定的。但對於跨一對多/多對多關係查詢來說,在第二種情況下,篩選條件針對的是主 model 所有的關聯物件,而不是被前面的 filter() 過濾後的關聯物件。

這聽起來會讓人迷糊,舉個例子會講得更清楚。要檢索這樣的 blog:它要關係一個大標題中含有 “Lennon” 並且在2008年出版的 entry (這個 entry 同時滿足這兩個條件),可以這樣寫:

Blog.objects.filter(entry__headline__contains='Lennon',
        entry__pub_date__year=2008)

要檢索另外一種 blog:它關聯一個大標題含有”Lennon”的 entry ,又關聯一個在2008年出版的 entry (一個 entry 的大標題含有 Lennon,同一個或另一個 entry 是在2008年出版的)。可以這樣寫:

Blog.objects.filter(entry__headline__contains='Lennon').filter(
        entry__pub_date__year=2008)

在第二個例子中,第一個過濾器(filter)先檢索與符合條件的 entry 的相關聯的所有 blogs。第二個過濾器在此基礎上從這些 blogs 中檢索與第二種 entry 也相關聯的 blog。第二個過濾器選擇的 entry 可能與第一個過濾器所選擇的完全相同,也可能不同。 因為過濾項過濾的是 Blog,而不是 Entry。

上述原則同樣適用於 exclude():一個單獨 exclude() 中的所有篩選條件都是作用於同一個例項 (如果這些條件都是針對同一個一對多/多對多的關係)。連續的 filter() 或 exclude() 卻根據同樣的篩選條件,作用於不同的關聯物件。

在過濾器中引用 model 中的欄位(Filters can reference fields on the model)

這部分是 Django 1.1 新增的: 請檢視版本記錄
在上面所有的例子中,我們構造的過濾器都只是將欄位值與某個常量做比較。如果我們要對兩個欄位的值做比較,那該怎麼做呢?

Django 提供 F() 來做這樣的比較。F() 的例項可以在查詢中引用欄位,來比較同一個 model 例項中兩個不同欄位的值。

例如:要查詢回覆數(comments)大於廣播數(pingbacks)的博文(blog entries),可以構造一個 F() 物件在查詢中引用評論數量:

>>> from django.db.models import F
>>> Entry.objects.filter(n_pingbacks__lt=F('n_comments'))

Django 支援 F() 物件之間以及 F() 物件和常數之間的加減乘除和取模的操作。例如,要找到廣播數等於評論數兩倍的博文,可以這樣修改查詢語句:

>>> Entry.objects.filter(n_pingbacks__lt=F('n_comments') * 2)

要查詢閱讀數量小於評論數與廣播數之和的博文,查詢如下:

>>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))

你也可以在 F() 物件中使用兩個下劃線做跨關係查詢。F() 物件使用兩個下劃線引入必要的關聯物件。例如,要查詢部落格(blog)名稱與作者(author)名稱相同的博文(entry),查詢就可以這樣寫:

>>> Entry.objects.filter(author__name=F('blog__name'))

主鍵查詢的簡捷方式

為使用方便考慮,Django 用 pk 代表主鍵”primary key”。

以 Blog 為例, 主鍵是 id 欄位,所以下面三個語句都是等價的:

>>> Blog.objects.get(id__exact=14) # Explicit form
>>> Blog.objects.get(id=14) # __exact is implied
>>> Blog.objects.get(pk=14) # pk implies id__exact

pk 對 __exact 查詢同樣有效,任何查詢項都可以用 pk 來構造基於主鍵的查詢:

# Get blogs entries with id 1, 4 and 7
>>> Blog.objects.filter(pk__in=[1,4,7])

# Get all blog entries with id > 14
>>> Blog.objects.filter(pk__gt=14)

pk 查詢也可以跨關係,下面三個語句是等價的:

>>> Entry.objects.filter(blog__id__exact=3) # Explicit form
>>> Entry.objects.filter(blog__id=3)        # __exact is implied
>>> Entry.objects.filter(blog__pk=3)        # __pk implies __id__exact

在LIKE語句中轉義百分號%和下劃線_

欄位篩選條件相當於 LIKE SQL 語句 (iexact, contains, icontains, startswith, istartswith, endswith 和 iendswith) ,它會自動轉義兩個特殊符號 – 百分號%和下劃線。(在 LIKE 語句中,百分號%表示多字元匹配,而下劃線表示單字元匹配。)

這就意味著我們可以直接使用這兩個字元,而不用考慮他們的 SQL 語義。例如,要查詢大標題中含有一個百分號%的 entry:

>>> Entry.objects.filter(headline__contains='%')

Django 會處理轉義;最終的 SQL 看起來會是這樣:

SELECT ... WHERE headline LIKE '%\%%';

下劃線_和百分號%的處理方式相同,Django 都會自動轉義。

快取和查詢

每個 QuerySet 都包含一個快取,以減少對資料庫的訪問。要編寫高效程式碼,就要理解快取是如何工作的。

一個 QuerySet 時剛剛建立的時候,快取是空的。 QuerySet 第一次執行時,會執行資料庫查詢,接下來 Django 就在 QuerySet 的快取中儲存查詢的結果,並根據請求返回這些結果(比如,後面再次呼叫這個 QuerySet 的時候)。再次執行 QuerySet 時就會重用這些快取結果。

要牢住上面所說的快取行為,否則在使用 QuerySet 時可能會給你造成不小的麻煩。例如,建立下面兩個 QuerySet ,並對它們求值,然後釋放:

>>> print [e.headline for e in Entry.objects.all()]
>>> print [e.pub_date for e in Entry.objects.all()]

這就意味著相同的資料庫查詢將執行兩次,事實上讀取了兩次資料庫。而且,這兩次讀出來的列表可能並不完全相同,因為存在這種可能:在兩次讀取之間,某個 Entry 被新增到資料庫中,或是被刪除了。

要避免這個問題,只要簡單地儲存 QuerySet 然後重用即可:

>>> queryset = Poll.objects.all()
>>> print [p.headline for p in queryset] # Evaluate the query set.
>>> print [p.pub_date for p in queryset] # Re-use the cache from the evaluation.

用 Q 物件實現複雜查詢 (Complex lookups with Q objects)

在 filter() 等函式中關鍵字引數彼此之間都是 “AND” 關係。如果你要執行更復雜的查詢(比如,實現篩選條件的 OR 關係),可以使用 Q 物件。

Q 物件(django.db.models.Q)是用來封裝一組查詢關鍵字的物件。這裡提到的查詢關鍵字請檢視上面的 “Field lookups”。

例如,下面這個 Q 物件封裝了一個單獨的 LIKE 查詢:

Q(question__startswith='What')

Q 物件可以用 & 和 | 運算子進行連線。當某個操作連線兩個 Q 物件時,就會產生一個新的等價的 Q 物件。

例如,下面這段語句就產生了一個 Q ,這是用 “OR” 關係連線的兩個 “question__startswith” 查詢:

Q(question__startswith='Who') | Q(question__startswith='What')

上面的例子等價於下面的 SQL WHERE 從句:

WHERE question LIKE 'Who%' OR question LIKE 'What%'

你可以用 & 和 | 連線任意多的 Q 物件,而且可以用括號分組。Q 物件也可以用 ~ 操作取反,而且普通查詢和取反查詢(NOT)可以連線在一起使用:

Q(question__startswith='Who') | ~Q(pub_date__year=2005)

每種查詢函式(比如 filter(), exclude(), get()) 除了能接收關鍵字引數以外,也能以位置引數的形式接受一個或多個 Q 物件。如果你給查詢函式傳遞了多個 Q 物件,那麼它們彼此間都是 “AND” 關係。例如:

Poll.objects.get(
    Q(question__startswith='Who'),
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)

… 大體可以翻譯為下面的 SQL:

SELECT * from polls WHERE question LIKE 'Who%'
    AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')

查詢函式可以混用 Q 物件和關鍵字引數。查詢函式的所有引數(Q 關係和關鍵字引數) 都是 “AND” 關係。但是,如果引數中有 Q 物件,它必須排在所有的關鍵字引數之前。例如:

Poll.objects.get(
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
    question__startswith='Who')

… 是一個有效的查詢。但下面這個查詢雖然看上去和前者等價:

# INVALID QUERY
Poll.objects.get(
    question__startswith='Who',
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)))

… 但這個查詢卻是無效的。

參見

在 Django 的單元測試 OR查詢例項(OR lookups examples) 中展示了 Q 的用例。

物件比較

要比較兩個物件,就和 Python 一樣,使用雙等號運算子:==。實際上比較的是兩個 model 的主鍵值。

以上面的 Entry 為例,下面兩個語句是等價的:

>>> some_entry == other_entry
>>> some_entry.id == other_entry.id

如果 model 的主鍵名稱不是 id,也沒關係。Django 會自動比較主鍵的值,而不管他們的名稱是什麼。例如,如果一個 model 的主鍵欄位名稱是 name,那麼下面兩個語句是等價的:

>>> some_obj == other_obj
>>> some_obj.name == other_obj.name

物件刪除

刪除方法就是 delete()。它執行時立即刪除物件而不返回任何值。例如:

e.delete()

你也可以一次性刪除多個物件。每個 QuerySet 都有一個 delete() 方法,它一次性刪除 QuerySet 中所有的物件。

例如,下面的程式碼將刪除 pub_date 是2005年的 Entry 物件:

Entry.objects.filter(pub_date__year=2005).delete()

要牢記這一點:無論在什麼情況下,QuerySet 中的 delete() 方法都只使用一條 SQL 語句一次性刪除所有物件,而並不是分別刪除每個物件。如果你想使用在 model 中自定義的 delete() 方法,就要自行呼叫每個物件的delete 方法。(例如,遍歷 QuerySet,在每個物件上呼叫 delete()方法),而不是使用 QuerySet 中的 delete()方法。

在 Django 刪除物件時,會模仿 SQL 約束 ON DELETE CASCADE 的行為,換句話說,刪除一個物件時也會刪除與它相關聯的外來鍵物件。例如:

b = Blog.objects.get(pk=1)
# This will delete the Blog and all of its Entry objects.
b.delete()

要注意的是: delete() 方法是 QuerySet 上的方法,但並不適用於 Manager 本身。這是一種保護機制,是為了避免意外地呼叫 Entry.objects.delete() 方法導致 所有的 記錄被誤刪除。如果你確認要刪除所有的物件,那麼你必須顯式地呼叫:

Entry.objects.all().delete()

一次更新多個物件 (Updating multiple objects at once)

這部分是 Django 1.0 中新增的: 請檢視版本文件
有時你想對 QuerySet 中的所有物件,一次更新某個欄位的值。這個要求可以用 update() 方法完成。例如:

# Update all the headlines with pub_date in 2007.
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')

這種方法僅適用於非關係欄位和 ForeignKey 外來鍵欄位。更新非關係欄位時,傳入的值應該是一個常量。更新 ForeignKey 欄位時,傳入的值應該是你想關聯的那個類的某個例項。例如:

>>> b = Blog.objects.get(pk=1)

# Change every Entry so that it belongs to this Blog.
>>> Entry.objects.all().update(blog=b)

update() 方法也是即時生效,不返回任何值的(與 delete() 相似)。 在 QuerySet 進行更新時,唯一的限制就是一次只能更新一個數據表,就是當前 model 的主表。所以不要嘗試更新關聯表和與此類似的操作,因為這是不可能執行的。

要小心的是: update() 方法是直接翻譯成一條 SQL 語句的。因此它是直接地一次完成所有更新。它不會呼叫你的 model 中的 save() 方法,也不會發出 pre_save 和 post_save 訊號(這些訊號在呼叫 save() 方法時產生)。如果你想儲存 QuerySet 中的每個物件,並且呼叫每個物件各自的 save() 方法,那麼你不必另外多寫一個函式。只要遍歷這些物件,依次呼叫 save() 方法即可:

for item in my_queryset:
    item.save()

這部分是在 Django 1.1 中新增的: 請檢視版本文件
在呼叫 update 時可以使用 F() 物件 來把某個欄位的值更新為另一個欄位的值。這對於自增記數器是非常有用的。例如,給所有的博文 (entry) 的廣播數 (pingback) 加一:

>>> Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)

但是,與 F() 物件在查詢時所不同的是,在filter 和 exclude子句中,你不能在 F() 物件中引入關聯關係(NO-Join),你只能引用當前 model 中要更新的欄位。如果你在 F() 物件引入了Join 關係object,就會丟擲 FieldError 異常:

# THIS WILL RAISE A FieldError
>>> Entry.objects.update(headline=F('blog__name'))

物件關聯

當你定義在 model 定義關係時 (例如, ForeignKey, OneToOneField, 或 ManyToManyField),model 的例項自帶一套很方便的API以獲取關聯的物件。

以最上面的 models 為例,一個 Entry 物件 e 能通過 blog 屬性獲得相關聯的 Blog 物件: e.blog。

(在場景背後,這個功能是由 Python 的 descriptors 實現的。如果你對此感興趣,可以瞭解一下。)

Django 也提供反向獲取關聯物件的 API,就是由從被關聯的物件得到其定義關係的主物件。例如,一個 Blog 類的例項 b 物件通過 entry_set 屬性得到所有相關聯的 Entry 物件列表: b.entry_set.all()。

這一節所有的例子都使用本頁頂部所列出的 Blog, Author 和 Entry model。

一對多關係

正向

如果一個 model 有一個 ForeignKey欄位,我們只要通過使用關聯 model 的名稱就可以得到相關聯的外來鍵物件。

例如:

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

你可以設定和獲得外來鍵屬性。正如你所期望的,改變外來鍵的行為並不引發資料庫操作,直到你呼叫 save()方法時,才會儲存到資料庫。例如:

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

如果外來鍵欄位 ForeignKey 有一個 null=True 的設定(它允許外來鍵接受空值 NULL),你可以賦給它空值 None 。例如:

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

在一對多關係中,第一次正向獲取關聯物件時,關聯物件會被快取。其後根據外來鍵訪問時這個例項,就會從快取中獲得它。例如:

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

要注意的是,QuerySet 的 select_related() 方法提前將所有的一對多關係放入快取中。例如:

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

逆向關聯

如果 model 有一個 ForeignKey外來鍵欄位,那麼外聯 model 的例項可以通過訪問 Manager 來得到所有相關聯的源 model 的例項。預設情況下,這個 Manager 被命名為 FOO_set, 這裡面的 FOO 就是源 model 的小寫名稱。這個 Manager 返回 QuerySets,它是可過濾和可操作的,在上面 “物件獲取(Retrieving objects)” 有提及。

例如:

>>> 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 的值來覆寫 FOO_set 的名稱。例如,如果 Entry model 中做一下更改: blog = ForeignKey(Blog, 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()

你不能在一個類當中訪問 ForeignKey Manager ;而必須通過類的例項來訪問:

>>> Blog.entry_set
Traceback:
    ...
AttributeError: "Manager must be accessed via instance".

除了在上面 “物件獲取Retrieving objects” 一節中提到的 QuerySet 方法之外,ForeignKey Manager 還有如下一些方法。下面僅僅對它們做一個簡短介紹,詳情請檢視 related objects reference。

add(obj1, obj2, ...)

將某個特定的 model 物件新增到被關聯物件集合中。

create(**kwargs)

建立並儲存一個新物件,然後將這個物件加被關聯物件的集合中,然後返回這個新物件。

remove(obj1, obj2, ...)

將某個特定的物件從被關聯物件集合中去除。

clear()

清空被關聯物件集合。
想一次指定關聯集合的成員,那麼只要給關聯集合分配一個可迭代的物件即可。它可以包含物件的例項,也可以只包含主鍵的值。例如:

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

在這個例子中,e1 和 e2 可以是完整的 Entry 例項,也可以是整型的主鍵值。

如果 clear() 方法是可用的,在迭代器(上例中就是一個列表)中的物件加入到 entry_set 之前,已存在於關聯集合中的所有物件將被清空。如果 clear() 方法 不可用,原有的關聯集合中的物件就不受影響,繼續存在。

這一節提到的每一個 “reverse” 操作都是實時操作資料庫的,每一個新增,建立,刪除操作都會及時儲存將結果儲存到資料庫中。

多對多關係

在多對多關係的任何一方都可以使用 API 訪問相關聯的另一方。多對多的 API 用起來和上面提到的 “逆向” 一對多關係關係非常相象。

唯一的差雖就在於屬性的命名: ManyToManyField 所在的 model (為了方便,我稱之為源model A) 使用欄位本身的名稱來訪問關聯物件;而被關聯的另一方則使用 A 的小寫名稱加上 ‘_set’ 字尾(這與逆向的一對多關係非常相象)。

下面這個例子會讓人更容易理解:

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 例項的 entry_set 屬性都被 entries 所代替。

一對一關係

相對於多對一關係而言,一對一關係不是非常簡單的。如果你在 model 中定義了一個 OneToOneField 關係,那麼你就可以用這個欄位的名稱做為屬性來訪問其所關聯的物件。

例如:

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

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

與 “reverse” 查詢不同的是,一對一關係的關聯物件也可以訪問 Manager 物件,但是這個 Manager 表現一個單獨的物件,而不是一個列表:

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

如果一個空物件被賦予關聯關係,Django 就會丟擲一個 DoesNotExist 異常。

和你定義正向關聯所用的方式一樣,類的例項也可以賦予逆向關聯方系:

e.entrydetail = ed

關係中的反向連線是如何做到的?

其他物件關係的對映(ORM)需要你在關聯雙方都定義關係。而 Django 的開發者則認為這違背了 DRY 原則 (Don’t Repeat Yourself),所以 Django 只需要你在一方定義關係即可。

但僅由一個 model 類並不能知道其他 model 類是如何與它關聯的,除非是其他 model 也被載入,那麼這是如何辦到的?

答案就在於 INSTALLED_APPS 設定中。任何一個 model 在第一次呼叫時,Django 就會遍歷所有的 INSTALLED_APPS 的所有 models,並且在記憶體中建立中必要的反向連線。本質上來說,INSTALLED_APPS 的作用之一就是確認 Django 完整的 model 範圍。

在關聯物件上的查詢

包含關聯物件的查詢與包含普通欄位值的查詢都遵循相同的規則。為某個查詢指定某個值的時候,你可以使用一個類例項,也可以使用物件的主鍵值。

例如,如果你有一個 Blog 物件 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

直接使用SQL

如果你發現某個 SQL 查詢用 Django 的資料庫對映來處理會非常複雜的話,你可以使用直接寫 SQL 來完成。

建議的方式是在你的 model 自定義方法或是自定義 model 的 manager 方法來執行查詢。雖然 Django 不要求資料操作必須在 model 層中執行。但是把你的商業邏輯程式碼放在一個地方,從程式碼組織的角度來看,也是十分明智的。詳情請檢視 執行原生SQL查詢(Performing raw SQL queries).

最後,要注意的是,Django的資料操作層僅僅是訪問資料庫的一個介面。你可以用其他的工具,程式語言,資料庫框架來訪問資料庫。對你的資料庫而言,沒什麼是非用 Django 不可的。