django模型查詢操做

一旦建立好了數據模型,Django就會自動爲咱們提供一個數據庫抽象API,容許建立、檢索、更新和刪除對象操做python

下面的示例都是經過下面參考模型來對模型字段進行操做說明:web

from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()
    def __str__(self):
        return self.name
class Author(models.Model):
    name = models.CharField(max_length=200)
    email = models.EmailField()
    def __str__(self):
        return self.name
class Entry(models.Model):
    blog = models.ForeignKey(to=Blog,on_delete=models.CASCADE)
    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):
        return self.headline

一、建立對象

Django使用更直觀的系統:模型類表示數據庫表,該類的實例表示數據庫表中的特定記錄也就是數據值正則表達式

要建立對象,請使用模型類的關鍵字參數對其進行實例化,而後調用save()以將其保存到數據庫中,假設模型存在於mysite/app01/models.py文件中數據庫

from app01.models import Blog
b = Blog(name='beatles blog',tagline='all the latest beatles news.')
b.save()

它會在後臺執行SQL語句INSERT,若是不調用save()方法,Django不會馬上將該操做反應到數據庫中,save()方法沒有返回值,它能夠接受一些額外的參數django

#修改對象的值,在後臺會執行一條SQL語句的UPDATE
b.name = 'python is django'
b.save()
Blog.objects.all()
<QuerySet [<Blog: python is django>]>

若是想要一行代碼完成上面的操做,請使用create()方法,它能夠省略save的步驟:編程

Blog.objects.create(name='Blog title',tagline='this is one blog title name')
Blog.objects.filter(name='Blog title')  #查詢數據 <QuerySet [<Blog: name:Blog title;tagline:this is one blog title name>]>

二、保存ForeignKey和ManyToManyField字段

保存一個ForeignKey字段和保存普通字段沒什麼區別,只須要注意值的類型要正確,下面的例子,有一個Entry的實例和一個Blog的實例,而後把cheese_blog做爲值賦給了entry的blog屬性,最後調用save方法進行保存。緩存

from app01.models import Blog,Entry
entry = Entry.objects.get(pk=1)
cheese_blog = Blog.objects.get(name='Blog title')
entry.blog = cheese_blog
entry.save()

ManyToManyField字段的保存稍微有點區別,須要調用add()方法,而不是直接給屬性賦值,但它不須要調用save方法,以下示例:安全

from app01.models import Author
joe = Author.objects.create(name='Joe')
entry.authors.add(joe)

在對對多關係中能夠一次性添加多條記錄,只需在調用add()時指定多個值便可:app

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)

#如指定了錯誤的類型對象,將引起異常

三、檢索對象

想要從數據庫中檢索對象,須要基於模型類,經過管理器(Manager)構造一個查詢結果集(QuerySet),每一個QuerySet表明一些數據庫對象的集合,它能夠包含零個、一個或多個過濾器(filters),Filters縮小查詢結果的範圍,在SQL語法中,一個QuerySet至關於一個SELECT語句,而filter至關於WHERE或者LIMIT一類的子句框架

經過模型的Manager得到QuerySet,每一個模型至少具備一個Manager,默認狀況下,它被稱做爲objects,能夠經過模型類直接調用它,但不能經過模型類的實例調用它,以此實現‘表級別’操做和‘記錄級別’操做的強制分離,以下所示:

Blog.objects
<django.db.models.manager.Manager object at 0x000001E3583EFDD8>
b = Blog(name='FOO',tagline='Bar')
b.objects
Traceback (most recent call last):
......
AttributeError: Manager isn't accessible via Blog instances

 Managers只能經過模型類訪問,不能經過模型實例訪問,它強制表級操做和記錄操做之間的分離

#檢索全部對象,可以使用all()方法,可獲取某張表的全部記錄
alll_entries = Entry.objects.all()

#過濾對象,有兩種方法用來過濾QuerySet的結果:
filter(**kwargs) :返回一個根據指定參數查詢出來的QuerySet
exclude(**kwargs) :返回給定查找參數不匹配的QuerySet
#例如:獲取2006年的博客條目,使用filter()
Entery.objects.filter(pub_date__year=2006)
#使用默認的manager類,它等同於上
Entery.objects.all().filter(pub_date__year=2006)

