一旦你建立好數(shù)據(jù)模型之后,django會自動生成一套數(shù)據(jù)庫抽象的API,可以讓你執(zhí)行增刪改查的操作。這篇文檔闡述了如何使用這些API。關(guān)于所有模型檢索選項的詳細(xì)內(nèi)容,請見數(shù)據(jù)模型參考。
在整個文檔(以及參考)中,我們會大量使用下面的模型,它構(gòu)成了一個博客應(yīng)用。
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
為了把數(shù)據(jù)庫表中的數(shù)據(jù)表示成python對象,django使用一種直觀的方式:一個模型類代表數(shù)據(jù)庫的一個表,一個模型的實例代表數(shù)據(jù)庫表中的一條特定的記錄。
使用關(guān)鍵詞參數(shù)實例化一個對象來創(chuàng)建它,然后調(diào)用save()把它保存到數(shù)據(jù)庫中。
假設(shè)模型存放于文件mysite/blog/models.py中,下面是一個例子:
>>> from blog.models import Blog
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
>>> b.save()
上面的代碼在背后執(zhí)行了sql的INSERT操作。在你顯式調(diào)用save()之前,django不會訪問數(shù)據(jù)庫。
save()方法沒有返回值。
請參見
save()方法帶有一些高級選項,它們沒有在這里給出,完整的細(xì)節(jié)請見save()文檔。
如果你想只用一條語句創(chuàng)建并保存一個對象,使用create()方法。
調(diào)用save()方法,來保存已經(jīng)存在于數(shù)據(jù)庫中的對象的改動。
假設(shè)一個Blog的實例b5已經(jīng)被保存在數(shù)據(jù)庫中,這個例子更改了它的名字,并且在數(shù)據(jù)庫中更新它的記錄:
>>> b5.name = 'New name'
>>> b5.save()
上面的代碼在背后執(zhí)行了sql的UPDATE操作。在你顯式調(diào)用save()之前,django不會訪問數(shù)據(jù)庫。
更新ForeignKey字段的方式和保存普通字段相同--只是簡單地把一個類型正確的對象賦值到字段中。下面的例子更新了Entry類的實例entry的blog屬性,假設(shè)Entry的一個合適的實例以及Blog已經(jīng)保存在數(shù)據(jù)庫中(我們可以像下面那樣獲取他們):
>>> 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()方法來增加關(guān)系的記錄。這個例子向entry對象添加Author類的實例joe:
>>> from blog.models import Author
>>> joe = Author.objects.create(name="Joe")
>>> entry.authors.add(joe)
為了在一條語句中,向ManyToManyField添加多條記錄,可以在調(diào)用add()方法時傳入多個參數(shù),像這樣:
>>> 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構(gòu)造一個QuertSet,來從你的數(shù)據(jù)庫中獲取對象。
QuerySet表示你數(shù)據(jù)庫中取出來的一個對象的集合。它可以含有零個、一個或者多個過濾器,過濾器根據(jù)所給的參數(shù)限制查詢結(jié)果的范圍。在sql的角度,QuerySet和SELECT命令等價,過濾器是像WHERE和LIMIT一樣的限制子句。
你可以從模型的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."
注意
管理器通常只可以通過模型類來訪問,不可以通過模型實例來訪問。這是為了強制區(qū)分表級別和記錄級別的操作。
對于一個模型來說,Manager是QuerySet的主要來源。例如, Blog.objects.all() 會返回持有數(shù)據(jù)庫中所有Blog對象的一個QuerySet。
獲取一個表中所有對象的最簡單的方式是全部獲取。使用Manager的all()方法:
>>> all_entries = Entry.objects.all()
all()方法返回包含數(shù)據(jù)庫中所有對象的QuerySet。
all()方法返回的結(jié)果集中包含全部對象,但是更普遍的情況是你需要獲取完整集合的一個子集。
要創(chuàng)建這樣一個子集,需要精煉上面的結(jié)果集,增加一些過濾器作為條件。兩個最普遍的途徑是:
filter(**kwargs) 返回一個包含對象的集合,它們滿足參數(shù)中所給的條件。
exclude(**kwargs) 返回一個包含對象的集合,它們不滿足參數(shù)中所給的條件。
查詢參數(shù)(上面函數(shù)定義中的**kwargs)需要滿足特定的格式,字段檢索一節(jié)中會提到。
舉個例子,要獲取年份為2006的所有文章的結(jié)果集,可以這樣使用filter()方法:
Entry.objects.filter(pub_date__year=2006)
在默認(rèn)的管理器類中,它相當(dāng)于:
Entry.objects.all().filter(pub_date__year=2006)
QuerySet的精煉結(jié)果還是QuerySet,所以你可以把精煉用的語句組合到一起,像這樣:
>>> Entry.objects.filter(
... headline__startswith='What'
... ).exclude(
... pub_date__gte=datetime.date.today()
... ).filter(
... pub_date__gte=datetime(2005, 1, 30)
... )
最開始的QuerySet包含數(shù)據(jù)庫中的所有對象,之后增加一個過濾器去掉一部分,在之后又是另外一個過濾器。最后的結(jié)果的一個QuerySet,包含所有標(biāo)題以”word“開頭的記錄,并且日期是2005年一月,日為當(dāng)天的值。
每次你篩選一個結(jié)果集,得到的都是全新的另一個結(jié)果集,它和之前的結(jié)果集之間沒有任何綁定關(guān)系。每次篩選都會創(chuàng)建一個獨立的結(jié)果集,可以被存儲及反復(fù)使用。
例如:
>>> 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 包含大標(biāo)題以"What"開頭的所有記錄。第二個則是第一個的子集,用一個附加的條件排除了出版日期 pub_date 是今天的記錄。 第三個也是第一個的子集,它只保留出版日期 pub_date 是今天的記錄。 最初的 QuerySet (q1) 沒有受到篩選的影響。
QuerySets 是惰性的 -- 創(chuàng)建 QuerySet 的動作不涉及任何數(shù)據(jù)庫操作。你可以一直添加過濾器,在這個過程中,Django 不會執(zhí)行任何數(shù)據(jù)庫查詢,除非 QuerySet 被執(zhí)行. 看看下面這個例子:
>>> q = Entry.objects.filter(headline__startswith="What")
>>> q = q.filter(pub_date__lte=datetime.now())
>>> q = q.exclude(body_text__icontains="food")
>>> print q
雖然上面的代碼看上去象是三個數(shù)據(jù)庫操作,但實際上只在最后一行 (print q) 執(zhí)行了一次數(shù)據(jù)庫操作,。一般情況下, QuerySet 不能從數(shù)據(jù)庫中主動地獲得數(shù)據(jù),得被動地由你來請求。對 QuerySet 求值就意味著 Django 會訪問數(shù)據(jù)庫。想了解對查詢集何時求值,請查看 何時對查詢集求值 (When QuerySets are evaluated).
大多數(shù)情況使用 all(), filter() 和 exclude() 就足夠了。 但也有一些不常用的;請查看 查詢API參考 (QuerySet API Reference) 中完整的 QuerySet 方法列表。
可以用 python 的數(shù)組切片語法來限制你的 QuerySet 以得到一部分結(jié)果。它等價于SQL中的 LIMIT 和 OFFSET 。
例如,下面的這個例子返回前五個對象 (LIMIT 5):
>>> Entry.objects.all()[:5]
這個例子返回第六到第十之間的對象 (OFFSET 5 LIMIT 5):
>>> Entry.objects.all()[5:10]
Django 不支持對查詢集做負(fù)數(shù)索引 (例如 Entry.objects.all()[-1]) 。
一般來說,對 QuerySet 切片會返回新的 QuerySet -- 這個過程中不會對運行查詢。不過也有例外,如果你在切片時使用了 "step" 參數(shù),查詢集就會被求值,就在數(shù)據(jù)庫中運行查詢。舉個例子,使用下面這個這個查詢集返回前十個對象中的偶數(shù)次對象,就會運行數(shù)據(jù)庫查詢:
>>> Entry.objects.all()[:10:2]
要檢索單獨的對象,而非列表 (比如 SELECT foo FROM bar LIMIT 1),可以直接使用索引來代替切片。舉個例子,下面這段代碼將返回大標(biāo)題排序后的第一條記錄 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() 方法中的關(guān)鍵字參數(shù)。
篩選條件的形式是 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 形式的參數(shù),并在運行時才確定name和value的值。詳情請參閱官方Python教程中的 關(guān)鍵字參數(shù)(Keyword Arguments)。
如果你傳遞了一個無效的關(guān)鍵字參數(shù),會拋出 TypeError 導(dǎo)常。
數(shù)據(jù)庫 API 支持24種查詢類型;可以在 字段篩選參考(field lookup reference) 查看詳細(xì)的列表。為了給您一個直觀的認(rèn)識,這里我們列出一些常用的查詢類型:
exact
"exact" 匹配。例如:
>>> Entry.objects.get(headline__exact="Man bites dog")
會生成如下的 SQL 語句:
SELECT ... WHERE headline = 'Man bites dog';
如果你沒有提供查詢類型 -- 也就是說關(guān)鍵字參數(shù)中沒有雙下劃線,那么查詢類型就會被指定為 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")
會匹配標(biāo)題是 "Beatles Blog", "beatles blog", 甚至 "BeAtlES blOG" 的 Blog
contains
大小寫敏感的模糊匹配。 例如:
Entry.objects.get(headline__contains='Lennon')
大體可以翻譯為如下的 SQL:
SELECT ... WHERE headline LIKE '%Lennon%';
要注意這段代碼匹配大標(biāo)題 'Today Lennon honored' ,而不能匹配 'today lennon honored'。
它也有一個忽略大小寫的版本,就是 icontains。
startswith, endswith
分別匹配開頭和結(jié)尾,同樣也有忽略大小寫的版本 istartswith 和 iendswith。 再強調(diào)一次,這僅僅是簡短介紹。完整的參考請參見 字段篩選條件參考(field lookup reference)。
Django 提供了一種直觀而高效的方式在查詢(lookups)中表示關(guān)聯(lián)關(guān)系,它能自動確認(rèn) SQL JOIN 聯(lián)系。要做跨關(guān)系查詢,就使用兩個下劃線來鏈接模型(model)間關(guān)聯(lián)字段的名稱,直到最終鏈接到你想要的 model 為止。
這個例子檢索所有關(guān)聯(lián) Blog 的 name 值為 'Beatles Blog' 的所有 Entry 對象:
>>> Entry.objects.filter(blog__name__exact='Beatles Blog')
跨關(guān)系的篩選條件可以一直延展。
關(guān)系也是可逆的??梢栽谀繕?biāo) model 上使用源 model 名稱的小寫形式得到反向關(guān)聯(lián)。
下面這個例子檢索至少關(guān)聯(lián)一個 Entry 且大標(biāo)題 headline 包含 'Lennon' 的所有 Blog 對象:
>>> Blog.objects.filter(entry__headline__contains='Lennon')
如果在某個關(guān)聯(lián) model 中找不到符合過濾條件的對象,Django 將視它為一個空的 (所有的值都是 NULL), 但是可用的對象。這意味著不會有異常拋出,在這個例子中:
Blog.objects.filter(entry__author__name='Lennon')
(假設(shè)關(guān)聯(lián)到 Author 類), 如果沒有哪個 author 與 entry 相關(guān)聯(lián),Django 會認(rèn)為它沒有 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)
跨一對多/多對多關(guān)系(Spanning multi-valued relationships)
這部分是Django 1.0中新增的: 請查看版本記錄 如果你的過濾是基于 ManyToManyField 或是逆向 ForeignKeyField 的,你可能會對下面這兩種情況感興趣?;仡?Blog/Entry 的關(guān)系(Blog 到 Entry 是一對多關(guān)系),如果要查找這樣的 blog:它關(guān)聯(lián)一個大標(biāo)題包含"Lennon",且在2008年出版的 entry ;或者要查找這樣的 blogs:它關(guān)聯(lián)一個大標(biāo)題包含"Lennon"的 entry ,同時它又關(guān)聯(lián)另外一個在2008年出版的 entry 。因為一個 Blog 會關(guān)聯(lián)多個的Entry,所以上述兩種情況在現(xiàn)實應(yīng)用中是很有可能出現(xiàn)的。
同樣的情形也出現(xiàn)在 ManyToManyField 上。例如,如果 Entry 有一個 ManyToManyField 字段,名字是 tags,我們想得到 tags 是"music"和"bands"的 entries,或者我們想得到包含名為"music" 的標(biāo)簽而狀態(tài)是"public"的 entry。
針對這兩種情況,Django 用一種很方便的方式來使用 filter() 和 exclude()。對于包含在同一個 filter() 中的篩選條件,查詢集要同時滿足所有篩選條件。而對于連續(xù)的 filter() ,查詢集的范圍是依次限定的。但對于跨一對多/多對多關(guān)系查詢來說,在第二種情況下,篩選條件針對的是主 model 所有的關(guān)聯(lián)對象,而不是被前面的 filter() 過濾后的關(guān)聯(lián)對象。
這聽起來會讓人迷糊,舉個例子會講得更清楚。要檢索這樣的 blog:它要關(guān)系一個大標(biāo)題中含有 "Lennon" 并且在2008年出版的 entry (這個 entry 同時滿足這兩個條件),可以這樣寫:
Blog.objects.filter(entry__headline__contains='Lennon',
entry__pub_date__year=2008)
要檢索另外一種 blog:它關(guān)聯(lián)一個大標(biāo)題含有"Lennon"的 entry ,又關(guān)聯(lián)一個在2008年出版的 entry (一個 entry 的大標(biāo)題含有 Lennon,同一個或另一個 entry 是在2008年出版的)??梢赃@樣寫:
Blog.objects.filter(entry__headline__contains='Lennon').filter(
entry__pub_date__year=2008)
在第二個例子中,第一個過濾器(filter)先檢索與符合條件的 entry 的相關(guān)聯(lián)的所有 blogs。第二個過濾器在此基礎(chǔ)上從這些 blogs 中檢索與第二種 entry 也相關(guān)聯(lián)的 blog。第二個過濾器選擇的 entry 可能與第一個過濾器所選擇的完全相同,也可能不同。 因為過濾項過濾的是 Blog,而不是 Entry。
上述原則同樣適用于 exclude():一個單獨 exclude() 中的所有篩選條件都是作用于同一個實例 (如果這些條件都是針對同一個一對多/多對多的關(guān)系)。連續(xù)的 filter() 或 exclude() 卻根據(jù)同樣的篩選條件,作用于不同的關(guān)聯(lián)對象。
在過濾器中引用 model 中的字段(Filters can reference fields on the model)
這部分是 Django 1.1 新增的: 請查看版本記錄 在上面所有的例子中,我們構(gòu)造的過濾器都只是將字段值與某個常量做比較。如果我們要對兩個字段的值做比較,那該怎么做呢?
Django 提供 F() 來做這樣的比較。F() 的實例可以在查詢中引用字段,來比較同一個 model 實例中兩個不同字段的值。
例如:要查詢回復(fù)數(shù)(comments)大于廣播數(shù)(pingbacks)的博文(blog entries),可以構(gòu)造一個 F() 對象在查詢中引用評論數(shù)量:
>>> from django.db.models import F
>>> Entry.objects.filter(n_pingbacks__lt=F('n_comments'))
Django 支持 F() 對象之間以及 F() 對象和常數(shù)之間的加減乘除和取模的操作。例如,要找到廣播數(shù)等于評論數(shù)兩倍的博文,可以這樣修改查詢語句:
>>> Entry.objects.filter(n_pingbacks__lt=F('n_comments') * 2)
要查找閱讀數(shù)量小于評論數(shù)與廣播數(shù)之和的博文,查詢?nèi)缦?
>>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))
你也可以在 F() 對象中使用兩個下劃線做跨關(guān)系查詢。F() 對象使用兩個下劃線引入必要的關(guān)聯(lián)對象。例如,要查詢博客(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 來構(gòu)造基于主鍵的查詢:
# 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 查詢也可以跨關(guān)系,下面三個語句是等價的:
>>> 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
字段篩選條件相當(dāng)于 LIKE SQL 語句 (iexact, contains, icontains, startswith, istartswith, endswith 和 iendswith) ,它會自動轉(zhuǎn)義兩個特殊符號 -- 百分號%和下劃線_。(在 LIKE 語句中,百分號%表示多字符匹配,而下劃線_表示單字符匹配。)
這就意味著我們可以直接使用這兩個字符,而不用考慮他們的 SQL 語義。例如,要查詢大標(biāo)題中含有一個百分號%的 entry:
>>> Entry.objects.filter(headline__contains='%')
Django 會處理轉(zhuǎn)義;最終的 SQL 看起來會是這樣:
SELECT ... WHERE headline LIKE '%\%%';
下劃線_和百分號%的處理方式相同,Django 都會自動轉(zhuǎn)義。
每個 QuerySet 都包含一個緩存,以減少對數(shù)據(jù)庫的訪問。要編寫高效代碼,就要理解緩存是如何工作的。
一個 QuerySet 時剛剛創(chuàng)建的時候,緩存是空的。 QuerySet 第一次運行時,會執(zhí)行數(shù)據(jù)庫查詢,接下來 Django 就在 QuerySet 的緩存中保存查詢的結(jié)果,并根據(jù)請求返回這些結(jié)果(比如,后面再次調(diào)用這個 QuerySet 的時候)。再次運行 QuerySet 時就會重用這些緩存結(jié)果。
要牢住上面所說的緩存行為,否則在使用 QuerySet 時可能會給你造成不小的麻煩。例如,創(chuàng)建下面兩個 QuerySet ,并對它們求值,然后釋放:
>>> print [e.headline for e in Entry.objects.all()]
>>> print [e.pub_date for e in Entry.objects.all()]
這就意味著相同的數(shù)據(jù)庫查詢將執(zhí)行兩次,事實上讀取了兩次數(shù)據(jù)庫。而且,這兩次讀出來的列表可能并不完全相同,因為存在這種可能:在兩次讀取之間,某個 Entry 被添加到數(shù)據(jù)庫中,或是被刪除了。
要避免這個問題,只要簡單地保存 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 對象實現(xiàn)復(fù)雜查找 (Complex lookups with Q objects)
在 filter() 等函式中關(guān)鍵字參數(shù)彼此之間都是 "AND" 關(guān)系。如果你要執(zhí)行更復(fù)雜的查詢(比如,實現(xiàn)篩選條件的 OR 關(guān)系),可以使用 Q 對象。
Q 對象(django.db.models.Q)是用來封裝一組查詢關(guān)鍵字的對象。這里提到的查詢關(guān)鍵字請查看上面的 "Field lookups"。
例如,下面這個 Q 對象封裝了一個單獨的 LIKE 查詢:
Q(question__startswith='What')
Q 對象可以用 & 和 | 運算符進(jìn)行連接。當(dāng)某個操作連接兩個 Q 對象時,就會產(chǎn)生一個新的等價的 Q 對象。
例如,下面這段語句就產(chǎn)生了一個 Q ,這是用 "OR" 關(guān)系連接的兩個 "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()) 除了能接收關(guān)鍵字參數(shù)以外,也能以位置參數(shù)的形式接受一個或多個 Q 對象。如果你給查詢函式傳遞了多個 Q 對象,那么它們彼此間都是 "AND" 關(guān)系。例如:
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 對象和關(guān)鍵字參數(shù)。查詢函式的所有參數(shù)(Q 關(guān)系和關(guān)鍵字參數(shù)) 都是 "AND" 關(guān)系。但是,如果參數(shù)中有 Q 對象,它必須排在所有的關(guān)鍵字參數(shù)之前。例如:
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,也沒關(guān)系。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() 方法,就要自行調(diào)用每個對象的delete 方法。(例如,遍歷 QuerySet,在每個對象上調(diào)用 delete()方法),而不是使用 QuerySet 中的 delete()方法。
在 Django 刪除對象時,會模仿 SQL 約束 ON DELETE CASCADE 的行為,換句話說,刪除一個對象時也會刪除與它相關(guān)聯(lián)的外鍵對象。例如:
b = Blog.objects.get(pk=1)
# This will delete the Blog and all of its Entry objects.
b.delete()
要注意的是: delete() 方法是 QuerySet 上的方法,但并不適用于 Manager 本身。這是一種保護機制,是為了避免意外地調(diào)用 Entry.objects.delete() 方法導(dǎo)致 所有的 記錄被誤刪除。如果你確認(rèn)要刪除所有的對象,那么你必須顯式地調(diào)用:
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')
這種方法僅適用于非關(guān)系字段和 ForeignKey 外鍵字段。更新非關(guān)系字段時,傳入的值應(yīng)該是一個常量。更新 ForeignKey 字段時,傳入的值應(yīng)該是你想關(guān)聯(lián)的那個類的某個實例。例如:
>>> 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 進(jìn)行更新時,唯一的限制就是一次只能更新一個數(shù)據(jù)表,就是當(dāng)前 model 的主表。所以不要嘗試更新關(guān)聯(lián)表和與此類似的操作,因為這是不可能運行的。
要小心的是: update() 方法是直接翻譯成一條 SQL 語句的。因此它是直接地一次完成所有更新。它不會調(diào)用你的 model 中的 save() 方法,也不會發(fā)出 pre_save 和 post_save 信號(這些信號在調(diào)用 save() 方法時產(chǎn)生)。如果你想保存 QuerySet 中的每個對象,并且調(diào)用每個對象各自的 save() 方法,那么你不必另外多寫一個函式。只要遍歷這些對象,依次調(diào)用 save() 方法即可:
for item in my_queryset:
item.save()
這部分是在 Django 1.1 中新增的: 請查看版本文檔 在調(diào)用 update 時可以使用 F() 對象 來把某個字段的值更新為另一個字段的值。這對于自增記數(shù)器是非常有用的。例如,給所有的博文 (entry) 的廣播數(shù) (pingback) 加一:
>>> Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)
但是,與 F() 對象在查詢時所不同的是,在filter 和 exclude子句中,你不能在 F() 對象中引入關(guān)聯(lián)關(guān)系(NO-Join),你只能引用當(dāng)前 model 中要更新的字段。如果你在 F() 對象引入了Join 關(guān)系object,就會拋出 FieldError 異常:
# THIS WILL RAISE A FieldError
>>> Entry.objects.update(headline=F('blog__name'))
當(dāng)你定義在 model 定義關(guān)系時 (例如, ForeignKey, OneToOneField, 或 ManyToManyField),model 的實例自帶一套很方便的API以獲取關(guān)聯(lián)的對象。
以最上面的 models 為例,一個 Entry 對象 e 能通過 blog 屬性獲得相關(guān)聯(lián)的 Blog 對象: e.blog。
(在場景背后,這個功能是由 Python 的 descriptors 實現(xiàn)的。如果你對此感興趣,可以了解一下。)
Django 也提供反向獲取關(guān)聯(lián)對象的 API,就是由從被關(guān)聯(lián)的對象得到其定義關(guān)系的主對象。例如,一個 Blog 類的實例 b 對象通過 entry_set 屬性得到所有相關(guān)聯(lián)的 Entry 對象列表: b.entry_set.all()。
這一節(jié)所有的例子都使用本頁頂部所列出的 Blog, Author 和 Entry model。
如果一個 model 有一個 ForeignKey字段,我們只要通過使用關(guān)聯(lián) model 的名稱就可以得到相關(guān)聯(lián)的外鍵對象。
例如:
>>> e = Entry.objects.get(id=2)
>>> e.blog # Returns the related Blog object.
你可以設(shè)置和獲得外鍵屬性。正如你所期望的,改變外鍵的行為并不引發(fā)數(shù)據(jù)庫操作,直到你調(diào)用 save()方法時,才會保存到數(shù)據(jù)庫。例如:
>>> e = Entry.objects.get(id=2)
>>> e.blog = some_blog
>>> e.save()
如果外鍵字段 ForeignKey 有一個 null=True 的設(shè)置(它允許外鍵接受空值 NULL),你可以賦給它空值 None 。例如:
>>> e = Entry.objects.get(id=2)
>>> e.blog = None
>>> e.save() # "UPDATE blog_entry SET blog_id = NULL ...;"
在一對多關(guān)系中,第一次正向獲取關(guān)聯(lián)對象時,關(guān)聯(lián)對象會被緩存。其后根據(jù)外鍵訪問時這個實例,就會從緩存中獲得它。例如:
>>> 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() 方法提前將所有的一對多關(guā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.
如果 model 有一個 ForeignKey外鍵字段,那么外聯(lián) model 的實例可以通過訪問 Manager 來得到所有相關(guān)聯(lián)的源 model 的實例。默認(rèn)情況下,這個 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() 的定義中設(shè)置 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()
你不能在一個類當(dāng)中訪問 ForeignKey Manager ;而必須通過類的實例來訪問:
>>> Blog.entry_set
Traceback:
...
AttributeError: "Manager must be accessed via instance".
除了在上面 "對象獲取Retrieving objects" 一節(jié)中提到的 QuerySet 方法之外,F(xiàn)oreignKey Manager 還有如下一些方法。下面僅僅對它們做一個簡短介紹,詳情請查看 related objects reference。
add(obj1, obj2, ...)
將某個特定的 model 對象添加到被關(guān)聯(lián)對象集合中。
create(**kwargs)
創(chuàng)建并保存一個新對象,然后將這個對象加被關(guān)聯(lián)對象的集合中,然后返回這個新對象。
remove(obj1, obj2, ...)
將某個特定的對象從被關(guān)聯(lián)對象集合中去除。
clear()
清空被關(guān)聯(lián)對象集合。 想一次指定關(guān)聯(lián)集合的成員,那么只要給關(guān)聯(lián)集合分配一個可迭代的對象即可。它可以包含對象的實例,也可以只包含主鍵的值。例如:
b = Blog.objects.get(id=1)
b.entry_set = [e1, e2]
在這個例子中,e1 和 e2 可以是完整的 Entry 實例,也可以是整型的主鍵值。
如果 clear() 方法是可用的,在迭代器(上例中就是一個列表)中的對象加入到 entry_set 之前,已存在于關(guān)聯(lián)集合中的所有對象將被清空。如果 clear() 方法 不可用,原有的關(guān)聯(lián)集合中的對象就不受影響,繼續(xù)存在。
這一節(jié)提到的每一個 "reverse" 操作都是實時操作數(shù)據(jù)庫的,每一個添加,創(chuàng)建,刪除操作都會及時保存將結(jié)果保存到數(shù)據(jù)庫中。
在多對多關(guān)系的任何一方都可以使用 API 訪問相關(guān)聯(lián)的另一方。多對多的 API 用起來和上面提到的 "逆向" 一對多關(guān)系關(guān)系非常相象。
唯一的差雖就在于屬性的命名: ManyToManyField 所在的 model (為了方便,我稱之為源model A) 使用字段本身的名稱來訪問關(guān)聯(lián)對象;而被關(guān)聯(lián)的另一方則使用 A 的小寫名稱加上 '_set' 后綴(這與逆向的一對多關(guā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 實例的 entry_set 屬性都被 entries 所代替。
相對于多對一關(guān)系而言,一對一關(guān)系不是非常簡單的。如果你在 model 中定義了一個 OneToOneField 關(guān)系,那么你就可以用這個字段的名稱做為屬性來訪問其所關(guān)聯(lián)的對象。
例如:
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" 查詢不同的是,一對一關(guān)系的關(guān)聯(lián)對象也可以訪問 Manager 對象,但是這個 Manager 表現(xiàn)一個單獨的對象,而不是一個列表:
e = Entry.objects.get(id=2)
e.entrydetail # returns the related EntryDetail object
如果一個空對象被賦予關(guān)聯(lián)關(guān)系,Django 就會拋出一個 DoesNotExist 異常。
和你定義正向關(guān)聯(lián)所用的方式一樣,類的實例也可以賦予逆向關(guān)聯(lián)方系:
e.entrydetail = ed
其他對象關(guān)系的映射(ORM)需要你在關(guān)聯(lián)雙方都定義關(guān)系。而 Django 的開發(fā)者則認(rèn)為這違背了 DRY 原則 (Don't Repeat Yourself),所以 Django 只需要你在一方定義關(guān)系即可。
但僅由一個 model 類并不能知道其他 model 類是如何與它關(guān)聯(lián)的,除非是其他 model 也被載入,那么這是如何辦到的?
答案就在于 INSTALLED_APPS 設(shè)置中。任何一個 model 在第一次調(diào)用時,Django 就會遍歷所有的 INSTALLED_APPS 的所有 models,并且在內(nèi)存中創(chuàng)建中必要的反向連接。本質(zhì)上來說,INSTALLED_APPS 的作用之一就是確認(rèn) Django 完整的 model 范圍。
包含關(guān)聯(lián)對象的查詢與包含普通字段值的查詢都遵循相同的規(guī)則。為某個查詢指定某個值的時候,你可以使用一個類實例,也可以使用對象的主鍵值。
例如,如果你有一個 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
如果你發(fā)現(xiàn)某個 SQL 查詢用 Django 的數(shù)據(jù)庫映射來處理會非常復(fù)雜的話,你可以使用直接寫 SQL 來完成。
建議的方式是在你的 model 自定義方法或是自定義 model 的 manager 方法來運行查詢。雖然 Django 不要求數(shù)據(jù)操作必須在 model 層中執(zhí)行。但是把你的商業(yè)邏輯代碼放在一個地方,從代碼組織的角度來看,也是十分明智的。詳情請查看 執(zhí)行原生SQL查詢(Performing raw SQL queries).
最后,要注意的是,Django的數(shù)據(jù)操作層僅僅是訪問數(shù)據(jù)庫的一個接口。你可以用其他的工具,編程語言,數(shù)據(jù)庫框架來訪問數(shù)據(jù)庫。對你的數(shù)據(jù)庫而言,沒什么是非用 Django 不可的。
譯者:Django 文檔協(xié)作翻譯小組,原文:Executing queries。
本文以 CC BY-NC-SA 3.0 協(xié)議發(fā)布,轉(zhuǎn)載請保留作者署名和文章出處。
Django 文檔協(xié)作翻譯小組人手緊缺,有興趣的朋友可以加入我們,完全公益性質(zhì)。交流群:467338606。