Django 1.10中文文檔: https://github.com/jhao104/django-chinese-doc
只要創建好 數據模型, Django 會自動為生成一套數據庫抽象的API, 可以讓你創建、檢索、更新和刪除對象。這篇文檔闡述如何使用這些API。 關於模型查詢所有選項的完整細節,請見 數據模型參考 。
在整個文檔(以及參考)中,都將引用下面的模型,它是一個博客應用:
1 from django.db import models 2 3 class Blog(models.Model): 4 name = models.CharField(max_length=100) 5 tagline = models.TextField() 6 7 def __str__(self): # __unicode__ on Python 2 8 return self.name 9 10 class Author(models.Model): 11 name = models.CharField(max_length=200) 12 email = models.EmailField() 13 14 def __str__(self): # __unicode__ on Python 2 15 return self.name 16 17 class Entry(models.Model): 18 blog = models.ForeignKey(Blog) 19 headline = models.CharField(max_length=255) 20 body_text = models.TextField() 21 pub_date = models.DateField() 22 mod_date = models.DateField() 23 authors = models.ManyToManyField(Author) 24 n_comments = models.IntegerField() 25 n_pingbacks = models.IntegerField() 26 rating = models.IntegerField() 27 28 def __str__(self): # __unicode__ on Python 2 29 return self.headline
創建對象
Django 使用一種直觀的方式把數據庫表中的數據表示成Python對象: 一個模型類代表數據庫中的一個表,一個模型類的實例代表這個數據庫表中的一條記錄。
使用關鍵字參數實例化模型實例來創建一個對象,然后調用 save() 把它保存到數據庫中。
假設模型位於文件 mysite/blog/models.py 中,下面是一個例子:
1 >>> from blog.models import Blog 2 >>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.') 3 >>> b.save()
上面的代碼其實是執行了SQL 的 INSERT 語句。在你調用 save() 之前,Django 不會訪問數據庫。
save() 方法沒有返回值。
修改對象
要保存一個數據庫已存在對象的修改也是使用 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)
可以在調用 add() 方法時傳入多參數,一次性向 ManyToManyField 添加多條記錄:
>>> 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 構造一個 QuerySet 來從數據庫中獲取對象。
QuerySet 表示從數據庫中取出來的對象的集合。 它可以包含零個、一個或者多個 過濾器 。 過濾器的功能是基於所給的參數過濾查詢的結果。 從SQL角度看, QuerySet 等價於 SELECT 語句, 過濾器就相當於 WHERE 或者 LIMIT 這樣的子句。
QuerySet 通過模型的 Manager 獲取。每個模型至少包含一個 Manager, 它們默認叫做 objects 。 可以通過模型類直接訪問, 例如:
1 >>> Blog.objects 2 <django.db.models.manager.Manager object at ...> 3 >>> b = Blog(name='Foo', tagline='Bar') 4 >>> b.objects 5 Traceback: 6 ... 7 AttributeError: "Manager isn't accessible via Blog instances."
注解
Managers 只能通過模型類訪問,而不是模型實例。 目的是為了強制區分“表級別”的操作和“記錄級別”的操作。
Manager 是 QuerySets 的主要來源。 比如, Blog.objects.all() 返回一個數據庫中所有 Blog 對象的 QuerySet 。
使用過濾器檢索
all() 方法返回包含數據庫所有記錄的 QuerySet。 但是往往只需要獲取其中的一個子集。
要創建這樣一個子集,你需要在原始的的 QuerySet 上增加一些過濾條件。 有兩種方式可以實現:
查詢參數 (上面函數中的 **kwargs ) 需要滿足特定的格式,下面 字段查詢 一節會提到。
例如, 使用 filter() 方法獲取年份為2006的所有文章的 QuerySet
Entry.objects.filter(pub_date__year=2006)
利用默認的管理器,它相當於:
Entry.objects.all().filter(pub_date__year=2006)
鏈式過濾
QuerySet 的篩選結果本身還是 QuerySet, 所以可以將篩選語句鏈接在一起:
1 >>> Entry.objects.filter( 2 ... headline__startswith='What' 3 ... ).exclude( 4 ... pub_date__gte=datetime.date.today() 5 ... ).filter( 6 ... pub_date__gte=datetime(2005, 1, 30) 7 ... )
這個例子最開始獲取數據庫中所有對象的一個 QuerySet, 之后增加一個過濾器,然后是一個排除器,再之后又是一個過濾器。 但是最終結果還是 QuerySet 。它包含標題以”What“開頭、發布日期在2005年1月30日至當天之間的所有記錄。
過濾后的 QuerySet 是獨立的
每次你篩選一個 QuerySet, 得到的都是全新的另一個 QuerySet , 它和之前的 QuerySet 之間沒有任何綁定關系。每次篩選都會創建一個獨立的 QuerySet ,它可以被存儲及反復使用。
例如:
>>> 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 都是獨立的。第一個是包含所有標題以“What”開頭的 QuerySet 。第二個是第一個的子集, 增加了限制條件,排除了 pub_date 大於等於今天的記錄。 第三個也是第一個的子集,限制條件是:只要 pub_date 大於等於今天的記錄。 而原來的 QuerySet (q1) 不會受到篩選的影響。
QuerySet 是惰性的
QuerySets 是惰性執行的 —— 創建 QuerySet 不會立即執行任何數據庫的訪問。 你可以將過濾器保持一整天,直到 QuerySet 被 求值 時,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)) 時才訪問一次數據庫。 一般來說,只有在“請求” QuerySet 的結果時才會到數據庫中去獲取它們。 當你確實需要結果時,QuerySet 通過訪問數據庫來求值。 關於求值發生的准確時間,參見 When QuerySets are evaluated.
使用 get() 獲取單個對象
filter() 始終返回一個 QuerySet ,即使只有一個對象滿足查詢條件。 —— 這種情況下, QuerySet將只包含一個元素。
如果你知道只有一個對象滿足你的查詢,你可以使用 Manager 的 get() 方法,它直接返回該對象:
>>> one_entry = Entry.objects.get(pk=1)
你可以對 get() 使用任何查詢表達式, 就和 filter() 一樣, 參考 字段查詢 。
但是 get() 和 filter() 有一點區別,如果沒有符合條件的查詢結果 get() 會拋出一個 DoesNotExist 異常。 這個異常是正在查詢的模型類的一個屬性,所以在上面的代碼中,如果沒有主鍵為 1 的 Entry, Django 將拋出一個 Entry.DoesNotExist。
同樣,如果 get() 滿足條件的結果超過1個,Django會拋出一個 MultipleObjectsReturned 異常。
其他 QuerySet 方法
查詢數據庫時,大多數會使用方法 all(), get(), filter() 和 exclude() 。 但是這只是其中一小部分方法,有關 QuerySet 完整的方法列表,請參見 QuerySet API Reference 。
QuerySet 的Limit
可以使用Python 的切片語法來限制 QuerySet 記錄的數目。它等同於SQL 的 LIMIT 和 OFFSET 子句。
例如,下面的語句返回前面5個對象 (LIMIT 5):
>>> Entry.objects.all()[:5]
下面這條語句返回第6至第10個對像 (OFFSET 5 LIMIT 5):
>>> Entry.objects.all()[5:10]
不支持負數索引 (i.e. Entry.objects.all()[-1]) 。
通常, QuerySet 的切片返回一個新的 QuerySet – 它不會立即執行查詢。但是,如果你使用了Python切片語法中的“步長”參數, 比如下面的語句將在前10個對象中每隔2個對象返回,這樣會立即執行數據庫查詢:
>>> Entry.objects.all()[:10:2]
若不想獲取列表,要一個單一的對象(e.g. 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 子句的內容, 它們通過 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 定義的函數可以接收任意的鍵/值對參數,這些名稱和參數可以在運行時求值。更多信息, 參見Python 官方文檔中的 關鍵字參數 。
查詢條件中指定的字段必須是模型字段的名稱。但有一個例外,對於 ForeignKey 你可以使用字段名加上 _id 后綴。在這種情況下,該參數的值應該是外鍵的原始值。例如:
>>> Entry.objects.filter(blog_id=4)
如果傳入的是一個不合法的參數,查詢函數將引發 TypeError。
這些數據庫API 支持大約二十多種查詢的類型; 完整的參考請參見 字段查詢 。 下面是一些可能用到的常見查詢:
-
exact -
“精確”匹配。例如:
>>> Entry.objects.get(headline__exact="Cat bites dog")
將生成下面的SQL:
SELECT ... WHERE headline = 'Cat 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。
上面羅列的僅僅是部分查詢方法,完整的參考: 字段查詢參考.
誇關聯關系查詢
Django 提供了強大而又直觀的方式來“處理”查詢中的關聯關系,它在后台自動幫你處理 JOIN 。 若要使用關聯關系的字段查詢,只需使用關聯的模型字段的名稱,並使用雙下划線分隔。
比如要獲取所有 Entry 中所有 Blog 的 name 為 'Beatles Blog' 的對象:
>>> 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 的關聯模型), 如果沒有找到符合條件的 author , 那么都會返回空, 而不是引發缺失 author 的異常, 這是一種比較好的處理方式,但是當使用 isnull 就會有二義性。 例如:
Blog.objects.filter(entry__authors__name__isnull=True)
這將會返回 author 中 name 為空的 Blog 對象,以及 entry 中 author 為空的``Blog`` 對象。 如果你不需要后者,你可以修改成:
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” 或者 pub_date為 2008的Entry。這兩種查詢都是有可能並且有意義的。
ManyToManyField 也有類似的情況。比如,如果 Entry 有一個 ManyToManyField tags,這樣可能想找到tag為 “music” and “bands” 的Entry, 或者我們想找一個tag名為 “music” 且狀態為“public”的Entry。
這些情況都可以使用 filter() 來處理。 在單個 filter() 中的條件都會被同時應用到匹配。
這種描述可能不好理解,用一個例子來說明。比如要選擇所有的entry包含 “Lennon” 標題並於2008年發表的博客(即這個Blog的entry要同時包含這兩個條件), 查詢代碼是這樣的:
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)
要選擇Blog的entry包含 “Lennon” 標題,或者是2008年出版的,查詢代碼是這樣的:
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)
假設有一個Blog擁有一個標題包含 “Lennon” 的entry和一個來自2008年的entry。 第一個查詢將匹配不到Blog, 第二個查詢才會匹配上這個Blog。
第二個例子中,第一個filter過濾出的查詢集是所有關聯有標題包含 “Lennon” 的entry的Blog, 第二個filter是在第一個的查詢集中過濾出關聯有發布時間是2008的entry的Blog。 第二個filter過濾出來的entry與第一個filter過濾出來的entry可能相同也可能不同。 每個filter語句過濾的是 Blog ,而不是 Entry 。
注解
誇關聯關系的多值 filter() 查詢和 exclude() 不同。 單個 exclude() 方法的條件不必引用同一個記錄。
例如,要排除標題中包含 “Lennon” 的entry和 發布在2008的entry:
Blog.objects.exclude(entry__headline__contains='Lennon',entry__pub_date__year=2008,)
但是,這個和 filter() 不一樣,它並不是排除同時滿足這兩個條件的Blog。 如果要排除Blog中entry的標題包含 “Lennon” 且發布時間為2008的,需要改成這樣:
Blog.objects.exclude(entry__in=Entry.objects.filter(headline__contains='Lennon',pub_date__year=2008,),)
Filters 引用模型字段
在上面例子中,最多是將模型字段和常量進行比較。那么如何將模型的一個字段與模型的另外一個字段進行比較?
Django 提供了 F 表達式 來完成這種操作。 F() 的實例作為查詢中模型字段的引用。可以在查詢filter中使用這些引用來比較相同模型不同instance上兩個不同字段的值。
比如, 如果要查找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)
查詢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 中適用。例如,下面三個語句是等同的:
1 >>> Entry.objects.filter(blog__id__exact=3) # Explicit form 2 >>> Entry.objects.filter(blog__id=3) # __exact is implied 3 >>> 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 看上去會是這樣s:
SELECT ... WHERE headline LIKE '%\%%';
對於下划線是同樣的道理。百分號和下划線都會自動地幫你處理。
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 並重新使用它:
1 >>> queryset = Entry.objects.all() 2 >>> print([p.headline for p in queryset]) # Evaluate the query set. 3 >>> print([p.pub_date for p in queryset]) # Re-use the cache from the evaluation.
何時查詢集不會被緩存
查詢集不會永遠緩存它們的結果。當只對查詢集的部分進行求值時會檢查緩存, 但是如果這個部分不在緩存中,那么接下來查詢返回的記錄都將不會被緩存。 這意味着使用切片或 限制查詢集 將不會填充緩存。
例如,重復獲取查詢集對象中一個特定的索引將每次都查詢數據庫:
1 >>> queryset = Entry.objects.all() 2 >>> print(queryset[5]) # Queries the database 3 >>> print(queryset[5]) # Queries the database again
然而,如果已經對全部查詢集求值過,則將檢查緩存:
1 >>> queryset = Entry.objects.all() 2 >>> [entry for entry in queryset] # Queries the database 3 >>> print(queryset[5]) # Uses cache 4 >>> print(queryset[5]) # Uses cache
下面是一些其它例子,它們都會使得全部的查詢集被求值並填充到緩存中:
1 >>> [entry for entry in queryset] 2 >>> bool(queryset) 3 >>> entry in queryset 4 >>> 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 對象,表示兩個 "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)
每個接受關鍵字參數的查詢函數 (e.g. filter(), exclude(), get()) 都可以傳遞一個或多個 Q 對象作為位置參數。 如果一個查詢函數有多個 Q 對象參數,這些參數的邏輯關系為“AND”。例如:
1 Poll.objects.get( 2 Q(question__startswith='Who'), 3 Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)) 4 )
大體上可以翻譯成這個SQL:
1 SELECT * from polls WHERE question LIKE 'Who%' 2 AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
查詢函數可以混合使用 Q 對象和關鍵字參數。 所有提供查詢函數的參數(關鍵字參數或 Q 對象)都將”AND”在一起。 但是,如果出現 Q 對象,它必須位於所有關鍵字參數的前面。例如:
1 Poll.objects.get( 2 Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)), 3 question__startswith='Who', 4 )
這是一個合法的查詢,等同於前面的例子; 但是:
1 # INVALID QUERY 2 Poll.objects.get( 3 question__startswith='Who', 4 Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)) 5 )
這個是不合法的。
參見
Django 單元測試中的 OR查詢示例 演示了幾種 Q 的用法.
對象比較
使用雙等號 == 比較兩個對象,其實是比較兩個模型主鍵的值。
使用上面的 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()
(1, {'weblog.Entry': 1})
1.9開始刪除方法才有返回值。
你還可以批量刪除對象。每個 QuerySet 都有 delete() 方法,它作用是刪除 QuerySet 中的所有成員。
例如, 下面語句刪除所有 pub_date 為2005的 Entry
>>> Entry.objects.filter(pub_date__year=2005).delete()
(5, {'webapp.Entry': 5})
上面的過程是通過SQL實現的,並不是依次調用每個對象的 delete() 方法。 如果你給模型類提供了一個自定義的 delete() 方法,並且希望刪除時方法被調用。 你需要”手動”調用實例的 delete()(例如:迭代 QuerySet 調用每個實例的 delete() 方法,使用 QuerySet 的 delete() 方法)。
當Django 刪除一個對象時,它默認使用SQL ON DELETE CASCADE 約束 —— 換句話講,任何有外鍵指向要刪除對象的對象都將一起刪除。 例如:
b = Blog.objects.get(pk=1) # 這會刪除該 Blog 和它所有的Entry對象 b.delete()
這種級聯的行為可以通過 ForeignKey 的 on_delete 參數定義。
注意, delete() 是唯一沒有在 Manager 上暴露出來的 QuerySet 方法。 這是一個安全機制來防止你意外地請求 Entry.objects.delete() , 而刪除所有的條目。 如果你確實想刪除所有的對象,你必須明確地請求一個完整的查詢集:
Entry.objects.all().delete()
復制對象
沒有內建的復制模型實例的方法,但可以通過創建一個新的實例並將它的所有字段都拷貝過來。最簡單的方法是,只需要將 pk 設置成 None 。使用blog作為演示:
1 blog = Blog(name='My blog', tagline='Blogging is easy') 2 blog.save() # blog.pk == 1 3 4 blog.pk = None 5 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 有一個 ManyToManyField 的 Author。在復制了一個entry后, 必須為這個新的entry設置一個多對多關聯關系:
1 entry = Entry.objects.all()[0] # some previous entry 2 old_authors = entry.authors.all() 3 entry.pk = None 4 entry.save() 5 entry.authors.set(old_authors)
如果是 OneToOneField, 您必須復制相關聯的對象並將其賦值給新對象的字段,避免出現復制后一對多的情況。 例如,假設 entry 已經是復制后的:
detail = EntryDetail.objects.all()[0] detail.pk = None detail.entry = entry detail.save()
同時更新多個對象
可以對 QuerySet 中的所有對象修改該某個字段的值。這就需要使用 update() 方法,例如:
# 修改所有pub_date為2007的headlines
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')
也可以對非關聯字段和 ForeignKey 字段使用這個方法。若要更新一個非關聯字段,只需提供一個新的常數值。 若要更新 ForeignKey 字段, 需設置新的值是你想指向的新的模型實例,例如:
>>> b = Blog.objects.get(pk=1)
# 修改所有的 Entry,使它們屬於這個Blog
>>> Entry.objects.all().update(blog=b)
The update() 方法會立即執行並返回匹配的行數 (如果有些行的值和新值相同,返回的行數可能和被更新的行數不相等)。 更新 QuerySet 唯一的限制是它只能訪問一個數據庫表,也就是模型的主表。 你可以根據關聯的字段過濾,但是你只能更新模型主表中的列,例如:
>>> 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 選項。 如果想保存 QuerySet 中的每個條目並確保每個實例的 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'))
使用原始 SQL
如果你發現自己需要編寫一個對非常復雜的SQL查詢(Django API不好實現),那么你就可以手動寫SQL了。 Django有幾個選擇來編寫原始的SQL查詢;參見 Performing raw SQL queries 。
最后,值得注意的是Django 的數據庫層只是數據庫的一個接口。你可以利用其它工具、編程語言或數據庫框架來訪問數據庫; 你的數據庫並不需要迎合django的任何東西。