鏈式過濾:filter和exclude的結果依然是QuerySet,所以它能夠繼續被filter和exclude調用,這樣就造成了鏈式過濾:

Entry.objects.filter(headline__startswith='what').exclude(pub_date__gte=datetime.date.today()).filter(pub_date__gte=datetime.date(2005.1.30))

#它將獲取數據庫中全部條目中帶'what'的首字母的QuerySet,而後排除,添加過濾器而後過濾是2005年1月30日的條目

被過濾的QuerySets都是惟一的,每次過濾,都會得到一個全新的QuerySet,它和以前的QuerySet沒有任何關係,能夠徹底獨立的被保存,使用和重用

QuerySets是懶惰的,一個建立QuerySet的動做不會馬上致使任何數據庫的行爲,你能夠不斷的進行filter動做,Django不會運行任何實際的數據庫查詢操做,直到QuerySet被提交(evaluated),簡而言之就是隻有碰到某些特定的操做,Django纔會將全部的操做體現到數據庫內,不然它只是保存在內存和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)

#看起來執行的3次數據庫訪問,實際上只是在print語句時才執行數據庫操做,一般QuerySet的檢索不會馬上執行實際的數據庫操做,知道出現相似print的請求,也就是evaluated

檢索單個對象:filter方法始終返回的是QuerySet,哪怕只有一個對象符合顧慮條件,返回的也是包含一個對象的QuerySets,這是一個集合類型對象,能夠理解爲python列表,可迭代、可索引

若是要檢索的對象是一個時,可使用Manager的get()方法來直接返回這個對象

one_entry = Entry.objects.get(pk=1)

在get方法中你也可使用任何filter方法中的查詢參數,用法也時同樣的

注意:使用get()和filter()方法後經過切片方式[0]時,有着不一樣的地方,看似二者都是獲取單一對象,但,若是在查詢時沒有匹配到對象,那麼get()方法將拋出DoesNotExist異常,這個異常時模型類的一個屬性,在上面的例子中,若是不存在主鍵爲1的Entry對象,那麼Django將拋出Entry.DoesNotExist異常

一樣在使用get()方法查詢時,若是結果超過1個,則會拋出MultipleObjectsReturned異常,這個異常也是模型類的要給屬性;因此get方法要慎用!

其餘QuerySet方法:大多數狀況下,須要從數據庫中查找對象時,使用all()、get()、filter()和exclude()就行,針對QuerySet的方法還有不少高級用法能夠參考官網

QuerySet使用限制:使用相似於python對列表進行切片的方法能夠對QuerySet進行範圍取值,它至關於SQL語句中的LIMIT和OFFSET子句

Entry.objects.all()[:5]    #返回前5個對象
Entry.objects.all()[5:10]  #返回第6個到第10個對象

注意:QuerySet不支持負索引,如:Entry.objects.all()[-1]是不容許的

一般狀況下,切片操做會返回一個新的QuerySet,而且不會被馬上執行,可是有一個例外,就是在指定step步長的時候,查詢操做會馬上在數據庫內執行,以下:

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

若要獲取單一的對象而不是一個列表(SELECT foo FROM bar LIMIT 1),能夠簡單地使用索引而不是切片,例如,下面的語句返回數據庫中根據標題排序後的第一條:

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(注意是雙下劃線)

#pub_date爲字段名連接雙下劃線再指定查找類型的關鍵字參數
Entry.objects.filter(pub_date__lte='2006-01-01')
#至關於SQL語句:
SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';

其中的字段必須是模型種定義的字段之一,可是有一個例外,那就是ForeignKey字段,你能夠爲其添加一個「_id「後綴(單下劃線),這種狀況下鍵值是外鍵模型的主鍵原始值,如:

#blog爲外鍵模型
Entry.objects.filter(blog_id=4)
#若是你傳遞了一個非法的鍵值,查詢函數會拋出TypeError異常

Django的數據庫API支持20多種查詢類型,下面介紹一些經常使用的:

#exact爲模型類型,若是不提供查詢類型,那查詢類型就是這個默認的exact,精確匹配
Entry.objects.get(headline__exact="Cat bites dog")
#SQL語句:
SELECT ... WHERE headline='Cat bites dog';
#下面兩個至關:
Blog.objects.get(id__exact=4)
Blog.objects.get(id=4)

