Django 1.8.2 文檔Home | Table of contents | Index | Modules? previous | up | next ?執行查詢?一旦你建立好數據模型,Django 會自動為你生成一套數據庫抽象的API,可以讓你創建、檢索、更新和刪除對象。這篇文檔闡述如何使用這些API。 關于模型查詢所有選項的完整細節,請見數據模型參考。在整個文檔(以及參考)中,我們將引用下面的模型,它構成一個博客應用:
from django.db import modelsclass Blog(models.Model):? ? name = models.CharField(max_length=100)? ? tagline = models.TextField()? ? def __str__(self):? ? ? ? ? ? ? # __unicode__ on Python 2? ? ? ? return self.nameclass Author(models.Model):? ? name = models.CharField(max_length=50)? ? email = models.EmailField()? ? def __str__(self):? ? ? ? ? ? ? # __unicode__ on Python 2? ? ? ? return self.nameclass 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
創建對象
?Django 使用一種直觀的方式把數據庫表中的數據表示成Python 對象:一個模型類代表數據庫中的一個表,一個模型類的實例代表這個數據庫表中的一條特定的記錄。使用關鍵字參數實例化模型實例來創建一個對象,然后調用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 已經被保存在數據庫中,下面這個例子將更改它的name 并且更新數據庫中的記錄:>>> b5.name = 'New name'>>> b5.save()上面的代碼在背后執行SQL 的UPDATE語句。在你顯式調用save()之前,Django不會訪問數據庫。保存ForeignKey和ManyToManyField字段?更新ForeignKey 字段的方式和保存普通字段相同 —— 只要把一個正確類型的對象賦值給該字段。下面的例子更新了Entry 類的實例entry 的blog 屬性,假設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 將會在你賦值或添加錯誤類型的對象時報錯。獲取對象?通過模型中的管理器構造一個查詢集,來從你的數據庫中獲取對象。查詢集表示從數據庫中取出來的對象的集合。它可以含有零個、一個或者多個過濾器。過濾器基于所給的參數限制查詢的結果。 從SQL 的角度,查詢集和SELECT 語句等價,過濾器是像WHERE 和LIMIT 一樣的限制子句。你可以從模型的管理器那里取得查詢集。每個模型都至少有一個管理器,它默認命名為objects。通過模型類來直接訪問它,像這樣:
>>> Blog.objects>>> b = Blog(name='Foo', tagline='Bar')
>>> b.objects
Traceback:
...
AttributeError: "Manager isn't accessible via Blog instances."
注
管理器只可以通過模型的類訪問,而不可以通過模型的實例訪問,目的是為了強制區分“表級別”的操作和“記錄級別”的操作。
對于一個模型來說,管理器是查詢集的主要來源。例如,Blog.objects.all() 返回包含數據庫中所有Blog 對象的一個查詢集。
獲取所有對象?
獲取一個表中所有對象的最簡單的方式是全部獲取。可以使用管理器的all() 方法:
>>> all_entries = Entry.objects.all()
all()方法返回包含數據庫中所有對象的一個查詢集。
使用過濾器獲取特定對象?
all() 方法返回了一個包含數據庫表中所有記錄查詢集。但在通常情況下,你往往想要獲取的是完整數據集的一個子集。
要創建這樣一個子集,你需要在原始的的查詢集上增加一些過濾條件。兩個最普遍的途徑是:
filter(**kwargs)
返回一個新的查詢集,它包含滿足查詢參數的對象。
exclude(**kwargs)
返回一個新的查詢集,它包含不滿足查詢參數的對象。
查詢參數(上面函數定義中的**kwargs)需要滿足特定的格式,下面字段查詢一節中會提到。
舉個例子,要獲取年份為2006的所有文章的查詢集,可以使用filter()方法:
Entry.objects.filter(pub_date__year=2006)
利用默認的管理器,它相當于:
Entry.objects.all().filter(pub_date__year=2006)
鏈式過濾?
查詢集的篩選結果本身還是查詢集,所以可以將篩選語句鏈接在一起。像這樣:
>>> Entry.objects.filter(
...? ? headline__startswith='What'
... ).exclude(
...? ? pub_date__gte=datetime.date.today()
... ).filter(
...? ? pub_date__gte=datetime(2005, 1, 30)
... )
這個例子最開始獲取數據庫中所有對象的一個查詢集,之后增加一個過濾器,然后又增加一個排除,再之后又是另外一個過濾器。最后的結果仍然是一個查詢集,它包含標題以”What“開頭、發布日期在2005年1月30日至當天之間的所有記錄。
過濾后的查詢集是獨立的?
每次你篩選一個查詢集,得到的都是全新的另一個查詢集,它和之前的查詢集之間沒有任何綁定關系。每次篩選都會創建一個獨立的查詢集,它可以被存儲及反復使用。
例如:
>>> 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())
這三個查詢集都是獨立的。第一個是一個基礎的查詢集,包含所有標題以“What”開頭的記錄。第二個查詢集是第一個的子集,它增加另外一個限制條件,排除pub_date 為今天和將來的記錄。第三個查詢集同樣是第一個的子集,它增加另外一個限制條件,只選擇pub_date 為今天或將來的記錄。原始的查詢集(q1)不會受到篩選過程的影響。
查詢集是惰性執行的?
查詢集 是惰性執行的 —— 創建查詢集不會帶來任何數據庫的訪問。你可以將過濾器保持一整天,直到查詢集 需要求值時,Django 才會真正運行這個查詢。看下這個例子:
>>> q = Entry.objects.filter(headline__startswith="What")
>>> q = q.filter(pub_date__lte=datetime.date.today())
>>> q = q.exclude(body_text__icontains="food")
>>> print(q)
雖然它看上去有三次數據庫訪問,但事實上只有在最后一行(print(q))時才訪問一次數據庫。一般來說,只有在“請求”查詢集 的結果時才會到數據庫中去獲取它們。當你確實需要結果時,查詢集 通過訪問數據庫來求值。 關于求值發生的準確時間,參見何時計算查詢集。
通過get 獲取一個單一的對象?
filter() 始終給你一個查詢集,即使只有一個對象滿足查詢條件 —— 這種情況下,查詢集將只包含一個元素。
如果你知道只有一個對象滿足你的查詢,你可以使用管理器的get() 方法,它直接返回該對象:
>>> one_entry = Entry.objects.get(pk=1)
可以對get() 使用任何查詢表達式,和filter() 一樣 —— 同樣請查看下文的字段查詢。
注意,使用get() 和使用filter() 的切片[0] 有一點區別。如果沒有結果滿足查詢,get() 將引發一個DoesNotExist 異常。這個異常是正在查詢的模型類的一個屬性 —— 所以在上面的代碼中,如果沒有主鍵為1 的Entry 對象,Django 將引發一個Entry.DoesNotExist。
類似地,如果有多條記錄滿足get() 的查詢條件,Django 也將報錯。這種情況將引發MultipleObjectsReturned,它同樣是模型類自身的一個屬性。
其它查詢集方法?
大多數情況下,需要從數據庫中查找對象時,你會使用all()、 get()、filter() 和exclude()。 然而,這只是冰山一角;查詢集 方法的完整列表,請參見查詢集API 參考。
限制查詢集?
可以使用Python 的切片語法來限制查詢集記錄的數目 。它等同于SQL 的LIMIT 和OFFSET 子句。
例如,下面的語句返回前面5 個對象(LIMIT 5):
>>> Entry.objects.all()[:5]
下面這條語句返回第6 至第10 個對象(OFFSET 5 LIMIT 5):
>>> Entry.objects.all()[5:10]
不支持負的索引(例如Entry.objects.all()[-1])。
通常,查詢集 的切片返回一個新的查詢集 —— 它不會執行查詢。有一個例外,是如果你使用Python 切片語法中"step"參數。例如,下面的語句將返回前10 個對象中每隔2個對象,它將真實執行查詢:
>>> 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 子句的內容。它們通過查詢集方法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 定義的函數可以接收任意的鍵/值對參數,這些名稱和參數可以在運行時求值。更多信息,參見Python 官方文檔中的關鍵字參數。
查詢條件中指定的字段必須是模型字段的名稱。但有一個例外,對于ForeignKey你可以使用字段名加上_id 后綴。在這種情況下,該參數的值應該是外鍵的原始值。例如:
>>> Entry.objects.filter(blog_id=4)
如果你傳遞的是一個不合法的參數,查詢函數將引發 TypeError。
這些數據庫API 支持大約二十多種查詢的類型;在字段查詢參考 中可以找到完整的參考。為了讓你嘗嘗鮮,下面是一些你可能用到的常見查詢:
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
分別表示以XXX開頭和以XXX結尾。當然還有大小寫不敏感的版本,叫做istartswith 和 iendswith。
同樣,這里只是表面。完整的參考可以在字段查詢參考中找到。
跨關聯關系的查詢?
Django 提供一種強大而又直觀的方式來“處理”查詢中的關聯關系,它在后臺自動幫你處理JOIN。 若要跨越關聯關系,只需使用關聯的模型字段的名稱,并使用雙下劃線分隔,直至你想要的字段:
下面這個例子獲取所有Blog 的name 為'Beatles Blog' 的Entry 對象:
>>> Entry.objects.filter(blog__name='Beatles Blog')
這種跨越可以是任意的深度。
它還可以反向工作。若要引用一個“反向”的關系,只需要使用該模型的小寫的名稱。
下面的示例獲取所有的Blog 對象,它們至少有一個Entry 的headline 包含'Lennon':
>>> Blog.objects.filter(entry__headline__contains='Lennon')
如果你在多個關聯關系直接過濾而且其中某個中介模型沒有滿足過濾條件的值,Django 將把它當做一個空的(所有的值都為NULL)但是合法的對象。這意味著不會有錯誤引發。例如,在下面的過濾器中:
Blog.objects.filter(entry__authors__name='Lennon')
(如果有一個相關聯的Author 模型),如果Entry 中沒有找到對應的author,那么它將當作其沒有name,而不會因為沒有author 引發一個錯誤。通常,這就是你想要的。唯一可能讓你困惑的是當你使用isnull 的時候。因此:
Blog.objects.filter(entry__authors__name__isnull=True)
返回的Blog 對象包括author __name 為空的Blog對象,以及author__name不為空但author__name關聯的entry __author 為空的對象。如果你不需要后者,你可以這樣寫:
Blog.objects.filter(entry__authors__isnull=False,
entry__authors__name__isnull=True)
跨越多值的關聯關系?
當你基于ManyToManyField 或反向的ForeignKey 來過濾一個對象時,有兩種不同種類的過濾器。考慮Blog/Entry 關聯關系(Blog 和 Entry 是一對多的關系)。我們可能想找出headline為“Lennon” 并且pub_date為'2008'年的Entry。或者我們可能想查詢headline為“Lennon” 的Entry或者pub_date為'2008'的Entry。因為實際上有和單個Blog 相關聯的多個Entry,所以這兩個查詢在某些場景下都是有可能并有意義的。
ManyToManyField 有類似的情況。例如,如果Entry 有一個ManyToManyField 叫做 tags,我們可能想找到tag 叫做“music” 和“bands” 的Entry,或者我們想找一個tag 名為“music” 且狀態為“public”的Entry。
對于這兩種情況,Django 有種一致的方法來處理filter() 調用。一個filter() 調用中的所有參數會同時應用以過濾出滿足所有要求的記錄。接下來的filter() 調用進一步限制對象集,但是對于多值關系,它們應用到與主模型關聯的對象,而不是應用到前一個filter() 調用選擇出來的對象。
這些聽起來可能有點混亂,所以希望展示一個例子使它變得更清晰。選擇所有包含同時滿足兩個條件的entry的blog,這兩個條件是headline 包含Lennon 和發表時間是2008 (同一個entry 滿足兩個條件),我們的代碼是:
Blog.objects.filter(entry__headline__contains='Lennon',
entry__pub_date__year=2008)
從所有的blog模型實例中選擇滿足以下條件的blog實例:blog的enrty的headline屬性值是“Lennon”,或者entry的發表時間是2008(兩個條件至少滿足一個,也可以同時滿足),我們的代碼是:
Blog.objects.filter(entry__headline__contains='Lennon').filter(
entry__pub_date__year=2008)
假設這里有一個blog擁有一條包含'Lennon'的entries條目和一條來自2008的entries條目,但是沒有一條來自2008并且包含"Lennon"的entries條目。第一個查詢不會返回任何blog,第二個查詢將會返回一個blog。
在第二個例子中, 第一個filter 限定查詢集中的blog 與headline 包含“Lennon” 的entry 關聯。第二個filter 又 限定查詢集中的blog ,這些blog關聯的entry 的發表時間是2008。(譯者注:難點在如何理解further這個詞!)第二個filter 過濾出來的entry 與第一個filter 過濾出來的entry 可能相同也可能不同。每個filter 語句過濾的是Blog,而不是Entry。
注
跨越多值關系的filter() 查詢的行為,與exclude() 實現的不同。單個exclude() 調用中的條件不必引用同一個記錄。
例如,下面的查詢排除headline 中包含“Lennon”的Entry和在2008 年發布的Entry:
Blog.objects.exclude(
entry__headline__contains='Lennon',
entry__pub_date__year=2008,
)
然而,這與使用filter() 的行為不同,它不是排除同時滿足兩個條件的Entry。為了實現這點,即選擇的Blog中不包含在2008年發布且healine 中帶有“Lennon” 的Entry,你需要編寫兩個查詢:
Blog.objects.exclude(
entry=Entry.objects.filter(
headline__contains='Lennon',
pub_date__year=2008,
),
)
Filter 可以引用模型的字段?
到目前為止給出的示例中,我們構造過將模型字段與常量進行比較的filter。但是,如果你想將模型的一個字段與同一個模型的另外一個字段進行比較該怎么辦?
Django 提供F 表達式 來允許這樣的比較。F() 返回的實例用作查詢內部對模型字段的引用。這些引用可以用于查詢的filter 中來比較相同模型實例上不同字段之間值的比較。
例如,為了查找comments 數目多于pingbacks 的Entry,我們將構造一個F() 對象來引用pingback 數目,并在查詢中使用該F() 對象:
>>> from django.db.models import F
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))
Django 支持對F() 對象使用加法、減法、乘法、除法、取模以及冪計算等算術操作,兩個操作數可以都是常數和其它F() 對象。為了查找comments 數目比pingbacks 兩倍還要多的Entry,我們將查詢修改為:
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)
New in Django 1.7:
添加 ** 操作符。
為了查詢rating 比pingback 和comment 數目總和要小的Entry,我們將這樣查詢:
>>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))
你還可以在F() 對象中使用雙下劃線標記來跨越關聯關系。帶有雙下劃線的F() 對象將引入任何需要的join 操作以訪問關聯的對象。例如,如要獲取author 的名字與blog 名字相同的Entry,我們可以這樣查詢:
>>> Entry.objects.filter(authors__name=F('blog__name'))
對于date 和date/time 字段,你可以給它們加上或減去一個timedelta 對象。下面的例子將返回發布超過3天后被修改的所有Entry:
>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
F() 對象支持.bitand() 和.bitor() 兩種位操作,例如:
>>> F('somefield').bitand(16)
查詢的快捷方式pk?
為了方便,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查詢在join 中也可以工作。例如,下面三個語句是等同的:
>>> 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 語句中使用的兩個特殊的字符 —— 百分號和下劃線。(在LIKE 語句中,百分號通配符表示多個字符,下劃線通配符表示單個字符)。
這意味著語句將很直觀,不會顯得太抽象。例如,要獲取包含一個百分號的所有的Entry,只需要像其它任何字符一樣使用百分號:
>>> Entry.objects.filter(headline__contains='%')
Django 會幫你轉義;生成的SQL 看上去會是這樣:
SELECT ... WHERE headline LIKE '%\%%';
對于下劃線是同樣的道理。百分號和下劃線都會透明地幫你處理。
緩存和查詢集?
每個查詢集都包含一個緩存來最小化對數據庫的訪問。理解它是如何工作的將讓你編寫最高效的代碼。
在一個新創建的查詢集中,緩存為空。首次對查詢集進行求值 —— 同時發生數據庫查詢 ——Django 將保存查詢的結果到查詢集的緩存中并返回明確請求的結果(例如,如果正在迭代查詢集,則返回下一個結果)。接下來對該查詢集 的求值將重用緩存的結果。
請牢記這個緩存行為,因為對查詢集使用不當的話,它會坑你的。例如,下面的語句創建兩個查詢集,對它們求值,然后扔掉它們:
>>> print([e.headline for e in Entry.objects.all()])
>>> print([e.pub_date for e in Entry.objects.all()])
這意味著相同的數據庫查詢將執行兩次,顯然倍增了你的數據庫負載。同時,還有可能兩個結果列表并不包含相同的數據庫記錄,因為在兩次請求期間有可能有Entry被添加進來或刪除掉。
為了避免這個問題,只需保存查詢集并重新使用它:
>>> queryset = Entry.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.
何時查詢集不會被緩存?
查詢集不會永遠緩存它們的結果。當只對查詢集的部分進行求值時會檢查緩存, 但是如果這個部分不在緩存中,那么接下來查詢返回的記錄都將不會被緩存。特別地,這意味著使用切片或索引來限制查詢集將不會填充緩存。
例如,重復獲取查詢集對象中一個特定的索引將每次都查詢數據庫:
>>> queryset = Entry.objects.all()
>>> print queryset[5] # Queries the database
>>> print queryset[5] # Queries the database again
然而,如果已經對全部查詢集求值過,則將檢查緩存:
>>> queryset = Entry.objects.all()
>>> [entry for entry in queryset] # Queries the database
>>> print queryset[5] # Uses cache
>>> print queryset[5] # Uses cache
下面是一些其它例子,它們會使得全部的查詢集被求值并填充到緩存中:
>>> [entry for entry in queryset]
>>> bool(queryset)
>>> entry in queryset
>>> list(queryset)
注
簡單地打印查詢集不會填充緩存。因為__repr__() 調用只返回全部查詢集的一個切片。
使用Q 對象進行復雜的查詢?
filter() 等方法中的關鍵字參數查詢都是一起進行“AND” 的。 如果你需要執行更復雜的查詢(例如OR 語句),你可以使用Q 對象。
Q 對象 (django.db.models.Q) 對象用于封裝一組關鍵字參數。這些關鍵字參數就是上文“字段查詢” 中所提及的那些。
例如,下面的Q 對象封裝一個LIKE 查詢:
from django.db.models import Q
Q(question__startswith='What')
Q 對象可以使用& 和| 操作符組合起來。當一個操作符在兩個Q 對象上使用時,它產生一個新的Q 對象。
例如,下面的語句產生一個Q 對象,表示兩個"question__startswith" 查詢的“OR” :
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 查詢示例演示了幾種Q 的用法。
比較對象?
為了比較兩個模型實例,只需要使用標準的Python 比較操作符,即雙等于符號:==。在后臺,它會比較兩個模型主鍵的值。
利用上面的Entry 示例,下面兩個語句是等同的:
>>> some_entry == other_entry
>>> some_entry.id == other_entry.id
如果模型的主鍵不叫id,也沒有問題。比較將始終使用主鍵,無論它叫什么。例如,如果模型的主鍵字段叫做name,下面的兩條語句是等同的:
>>> some_obj == other_obj
>>> some_obj.name == other_obj.name
刪除對象?
刪除方法,為了方便,就取名為delete()。這個方法將立即刪除對象且沒有返回值。例如:
e.delete()
你還可以批量刪除對象。每個查詢集 都有一個delete() 方法,它將刪除該查詢集中的所有成員。
例如,下面的語句刪除pub_date 為2005 的所有Entry 對象:
Entry.objects.filter(pub_date__year=2005).delete()
記住,這將盡可能地使用純SQL 執行,所以這個過程中不需要調用每個對象實例的delete()方法。如果你給模型類提供了一個自定義的delete() 方法并希望確保它被調用,你需要手工刪除該模型的實例(例如,迭代查詢集并調用每個對象的delete())而不能使用查詢集的批量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()
這種級聯的行為可以通過的ForeignKey 的on_delete 參數自定義。
注意,delete() 是唯一沒有在管理器 上暴露出來的查詢集方法。這是一個安全機制來防止你意外地請求Entry.objects.delete(),而刪除所有 的條目。如果你確實想刪除所有的對象,你必須明確地請求一個完全的查詢集:
Entry.objects.all().delete()
拷貝模型實例?
雖然沒有內建的方法用于拷貝模型實例,但還是很容易創建一個新的實例并讓它的所有字段都拷貝過來。最簡單的方法是,只需要將pk 設置為None。利用我們的Blog 示例:
blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk == 1
blog.pk = None
blog.save() # blog.pk == 2
如果你用繼承,那么會復雜一些。考慮下面Blog 的子類:
class ThemeBlog(Blog):
theme = models.CharField(max_length=200)
django_blog = ThemeBlog(name='Django', tagline='Django is easy', theme='python')
django_blog.save() # django_blog.pk == 3
由于繼承的工作方式,你必須設置pk 和 id 都為None:
django_blog.pk = None
django_blog.id = None
django_blog.save() # django_blog.pk == 4
這個過程不會拷貝關聯的對象。如果你想拷貝關聯關系,你必須編寫一些更多的代碼。在我們的例子中,Entry 有一個到Author 的多對多字段:
entry = Entry.objects.all()[0] # some previous entry
old_authors = entry.authors.all()
entry.pk = None
entry.save()
entry.authors = old_authors # saves new many2many relations
一次更新多個對象?
有時你想為一個查詢集中所有對象的某個字段都設置一個特定的值。這時你可以使用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() 方法會立即執行并返回查詢匹配的行數(如果有些行已經具有新的值,返回的行數可能和被更新的行數不相等)。更新查詢集 唯一的限制是它只能訪問一個數據庫表,也就是模型的主表。你可以根據關聯的字段過濾,但是你只能更新模型主表中的列。例如:
>>> b = Blog.objects.get(pk=1)
# Update all the headlines belonging to this Blog.
>>> Entry.objects.select_related().filter(blog=b).update(headline='Everything is the same')
要注意update() 方法會直接轉換成一個SQL 語句。它是一個批量的直接更新操作。它不會運行模型的save() 方法,或者發出pre_save 或 post_save信號(調用save()方法產生)或者查看auto_now 字段選項。如果你想保存查詢集中的每個條目并確保每個實例的save() 方法都被調用,你不需要使用任何特殊的函數來處理。只需要迭代它們并調用save():
for item in my_queryset:
item.save()
對update 的調用也可以使用F 表達式 來根據模型中的一個字段更新另外一個字段。這對于在當前值的基礎上加上一個值特別有用。例如,增加Blog 中每個Entry 的pingback 個數:
>>> Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)
然而,與filter 和exclude 子句中的F() 對象不同,在update 中你不可以使用F() 對象引入join —— 你只可以引用正在更新的模型的字段。如果你嘗試使用F() 對象引入一個join,將引發一個FieldError:
# THIS WILL RAISE A FieldError
>>> Entry.objects.update(headline=F('blog__name'))
關聯的對象?
當你在一個模型中定義一個關聯關系時(例如,ForeignKey、 OneToOneField 或ManyToManyField),該模型的實例將帶有一個方便的API 來訪問關聯的對象。
利用本頁頂部的模型,一個Entry 對象e 可以通過blog 屬性e.blog 獲取關聯的Blog 對象。
(在幕后,這個功能是通過Python 的描述器實現的。這應該不會對你有什么真正的影響,但是這里我們指出它以滿足你的好奇)。
Django 還會創建API 用于訪問關聯關系的另一頭 —— 從關聯的模型訪問定義關聯關系的模型。例如,Blog 對象b 可以通過entry_set 屬性 b.entry_set.all()訪問與它關聯的所有Entry 對象。
這一節中的所有示例都將使用本頁頂部定義的Blog、 Author 和Entry 模型。
一對多關系?
前向查詢?
如果一個模型具有ForeignKey,那么該模型的實例將可以通過屬性訪問關聯的(外部)對象。
例如:
>>> 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.
注意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.
反向查詢?
如果模型有一個ForeignKey,那么該ForeignKey 所指的模型實例可以通過一個管理器返回前一個有ForeignKey的模型的所有實例。默認情況下,這個管理器的名字為foo_set,其中foo 是源模型的小寫名稱。該管理器返回的查詢集可以用上一節提到的方式進行過濾和操作。
例如:
>>> 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 模型改成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()
使用自定義的反向管理器?
New in Django 1.7.
默認情況下,用于反向關聯關系的RelatedManager 是該模型默認管理器 的子類。如果你想為一個查詢指定一個不同的管理器,你可以使用下面的語法:
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() 方法中使用默認的過濾,那么該過濾將適用于all() 調用。
當然,指定一個自定義的管理器還可以讓你調用自定義的方法:
b.entry_set(manager='entries').is_published()
處理關聯對象的其它方法?
除了在上面”獲取對象“一節中定義的查詢集 方法之外,ForeignKey 管理器 還有其它方法用于處理關聯的對象集合。下面是每個方法的大概,完整的細節可以在關聯對象參考 中找到。
add(obj1, obj2, ...)
添加一指定的模型對象到關聯的對象集中。
create(**kwargs)
創建一個新的對象,將它保存并放在關聯的對象集中。返回新創建的對象。
remove(obj1, obj2, ...)
從關聯的對象集中刪除指定的模型對象。
clear()
從關聯的對象集中刪除所有的對象。
若要一次性給關聯的對象集賦值,只需要給它賦值一個可迭代的對象。這個可迭代的對象可以包含對象的實例,或者一個主鍵值的列表。例如:
b = Blog.objects.get(id=1)
b.entry_set = [e1, e2]
在這個例子中,e1 和e2 可以是Entry 實例,也可以是主鍵的整數值。
如果有clear() 方法,那么在將可迭代對象中的成員添加到集合中之前,將從entry_set 中刪除所有已經存在的對象。如果沒有clear() 方法,那么將直接添加可迭代對象中的成員而不會刪除所有已存在的對象。
這一節中提到的每個”反向“操作都會立即對數據庫產生作用。每個添加、創建和刪除操作都會立即并自動保存到數據庫中。
多對多關系?
多對多關系的兩端都會自動獲得訪問另一端的API。這些API 的工作方式與上面提到的“方向”一對多關系一樣。
唯一的區別在于屬性的名稱:定義 ManyToManyField 的模型使用該字段的屬性名稱,而“反向”模型使用源模型的小寫名稱加上'_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 實例將使用 entries 屬性而不是entry_set。
一對一關系?
一對一關系與多對一關系非常相似。如果你在模型中定義一個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.
在“反向”查詢中有所不同。一對一關系中的關聯模型同樣具有一個管理器對象,但是該管理器表示一個單一的對象而不是對象的集合:
e = Entry.objects.get(id=2)
e.entrydetail # returns the related EntryDetail object
如果沒有對象賦值給這個關聯關系,Django 將引發一個DoesNotExist 異常。
實例可以賦值給反向的關聯關系,方法和正向的關聯關系一樣:
e.entrydetail = ed
反向的關聯關系是如何實現的??
其它對象關系映射要求你在關聯關系的兩端都要定義。Django 的開發人員相信這是對DRY(不要重復你自己的代碼)原則的違背,所以Django 只要求你在一端定義關聯關系。
但是這怎么可能?因為一個模型類直到其它模型類被加載之后才知道哪些模型類是關聯的。
答案在app registry 中。當Django 啟動時,它導入INSTALLED_APPS 中列出的每個應用,然后導入每個應用中的models 模塊。每創建一個新的模型時,Django 添加反向的關系到所有關聯的模型。如果關聯的模型還沒有導入,Django 將保存關聯關系的記錄并在最終關聯的模型導入時添加這些關聯關系。
由于這個原因,你使用的所有模型都定義在INSTALLED_APPS 列出的應用中就顯得特別重要。否則,反向的關聯關系將不能正確工作。
通過關聯的對象進行查詢?
在關聯對象字段上的查詢與正常字段的查詢遵循同樣的規則。當你指定查詢需要匹配的一個值時,你可以使用一個對象實例或者對象的主鍵的值。
例如,如果你有一個id=5 的Blog 對象b,下面的三個查詢將是完全一樣的:
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。Django 對于編寫原始的SQL 查詢有多個選項;參見執行原始的SQL 查詢。
最后,值得注意的是Django 的數據庫層只是數據庫的一個接口。你可以利用其它工具、編程語言或數據庫框架來訪問數據庫;對于數據庫,Django 沒有什么特別的地方。
2015年5月13日
? previous | up | next ?