Django 文檔協(xié)作翻譯小組人手緊缺姻政,有興趣的朋友可以加入我們鼎姊,完全公益性質(zhì)巡李。
交流群:467338606
執(zhí)行查詢
一但你建立好數(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
創(chuàng)建對象
為了把數(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和ManyToManyField字段
更新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."
注意
管理器通常只可以通過模型類來訪問疯兼,不可以通過模型實例來訪問。這是為了強(qiáng)制區(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)
鏈?zhǔn)竭^濾
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é)果集是獨(dú)立的
每次你篩選一個結(jié)果集,得到的都是全新的另一個結(jié)果集傲宜,它和之前的結(jié)果集之間沒有任何綁定關(guān)系运杭。每次篩選都會創(chuàng)建一個獨(dú)立的結(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 -- 這個過程中不會對運(yùn)行查詢飘痛。不過也有例外珊膜,如果你在切片時使用了 "step" 參數(shù),查詢集就會被求值宣脉,就在數(shù)據(jù)庫中運(yùn)行查詢车柠。舉個例子,使用下面這個這個查詢集返回前十個對象中的偶數(shù)次對象塑猖,就會運(yùn)行數(shù)據(jù)庫查詢:
>>> Entry.objects.all()[:10:2]
要檢索單獨(dú)的對象竹祷,而非列表 (比如 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ù)输瓜,并在運(yùn)行時才確定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。
再強(qiáng)調(diào)一次左刽,這僅僅是簡短介紹捺信。完整的參考請參見 字段篩選條件參考(field lookup reference)。
跨關(guān)系查詢
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 拋出異常。通常來說标捺,這正是你所希望的機(jī)制懊纳。唯一的例外是使用 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():一個單獨(dú) 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
在LIKE語句中轉(zhuǎn)義百分號%和下劃線_
字段篩選條件相當(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 第一次運(yùn)行時凡壤,會執(zhí)行數(shù)據(jù)庫查詢署尤,接下來 Django 就在 QuerySet 的緩存中保存查詢的結(jié)果,并根據(jù)請求返回這些結(jié)果(比如亚侠,后面再次調(diào)用這個 QuerySet 的時候)曹体。再次運(yùn)行 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 對象封裝了一個單獨(dú)的 LIKE 查詢:
Q(question__startswith='What')
Q 對象可以用 & 和 | 運(yùn)算符進(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 一樣,使用雙等號運(yùn)算符:==洞翩。實際上比較的是兩個 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()俱笛。它運(yùn)行時立即刪除對象而不返回任何值捆姜。例如:
e.delete()
你也可以一次性刪除多個對象。每個 QuerySet 都有一個 delete() 方法迎膜,它一次性刪除 QuerySet 中所有的對象泥技。
例如,下面的代碼將刪除 pub_date 是2005年的 Entry 對象:
Entry.objects.filter(pub_date__year=2005).delete()
要牢記這一點(diǎn):無論在什么情況下星虹,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 本身决侈。這是一種保護(hù)機(jī)制,是為了避免意外地調(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)表和與此類似的操作隙袁,因為這是不可能運(yù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'))
對象關(guān)聯(lián)
當(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。
一對多關(guān)系
正向
如果一個 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.
逆向關(guān)聯(lián)
如果 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)系
在多對多關(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)系而言店归,一對一關(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)一個單獨(dú)的對象秩伞,而不是一個列表:
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)系中的反向連接是如何做到的?
其他對象關(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ā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
直接使用SQL
如果你發(fā)現(xiàn)某個 SQL 查詢用 Django 的數(shù)據(jù)庫映射來處理會非常復(fù)雜的話却盘,你可以使用直接寫 SQL 來完成。
建議的方式是在你的 model 自定義方法或是自定義 model 的 manager 方法來運(yùn)行查詢媳拴。雖然 Django 不要求數(shù)據(jù)操作必須在 model 層中執(zhí)行黄橘。但是把你的商業(yè)邏輯代碼放在一個地方,從代碼組織的角度來看屈溉,也是十分明智的塞关。詳情請查看 執(zhí)行原生SQL查詢(Performing raw SQL queries).
最后,要注意的是子巾,Django的數(shù)據(jù)操作層僅僅是訪問數(shù)據(jù)庫的一個接口帆赢。你可以用其他的工具小压,編程語言,數(shù)據(jù)庫框架來訪問數(shù)據(jù)庫椰于。對你的數(shù)據(jù)庫而言怠益,沒什么是非用 Django 不可的。