#iexact:不區分大小寫的徹底匹配
Blog.objects.get(name__iexact='beatles blog')
SELECT ... WHERE name ILIKE 'beatles blog'

#contains:區分大小寫
Entry.objects.get(headline__contains='Lennon')
SELECT ... WHERE headline LIKE '%Lennon%';

#icontains:不區分大小寫
Entry.objects.get(headline__icontains='Lennon')
SELECT ... WHERE headline ILIKE '%Lennon%';

#in:包含在列表、元組或集合的,也可接受字符串(可迭代)
Entry.objects.filter(id__in=[1,3,4])

#gt:大於
Entry.objects.filter(id__gt=3)

#gte:大於或等於
Entry.objects.filter(id__gte=3)

#lt:小於
Entry.objects.filter(id__lt=3)

#lte:小於或等於
Entry.objects.filter(id__lte=3)

#startswith:區分大小寫的開頭
Entry.objects.filter(headline__startswith='Lennon')

#istartswith:不區分大小寫的開頭
Entry.objects.filter(headline__istartswith='Lennon')

#endswith:區分大小寫的結尾;iendswith:不區分大小寫的結尾
Entry.objects.filter(headline__endswith='Lennon')
Entry.objects.filter(headline__iendswith='Lennon')

#range:範圍在內
import datetime
start_date = datetime.date(2005,1,1)
end_date = datetime.date(2005,12,31)
Entry.objects.filter(pub_date__range=(start_date,end_date))

#date:對於datetime字段,將值轉換爲日期,採用日期值查找
Entry.objects.filter(pub_date__date=datetime.date(2005,1,1))
Entry.objects.filter(pub_date__date__gt=datetime.date(2005,1,1))

#year:對於日期和時間字段,確切的年份匹配,須要整數年
Entry.objects.filter(pub_date__year=2005)
Entry.objects.filter(pub_date__year__gte=2005)

#iso_year:精確的ISO 8601周編年份匹配,須要整數年
Entry.objects.filter(pub_date__iso_year=2005)
Entry.objects.filter(pub_date__iso_year__gte=2005)

#month:日期和日期時間字段,確切的月份匹配
Entry.objects.filter(pub_date__month=1)
Entry.objects.filter(pub_date__month__gte=5)

#day:確切的天匹配
Entry.objects.filter(pub_date__day=3)
Entry.objects.filter(pub_date__day__gte=3)

#week:日期和時間字段,根據ISO-8601返回週數
Entry.objects.filter(pub_date__week=52)
Entry.objects.filter(pub_date__week__gte=32,pub_date__week__lte=38)

#week_day:匹配星期幾,從1(星期日)到7(星期六)的整數值
Entry.objects.filter(pub_date__week_day=2)
Entry.objects.filter(pub_date__week_day__gte=2)

#quarter:匹配四季,取1到4之間的整數值
Entry.objects.filter(pub_date__quarter=2)

#time:對於datetime字段,將值轉換爲時間,取一個datetime.time值
Entry.objects.filter(pub_date__time=datetime.time(14,30))

#hour:對日期時間和時間字段,精確的小時匹配,取值0-23
Entry.objects.filter(timestamp__hour=11)

#minute:對於日期時間和時間字段,精確分鐘匹配
Entry.objects.filter(timestamp__minute=29)

#second:對於日期時間和時間字段,確切的妙匹配
Entry.objects.filter(timestamp__second=31)

#isnull:採用任一True或False,其對應於SQL查詢IS NULLIS NOT NULL
Entry.objects.filter(pub_date__isnull=True)
SELECT ... WHERE pub_date IS NULL

#regex:區分大小寫的正則表達式匹配,語法爲python re模塊語法
Entry.objects.filter(headline__regex=r'^(an?|The)+')

#iregex:不區分大小寫的正則表達式匹配
Entry.objects.filter(headline__iregex=r'^(an?|the)+')

跨越關係的查詢:Django提供了強大而且直觀的方式解決跨越關聯的查詢,它在後臺自動執行包含JOIN的SQL語句,要跨越某個關聯,只需使用關聯的模型字段名稱,並使用雙下劃線分隔,直至你想要的字段(能夠鏈式跨越,無限跨越)。

