【Django】模型层说明
【Django模型層】
之前大概介紹Django的文章居然寫了兩篇。。這篇是重點(diǎn)關(guān)注了Django的模型層來進(jìn)行學(xué)習(xí)。
■ 模型定義
眾所周知,Django中的模型定義就是定義一個(gè)類,其基本結(jié)構(gòu)是這樣的:
from django.db import modelsclass ModelName(models.Model):field1 = models.XXField(...)field2 = models.XXField(...)class Meta:db_table = ...other_metas = ...?
模型類的子類Meta決定了一些模型的元數(shù)據(jù),比如數(shù)據(jù)庫中對(duì)應(yīng)的表名,表的默認(rèn)排序方式等等
Meta類的屬性已經(jīng)有Django進(jìn)行了預(yù)定義,一般不需要特地再去改變。Meta類有以下這些屬性可供我們改變
abstract True或者False,標(biāo)識(shí)本類是否是抽象基類。一般不動(dòng)
app_label 本模型所屬模塊的名稱,一般不動(dòng)
db_table 改變此值可以改變對(duì)應(yīng)這個(gè)模型的數(shù)據(jù)庫中的表,如果不指定的話那么在Django會(huì)按照默認(rèn)規(guī)則取一個(gè)表名,規(guī)則是全小寫的appname_modelname
get_latest_by 通常指向本模型中的一個(gè)時(shí)間或者日期字段,以獲得開始和結(jié)束截取記錄的標(biāo)志
managed True或者False,指出是否可以通過manage.py管理這個(gè)模型
ordering 本模型的默認(rèn)排序依據(jù)字段,默認(rèn)是以降序排序
default_permissions 模型操作權(quán)限,默認(rèn)為default_permissions = ('add','change','delete')
其他還有一些奇奇怪怪的屬性可以設(shè)置,這里不說了
?
●? 普通字段選擇
XXField就是指普通的字段了,普通字段的選擇主要有以下這些:
AutoField 遞增的整型字段,相當(dāng)于一個(gè)自動(dòng)遞增的id,如果沒有設(shè)置Django也會(huì)自動(dòng)幫我們的模型添加(尤其是沒有指定主鍵的時(shí)候)
BooleanField 布爾值字段,當(dāng)被設(shè)置為表單類的Meta子類的model時(shí),這個(gè)表單類中對(duì)應(yīng)的字段在HTML上的表現(xiàn)是<input type="checkbox">
CharField 比較短的字符串,相當(dāng)于<input type="text">
TextField 長文本字段,相當(dāng)于<textarea>
CommaSeperatedItegerField 規(guī)定輸入只能是用逗號(hào)分隔開的數(shù)字的<input type="text">,這個(gè)Field已經(jīng)過時(shí),以后不支持了
DateField/DateTimeField 規(guī)定輸入只能是日期/時(shí)間日期的input,格式是%Y-%m-%d %H:%M:%S。在admin界面上,這個(gè)字段非常好用因?yàn)橛斜容^友好的日歷插件使用。不過在自己渲染的界面中,還是一個(gè)普通的input,需要進(jìn)一步改善
DurationField 時(shí)間段的字段,用Python的timedelta類構(gòu)建
EmailField 規(guī)定輸入只能是Email格式的字段
FileField 文件上傳字段,在定義這個(gè)字段時(shí)必須有參數(shù)upload_to用于指出上傳過來的文件在服務(wù)器端的保存路徑。具體文件怎么上傳可能需要單開一篇來說明
IntegerField/FloatField 分別保存一個(gè)整數(shù)和浮點(diǎn)數(shù)的字段
GenericIPAddressField 保存IP地址的字段
NullBooleanField 和BooleanField類似,不過除了T和F外還有一個(gè)Null選項(xiàng)。在HTML上就是除了Yes和No以外還有一個(gè)Unknown選項(xiàng)
ImageField 和FileField類似,不過會(huì)校驗(yàn)上傳的是否是一個(gè)有效的圖片文件
URLField 校驗(yàn)是否是有效URL的字段
?
●? 普通字段常用參數(shù)
字段在初始化的時(shí)候可以加入?yún)?shù)以指定其更多的性質(zhì),常用的參數(shù)有以下這些:
default 設(shè)置字段的默認(rèn)值
help_text 設(shè)置HTML中輸入控件的幫助字符串,從HTML代碼來看就是在input標(biāo)簽后面加上了個(gè)span.help_text標(biāo)簽
null 是否允許字段是沒有值,默認(rèn)為False
blank 是否允許字段是空值,和null的區(qū)別在于,null設(shè)置的是數(shù)據(jù)庫模型表結(jié)構(gòu)中字段是否能是空,而blank則是規(guī)定了HTML界面上的input能否提交空值
choices 一個(gè)二元元組的列表,每個(gè)元組的第一項(xiàng)是實(shí)際值,第二項(xiàng)是HTML界面上顯示的文字
primary_key 是否為主鍵
unique 值是否要唯一,定義數(shù)據(jù)庫的唯一約束
除了上面這些參數(shù)之外,還可以在所有參數(shù)前面加上一個(gè)無指定名字的參數(shù)用來表示HTML界面上輸入框前面的文字。一般如果不設(shè)置這個(gè)參數(shù)的話那么會(huì)以這個(gè)字段對(duì)象的變量名加上首字母大寫給出。
?
■ ORM操作
我們定義了如下這么一個(gè)模型以供后續(xù)操作的演示:
from __future__ import unicode_literals from django.db import modelsclass Comment(models.Model):headline = models.CharField(max_length=255)body_text = models.TextField()pub_date = models.DateField()n_visits = models.IntegerField()def __unicode__(self):return self.headline?
●? 基本查詢
模型類如Comment會(huì)自帶一個(gè)objects對(duì)象可以通過它來實(shí)現(xiàn)對(duì)數(shù)據(jù)的查詢:
Comment.objects.all() 這個(gè)方法返回的就是數(shù)據(jù)庫中對(duì)應(yīng)表中所記錄的對(duì)象。也就是類似于一個(gè)Comment類對(duì)象組成的列表,每個(gè)對(duì)象都可以調(diào)用屬性headline,body_text,pub_date等等
除了all這種傻大黑粗的方法,還有filter和exclude可以用來進(jìn)行進(jìn)一步的結(jié)果篩選。這兩個(gè)方法的一般形式是filter(**kwargs)和exclude(**kwargs)。用法如:
Comment.objects.filter(pub_date = '2017-11-01') 返回所有pub_date字段是2017-11-01的記錄的列表
Comment.objects.exclude(pub_date = '2017-11-01') 返回所有pub_date字段非2017-11-01的記錄的列表
很明顯,filter和exclude可以像jQuery方法一樣一連串地寫下去。
這么看來查詢的功能似乎不是很強(qiáng)大,但是Django還隱藏了特別一些手段。可以先看一下下面這個(gè)語句:
Comment.objects.filter(pub_date__day=3) 返回pub_date字段(這是一個(gè)日期字段)日期為3的記錄
耐人尋味的是pub_date__day這個(gè)東西,這是Django中獨(dú)特的被稱為字段查詢的方式。其表現(xiàn)形式是“字段名稱__謂詞”。謂詞還有很多比如:
exact 精確等于,id__exact=14 相當(dāng)于 select * from xxx where id=14
iexact 大小寫不敏感等于,head_line__iexact='SOMESTRING' 相當(dāng)于 select * from xxx where upper(head_line)='SOMESTRING'
contains 模糊匹配,相當(dāng)于 字段='%keyword%'的查詢條件
in 包含,比如objects.filter(id__in=[1,2,3])
gt 大于,n_visits__gt=3相當(dāng)于n_visits > 3的條件
gte/lt/lte 和gt類似,分別是大于等于,小于,小于等于
startswith/endswith 相當(dāng)于'keyword%'和‘%keyword’這樣的where子句條件
range 比如start_date = datetime.date(2017,1,1)? end_date=datetime.date(2017,12,31),然后filter(pub_date__range=(start_date,end_date))來表示篩選出pub_date在2017年內(nèi)的一些數(shù)據(jù)。從這個(gè)例子中也可以看出,Django的DateField其實(shí)是基于python中的datetime.date類來的。
year/month/day/week_day 針對(duì)日期時(shí)間的字段可以進(jìn)行進(jìn)一步的篩選
isnull 是否為空,比如pub_date__isnull=True就是篩選出了所有pub_date字段為NULL的所有記錄
更詳細(xì)的查詢可以參考【https://www.cnblogs.com/ajianbeyourself/p/3604332.html】
還有就是和flask的ORM類似的,有個(gè)get方法可以根據(jù)id來獲取單條記錄。SQL中的limit,offset等關(guān)鍵則可以通過對(duì)all()返回的列表進(jìn)行切片操作來實(shí)現(xiàn):
Comment.objects.all()[:10]
?
上面的查詢都還是單表查詢,如果涉及到連接查詢呢?連接查詢的建立方法在下面講,這里先借用下面三個(gè)例子中的一對(duì)多關(guān)系的實(shí)例。如果想通過多表?xiàng)l件查詢一表信息的時(shí)候可以用到distinct()方法,相當(dāng)于是從多表?xiàng)l件篩選出來的一部分結(jié)果集中進(jìn)行set,再通過外鍵就可以得到一表中的結(jié)果集。改寫稱為ORM的方法序列的話大概是這樣: Account.objects.filter(contact__多表屬性名__條件名),這里的contact是表名Contact的小寫,從一表出發(fā),查詢多表的某個(gè)屬性符合某個(gè)條件的數(shù)據(jù)記錄。類似的通過一表?xiàng)l件查詢多表記錄時(shí):Contact.objects.filter(account__一表屬性名__條件名),這里的account不一定是表名小寫,而是定義Contact表時(shí)定義成外鍵的那個(gè)字段的名字。
?
●? 花樣查詢
查詢操作是非常靈活的,比如我們可以動(dòng)態(tài)地指定條件字段,可以用AND,OR等多種邏輯串聯(lián)條件,可以指定輸出的字段等等。下面針對(duì)更加靈活一點(diǎn)的查詢做出解決方案的說明。
動(dòng)態(tài)的指定條件可以改變傳遞參數(shù)的形式實(shí)現(xiàn)。比如filter,get方法接收的其實(shí)是**kwargs,因此我們可以通過filter(**{'key1','value1', 'key2': 'value2'})的形式來進(jìn)行動(dòng)態(tài)條件字段的結(jié)果集過濾或單個(gè)對(duì)象的獲取。
指定輸出字段通常通過values方法指定,比如values('field1','field2')使得輸出只有這兩個(gè)字段的值而不是全部。values方法返回的是比較標(biāo)準(zhǔn)的JSON格式數(shù)據(jù),而另一個(gè)方法values_list可以返回tuple的list這樣一種形式的數(shù)據(jù)
排序可以通過order_by()方法完成,默認(rèn)升序,如果要降序可以在后面再加上reverse()
對(duì)于簡單的條件進(jìn)行AND邏輯連接,可以直接在后面寫,但是如果還要進(jìn)行OR連接,或者條件很多很多寫起來很麻煩,那么可以通過Q對(duì)象來進(jìn)行條件的邏輯處理。Q對(duì)象的基本用法如下:
from django.db.models import Q# 將常用的長條件固化下來: q = Q(name__startswith='F')# 邏輯串聯(lián)多個(gè)條件: MyModel.objects.filter(Q(name__startswith='F') | Q(ip__contains='127') # OR條件 )''' 用&連接兩個(gè)Q對(duì)象自然就是AND條件 在Q對(duì)象前面加上~如~Q(xxx)就是代表非,即NOT條件 若在filter,get等可以填充Q對(duì)象的地方傳入了多個(gè)Q對(duì)象作為參數(shù),那么默認(rèn)這些Q對(duì)象以AND邏輯連接如get(Q(xxx),Q(yyy))等同于get(Q(xxx) & Q(yyy)) '''實(shí)際開發(fā)過程中還偶爾會(huì)遇到需要比較同一個(gè)記錄中的兩個(gè)不同字段,或者在寫操作鏈的代碼的時(shí)候就需要進(jìn)行對(duì)記錄某個(gè)字段的值進(jìn)行引用的時(shí)候,就需要用到F對(duì)象:
from django.db.models import F,Q# 同時(shí)用了Q和F,這個(gè)filter過濾出來的是所有public_ip字段不等于private_ip字段值的記錄 SLB.objects.filter(~Q(public_ip=F('private_ip')))# F對(duì)象之間還可以做簡單的數(shù)學(xué)運(yùn)算,這里就是將所有記錄按照amt字段與price字段的值的乘積進(jìn)行排序的前十位 totalPriceList = Good.objects.all().order_by(F('amt')*F('price')).reverse()[:10]# 通過F對(duì)象也可以跨表調(diào)取字段值,只要表達(dá)合法即可 Module.objects.filter(port__in=F('for_server__behind_slb__intern_ports'))?
?● 查詢結(jié)果的簡單統(tǒng)計(jì)處理
在SQL中常見的統(tǒng)計(jì)處理就是count, max, avg之類的。另外統(tǒng)計(jì)還常常和group by一起出現(xiàn)。針對(duì)以上這些場景分別給出django orm的解決方案。
首先要介紹的是四個(gè)最常用的統(tǒng)計(jì)方法,在django.db.models中的Count,Avg,Max,Sum。
針對(duì)Count最簡單的統(tǒng)計(jì)有多少條結(jié)果可以直接通過結(jié)果集調(diào)用count方法。Avg和Max兩個(gè)方法通常和aggregate結(jié)合起來用,如下所示:
# 假設(shè)我們有一個(gè)叫Server的模型,其中有mem_info字段,是bigint,記錄服務(wù)器內(nèi)存量。Server對(duì)應(yīng)的表名叫server,將與orm操作等同的sql寫在后面 s = Server.objects.all() s.count() # 1.select count(*) from server; s.aggregate(Avg('mem_info')) # 2.select avg(mem_info) from server; s.aggregate(mem_avg=Avg('mem_info')) # 3.select avg(mem_info) as mem_avg from server; s.aggregate(max_mem=Max('mem_info')) # 4.select max(mem_info) as max_mem from server; s.aggregate(sum_mem=Sum('mem_info')) # 5. select sum(mem_info) as sum_mem from server;上述aggregate返回的不是一個(gè)QuerySet,而是一個(gè)字典。比如3返回的{"mem_avg": xxxxx},2返回的是{"mem_info__average": xxxx}(默認(rèn)字段名是這樣的)。如果想要同時(shí)獲取多個(gè)統(tǒng)計(jì)值,那么直接在aggregate中寫多個(gè)參數(shù)即可,如aggregate(max_mem=Max('mem_info'), mem_avg=Avg('mem_info'))
接下來就是介紹如何通過orm實(shí)現(xiàn)簡單的groupby了。主要用到的是取指定列值的values和組分聚合方法annotate。示例如下:
# 假設(shè)Server模型還有字段environment,下面根據(jù)環(huán)境(生產(chǎn)、測試等)組分分別統(tǒng)計(jì) s.values('environment').annotate(server_count=Count('environment')) # 1. select environment,count(environment) as server_count from server group by environment; s.values('environment').annotate(max_mem=Max('mem_info')) # 2.select environment,max(mem_info) as max_mem from server group by environment;values后面直接接上annotate的話,就有了group by的功能。annotate之后返回的也不是簡單的字典,而是一個(gè)QuerySet對(duì)象。但是和objects.all()等得到的QuerySet不同的是,它更像一個(gè)字典的生成器。比方說1的返回可能是?<QuerySet [{'environment': u'0', 'server_count': 2}, {'environment': u'1', 'server_count': 1}]>,表明environment值為0的Server有兩個(gè),而值為1的有一個(gè)。
同理,2的返回可能是<QuerySet [{'environment': u'0', 'max_mem': 4194304}, {'environment': u'1', 'max_mem': 2097152}]>,這代表相應(yīng)environment值的組分中mem_info字段最大分別是多少。針對(duì)這兩個(gè)QuerySet我們可以for server in xxx去遍歷取值。
?
再來細(xì)說下annotate這個(gè)方法。在上面的實(shí)踐中,我們已經(jīng)可以區(qū)分到,QuerySet對(duì)象其實(shí)分成兩種。一種是通過類似于Server.objects.all()得到的一個(gè)可迭代,并且其中每一個(gè)元素都是一個(gè)ORM對(duì)象;另一種則是通過Server.objects.all()[0].values()獲取到的數(shù)據(jù)結(jié)構(gòu)。后一種本質(zhì)上每個(gè)元素是一個(gè)字典。當(dāng)然在values方法中指定一些字段名的話還可以精簡化這個(gè)字典的結(jié)構(gòu)。
annotate最常見的調(diào)用方法,是接在后一種形式的QuerySet后面。如Model.objects.values('colA','colB').annotate(alias=Count('colA'))這個(gè)等價(jià)成SQL的話就是:
SELECT colA, colB, count(colA) as alias FROM table GROUP BY colA, colB;
其他annotate也還有很多很魔幻的用法… 一旦稍微復(fù)雜一點(diǎn),annotate就要爆炸了… 連官方文檔都指出,情景較為復(fù)雜的時(shí)候,設(shè)計(jì)annotate相關(guān)邏輯時(shí)最好的準(zhǔn)則就是,這個(gè)語句能夠取到正確的數(shù)據(jù)那么他就是正確的,不要太深究其原理了。 所以在有些時(shí)候,ORM可能還是直接寫SQL更好。
?
●? 數(shù)據(jù)保存和刪除
Django的ORM沒有區(qū)分insert和update,只有統(tǒng)一的save方法。比如下面這樣的:
new_comment = Comment(headline='Headline DDD',pub_date=datetime.datetime.now(),n_visits=0) new_comment.save() ###此時(shí)庫中已經(jīng)有了一條新紀(jì)錄,是insert進(jìn)去的### id = new_comment.id comment = Comment.objects.get(id__exact=id) ###這步其實(shí)是多此一舉的,不過為了模擬下入庫后再從庫中取數(shù)據(jù)的過程### comment.body_text = 'Body Text DDD is Here.' comment.save() ###此時(shí)只是改變了對(duì)象的某個(gè)字段的值,save其實(shí)就是update語句###?
刪除記錄很清楚就是用delete方法了:
Comment.objects.get(id__exact=13).delete()
還有一個(gè)常用的操作是復(fù)制。djangoORM沒有直接給出復(fù)制的操作接口,不過我們可以通過將一個(gè)對(duì)象賦值給一個(gè)變量,然后改變此變量的主鍵值再save這個(gè)變量,同樣可以達(dá)到復(fù)制的目的。如果在一些模型中主鍵是自增的AutoField的話可以置主鍵值為None,再save,django就會(huì)自動(dòng)幫我們?cè)O(shè)置一個(gè)新的合理的自增主鍵值。如:
new_server = Server.objects.get(id=100) new_server.id = None new_server.save()? 這樣的復(fù)制方法無法處理一些復(fù)雜的關(guān)系,比如一對(duì)一關(guān)系時(shí)要考慮主鍵沖突而引起做一些額外的處理;多對(duì)多關(guān)系也不會(huì)被自動(dòng)復(fù)制,因此需要手動(dòng)額外的進(jìn)行類似new.relations.set(old.relations.all())這樣的操作(另外注意,如果按是old = xxx.objects.get(xxx),然后new = old的話,改變new的值old也會(huì)被改變,應(yīng)該使用copy模塊的deepcopy(old)的方式來賦值);對(duì)于一對(duì)多關(guān)系則有兩種情況,復(fù)制操作的模型在關(guān)系中是“多”側(cè),則會(huì)被復(fù)制過去,如果是“一”側(cè),那么也要進(jìn)行手動(dòng)的信息復(fù)制的維護(hù)。
?
●? 關(guān)系操作
我有種非常蛋疼的預(yù)感。。
和flask中的ORM一樣,關(guān)系分成一對(duì)一,一對(duì)多,多對(duì)多關(guān)系。
就一對(duì)一關(guān)系而言,就是制定兩張表的主鍵一樣,那么在建模時(shí)可以用models.OneToOneField來實(shí)現(xiàn):
class Accout(models.Model):user_name = models.CharField(max_length=80)password = models.CharField(max_length=255)reg_date = models.DateField()def __unicode__(self):return 'Account: %s' % self.user_nameclass Contact(models.Model):account = models.OneToOneField(Account,on_delete=models.CASCADE,primary_key=True)zip_code = models.CharField(max_length=10)address = models.CharField(max_length=80)mobile = models.CharField(max_length=20)def __unicode__(self):return '%s %s' % (self.account.user_name,mobile)? 首先看到OneToOneField并不是像CharField這種一樣,傳的參數(shù)就是要做關(guān)聯(lián)的那個(gè)類,on_delete參數(shù)指定了當(dāng)關(guān)聯(lián)模型記錄被刪除時(shí)本模型的相關(guān)記錄如何處理的方式,CASCADE是將本模型的相關(guān)記錄一并刪除。
在這么處理之后我們先python manage.py makemigrations mytest和python manage.py migrate mytest來同步數(shù)據(jù)庫,完成之后可以python manage.py shell來進(jìn)入shell模式,這樣可以更清晰地看模型間的關(guān)系:
>>>from mytest.models import Account,Contact >>>a1 = Account(user_name='david',password='0',reg_date=datetime.date.today()) >>>a2 = Account(user_name='rose',password='0',reg_date=datetime.date.today()) >>>c1 = Contact(account=a1,mobile='13100010001') #注意這里,其實(shí)是把a(bǔ)1作為對(duì)象傳遞給account參數(shù),以表明c1這條記錄和a1這條記錄之間的聯(lián)系 >>>c1.save() >>>print c1 <Contact: david,13100010001> >>>print c1.account <Account: david> >>>print a1.contact <Contact: david 13100010001>至于為何彼此的屬性叫account和contact而不是Account,Contact,我只能認(rèn)為是Django的設(shè)定就是這樣了。。
?
一對(duì)多關(guān)系模型,比如1對(duì)N的模型建立,主要是靠在N表中建立一個(gè)外鍵列,這個(gè)列的取值只能是1表中的主鍵。對(duì)應(yīng)到Django的models里面就是使用models.ForeignKey,如:
from django.db import modelsclass Account(models.Model):user_name = models.CharField(max_length=80)password = models.CharField(max_length=200)reg_date = models.DateField()def __unicode__(self):return 'Account: %s' % self.user_nameclass Contact(models.Model):account = models.ForeignKey(Account,on_delete=models.CASCADE)zip_code = models.CharField(max_length=10)address = models.CharField(max_length=80)mobile = models.CharField(max_length=20)def __unicode__(self):return '%s: %s' % (self.account.user_name,self.mobile)?
在建立這樣的模型之后我們就可以通過shell進(jìn)行如下實(shí)驗(yàn):
>>>a1 = Account(user_name='Rose',password='0',reg_date=datetime.date.today()) >>>a1.save() >>>c1=Contact(account=a1,mobile='13100010001') >>>c1.save() >>>c2=Contact(account=a1,mobile='13100010002') >>>c2.save() >>>print c1.account == c2.account True >>>print a1.contact_set [<Contact: Rose,13100010001>,<Contact: Rose,13100010002>] >>>print a1.count_set.count() 2 >>>a1.delete() #由于on_delete設(shè)置的是CASCADE,所以當(dāng)a1被刪掉之后,關(guān)聯(lián)在a1上的c1和c2也都會(huì)被刪除多對(duì)多關(guān)系模型的建立一般而言需要一張第三表來記錄這種關(guān)系,在Django中用models.ManyToManyField來表示。比如繼續(xù)沿用上面的Account和Contact兩個(gè)模型(把Contact中的account字段名改成accounts以體現(xiàn)多對(duì)多關(guān)系,另外需要注意的是,多對(duì)多關(guān)系是沒有on_delete選項(xiàng)的。),把關(guān)聯(lián)的方法變成ManyToManyField之后,進(jìn)行如下shell操作:
>>>a1=Account(user_name='Leon') >>>a1.save() >>>c1=Contact(mobile='13100010001') >>>c1.save()>>>c1.accounts.add(a1) #通過Contact對(duì)象建立聯(lián)系,將a1和c1關(guān)聯(lián)了起來>>>a2=Account(user_name='Terry') >>>a2.save() #一定要save過,否則建立聯(lián)系會(huì)報(bào)錯(cuò) >>>c2=Contact(mobile='13100010002') >>>a1.contact_set.add(c2) #Leon的第二個(gè)聯(lián)系方式 >>>a1.save() >>>a1.contact_set.remove(c1) #消除單個(gè)對(duì)象的關(guān)聯(lián) >>>a1.save() >>>a1.contact_set.clear() #清除所有對(duì)象的關(guān)聯(lián)? ***一點(diǎn)對(duì)多對(duì)多關(guān)系的說明:
上面的演示是基于那本紅書的,而里面的django版本可能比較老,在我實(shí)驗(yàn)中,已經(jīng)不需要加_set,直接引用屬性名加上all,add,remove等方法即可(那是因?yàn)檫@是關(guān)系字段的發(fā)起者。。比如在模型A中定義字段b=ManyToManyField(B),然后A模型的實(shí)例a可以a.b.all(),而反過來引用時(shí)就要用到_set,如b.a_set.all()這樣子)。
另外當(dāng)一個(gè)表連續(xù)關(guān)聯(lián)了兩次另一個(gè)表時(shí),為了通過那個(gè)表對(duì)象引用這個(gè)表信息時(shí)不出現(xiàn)混淆,需要在設(shè)置關(guān)系的方法(比如OneToOneField或者M(jìn)anyToManyField等)中加上related_name參數(shù),來為不同字段引用設(shè)置不同的關(guān)聯(lián)名稱。
可以看到,無論是定義還是操作中,我們都沒有直接涉及到傳說中的第三張表,這也就是說Django幫我們封裝了這一過程,使得框架的使用者不用再維護(hù)第三張關(guān)系表了。
?
●? 其他操作
Module.objects.values('field_name'[, 'other_field',...])該模型對(duì)應(yīng)表中指定若干列的所有值,返回是一個(gè)字典的列表。
?
■ ORM的實(shí)際SQL顯示
ORM的原理,實(shí)際上是將程序中的代碼邏輯轉(zhuǎn)化成SQL,然后通過數(shù)據(jù)庫連接去執(zhí)行這些SQL并返回結(jié)果。所以如果ORM執(zhí)行效率比較低或者怎么樣的時(shí)候,可以通過調(diào)取ORM操作對(duì)應(yīng)的原生SQL來查看優(yōu)化。
對(duì)于查詢操作,由于ORM的查詢操作載體都是QuerySet類的對(duì)象,而這類對(duì)象的有一個(gè)屬性叫做query。這個(gè)query屬性實(shí)現(xiàn)了__str__方法,打印出來就是這個(gè)QuerySet對(duì)應(yīng)的SQL了。
對(duì)于其他操作,略微麻煩一點(diǎn)。有一個(gè)辦法就是from django.db import connection,然后再調(diào)取connection.queries這個(gè)屬性。這個(gè)屬性是一個(gè)列表,其中按照時(shí)間順序從前到后記錄了一個(gè)數(shù)據(jù)庫連接執(zhí)行過的所有SQL,其中也包括除了查詢操作之外其他一些類型的操作。
■ ORM繼承
Django模型層的ORM另一個(gè)比較強(qiáng)大的地方在于可以進(jìn)行模型的繼承。這也正是有機(jī)結(jié)合了面向?qū)ο缶幊讨械念惱^承的思想。根據(jù)不同的繼承手段,繼承大概可以被分成三種:
●??? 抽象類繼承
就像java中可以繼承抽象類一樣,這里的抽象類就是一個(gè)不在數(shù)據(jù)庫中落地,但是可以被其他表類繼承其中字段的基類。在多個(gè)表中有若干相同的字段的時(shí)候,我們就可以搞一個(gè)基類,避免重復(fù)定義這些字段:
class MessageBase(models.Model):id = models.AutoField()content = models.CharField(max_length=120)user_name = models.CharField(max_length=80)pub_date = models.DateField()class Meta:abstract = Trueclass Moment(MessageBase):headline = models.CharField(max_length=50)LEVELS = (('1','Very Good'),('2','Good'),('3','Normal'),('4','Bad') )class Comment(MessageBase):level = models.CharField(max_length=1,choices=LEVELS)在子類模型的實(shí)例中,可以直接用普通的方法來引用在父類模型中定義的字段。
●? 多表繼承
所謂多表繼承和抽象類繼承基本一樣,就是不設(shè)置abstract=True,然后父類的這張表也是在數(shù)據(jù)庫中落地的,僅此而已。。可以說是比較正常的,符合預(yù)期的表繼承方法
●? 代理模型繼承
上面兩種方法中,繼承父類的子類最終都是會(huì)實(shí)際儲(chǔ)存數(shù)據(jù)到數(shù)據(jù)庫中的,而代理模型繼承中,繼承下來的子類是不存儲(chǔ)數(shù)據(jù)的,比如:
from django.db import modelsclass Moment(models.Model):id = models.AutoField()headline = models.CharField(max_length=50)content = models.TextField()user_name = models.CharField(max_length=20)pub_date = models.DateField()class OrderedMoment(Moment):class Meta:proxy = True #這個(gè)就是代理模型繼承的方法ordering = ['-pub_date']上面的OrderedMoment模型,只是借用了父模型Moment中的數(shù)據(jù)并且進(jìn)行了一個(gè)排序,自身不實(shí)際存儲(chǔ)數(shù)據(jù)。
?
■ migrate和migrations
用了一段時(shí)間django之后,發(fā)現(xiàn)manage.py migrate以及makemigrations等操作真的很迷惑。。至今還沒弄得很明白。
需要知道的是,這個(gè)migrations的記錄會(huì)以數(shù)據(jù)庫結(jié)構(gòu)的變化(實(shí)際的變化),模塊中migrations目錄下的文件的生成(變化的內(nèi)容記錄)以及數(shù)據(jù)庫內(nèi)django_migrations表中記錄的新增(變化的總體記錄),這樣三種方式體現(xiàn)出來。三個(gè)地方只要出現(xiàn)不統(tǒng)一的情況,就可能會(huì)出現(xiàn)各種各樣的問題。。
總體來說,簡單的實(shí)踐就是在models中定義模型,然后makemigrations生成migrations的文件,然后migrate,將本次model的變更反映到數(shù)據(jù)庫結(jié)構(gòu)中去,同時(shí)也會(huì)再django_migrations表中記錄下變更。模型的變更頻率應(yīng)該要適當(dāng)。如果太短則會(huì)導(dǎo)致每次生成一個(gè)migration文件,最終導(dǎo)致migrations目錄下有很多很多文件。如果太長則一次性變化太多,可能會(huì)發(fā)生很多意想不到的錯(cuò)誤,更折磨人。
相比之下,還是相對(duì)快地更新迭代比較保險(xiǎn),因?yàn)橛蟹椒梢哉系舳嘤嗟膍igrations文件并且保證能繼續(xù)按照上述實(shí)踐迭代模型。下面來介紹兩種方法,請(qǐng)注意這些方法的難點(diǎn)在于整合掉一些migrations文件后整個(gè)模型層的正常工作如正常的模型變更迭代等是否還能順利進(jìn)行:
* 友情提示: 操作前請(qǐng)備份數(shù)據(jù)以防萬一。
第一種,如果對(duì)于數(shù)據(jù)庫中現(xiàn)有內(nèi)容不要求保留,那就很簡單。首先把數(shù)據(jù)庫中所有表清空(甚至直接drop掉整個(gè)數(shù)據(jù)庫再重新建一個(gè)),然后將所有模塊下的所有migrations文件刪除(除了每個(gè)migrations目錄中的__init__.py文件,這是包提示文件,不能刪)。然后manage.py makemigrations,manage.py migrate,就可以將定義與models.py中的那些模型映射成數(shù)據(jù)庫中的表了。很顯然這種方法雖然能夠保存下數(shù)據(jù)庫的表結(jié)構(gòu),但是只能用用在開發(fā)環(huán)境中,而且就算是開發(fā)環(huán)境有時(shí)候會(huì)有比較多的數(shù)據(jù),直接這么暴力全部刪除也不好。
第二種方法是django給出的一種解決方案。其操作流程是這樣的:
首先要確認(rèn)表結(jié)構(gòu)已經(jīng)和migrations以及django_migrations中的記錄對(duì)應(yīng)了。確認(rèn)方法可以是manage.py makemigrations來看下是否提示no changes(當(dāng)然也有少數(shù)情況每次make的時(shí)候都會(huì)有更新,比如某個(gè)DateField被指定了default=datetime.datetime.now()之類的,這種就可以忽略了。)。另外manage.py showmigrations命令可以查看migrate和現(xiàn)有migrations文件的對(duì)應(yīng)關(guān)系。文件前面的框框里有叉表示這個(gè)文件已經(jīng)被migrate了。
然后運(yùn)行命令 ./manage.py migrate --fake app_name zero,app_name是你要重置整合migration文件的模塊名。這一步過后,該模塊的migrate記錄會(huì)被重置,可以再showmigrations一下看到,這個(gè)模塊的所有文件前面都沒有叉了。接下來就可以刪掉除了__init__.py之外migrations下所有文件了。當(dāng)然為了以防萬一,強(qiáng)烈推薦同目錄下創(chuàng)建一個(gè)backup目錄,把這些文件暫時(shí)先放進(jìn)去,不要真的刪掉。
然后再執(zhí)行 ./manage.py makemigrations。此時(shí)可以看到在migrations下面有了一個(gè)0001_initial.py文件,這個(gè)文件中有完整的表結(jié)構(gòu)。
最后一步,執(zhí)行 ./manage.py migrate --fake-initial ,這一步將數(shù)據(jù)庫的django_migrations表中,本模塊的所有記錄刪除并記錄成我們只migrate到了0001_initial,(所以叫fake)。不能真執(zhí)行的原因是因?yàn)槟壳皵?shù)據(jù)庫中是有結(jié)構(gòu)的,而嘗試真的執(zhí)行會(huì)去建表干嘛的,當(dāng)然就會(huì)報(bào)錯(cuò)了。
至此,之前漫長的migrations文件都沒有了,取而代之的是從頭開始的0001。
*這樣做也不是萬事大吉了。。比如碰到開發(fā)、測試、生產(chǎn)環(huán)境中,只有一個(gè)做了migration文件壓縮操作,另外的沒有,此時(shí)如果再來一個(gè)新的migration文件,會(huì)因?yàn)榫幪?hào)在各個(gè)環(huán)境中不匹配(在壓縮過的那個(gè)環(huán)境中編號(hào)肯定是0002)導(dǎo)致無法通用。
?
上面這種方法盡量不要繞過django完成,比如不能手動(dòng)刪掉所有migrations文件,再刪django_migrations記錄,然后再重新makemigrations,會(huì)出錯(cuò)。。因?yàn)榇藭r(shí)數(shù)據(jù)庫結(jié)構(gòu)沒有回滾。
?
●? 關(guān)于fake和fake-initial參數(shù) 以及其他的一些migrate可選用參數(shù)
migrate命令的--fake參數(shù)上面也用到過了。這個(gè)參數(shù)在官方文檔中的解釋是,記錄或消除migrate記錄,但不去真的運(yùn)行SQL以改變數(shù)據(jù)結(jié)構(gòu)。換句話說,一般的migrate的流程是1. 讀取migrations文件,解析成SQL;2. 執(zhí)行SQL改變數(shù)據(jù)庫結(jié)構(gòu)或內(nèi)容;3. 將本次migrate的信息錄入django_migrations。但是如果加上--fake參數(shù),那么第2步會(huì)被直接跳過而變更直接被記錄到django_migrations中。
這個(gè)參數(shù)的使用場景常常是這樣的: 當(dāng)一個(gè)migration文件執(zhí)行出錯(cuò),而我們明確知道這個(gè)錯(cuò)該如何修正,我們完全可以手動(dòng)去對(duì)數(shù)據(jù)庫結(jié)構(gòu)做一些修改。然后再帶--fake參數(shù)地運(yùn)行本次變更對(duì)應(yīng)的migration文件。“假裝”我們正確地做了一次migrate。
--fake-initial的原理是類似的,只不過其針對(duì)的只是執(zhí)行0001_initial這個(gè)文件。所以說fake-initial的使用場景更加狹窄,即上面所說的對(duì)于數(shù)據(jù)庫中有結(jié)構(gòu),但是0001_initial.py重新生成并且需要寫入django_migrations記錄時(shí),用到這個(gè)參數(shù)。
?
另外migrate還有一個(gè)比較有用的參數(shù)是sqlmigrate。其用法是migrate sqlmigrate app 000x_xxx。它的意思是將某個(gè)特定的migrations文件翻譯成SQL打印到屏幕上。我們手動(dòng)去執(zhí)行這些SQL效果和自動(dòng)去migrate的效果是一樣的。因此在自動(dòng)migrate時(shí)出錯(cuò)的時(shí)候,可以利用sqlmigrate打印出SQL,具體查看哪些SQL有問題。如果可以排除,那么可以手動(dòng)修改出錯(cuò)的SQL并執(zhí)行,再帶有--fake參數(shù)執(zhí)行下這個(gè)migrate即可。
轉(zhuǎn)載于:https://www.cnblogs.com/franknihao/p/7773949.html
總結(jié)
以上是生活随笔為你收集整理的【Django】模型层说明的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。