#先指定字段名而後雙下劃線分隔指定外鍵字段名
Entry.objects.filter(blog__name='Blog title')

若是要引用一個反向關聯只須要使用模型的小寫名便可:

#獲取全部的blog對象,但前提是所關聯的Entry模型的headline字段包含'django models'
Blog.objects.filter(entry__headline__icontains='django models')

若是多級關聯種進行過濾並且其中某個模型沒有知足過濾條件的值,Django將把它看成一個空(null)可是合法的對象,不會拋出任何異常或錯誤:

#查詢Blog的全部對象,但關聯entry模型種authors字段關聯名必須時py.qi
Blog.objects.filter(entry__authors__name='py.qi')

若是Entry中沒有關聯任何的author,那麼它將看成其沒有name,而不會由於沒有author引起一個錯誤,一般這是比較符合邏輯的處理方式,惟一可能讓你困惑的時當使用isnull的時候:

Blog.objects.filter(entry__authors__name__isnull=True)

這將返回Blog對象,它關聯的entry對象的author字段的name字段爲空,以及Entry對象的author字段爲空,若是你不須要後者,能夠這樣寫:

Blog.objects.filter(entry__authors__isnull=False,entry__authors__name__isnull=True)

跨越多值的關係查詢

最基本的filter和exclude的關鍵字參數只有一個,這種狀況很好理解,可是當關聯字段有多個,且是跨越外鍵或多對多的狀況下,那麼就比較複雜了

Blog.objects.filter(entry__headline__contains='django models',entry__pub_date__year=2019)

這是一個跨外鍵、兩個過濾參數的查詢,此時咱們理解兩個參數之間屬於‘and’的關係,也就是說,過濾出來的Blog對象對用的entry對象必須知足上面兩個條件,可是,咱們看下面的用法:

Blog.objects.filter(entry__headline__contains='django models').filter(entry__pub_date__year=2019)

把兩個參數拆分,放在兩個filer調用裏面,安裝咱們前面說過的鏈式過濾,這裏的結果和上面的例子應該同樣,可實際上,它不同,Djang在這種狀況下,將兩個filter之間的關係設計爲'or',多對對關係下的多值查詢和外鍵foreignkey的狀況同樣。

可是,exclude的策略設計又和filter不同:

Blog.objects.exclude(entry__headline__contains='django models',entry__pub_date__year=2019)

它會排除headline中包含‘django models'的entry或在2019年發佈的Entry,中間是一個'or'的關係

那要排除同時知足上面兩個條件的對象,應該這麼作:

Blog.objects.exclude(entry__in=Entry.objects.filter(headline__contains='django models',pub_date__year=2019,),)

使用F表達式引用模型的字段:

到目前爲止的例子中,咱們都是將模型字段於常量進行比較,可是,若是你想將模型的一個字段與同一個模型的另一個字段進行比較該怎麼辦呢?

使用Django提供的F表達式;例如:爲了查找commnets數目多於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比pinggback和comments數目綜合還要小的Entry:

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

還能夠在F()中使用雙下劃線來進行跨表查詢,如:查詢author的名字與Blog名字相同的Entry:

Entry.objects.filter(authors__name=F('blog__name'))

對於date和datetime字段,還能夠加或減去一個timedelta對象,下面的例子將返回發佈時間超過3天后別修改的全部Entry:

Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))

F()對象還支持:.bitand()、.bitor()、.bitrightshift()和.bitleftshift()4種位操做:

F('somefield').bitand(16)

主鍵的快捷查詢方式:pk

pk就是primary key的縮寫,一般狀況下,一個模型的主鍵位'id',因此下面三個語句的效果同樣:

Blog.objects.get(id__exact=1)
Blog.objects.get(id=1)
Blog.objects.get(pk=1)

能夠聯合其餘類型的參數:

Blog.objects.filter(pk__in=[1,3,4])
Blog.objects.filter(pk__gt=10)

能夠跨表操做:

Entry.objects.filter(blog__id__exact=3)
Entry.objects.filter(blog__id=3)
Entry.objects.filter(blog__pk=3)

在LIKE語句中轉義百分符號和下劃線:在原生SQL語句中%符號有特殊的做用,Django幫你自動轉義了百分符號和下劃線,你能夠和普通字符同樣使用它們,以下:

Entry.objects.filter(headline__contains='%')
#SQL語句
SELECT ... WHERE headline LIKE '%\%%';

四、緩存和查詢集

每一個QuerySet都包含一個緩存,用於減小對數據庫的實際操做,有助於你提升查詢效率

對於新建立的QuerySet它的緩存是空的,當QuerySet第一次被提交後,數據庫執行實際的查詢操做,Django會把查詢的結果保存在QuerySet的緩存內,隨後對於該QuerySet的提交將重用這個緩存的數據。

要想高效的利用查詢結果,下降數據庫負載,你必須善於利用緩存,看下面的例子,這會形成2次實際的數據庫操做,加倍數據庫的負載,同時因爲時間差的問題,可能在兩次操做之間數據庫被刪除或修改或添加,致使髒數據的問題:

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

爲了不上面的問題,好的使用方式以下,這隻產生一次實際的查詢操做,並保持了數據的一致性:

queryset = Entry.objects.all()
print([p.headline for p in queryset])   #提交查詢
print([p.pub_date for p in queryset])  #重用查詢緩存

什麼時候不會被緩存呢,有一些操做不會緩存QuerySet,例如切片和索引,這就致使這些操做沒有緩存可用,每次都會執行實際的數據庫查詢操做,以下:

queryset = Entry.objects.all()
print(queryset[5]) #查詢數據庫
print(queryset[2]) #再次查詢數據庫

可是,若是已經遍歷過整個QuerySet,那麼就至關於緩存過,後續的操做則會使用緩存,如:

queryset = Entry.objects.all()
[entry for entry in queryset]   #查詢數據庫
print(queryset[3])   #使用緩存
print(queryset[1])   #使用緩存

下面的操做都將遍歷QuerySet並創建緩存:

[entry for entry in queryset]
bool(queryset)
entry in queryset
list(queryset)

注意:簡單的打印QuerySet並不會創建緩存,由於__repr__()調用只返回所有查詢集的一個切片

五、使用Q對象進行復雜查詢

普通的filter函數裏的條件都是'and’邏輯,若是你想實現'or'邏輯怎麼辦呢?用Q查詢

Q來自django.db.models.Q ,用於封裝關鍵字參數的集合,可用做爲關鍵字參數用於filter、exclude和get等函數;例如:

from django.db.models import Q
Q(question__startswith='what')

可用使用「&」或者「|」或"~"來組合Q對象,分別表示與或非邏輯,它將返回一個新的Q對象

Q(question__startswith='who')|Q(question__startswith='what')
#這至關於:
WHERE question LIKE 'who%' OR question LIKE 'what%'

更多的例子:

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

也能夠這樣使用,默認狀況下,以逗號分隔都表示AND關係:

from app01.models import Entry
from django.db.models import Q
from datetime import date
Entry.objects.get(Q(headline__startswith='a'),Q(pub_date=date(2005,1,1)) | Q(pub_date=date(2019,1,1)))

#它至關於SQL的
SELECT * FROM Entry WHERE headline LIKE 'a%' AND (pub_date =  '2005,1,1' OR pub_date = '2019,1,1)

當關鍵字參數和Q對象組合使用時,Q對象必須放在前面,不然會報錯

Entry.objects.filter(Q(pub_date=date(2005,4,2)) | Q(pub_date=date(2019,4,2)),headline__startswith='a')

#若是將headline__startswith='a'放在前面將會報錯

六、比較對象

要比較兩個模型實例,只須要使用python提供的雙等號比較符就能夠了,在後臺,其實比較的是兩個實例的主鍵的值,下面兩種方法是同等的:

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})

也能夠批量刪除,每一個QuerySet都有一個delete()方法,它能刪除該QuerySet的全部成員,如:

Entry.objects.filter(pub_date__year=2005).delete()
(5, {'webapp.Entry': 5})

須要注意的是,有可能不是每個對象的delete方法都被執行,若是你改寫了delete方法,爲了確保對象被刪除,你必須手動迭代QuerySet進行逐一刪除操做。

當django刪除一個對象時,它默認使用SQL的ON DELETE CASCADE約束,也就是說,任何有外鍵指向要刪除對象的對象將一塊兒被刪除,如:

b = Blog.objects.get(pk=1)
# 下面的動做將刪除該條Blog和全部的它關聯的Entry對象
b.delete()

這種級聯的行爲能夠經過ForeignKey的on_delete參數自定義

注意:delete()是惟一沒有在管理器上暴露出來的方法,這是刻意設計的一個安全機制,用來防止你意外的請求相似Entry.objects.delete()的動做,而不慎刪除的全部條目,若是你肯定想刪除全部的對象,你必須明確的請求一個徹底的查詢集,像下面這樣:

Entry.objects.all().delete()

八、複製模型實例

雖然沒有內置的方法用於複製模型的實例,但仍是很容易建立一個新的實例並將原實例的全部字段都拷貝過來,最簡單的方法是將原實例的pk設置爲None,這會建立一個新的實例copy,以下:

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有一個ManyToManyField到Author,複製條目後,你必須爲新的條目設置多對多關係,像下面這樣:

entry = Entry.objects.all()[0] # some previous entry
old_authors = entry.authors.all()
entry.pk = None
entry.save()
entry.authors.set(old_authors)

對於OneToOneField,還要複製相關對象並將其分配給新對象的字段,以免違反一對一惟一約束,如:假設entry已經如上所述重複:

detail = EntryDetail.objects.all()[0]
detail.pk = None
detail.entry = entry
detail.save()

九、批量更新對象

使用update()方法能夠批量爲QuerySet中的全部對象進行更新操做

# 更新全部2007年發佈的entry的headline
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')

只能夠對普通字段和ForignKey字段使用這個方法,若要更新一個普通字段,只需提供一個新的常數值,若要更新ForeignKey字段,需設置新值爲你想指向的新模型實例:

 b = Blog.objects.get(pk=1)
# 修改全部的Entry,讓他們都屬於b
 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字段選項;若是你想保存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()對象進行跨表操做,你只能夠引用正在更新的模型的字段,若是你嘗試使用F()對象引入另一張表的字段,將拋出FieldError異常:

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

十、關係的對象

利用上面一開始的模型,一個Entry對象e能夠經過blog屬性e.blog獲取關聯的Blog對象,反過來,Blog對象b能夠經過entry_set屬性b.entry_set.all()訪問與它關聯的全部Entry對象

1)一對多外鍵

正向查詢:

直接經過圓點加屬性,訪問外鍵對象:

e = Entry.objects.get(id=2)
e.blog

要注意的是,對外鍵的修改,必須調用save方法進行保存

e = Entry.objects.get(id=2)
other_blog = Blog.objects.get(name='django')
e.blog = other_blog
e.save()

若是一個外鍵字段設置有Null=True屬性,那麼能夠經過給該字段賦值爲None的方法移除外鍵值:

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

在第一次對一個外鍵關係進行正向訪問的時候,關係對象會被緩存,隨後對一樣外鍵關係對象的訪問會使用這個緩存,如:

e = Entry.objects.get(id=2)
print(e.blog)  # 訪問數據庫,獲取實際數據
print(e.blog)  # 不會訪問數據庫,直接使用緩存的版本

請注意QuerySet的select_related()方法會遞歸的預填充全部的一對多關係到緩存中:

e = Entry.objects.select_related().get(id=2)
print(e.blog)  #不會訪問數據庫,直接使用緩存
print(e.blog)  #不會訪問數據庫,直接使用緩存

反向查詢:

若是一個模型有ForeignKey,那麼該ForeignKey所指向的外鍵模型的實例能夠經過一個管理器進行反向查詢,返回源冒險島全部實例;默認狀況下,這個管理器的名字爲FOO_set,其中FOO是源模型的小寫名稱,該管理器返回的查詢集能夠用前面提到的方式進行過濾和操做。

b = Blog.objects.get(id=7)  
b.entry_set.all()  #Returns all Entry objects related to Blog
b.entry_set.filter(headline__contains='Every')  #b.entries is a Manager that returns QuerySets
b.entry_set.count()  

使用自定義的反向管理器:默認狀況下,用於反向關聯的RelatedManager是該模型默認管理器子類,若是你想爲一個查詢指定一個不一樣的管理器,你可使用下面的語法:

from django.db import models

class Entry(models.Model):
    #...
    objects = models.Manager()  # 默認管理器
    entries = EntryManager()    # 自定義管理器

b = Blog.objects.get(id=1)
b.entry_set(manager='entries').all()

指定的自定義反向管理器也能夠調用它的自定義方法:

b.entry_set(manager='entries').is_published()

處理關聯對象的其餘方法:

除了在前面定義的QuerySet方法以外,ForeignKey管理器還有其餘方法用於處理關聯的對象集合,下面是每一個方法的歸納

add(obj1, obj2, ...):添加指定的模型對象到關聯的對象集中。

create(**kwargs):建立一個新的對象,將它保存並放在關聯的對象集中。返回新建立的對象。

remove(obj1, obj2, ...):從關聯的對象集中刪除指定的模型對象。

clear():清空關聯的對象集。

set(objs):重置關聯的對象集。

若要一次性給關聯的對象集賦值,使用set()方法,並給它賦值一個可迭代的對象集合或者一個主鍵值的列表,如:

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

在上面的例子中,e1和e2能夠是完整的Entry實例,也能夠是整數的主鍵值

若是clear()方法可用,那麼在將可迭代對象中的成員添加到集合中以前,將從entry_set中刪除全部已經存在的對象

若是clear()方法不可用,那麼將直接添加可迭代對象中的成員而不會刪除全部已存在的對象

以上的每一個反向操做都將馬上在數據庫內執行,全部的增長、建立和刪除操做也將馬上自動地保存到數據庫內。

2)多對多

多對多關係的兩端都會自動獲取訪問另外一端的API,這些API的工做方式與前面提到的‘反向’一對多關係的用法同樣,惟一的區別在於屬性的名稱,定義ManyToMangField的模型使用該字段的屬性名稱,而‘反向’模型使用源模型的小寫名稱加上‘_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.

與外鍵字段中同樣,在多對多的字段中也能夠指定related_name名;注意:在一個模型中,若是存在多個外鍵或多對多的關係指向同一個外部模型,必須給他們分別加上不一樣的related_name,用於反向查詢

3)一對一

一對一很是相似多對一關係,能夠簡單的經過模型的屬性訪問關聯的模型

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

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

不一樣之處在於反向查詢的時候,一對一關係中的關聯模型一樣具備一個管理器對象,可是該管理器表示一個單一的對象而不是對象的集合:

e = Entry.objects.get(id=2)
e.entrydetail # 返回關聯的EntryDetail對象

若是沒有對象賦值給這個關係,Django將拋出一個DoesNotExist異常,能夠給反向關聯進行賦值,方法和正向的關聯同樣:

e.entrydetail = ed

4)反向關聯是如何實現的:

一些ORM框架須要你在關係的兩端都進行定義,Django的開發者認爲這違反了DRY(Don't Repeat Yourself)原則,因此在django中你只需在一端進行定義;那麼這是怎麼實現的呢?由於在關聯的模型類沒有被加載以前,一個模型類根本不知道有哪些類與它關聯

答案在app registry;在django啓動的時候,它會導入全部INSTALLED_APPS中的應用和每一個應用中的模型模塊,沒建立一個新的模型時,django會自動添加反向的關係到全部關聯的模型,若是關聯的模型尚未導入,django將保存關聯的記錄並在關聯的模型導入時添加這些關係

因爲這個緣由,將模型所在的應用都定義在INSTALLED_APPS的應用列表就顯得特定重要,不然,反向關聯將不能正確工做。

5)經過關聯對象進行查詢

涉及關聯對象的查詢與正常值的字段查詢遵循一樣的規則,當你指定查詢須要匹配的值時,你可使用一個對象實例或者對象的主鍵值。

例如,若是你有一個id=5的Blog對象b,下面的三個查詢將是徹底同樣的:

Entry.objects.filter(blog=b) # 使用對象實例
Entry.objects.filter(blog=b.id) # 使用實例的id
Entry.objects.filter(blog=5) # 直接使用id

十一、使用原生SQL語句

若是你發現須要編寫的django查詢語句太複雜,你能夠迴歸到手工編寫SQL語句,django對於編寫原生的SQL查詢有許多選項。

最後須要注意的是Django的數據庫層只是一個數據庫接口,你能夠利用其它的工具,編程語言或數據庫框架來訪問數據庫,django沒有強制指定你非要使用它的某個功能或模塊。

相關文章
相關標籤/搜索