




版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
深入理解Flask掌握用PHP創(chuàng)建強(qiáng)大動(dòng)態(tài)Web應(yīng)用的專項(xiàng)技術(shù)目錄TOC\h\h第1章入門\h使用Git進(jìn)行版本控制\h安裝Git\hGit基礎(chǔ)\h使用pip管理Python包\h在Windows上安裝pip\h在MacOSX和Linux上安裝Python包管理器pip\hpip基本操作\h用virtualenv的沙盒管理依賴\hvirtualenv基礎(chǔ)\h開(kāi)始我們的項(xiàng)目\h使用FlaskScript\h總結(jié)\h第2章使用SQLAlchemy創(chuàng)建數(shù)據(jù)模型\h設(shè)置SQLAlchemy\hPython安裝包\hFlaskSQLAlchemy\h我們的第1個(gè)數(shù)據(jù)模型\h創(chuàng)建user表\hCRUD\h新增數(shù)據(jù)\h讀取數(shù)據(jù)\h修改數(shù)據(jù)\h刪除數(shù)據(jù)\h數(shù)據(jù)模型之間的關(guān)聯(lián)\h一對(duì)多\h多對(duì)多\hSQLAlchemy會(huì)話對(duì)象的方便之處\h使用Alembic進(jìn)行數(shù)據(jù)庫(kù)遷移\h總結(jié)\h第3章通過(guò)模板創(chuàng)建視圖\hJinja的語(yǔ)法\h過(guò)濾器\h注釋\hif語(yǔ)句\h循環(huán)\h宏\hFlask特有的變量和函數(shù)\h創(chuàng)建視圖\h視圖函數(shù)\h編寫和繼承模板\hFlaskWTForms\hWTForms基礎(chǔ)\h自定義檢驗(yàn)器\h發(fā)布評(píng)論\h總結(jié)\h第4章使用藍(lán)圖創(chuàng)建控制器\h請(qǐng)求的構(gòu)建和銷毀,以及全局變量\h錯(cuò)誤頁(yè)面\h使用類描述視圖\h方法視圖\h藍(lán)圖\h總結(jié)\h第5章進(jìn)階的應(yīng)用結(jié)構(gòu)\h模塊項(xiàng)目\h重構(gòu)代碼\h應(yīng)用的工廠模式\h總結(jié)\h第6章保護(hù)應(yīng)用安全\h準(zhǔn)備工作\h修改用戶模型\h創(chuàng)建表單\h創(chuàng)建視圖\h社交網(wǎng)絡(luò)登錄\h使用會(huì)話\h使用FlaskLogin\h用戶角色\h總結(jié)\h第7章在Flask中使用NoSQL數(shù)據(jù)庫(kù)\hNoSQL數(shù)據(jù)庫(kù)的種類\h鍵值數(shù)據(jù)庫(kù)\h文檔數(shù)據(jù)庫(kù)\h列式數(shù)據(jù)庫(kù)\h基于圖的數(shù)據(jù)庫(kù)\h關(guān)系型數(shù)據(jù)庫(kù)與NoSQL的比較\h關(guān)系型數(shù)據(jù)庫(kù)的優(yōu)勢(shì)\hNoSQL數(shù)據(jù)庫(kù)的優(yōu)勢(shì)\h在什么情況下用什么數(shù)據(jù)庫(kù)\h在Flask中使用MongoDB\h安裝MongoDB\h配置MongoEngine\h定義文檔\hCRUD\hNoSQL中的關(guān)聯(lián)關(guān)系\h利用NoSQL的強(qiáng)大能力\h總結(jié)\h第8章構(gòu)建RESTfulAPI\hREST是什么\h構(gòu)建RESTfulFlaskAPI\hGET請(qǐng)求\h格式化輸出\h請(qǐng)求中的參數(shù)\hPOST請(qǐng)求\h身份認(rèn)證\hPUT請(qǐng)求\hDELETE請(qǐng)求\h總結(jié)\h第9章使用Celery編寫異步任務(wù)\hCelery是什么\h配置Celery和RabbitMQ\h在Celery中創(chuàng)建任務(wù)\h運(yùn)行Celery任務(wù)\hCelery工作流\h監(jiān)控Celery\h在Flower中通過(guò)網(wǎng)頁(yè)進(jìn)行監(jiān)控\h創(chuàng)建一個(gè)提醒應(yīng)用\h生成每周摘要\h總結(jié)\h第10章有用的Flask擴(kuò)展\hFlaskScript\hFlaskDebugToolbar\hFlaskCache\h緩存函數(shù)和視圖\h緩存帶參數(shù)的函數(shù)\h緩存帶有查詢參數(shù)的路徑\h使用Redis作為緩存后端\h使用memcached作為緩存后端\hFlaskAssets\hFlaskAdmin\h編寫基礎(chǔ)管理頁(yè)面\h編寫數(shù)據(jù)庫(kù)管理頁(yè)面\h增強(qiáng)文章管理功能\h編寫文件系統(tǒng)管理頁(yè)面\h保護(hù)FlaskAdmin的安全\hFlaskMail\h總結(jié)\h第11章構(gòu)建你自己的擴(kuò)展\h編寫一個(gè)YouTubeFlask擴(kuò)展\h創(chuàng)建Python包\h通過(guò)Flask擴(kuò)展修改響應(yīng)數(shù)據(jù)\h總結(jié)\h第12章測(cè)試Flask應(yīng)用\h什么是單元測(cè)試\h怎樣進(jìn)行測(cè)試\h對(duì)應(yīng)用進(jìn)行單元測(cè)試\h測(cè)試路由函數(shù)\h用戶界面測(cè)試\h測(cè)試覆蓋率\h測(cè)試驅(qū)動(dòng)的開(kāi)發(fā)\h總結(jié)\h第13章部署Flask應(yīng)用\h部署在你自己的服務(wù)器上\h使用fabric把代碼推送到服務(wù)器\h使用supervisor運(yùn)行你的Web服務(wù)器\hGevent\hTornado\hNginx和uWSGI\hApache和uWSGI\h部署在Heroku上\h使用HerokuPostgres\h在Heroku中使用Celery\h在AWS上部署應(yīng)用\h在AmazonElasticBeanstalk上使用Flask\h使用AmazonRelationalDatabaseService\h在AmazonSimpleQueueService中使用Celery第1章入門Python是一門靈活的語(yǔ)言,給予了程序員極大的自由,使程序員可以使用任意文件結(jié)構(gòu)和開(kāi)發(fā)環(huán)境。但這種自由帶來(lái)的一個(gè)危險(xiǎn)后果是,也許程序員從一開(kāi)始創(chuàng)建新項(xiàng)目的方式就不正確,導(dǎo)致在開(kāi)發(fā)過(guò)程中可能出現(xiàn)一些問(wèn)題。比如,可能寫到一半的時(shí)候,我們突然意識(shí)到現(xiàn)在需要的某些文件或部分代碼,在5天前就被自己刪掉了;或者想使用兩個(gè)Python包,發(fā)現(xiàn)它們需要依賴同一個(gè)底層的包,卻需要是不同版本的。如果不使用本章介紹的工具,則可能需要做一大堆額外的工作來(lái)處理這些問(wèn)題。但其實(shí)對(duì)這些問(wèn)題早就有解決方案了,在一開(kāi)始做一點(diǎn)額外的準(zhǔn)備,可以避免在將來(lái)浪費(fèi)大量的時(shí)間。為此,我們需要安裝3個(gè)程序:Git、pip和virtualenv。使用Git進(jìn)行版本控制為了防止項(xiàng)目遭到人為破壞,我們需要一個(gè)叫作Git的版本控制系統(tǒng)。版本控制系統(tǒng)是在文件更改過(guò)程中記錄變更的工具,這能夠使開(kāi)發(fā)者看到代碼在歷史版本中是怎樣變化的,也可以把代碼回滾到過(guò)去的某個(gè)狀態(tài)。版本控制系統(tǒng)也讓協(xié)作變得比以往簡(jiǎn)單,因?yàn)槌绦騿T之間可以分享變更,也可以快速合并變更到當(dāng)前的版本中,而不需要手動(dòng)去復(fù)制粘貼數(shù)百行代碼。簡(jiǎn)而言之,版本管理系統(tǒng)就像代碼備份,不過(guò)要強(qiáng)大得多。安裝Git安裝Git非常簡(jiǎn)單,只需要前往/downloads,單擊正在使用的操作系統(tǒng)(OperationSystem),就會(huì)下載一個(gè)安裝文件,并引導(dǎo)你完成基本的安裝流程。在Windows上使用GitGit最初是被開(kāi)發(fā)出來(lái)僅供類Unix系統(tǒng)使用的(比如Linux、MacOSX),所以在Windows上不能完全無(wú)縫地直接使用Git。在安裝過(guò)程中,程序會(huì)詢問(wèn)你是否希望在普通Windows命令行中使用Git,這時(shí)要選“否”。選擇默認(rèn)的選項(xiàng),程序會(huì)在你的系統(tǒng)中安裝一個(gè)叫作Bash的新命令行程序。這跟在Unix系統(tǒng)中使用的Bash是一樣的。Bash比Windows默認(rèn)的命令行程序要強(qiáng)大得多,在本書的所有例子中都會(huì)用到。在/learning_the_shell.php#contents上有個(gè)很不錯(cuò)的為初學(xué)者準(zhǔn)備的Bash介紹。Git基礎(chǔ)Git是一個(gè)頗為復(fù)雜的工具。這里只介紹會(huì)在本書中用到的基礎(chǔ)部分。要學(xué)習(xí)更多內(nèi)容,請(qǐng)參考Git官方文檔/doc。Git不會(huì)自動(dòng)跟蹤你的變更。為了讓Git正確工作,我們需要向它提供如下信息。要跟蹤哪個(gè)目錄。什么時(shí)候保存代碼的狀態(tài)。哪些變更需要被跟蹤,哪些不需要。在一切開(kāi)始之前,首先讓Git在我們的目錄下創(chuàng)建一個(gè)git實(shí)例。從終端進(jìn)入你的項(xiàng)目目錄下,運(yùn)行下面的命令:$gitinit
Git會(huì)開(kāi)始在你的項(xiàng)目中跟蹤變更。既然git已經(jīng)開(kāi)始追蹤這些文件,那么我們可以通過(guò)以下命令來(lái)顯示被跟蹤的文件狀態(tài),同時(shí)會(huì)顯示所有沒(méi)有被跟蹤的文件:$gitstatus
現(xiàn)在可以保存我們的第1個(gè)提交(commit),它實(shí)際上就是在你執(zhí)行commit命令時(shí),所有代碼的一個(gè)快照。#在Bash中,注釋語(yǔ)句用#標(biāo)示,就跟在Python里一樣
#把所有你希望提交的變更添加進(jìn)暫存區(qū)
$gitaddmain.py
#提交這些變更,使用-m參數(shù)加入提交信息(commitmessage)
$gitcommit-m"Ourfirstcommit"
將來(lái)我們可以在項(xiàng)目中回退到現(xiàn)在的這個(gè)時(shí)間點(diǎn)。這一操作在Git中叫作暫存。如果你已經(jīng)準(zhǔn)備好提交文件,則記得先把它們暫存起來(lái)。注意,即使暫存了一個(gè)被修改的文件,這個(gè)文件的后繼修改也不會(huì)自動(dòng)進(jìn)入暫存區(qū)。下面是一個(gè)稍有難度的Git用例,使用文本編輯器把一些文本加到你的main.py文件中,然后執(zhí)行下面的命令:#看一下從上一個(gè)提交起,代碼有什么變化
$gitdiff
#查看你的提交歷史
$gitlog
#作為一個(gè)例子,我們先暫存main.py
#然后從暫存區(qū)移除所有被添加進(jìn)來(lái)的文件
$gitaddmain.py
$gitstatus
$gitresetHEADmain.py
#在每次復(fù)雜的更改之后,記得跑一下status
#來(lái)確保所有的東西都沒(méi)錯(cuò)
$gitstatus
#我們現(xiàn)在刪除main.py上的所有更改,回滾到它上次被提交的狀態(tài)
#這只能對(duì)沒(méi)有被暫存的文件使用
$gitcheckout--main.py
你的終端看起來(lái)應(yīng)該如下所示。這里用到了Git系統(tǒng)的checkout命令,對(duì)于我們的Git簡(jiǎn)介來(lái)說(shuō),似乎有點(diǎn)過(guò)于高級(jí)。它可以用來(lái)改變Git系統(tǒng)中HEAD指針的當(dāng)前狀態(tài),也就是在項(xiàng)目歷史中我們的代碼目前的位置。在下個(gè)例子中我們會(huì)進(jìn)一步了解?,F(xiàn)在,要查看代碼在上一個(gè)提交中的狀態(tài),首先運(yùn)行:$gitlog
FriJan2319:16:432015-0500f01d1e2Ourfirstcommit[JackStouffer]
在提交信息之后的那串字符f01d1e2,是這個(gè)提交的唯一標(biāo)識(shí),被稱為這個(gè)提交的哈希值。使用此哈希值,可以讓項(xiàng)目代碼回到這個(gè)時(shí)刻的狀態(tài)。運(yùn)行下面的命令就可以做到:$gitcheckoutf01d1e2
你的Git項(xiàng)目現(xiàn)在進(jìn)入了一種特殊的狀態(tài),在此狀態(tài)下,你的任何改動(dòng)和提交既不會(huì)被保存,也不會(huì)影響你檢出的這個(gè)提交之后的任何提交。這種狀態(tài)只用來(lái)查看老代碼。要回到普通的模式,可運(yùn)行:$gitcheckoutmaster
使用pip管理Python包在開(kāi)發(fā)Python項(xiàng)目時(shí),我們可以從其他程序員那里下載庫(kù)文件來(lái)擴(kuò)展Python標(biāo)準(zhǔn)庫(kù)的功能。正如你所知道的,在使用Flask時(shí),可以從社區(qū)創(chuàng)建的大量Python庫(kù)中獲取我們所需要的各種功能。不過(guò),要完全正確地使用第三方庫(kù),卻并不容易。比如你想安裝一個(gè)包X,很簡(jiǎn)單,下載Zip文件,執(zhí)行一下setup.py,對(duì)吧?其實(shí)這樣可能有問(wèn)題。如果包X依賴包Y,包Y又依賴包Z和包Q,這些信息都沒(méi)有在包X的網(wǎng)站上列出來(lái),若要安裝X并讓它正確工作,則你又必須一個(gè)個(gè)地找到這些包并安裝,并且期望你正在安裝的這些包沒(méi)有再依賴別的包。為了自動(dòng)化這個(gè)流程,我們使用pip——Python的包管理器。在Windows上安裝pip如果你用的是Windows操作系統(tǒng),而且安裝了最新版本的Python,那么你已經(jīng)有pip了!如果你的Python不是最新版本,那么最簡(jiǎn)單的辦法就是重新安裝它,可以從/downloads/上下載Python的Windows安裝包。Windows中的環(huán)境變量path,決定了在命令行里能夠使用哪些程序。為了在path里包含Python和pip,我們可以把C:\Python27和C:\Python27\tools添加進(jìn)去。在Windows菜單中可以編輯path,用鼠標(biāo)右鍵單擊我的電腦,選擇屬性,在系統(tǒng)高級(jí)設(shè)置中,單擊環(huán)境變量…,往下滾動(dòng),找到Path,雙擊它,在末尾添加;C:\Python27;C:\Python27\Tools。你可以關(guān)閉并重新打開(kāi)終端,在命令行中輸入如下命令,確認(rèn)path已經(jīng)被正確地修改:pip--help
下載示例代碼文件若你在上購(gòu)買了博文視點(diǎn)的書,那么你可以從你的賬戶中下載示例代碼文件。如果你是在其他地方購(gòu)買的書,則可以在上注冊(cè),按照提示完成下載。pip會(huì)打印出它的使用說(shuō)明,就像下面這個(gè)截屏顯示的一樣。在MacOSX和Linux上安裝Python包管理器pipLinux上的某些Python版本沒(méi)有同時(shí)安裝pip,MacOSX也不會(huì)默認(rèn)安裝pip。要安裝pip,可以從/pypa/pip/master/contrib/get-pip.py下載get-pip.py。在下載完畢后,可使用管理員權(quán)限執(zhí)行該腳本,代碼如下:$sudopythonget-pip.py
pip會(huì)被自動(dòng)安裝到系統(tǒng)中。pip基本操作用pip安裝一個(gè)包很簡(jiǎn)單,代碼如下:$pipinstall[包名]
在Mac和Linux上,因?yàn)槟阋谟脩裟夸浿獍惭b程序,所以必須在安裝命令之前添加sudo。要安裝Flask,命令非常簡(jiǎn)單:$pipinstallflask
這樣就會(huì)為你安裝Flask及其所有依賴。如果你希望移除一個(gè)不再需要的包,則可運(yùn)行:$pipuninstall[包名]
如果你想查找一個(gè)還不清楚其確切名字的包,則可以使用搜索命令:$pipsearch[搜索關(guān)鍵詞]
現(xiàn)在我們已經(jīng)安裝了一些包,按照Python社區(qū)的慣例,我們需要?jiǎng)?chuàng)建一個(gè)列表文件,來(lái)指明運(yùn)行這個(gè)項(xiàng)目需要依賴哪些包。這也為你的項(xiàng)目新成員提供了便利,使他們能夠快速上手并運(yùn)行你的代碼??梢允褂胮ip執(zhí)行以下命令,來(lái)生成這個(gè)列表:$pipfreeze>requirements.txt
這個(gè)命令具體做了什么?pipfreeze命令自己會(huì)打印一個(gè)列表,包括了已經(jīng)安裝的包,以及它們的版本號(hào),如下所示:Flask==0.10.1
itsdangerous==0.24
Jinja2==2.7.3
MarkupSafe==0.23
Werkzeug==0.10.4
wheel==0.24.0
大于操作符>會(huì)告訴Bash把它之前的最后一個(gè)命令輸出的內(nèi)容寫入文件中。查看項(xiàng)目文件夾,你會(huì)發(fā)現(xiàn)一個(gè)叫作requirements.txt的新文件,里面包含了pipfreeze輸出的內(nèi)容。要安裝這個(gè)文件指定的所有包,新的項(xiàng)目維護(hù)者需要運(yùn)行如下命令:$pipinstall-rrequirements.txt
這會(huì)讓pip讀取requirements.txt列出的所有包,并且安裝它們。用virtualenv的沙盒管理依賴現(xiàn)在你已經(jīng)安裝了新項(xiàng)目所需要的所有包,太棒了!但是,如果你接下來(lái)要開(kāi)發(fā)第2個(gè)項(xiàng)目,會(huì)用到一些同樣的包,版本卻更新了,那么會(huì)發(fā)生什么?如果一個(gè)包依賴了你在之前的項(xiàng)目開(kāi)發(fā)中安裝過(guò)的另一個(gè)包,但這次它依賴一個(gè)更老的版本,那么又會(huì)發(fā)生什么?當(dāng)依賴包的新版本包含了與老版本不兼容的改動(dòng)時(shí),要升級(jí)這些包,就意味著必須對(duì)老項(xiàng)目做一些額外的開(kāi)發(fā),你可能會(huì)不樂(lè)意。幸運(yùn)的是,我們還有virtualenv,一個(gè)能把Python項(xiàng)目沙盒化的工具。virtualenv的秘密在于,它讓你的電腦從項(xiàng)目目錄而不是系統(tǒng)全局的Python主目錄下查找和安裝包,這樣就可以把它們的環(huán)境完全隔離開(kāi)了。既然我們已經(jīng)有了pip,要安裝virtualenv,則只需運(yùn)行:$pipinstallvirtualenv
virtualenv基礎(chǔ)首先,用virtualenv來(lái)初始化你的項(xiàng)目:$virtualenvenv
后面這個(gè)env告訴virtualenv,把所有的包都裝在一個(gè)叫作env的文件夾里。接下來(lái),virtualenv需要你激活沙盒環(huán)境,這樣就可以對(duì)你的項(xiàng)目進(jìn)行沙盒化。$sourceenv/bin/activate
#你的提示符可能會(huì)變成下面這樣
(env)$
這個(gè)source命令會(huì)讓Bash在當(dāng)前目錄中運(yùn)行腳本env/bin/activate?,F(xiàn)在我們可以在新的沙盒環(huán)境中重新安裝Flask:#這次不需要sudo了
(env)$pipinstallflask
#退出沙盒,返回全局的Python環(huán)境
(env)$deactivate
另外,我們要避免跟蹤第三方庫(kù)的代碼變更,因?yàn)楦櫜粚儆谀愕拇a,是和Git最佳實(shí)踐相沖突的。為了忽略我們項(xiàng)目中的特定文件,需要?jiǎng)?chuàng)建一個(gè)gitignore文件:$touch.gitignore
touch是用來(lái)創(chuàng)建文件的Bash指令。文件名開(kāi)頭的點(diǎn)會(huì)告訴Bash,不要把這個(gè)文件顯示出來(lái),除非特意要求它顯示隱藏文件?,F(xiàn)在我們來(lái)編寫一個(gè)簡(jiǎn)單的gitignore文件:env/
*.pyc
這會(huì)告訴Git忽略整個(gè)env文件夾及所有以.pyc結(jié)尾的文件(Python編譯生成的文件)。當(dāng)我們這樣寫的時(shí)候,“*”符號(hào)叫作通配符(wildcard)。開(kāi)始我們的項(xiàng)目終于,我們開(kāi)始了第1個(gè)Flask項(xiàng)目。為了在本書的最后實(shí)現(xiàn)一個(gè)復(fù)雜的項(xiàng)目,我們需要一個(gè)簡(jiǎn)單的項(xiàng)目作為開(kāi)始。在config.py文件中添加如下內(nèi)容:classConfig(object):
pass
classProdConfig(Config):
pass
classDevConfig(Config):
DEBUG=True
現(xiàn)在,在另一個(gè)main.py文件中添加以下內(nèi)容:fromflaskimportFlask
fromconfigimportDevConfig
app=Flask(__name__)
app.config.from_object(DevConfig)
@app.route('/')
defhome():
return'<h1>HelloWorld!</h1>'
if__name__=='__main__':
app.run()
對(duì)于了解一些FlaskAPI的讀者來(lái)說(shuō),這個(gè)程序非?;A(chǔ),它只是在我們?cè)L問(wèn):5000的時(shí)候,在瀏覽器中顯示一行“HelloWorld!”。另外,F(xiàn)lask用戶可能不很熟悉的一個(gè)地方是,這里使用了config.from_object,而不是app.config['DEBUG']。使用from_object是因?yàn)槲磥?lái)我們會(huì)加入很多配置項(xiàng),如果要在不同的配置集之間切換,那么手動(dòng)去改每個(gè)變量是一件煩瑣乏味的事情。記得在Git中提交這些改動(dòng):#--all標(biāo)志會(huì)告訴Git把你的所有改動(dòng)全部加入暫存
#包括刪除的和新增的文件
$gitadd--all
$gitcommit-m"createdthebaseapplication"之后我們將不會(huì)再提示什么時(shí)候把代碼提交到Git。讀者自己應(yīng)該培養(yǎng)這樣的習(xí)慣,并且自己決定在何時(shí)可以暫停工作、提交代碼。我們也假設(shè)你一直在虛擬環(huán)境中操作,因此命令行提示符前的(env)前綴將不會(huì)被打印出來(lái)。使用FlaskScript為了讓讀者更容易學(xué)習(xí)第2章,我們會(huì)在眾多Flask擴(kuò)展(用來(lái)擴(kuò)充Flask功能的包)中,首先選用一個(gè)叫作FlaskScript的擴(kuò)展。使用FlaskScript可以創(chuàng)建命令,并在Flask的應(yīng)用上下文(ApplicationContext)中執(zhí)行,因?yàn)檫@樣才能對(duì)Flask對(duì)象進(jìn)行修改。FlaskScript自帶了一些默認(rèn)的命令,可以運(yùn)行服務(wù)器或者開(kāi)啟帶應(yīng)用上下文的Python命令行。下面使用pip安裝FlaskScript:$pipinstallflask-script
在第10章中會(huì)介紹更多關(guān)于FlaskScript的高級(jí)用法,現(xiàn)在先從創(chuàng)建一個(gè)簡(jiǎn)單的manage.py腳本開(kāi)始。首先導(dǎo)入FlaskScript的對(duì)象,代碼如下:fromflask.ext.scriptimportManager,Server
frommainimportapp
然后把你的app傳給Manager對(duì)象,以初始化FlaskScript:manager=Manager(app)
現(xiàn)在我們來(lái)添加一些命令。這里運(yùn)行的服務(wù)器跟通過(guò)main.py運(yùn)行的普通開(kāi)發(fā)服務(wù)器是一樣的。make_shell_context函數(shù)會(huì)創(chuàng)建一個(gè)Python命令行,并且在應(yīng)用上下文中執(zhí)行。返回的字典會(huì)告訴FlaskScript在打開(kāi)命令行時(shí)進(jìn)行一些默認(rèn)的導(dǎo)入工作。manager.add_command("server",Server())
@manager.shell
defmake_shell_context():
returndict(app=app)通過(guò)manage.py運(yùn)行命令行在將來(lái)會(huì)十分必要,因?yàn)橐恍〧lask擴(kuò)展只有在Flask應(yīng)用對(duì)象被創(chuàng)建之后才會(huì)被初始化。直接運(yùn)行默認(rèn)的Python命令行會(huì)令這些擴(kuò)展返回錯(cuò)誤。然后,在文件結(jié)尾添加如下代碼,這是Python的標(biāo)準(zhǔn)方式,用來(lái)限制僅在用戶直接運(yùn)行文件的時(shí)候,才執(zhí)行上面的代碼:if__name__=="__main__":
manager.run()
你現(xiàn)在可以這樣來(lái)運(yùn)行開(kāi)發(fā)環(huán)境服務(wù)器:$pythonmanage.pyserver
以及使用命令行:$pythonmanage.pyshell
#我們來(lái)看下app有沒(méi)有被正確導(dǎo)入
>>>app
<Flask‘main'>
總結(jié)現(xiàn)在我們已經(jīng)搭建了開(kāi)發(fā)環(huán)境,可以繼續(xù)在Flask里面實(shí)現(xiàn)更高級(jí)的特性了。我們?cè)诰帉懣梢燥@示的內(nèi)容之前,首先要準(zhǔn)備一些用以展示的內(nèi)容。在第2章中,你會(huì)理解并熟練地掌握如何在Flask中使用數(shù)據(jù)庫(kù)。第2章使用SQLAlchemy創(chuàng)建數(shù)據(jù)模型如前所述,模型(models)是對(duì)數(shù)據(jù)抽象并提供通用訪問(wèn)接口的一種方式。在大多數(shù)網(wǎng)絡(luò)應(yīng)用中,數(shù)據(jù)會(huì)被存儲(chǔ)在一個(gè)關(guān)系數(shù)據(jù)庫(kù)管理系統(tǒng)(RDBMS)中,也就是把數(shù)據(jù)格式化存儲(chǔ)在由行與列組成的表格中,且能夠跨表對(duì)數(shù)據(jù)進(jìn)行比較。例如MySQL、Postgres、Oracle和MSSQL。為了基于數(shù)據(jù)庫(kù)抽象出數(shù)據(jù)模型,我們需要使用一個(gè)叫作SQLAlchemy的Python包。SQLAlchemy在最底層包裝了數(shù)據(jù)庫(kù)操作接口,在最上層提供了對(duì)象關(guān)系映射(ORM)。ORM是在基于不同的數(shù)據(jù)結(jié)構(gòu)和系統(tǒng)類型的數(shù)據(jù)源之間傳遞和轉(zhuǎn)換數(shù)據(jù)的技術(shù)。在這里,它用來(lái)把大量的不同類型的數(shù)據(jù)庫(kù)中的數(shù)據(jù),轉(zhuǎn)換成Python對(duì)象的集合。同時(shí),像Python這樣的語(yǔ)言,允許你在不同的對(duì)象之間建立引用,讀取和設(shè)置它們的屬性;而SQLAlchemy這樣的ORM,能為你將對(duì)象操作轉(zhuǎn)換為傳統(tǒng)的數(shù)據(jù)庫(kù)操作。為了把SQLAlchemy綁定到我們的應(yīng)用上下文中,我們可以使用FlaskSQLAlchemy。FlaskSQLAlchemy在SQLAlchemy上提供了一層包裝,這樣就可以結(jié)合Flask的一些特性來(lái)方便地調(diào)用SQLAlchemy的功能。如果你對(duì)SQLAlchemy已經(jīng)很熟悉,那么你可以單獨(dú)地使用它,而無(wú)須和FlaskSQLAlchemy一起使用。在本章的最后,我們會(huì)為博客應(yīng)用準(zhǔn)備完整的數(shù)據(jù)庫(kù)結(jié)構(gòu),以及與之交互的數(shù)據(jù)模型。設(shè)置SQLAlchemy為了跟上第1章的內(nèi)容,如果你還沒(méi)有數(shù)據(jù)庫(kù),那么你需要先選擇一個(gè)。如果沒(méi)安裝過(guò)數(shù)據(jù)庫(kù)或者沒(méi)什么偏好,那么SQLite會(huì)是初學(xué)者的最佳選擇。SQLite是無(wú)須運(yùn)行服務(wù)的SQL數(shù)據(jù)庫(kù)。它的運(yùn)行速度很快,所有數(shù)據(jù)都包含在一個(gè)文件中,而且支持Python。如果你選擇了SQLite,那么將在我們的第1個(gè)數(shù)據(jù)模型一節(jié)中,為你創(chuàng)建一個(gè)SQLite數(shù)據(jù)庫(kù)。Python安裝包使用pip安裝FlaskSQLAlchemy,運(yùn)行如下命令:$pipinstallflask-sqlalchemy
我們還需要安裝一些特定的包,作為SQLAlchemy與你所選擇的數(shù)據(jù)庫(kù)之間的連接器。SQLite用戶可以跳過(guò)這一步:#MySQL
$pipinstallPyMySQL
#Postgres
$pipinstallpsycopg2
#MSSQL
$pipinstallpyodbc
#Oracle
$pipinstallcx_Oracle
FlaskSQLAlchemy在開(kāi)始抽象數(shù)據(jù)結(jié)構(gòu)之前,我們需要先設(shè)置FlaskSQLAlchemy。AQLAlchemy通過(guò)一個(gè)特殊的數(shù)據(jù)庫(kù)URI來(lái)創(chuàng)建數(shù)據(jù)庫(kù)連接,這個(gè)URI是一個(gè)類似于URL的字符串,包含了SQLAlchemy建立連接所需要的所有信息。下面是它的一般形式:databasetype+driver://user:password@ip:port/db_name
對(duì)于你在之前安裝的每個(gè)驅(qū)動(dòng)程序來(lái)說(shuō),對(duì)應(yīng)的URI會(huì)是:#SQLite
sqlite:///database.db
#MySQL
mysql+pymysql://user:password@ip:port/db_name
#Postgres
postgresql+psycopg2://user:password@ip:port/db_name
#MSSQL
mssql+pyodbc://user:password@dsn_name
#Oracle
oracle+cx_oracle://user:password@ip:port/db_name
在我們的config.py文件中將URI添加到DevConfig中:classDevConfig(Config):
debug=True
SQLALCHEMY_DATABASE_URI="YOURURI"
我們的第1個(gè)數(shù)據(jù)模型你可能已經(jīng)注意到,我們還沒(méi)有真正進(jìn)入數(shù)據(jù)庫(kù)中去創(chuàng)建任何表結(jié)構(gòu)。這是因?yàn)镾QLAlchemy不但允許我們根據(jù)數(shù)據(jù)庫(kù)表結(jié)構(gòu)創(chuàng)建數(shù)據(jù)模型(model),也允許我們根據(jù)數(shù)據(jù)模型創(chuàng)建數(shù)據(jù)庫(kù)表結(jié)構(gòu)。所以當(dāng)我們把第1個(gè)模型創(chuàng)建出來(lái)之后,表結(jié)構(gòu)也就有了。首先,要在main.py文件中將我們的app對(duì)象傳給SQLAlchemy,將SQLAlchemy初始化:fromflask.ext.sqlalchemyimportSQLAlchemy
app=Flask(__name__)
app.config.from_object(DevConfig)
db=SQLAlchemy(app)
SQLAlchemy會(huì)從app的配置中讀取信息,自動(dòng)連接到數(shù)據(jù)庫(kù)。我們首先在main.py中創(chuàng)建一個(gè)User模型,它會(huì)跟相應(yīng)的一個(gè)user表進(jìn)行交互。classUser(db.Model):
id=db.Column(db.Integer(),primary_key=True)
username=db.Column(db.String(255))
password=db.Column(db.String(255))
def__init__(self,username):
self.username=username
def__repr__(self):
return"<User‘{}'>".format(self.username)這段代碼做了什么呢?實(shí)際上,我們已經(jīng)得到了User模型,它基于一個(gè)user表,該表?yè)碛?個(gè)字段。當(dāng)我們繼承db.Model時(shí),與數(shù)據(jù)庫(kù)連接和通信的工作已經(jīng)自動(dòng)完成了。User的某些類的屬性值是db.Column類的實(shí)例,每個(gè)這樣的屬性都代表了數(shù)據(jù)庫(kù)表里的一個(gè)字段。在db.Column的構(gòu)造函數(shù)里,第1個(gè)參數(shù)是可選的,通過(guò)這個(gè)參數(shù),我們可以指定該屬性在數(shù)據(jù)庫(kù)中的字段名。如果沒(méi)有指定,則SQLAlchemy會(huì)認(rèn)為字段名與這個(gè)屬性的名字是一樣的。如果要指定這個(gè)可選參數(shù),則可以這樣寫:username=db.Column('user_name',db.String(255))
傳給db.Column的第2個(gè)參數(shù)會(huì)告訴SQLAlchemy,應(yīng)該把這個(gè)字段作為什么類型來(lái)處理。我們?cè)跁袑?huì)用到的主要類型有:db.Stringdb.Textdb.Integerdb.Floatdb.Booleandb.Datedb.DateTimedb.Time每種類型的含義都很簡(jiǎn)單。String和Text類型會(huì)接收Python的字符串,并且把它們轉(zhuǎn)為varchar和text類型的字段。Integer和Float類型則會(huì)接收Python的任意數(shù)值類型,把它們分別轉(zhuǎn)換為對(duì)應(yīng)的正確類型,再插入數(shù)據(jù)庫(kù)中。Boolean類型會(huì)接收Python的True或False值,如果數(shù)據(jù)庫(kù)支持boolean類型的字段,則直接把Python的布爾值轉(zhuǎn)換成Boolean類型的字段;如果數(shù)據(jù)庫(kù)不支持boolean類型的字段,則SQLAlchemy會(huì)自動(dòng)把Python的布爾值轉(zhuǎn)換為0和1保存在數(shù)據(jù)庫(kù)中。Date、DateTime和Time類型使用了Python的datetime原生包中的同名類,并把它們轉(zhuǎn)換后保存到數(shù)據(jù)庫(kù)中。String、Integer和Float類型都會(huì)接收一個(gè)額外的參數(shù),來(lái)告訴SQLAlchemy該字段的存儲(chǔ)長(zhǎng)度限制。如果你希望真正理解SQLAlchemy是怎么把你的代碼翻譯成SQL查詢語(yǔ)句的,則可以在DevConfig文件中加入:SQLALCHEMY_ECHO=True
這樣就會(huì)把生成的查詢語(yǔ)句打印到終端。但當(dāng)你繼續(xù)學(xué)習(xí)本書的后續(xù)章節(jié)時(shí),則可能會(huì)想把這個(gè)特性關(guān)掉,因?yàn)槟忝看蜷_(kāi)一個(gè)頁(yè)面,終端都會(huì)打印出大量的查詢語(yǔ)句。primary_key參數(shù)會(huì)告訴SQLAlchemy,這個(gè)字段需要做主鍵索引(primarykeyindex)。每個(gè)SQLAlchemy模型類都必須有一個(gè)主鍵才能正常工作。SQLAlchemy會(huì)假設(shè)你的表名就是模型類名的小寫版本。但是,如果我們想給表起個(gè)別的名字,不叫作user,則應(yīng)該怎么做呢?要告訴SQLAlchemy使用指定的表名,可以添加叫作__tablename__的類屬性。另外,通過(guò)采用這種方式,你也可以使用在數(shù)據(jù)庫(kù)中已經(jīng)存在的表,只需把表名設(shè)為該屬性的值:classUser(db.Model):
__tablename__='user_table_name'
id=db.Column(db.Integer(),primary_key=True)
username=db.Column(db.String(255))
password=db.Column(db.String(255))
我們不需要定義__init__或__repr__方法,如果我們沒(méi)有定義,則SQLAlchemy會(huì)自動(dòng)創(chuàng)建__init__方法。你定義的所有字段名將會(huì)成為此方法所接收的關(guān)鍵字的參數(shù)名。創(chuàng)建user表現(xiàn)在有SQLAlchemy來(lái)完成繁重的勞動(dòng),我們就可以輕松地在數(shù)據(jù)庫(kù)中創(chuàng)建user表了。更新manage.py如下:frommainimportapp,db,User
...
@manager.shell
defmake_shell_context():
returndict(app=app,db=db,User=User)
Style-"db","User"infirstlineasCodeHighlight從現(xiàn)在開(kāi)始,我們每新增一個(gè)數(shù)據(jù)模型,都會(huì)在這個(gè)地方把它導(dǎo)入,并添加到返回的dict中。這樣就能夠在命令行中使用我們的模型了?,F(xiàn)在可以運(yùn)行命令行,并用db.create_all()來(lái)創(chuàng)建所有的表:$pythonmanage.pyshell
>>>db.create_all()
你現(xiàn)在應(yīng)該能在數(shù)據(jù)庫(kù)中找到一個(gè)叫作users的表,該表中有你所指定的那些字段。同樣,如果你使用的是SQLite,則也會(huì)在你的目錄結(jié)構(gòu)中找到一個(gè)叫作database.db的文件。CRUD在每種數(shù)據(jù)存儲(chǔ)策略中,都存在4個(gè)基本功能類型:添加、讀取、修改和刪除(CRUD)。CRUD提供了在我們的網(wǎng)絡(luò)應(yīng)用中需要的所有操作和檢視數(shù)據(jù)的基礎(chǔ)功能。要使用這些功能,我們需要在數(shù)據(jù)庫(kù)中用到一個(gè)叫作會(huì)話(session)的對(duì)象。會(huì)話的含義會(huì)在本章稍后解釋,但現(xiàn)在可以先把它們看作保存對(duì)數(shù)據(jù)庫(kù)的改動(dòng)的地方。新增數(shù)據(jù)要使用我們的數(shù)據(jù)模型在數(shù)據(jù)庫(kù)中新增一條記錄,可以把數(shù)據(jù)添加到會(huì)話對(duì)象中,并將其提交(commit)。在會(huì)話中添加(add)一個(gè)對(duì)象,這個(gè)改動(dòng)將在會(huì)話中被標(biāo)記為待保存。而提交則可以把這個(gè)會(huì)話的改動(dòng)保存進(jìn)數(shù)據(jù)庫(kù)。代碼如下:>>>user=User(username='fake_name')
>>>db.session.add(user)
>>>mit()
在我們的表里添加一行新數(shù)據(jù)就是這么簡(jiǎn)單。讀取數(shù)據(jù)把數(shù)據(jù)添加進(jìn)數(shù)據(jù)庫(kù)后,SQLAlchemy可以通過(guò)Model.query方法對(duì)數(shù)據(jù)進(jìn)行查詢。Model.query是.db.session.query(Model)的簡(jiǎn)寫。下面是第1個(gè)例子,使用all()獲取數(shù)據(jù)庫(kù)中的所有行,并作為列表返回。>>>users=User.query.all()
>>>users
[<User'fake_name'>]
當(dāng)數(shù)據(jù)庫(kù)中的記錄數(shù)量越來(lái)越多時(shí),查詢操作就會(huì)變慢。同使用SQL一樣,在SQLAlchemy里,我們可以使用limit函數(shù)來(lái)指定希望返回的總行數(shù):>>>users=User.query.limit(10).all()
在默認(rèn)情況下,SQLAlchemy會(huì)根據(jù)主鍵排序并返回記錄。要控制排序方式,我們可以使用order_by函數(shù),使用方式如下:#正向排序
>>>users=User.query.order_by(User.username).all()
#逆向排序
>>>users=User.query.order_by(User.username.desc()).all()
如果想只返回一行數(shù)據(jù),則可以使用first()來(lái)替代all():>>>user=User.query.first()
>>>user.username
fake_name
要通過(guò)主鍵取得一行數(shù)據(jù),可使用query.get():>>>user=User.query.get(1)
>>>user.username
fake_name
所有的這些函數(shù)都是可以鏈?zhǔn)秸{(diào)用的,也就是說(shuō),可以把它們追加在一起,來(lái)修改最終的返回結(jié)果。我們?nèi)绻↗avaScript,則會(huì)對(duì)這樣的語(yǔ)法非常熟悉。>>>users=User.query.order_by(
User.username.desc()
).limit(10).first()
first()和all()方法會(huì)返回結(jié)果,并且終止鏈?zhǔn)秸{(diào)用。另外,還存在一個(gè)FlaskSQLAlchemy專有的方法,叫作pagination(分頁(yè)),可以用來(lái)替代first()和all()。這個(gè)方法是專門設(shè)計(jì)用來(lái)實(shí)現(xiàn)分頁(yè)功能的,大多數(shù)網(wǎng)站都會(huì)用分頁(yè)的方式來(lái)展示長(zhǎng)列表。第1個(gè)參數(shù)指示查詢應(yīng)該返回第幾頁(yè)的內(nèi)容,第2個(gè)參數(shù)是每頁(yè)展示的對(duì)象數(shù)量。所以,如果我們傳入1和10作為參數(shù),則會(huì)獲得前10個(gè)對(duì)象作為返回。如果我們傳入2和10,則會(huì)得到第11~20個(gè)對(duì)象,以此類推。pagination方法跟first()和all()方法有不同之處,因?yàn)樗祷氐氖且粋€(gè)pagination對(duì)象,而不是數(shù)據(jù)模型對(duì)象的列表。比如,我們想得到前10個(gè)(虛構(gòu)的)Post對(duì)象,并將其顯示在博客的第1頁(yè)上:>>>Post.query.paginate(1,10)
<flask_sqlalchemy.Paginationat0x105118f50>
這個(gè)對(duì)象有幾個(gè)有用的屬性:>>>page=User.query.paginate(1,10)
#返回這一頁(yè)包含的數(shù)據(jù)對(duì)象
>>>page.items
[<User'fake_name'>]
#返回這一頁(yè)的頁(yè)數(shù)
>>>page.page
1
#返回總頁(yè)數(shù)
>>>page.pages
1
#上一頁(yè)和下一頁(yè)是否有對(duì)象可以顯示
>>>page.has_prev,page.has_next
(False,False)
#返回上一頁(yè)和下一頁(yè)的pagination對(duì)象
#如果不存在的話則返回當(dāng)前頁(yè)
>>>page.prev(),page.next()
(<flask_sqlalchemy.Paginationat0x10812da50>,
<flask_sqlalchemy.Paginationat0x1081985d0>)
條件查詢現(xiàn)在我們來(lái)看SQL最擅長(zhǎng)的事情,根據(jù)一些條件的集合獲得過(guò)濾后的數(shù)據(jù)。要得到滿足一系列等式條件的數(shù)據(jù)列表,則我們可以使用query.filter_by過(guò)濾器。query.filter_by過(guò)濾器接收關(guān)鍵字參數(shù),并把接收到的參數(shù)作為我們想要在數(shù)據(jù)庫(kù)里查詢的字段名值對(duì)。比如,要得到用戶名為fake_name的用戶列表,則可以這樣:>>>users=User.query.filter_by(username='fake_name').all()
這個(gè)例子只基于一個(gè)值進(jìn)行過(guò)濾,但filter_by過(guò)濾器也可以接收多個(gè)值進(jìn)行過(guò)濾。跟我們之前的函數(shù)類似,filter_by也是可鏈?zhǔn)秸{(diào)用的。>>>users=User.query.order_by(User.username.desc())
.filter_by(username='fake_name')
.limit(2)
.all()
query.filter_by只有在你確切地知道要查找的值時(shí),才能夠工作。使用query.filter則可以避免這一不便之處,你可以把一個(gè)比較大小的Python表達(dá)式傳給它:>>>user=User.query.filter(
User.id>1
).all()
這只是個(gè)簡(jiǎn)單的例子,實(shí)際上query.filter可以接收任何Python的比較表達(dá)式。對(duì)于Python的常規(guī)類型,比如整數(shù)(integers)、字符串(strings)和日期(dates),你可以使用==操作符來(lái)表示相等的比較。對(duì)于類型為整數(shù)(integer)、浮點(diǎn)(float)或者日期(date)的列,還可以用>、<、<=和>=操作符來(lái)表示不等的比較。另外,一些復(fù)雜的SQL查詢也可以轉(zhuǎn)為用SQLAlchemy的函數(shù)來(lái)表示。例如,可以像下面這樣實(shí)現(xiàn)SQL中IN、OR和NOT的比較操作。>>>fromsqlalchemy.sql.expressionimportnot_,or_
>>>user=User.query.filter(
User.username.in_(['fake_name']),
User.password==None
).first()
#找出擁有密碼的用戶
>>>user=User.query.filter(
not_(User.password==None)
).first()
#這些方法都可以被組合起來(lái)
>>>user=User.query.filter(
or_(not_(User.password==None),User.id>=1)
).first()
在SQLAlchemy中,與None的比較會(huì)被翻譯成與NULL的比較。修改數(shù)據(jù)在使用first()或者all()等方法返回?cái)?shù)據(jù)之前,調(diào)用update方法可以修改已存在的數(shù)據(jù)的值。>>>User.query.filter_by(username='fake_name').update({
'password':'test'
})
#對(duì)數(shù)據(jù)模型的修改已被自動(dòng)加入session中
>>>mit()
刪除數(shù)據(jù)如果我們要從數(shù)據(jù)庫(kù)中刪除一行數(shù)據(jù),則可以:>>>user=User.query.filter_by(username='fake_name').first()
>>>db.session.delete(user)
>>>mit()
數(shù)據(jù)模型之間的關(guān)聯(lián)數(shù)據(jù)模型之間的關(guān)聯(lián)在SQLAlchemy里表現(xiàn)為兩個(gè)或更多模型之間的鏈接,模型之間可以互相建立引用。這使得相關(guān)聯(lián)的數(shù)據(jù)能夠很容易地從數(shù)據(jù)庫(kù)中取出,例如文章和它的評(píng)論,這就是關(guān)系數(shù)據(jù)庫(kù)管理系統(tǒng)(RDBMS)中“關(guān)系”(Relational)的含義,它給這類數(shù)據(jù)庫(kù)帶來(lái)了強(qiáng)大的功能?,F(xiàn)在讓我們來(lái)創(chuàng)建第1個(gè)關(guān)聯(lián)關(guān)系。在我們的博客網(wǎng)站上會(huì)有一些博客文章,每篇文章都有一個(gè)特定的作者。通過(guò)把每個(gè)作者的文章跟這個(gè)作者建立關(guān)聯(lián),可以方便地獲取這個(gè)作者的所有文章,這顯然是合理的做法。這就是一對(duì)多關(guān)系的一個(gè)范例。一對(duì)多我們先建立一個(gè)數(shù)據(jù)模型,用來(lái)表示網(wǎng)站上的博客文章:classPost(db.Model):
id=db.Column(db.Integer(),primary_key=True)
title=db.Column(db.String(255))
text=db.Column(db.Text())
publish_date=db.Column(db.DateTime())
user_id=db.Column(db.Integer(),db.ForeignKey('user.id'))
def__init__(self,title):
self.title=title
def__repr__(self):
return"<Post'{}'>".format(self.title)
注意user_id字段,對(duì)關(guān)系數(shù)據(jù)庫(kù)熟悉的讀者立刻會(huì)明白,它表示了一個(gè)外鍵約束(ForeignKeyConstraint)。外鍵約束是數(shù)據(jù)庫(kù)中的一種約束規(guī)則,在這里,它強(qiáng)制要求user_id字段的值存在于user表的id列中。這是數(shù)據(jù)庫(kù)進(jìn)行的一項(xiàng)檢查,用來(lái)保證每個(gè)Post對(duì)象都會(huì)對(duì)應(yīng)到一個(gè)已有的user。傳給db.ForeignKey的參數(shù),是一個(gè)用來(lái)代表user表id列的字符串。如果你要用__tablename__自定義表名,則需要同時(shí)修改這個(gè)字符串。之所以直接用表名,而不是使用User.id引用,是因?yàn)樵赟QLAlchemy初始化期間,User對(duì)象可能還沒(méi)有被創(chuàng)建出來(lái)。user_id字段還不足以讓SQLAlchemy建立我們想要的關(guān)聯(lián),我們還需要這樣修改User對(duì)象:classUser(db.Model):
id=db.Column(db.Integer(),primary_key=True)
username=db.Column(db.String(255))
password=db.Column(db.String(255))
posts=db.relationship(
'Post',
backref='user',
lazy='dynamic'
)
db.relationship函數(shù)在SQLAlchemy中創(chuàng)建了一個(gè)虛擬的列,它會(huì)和我們的Post對(duì)象中的db.ForeignKey建立聯(lián)系。待會(huì)兒我們?cè)賮?lái)講backref的含義,不過(guò)lazy參數(shù)又是什么?lazy參數(shù)會(huì)告訴SQLAlchemy如何去加載我們指定的關(guān)聯(lián)對(duì)象。如果設(shè)為子查詢方式(subquery),則會(huì)在加載完P(guān)ost對(duì)象的時(shí)候,就立即加載與其關(guān)聯(lián)的對(duì)象。這樣會(huì)讓總查詢數(shù)量減少,但如果返回的條目數(shù)量很多,就會(huì)比較慢。另外,也可以設(shè)置為動(dòng)態(tài)方式(dynamic),這樣關(guān)聯(lián)對(duì)象會(huì)在被使用的時(shí)候再進(jìn)行加載,并且在返回前進(jìn)行過(guò)濾。如果返回的對(duì)象數(shù)很多,或者未來(lái)會(huì)變得很多,那最好采用這種方式。我們現(xiàn)在可以使用User.posts屬性來(lái)得到一個(gè)posts列表,其中每項(xiàng)的user_id值都跟我們的User.id值相等。下面可以在命令行里試一下:>>>user=User.query.get(1)
>>>new_post=Post('PostTitle')
>>>new_post.user_id=user.id
>>>user.posts
[]
>>>db.session.add(new_post)
>>>mit()
>>>user.posts
[<Post'PostTitle'>]
可以從上面的例子中注意到,在把新增變更提交進(jìn)數(shù)據(jù)庫(kù)之前,是無(wú)法通過(guò)關(guān)聯(lián)對(duì)象獲取新的Post對(duì)象的。backref參數(shù)則可以使我們通過(guò)Post.user屬性對(duì)User對(duì)象進(jìn)行讀取和修改。例如:>>>second_post=Post('SecondTitle')
>>>second_post.user=user
>>>db.session.add(second_post)
>>>mit()
>>>user.posts
[<Post'PostTitle'>,<Post'SecondTitle'>]
由于user.posts是一個(gè)列表,所以我們也可以通過(guò)把Post對(duì)象直接添加進(jìn)這個(gè)列表,來(lái)自動(dòng)保存它:>>>second_post=Post('SecondTitle')
>>>user.posts.append(second_post)
>>>db.session.add(user)
>>>mit()
>>>user.posts
[<Post'PostTitle'>,<Post'SecondTitle'>]
由于backref選項(xiàng)被設(shè)置為動(dòng)態(tài)方式,所以我們既可以把這個(gè)關(guān)聯(lián)字段看作列表,也可以把它看作一個(gè)查詢對(duì)象:>>>user.posts
[<Post'PostTitle'>,<Post'SecondTitle'>]
>>>user.posts.order_by(Post.publish_date.desc()).all()
[<Post'SecondTitle'>,<Post'PostTitle'>]
在開(kāi)始學(xué)習(xí)下一種關(guān)聯(lián)類型之前,我們?cè)賱?chuàng)建一個(gè)數(shù)據(jù)模型,用來(lái)實(shí)現(xiàn)用戶評(píng)論,并加上一對(duì)多的關(guān)聯(lián),稍后在書中將會(huì)用到:classPost(db.Model):
id=db.Column(db.Integer(),primary_key=True)
title=db.Column(db.String(255))
text=db.Column(db.Text())
publish_date=db.Column(db.DateTime())
comments=db.relationship(
'Comment',
backref='post',
lazy='dynamic'
)
user_id=db.Column(db.Integer(),db.ForeignKey(‘user.id'))
def__init__(self,title):
self.title=title
def__repr__(self):
return"<Post‘{}'>".format(self.title)
classComment(db.Model):
id=db.Column(db.Integer(),primary_key=True)
name=db.Column(db.String(255))
text=db.Column(db.Text())
date=db.Column(db.DateTime())
post_id=db.Column(db.Integer(),db.ForeignKey(‘post.id'))
def__repr__(self):
return"<Comment'{}'>".format(self.text[:15])
多對(duì)多如果我們有兩個(gè)數(shù)據(jù)模型,它們不但可以互相引用,而且其中的每個(gè)對(duì)象都可以引用多個(gè)對(duì)應(yīng)的對(duì)象,那應(yīng)該怎么做呢?比如,我們的博客文章需要加上標(biāo)簽,這樣用戶就能輕松地把相似的文章分組。每個(gè)標(biāo)簽都對(duì)應(yīng)了多篇文章,而每篇文章同時(shí)對(duì)應(yīng)了多個(gè)標(biāo)簽。這樣的關(guān)聯(lián)方式叫作多對(duì)多的關(guān)聯(lián)??紤]如下的例子:tags=db.Table('post_tags',
db.Column('post_id',db.Integer,db.ForeignKey('post.id')),
db.Column('tag_id',db.Integer,db.ForeignKey('tag.id'))
)
classPost(db.Model):
id=db.Column(db.Integer(),primary_key=True)
title=db.Column(db.String(255))
text=db.Column(db.Text())
publish_date=db.Column(db.DateTime())
comments=db.relationship(
'Comment',
backref='post',
lazy='dynamic'
)
user_id=db.Column(db.Integer(),db.ForeignKey('user.id'))
tags=db.relationship(
'Tag',
secondary=tags,
backref=db.backref('posts',lazy='dynamic')
)
def__init__(self,title):
self.title=title
def__repr__(self):
return"<Post'{}'>".format(self.title)
classTag(db.Model):
id=db.Column(db.Integer(),primary_key=True)
title=db.Column(db.String(255))
def__init__(self,title):
self.title=title
def__repr__(self):
return"<Tag'{}'>".format(self.title)
db.Table對(duì)象對(duì)數(shù)據(jù)庫(kù)的操作比db.Model更底層。db.Model是基于db.Table提供的一種對(duì)象化包裝方式,用來(lái)表示數(shù)據(jù)庫(kù)表里的某行記錄。這里之所以使用了db.Table,正是因?yàn)槲覀儾恍枰獙iT讀取這個(gè)表的某行記錄。我們用tags變量來(lái)代表post_tags表,這個(gè)表有兩個(gè)字段:一個(gè)表示博客文章的id,另一個(gè)表示某個(gè)標(biāo)簽的id。下面的例子演示了這種用法,如果表中有如下數(shù)據(jù):post_idtag_id
11
13
23
24
25
31
32則SQLAlchemy會(huì)將其翻譯成:id為1的文章?lián)碛衖d為1和3的標(biāo)簽。id為2的文章?lián)碛衖d為3、4和5的標(biāo)簽。id為3的文章?lián)碛衖d為1和2的標(biāo)簽。你可以把這組數(shù)據(jù)簡(jiǎn)單地理解為標(biāo)簽和文章的關(guān)聯(lián)關(guān)系。在上面的程序中我們又使用了db.relationship函數(shù)來(lái)設(shè)置所需的關(guān)聯(lián),但這次多傳了一個(gè)secondary(次級(jí))參數(shù),secondary參數(shù)會(huì)告知SQLAlchemy該關(guān)聯(lián)被保存在tags表里。讓我們?cè)谙旅娴拇a中體會(huì)一下這種用法:>>>post_one=Post.query.filter_by(title='PostTitle').first()
>>>post_two=Post.query.filter_by(title='SecondTitle').first()
>>>tag_one=Tag('Python')
>>>tag_two=Tag('SQLAlchemy')
>>>tag_three=Tag('Flask')
>>>post_one.tags=[tag_two]
>>>post_two.tags=[tag_one,tag_two,tag_three]
>>>tag_two.posts
[<Post'PostTitle'>,<Post'SecondTitle'>]
>>>db.session.add(post_one)
>>>db.session.add(post_two)
>>>mit()
在設(shè)置一對(duì)多的關(guān)聯(lián)時(shí),主關(guān)聯(lián)字段實(shí)際上是一個(gè)列表?,F(xiàn)在主要的不同之處在于,backref也變成了一個(gè)列表。由于它是個(gè)列表,所以我們也可以像這樣把文章加到標(biāo)簽里:>>>tag_one.posts.append(post_one)
[<Post'PostTitle'>,<Post'SecondTitle'>]
>>>post_one.tags
[<Tag'SQLAlchemy'>,<Tag'Python'>]
>>>db.session.add(tag_one)
>>>mit()
SQLAlchemy會(huì)話對(duì)象的方便之處現(xiàn)在你了解了SQLAlchemy的好處,也就應(yīng)該能了解SQLAlchemy的會(huì)話對(duì)象是什么,以及為什么開(kāi)發(fā)網(wǎng)絡(luò)應(yīng)用少不了它們。如之前所說(shuō),會(huì)話可以被簡(jiǎn)單地描述為用來(lái)跟蹤數(shù)據(jù)模型變化的對(duì)象,它還可以根據(jù)我們的指令將這些變化提交進(jìn)數(shù)據(jù)庫(kù)。不過(guò),它的作用遠(yuǎn)不止這些。首先,會(huì)話可以用來(lái)控制事務(wù)。事務(wù)是一組變更集,在提交的時(shí)候被一起寫入數(shù)據(jù)庫(kù)。事務(wù)提供了很多看不見(jiàn)的功能。首先,當(dāng)對(duì)象之間有關(guān)聯(lián)的時(shí)候,事務(wù)會(huì)自動(dòng)決定保存的先后順序。在上一節(jié)我們保存標(biāo)簽的時(shí)候你可能已經(jīng)注意到了這一點(diǎn),當(dāng)我們把新標(biāo)簽關(guān)聯(lián)到文章的時(shí)候,會(huì)話對(duì)象會(huì)自動(dòng)先把標(biāo)簽保存進(jìn)來(lái),盡管我們沒(méi)有專門告訴它要提交標(biāo)簽對(duì)象。如果我們直接使用底層的數(shù)據(jù)庫(kù)連接和SQL查詢進(jìn)行開(kāi)發(fā),就必須格外小心,對(duì)于哪些記錄跟哪些記錄有關(guān)聯(lián),需要自己記錄下來(lái),以避免在保存外鍵時(shí)指向了不存在的對(duì)象。事務(wù)還會(huì)在數(shù)據(jù)庫(kù)發(fā)生變更的時(shí)候,將當(dāng)前數(shù)據(jù)標(biāo)記為舊數(shù)據(jù),當(dāng)我們下次讀取這項(xiàng)數(shù)據(jù)的時(shí)候,它就會(huì)先向數(shù)據(jù)庫(kù)發(fā)送一條查詢,以更新當(dāng)前數(shù)據(jù)。這些工作都是在背后自動(dòng)進(jìn)行的。如果沒(méi)有使用SQLAlchemy,則我們必須手工記錄哪些數(shù)據(jù)行需要被更新,并且只更新那些必須更新的數(shù)據(jù)行,以高效地使用數(shù)據(jù)庫(kù)資源。其次,事務(wù)會(huì)避免出現(xiàn)兩個(gè)不同的引用指向數(shù)據(jù)庫(kù)中的同一行記錄的情況。這都?xì)w功于查詢是在會(huì)話中進(jìn)行的(Model.query實(shí)際上是db.session.query(Model)),如果事務(wù)中的一個(gè)數(shù)據(jù)行已經(jīng)被查詢過(guò),則會(huì)直接返回指向這個(gè)數(shù)據(jù)對(duì)象的引用,而不會(huì)創(chuàng)建一個(gè)新的對(duì)象。如果沒(méi)有這樣的檢查,則可能會(huì)出現(xiàn)兩個(gè)表示同一行數(shù)據(jù)的不同對(duì)象,分別把不同的修改提交到數(shù)據(jù)庫(kù),這會(huì)造成很難發(fā)現(xiàn)和捕捉的隱性問(wèn)題。要注意,F(xiàn)laskSQLAlchemy會(huì)為每一個(gè)request創(chuàng)建一個(gè)新的會(huì)話對(duì)象,在request處理結(jié)束的時(shí)候,會(huì)丟棄沒(méi)有提交的所有更改。因此一定要記得把工作保存下來(lái)。SQLAlchemy的作者M(jìn)ikeBayer于2012年在加拿大的PyCon上做過(guò)一個(gè)演講,演講題目是TheSQLAlchemySession-InDepth,如果想要深入了解會(huì)話,則在這里可以查看這個(gè)演講/watch?v=PKAdehPHOMo。使用Alembic進(jìn)行數(shù)據(jù)庫(kù)遷移一個(gè)網(wǎng)絡(luò)應(yīng)用的功能總會(huì)不斷地發(fā)生改變,增加新功能的時(shí)候,我們通常需要修改數(shù)據(jù)庫(kù)結(jié)構(gòu)。不論你是增刪字段還是創(chuàng)建新表,數(shù)據(jù)模型的修改會(huì)貫穿你的應(yīng)用開(kāi)發(fā)的始終。但是,當(dāng)數(shù)據(jù)庫(kù)更改變得頻繁后,你會(huì)很快面臨一個(gè)問(wèn)題:當(dāng)把這些更改從開(kāi)發(fā)環(huán)境遷移到生產(chǎn)環(huán)境時(shí),如果不人工對(duì)數(shù)據(jù)模型和對(duì)應(yīng)表的每一行修改進(jìn)行仔細(xì)比較,那么你怎樣才能保證所有的更改都會(huì)被遷移過(guò)去?又比如,要是你想把開(kāi)發(fā)環(huán)境的代碼回滾到Git中的某個(gè)歷史版本,用來(lái)嘗試復(fù)現(xiàn)目前的生產(chǎn)環(huán)境中該版本代碼出現(xiàn)的某個(gè)問(wèn)題,那么你應(yīng)該怎樣把你的數(shù)據(jù)庫(kù)結(jié)構(gòu)調(diào)整到該版本對(duì)應(yīng)的狀態(tài),而無(wú)須做大量的額外工作呢?作為程序員,我們痛恨除開(kāi)發(fā)外的額外工作。還好有個(gè)工具可以解決這個(gè)問(wèn)題,這個(gè)工具是Alembic,可以根據(jù)我們的SQLAlchemy模型的變化,自動(dòng)創(chuàng)建數(shù)據(jù)庫(kù)遷移記錄。數(shù)據(jù)庫(kù)遷移記錄(Databasemigration)保存了我們的數(shù)據(jù)庫(kù)結(jié)構(gòu)變化的歷史信息。Alembic讓我們可以把數(shù)據(jù)庫(kù)升級(jí)或者降級(jí)到某個(gè)已保存的特定版本,而跨越好幾個(gè)版本之間的升級(jí)或者降級(jí),則會(huì)執(zhí)行這兩個(gè)選定版本之間的所有歷史記錄文件。Alembic最棒的地方在于,這些歷史文件本身就是Python程序文件。下面,我們創(chuàng)建第1個(gè)數(shù)據(jù)庫(kù)遷移記錄,你會(huì)發(fā)現(xiàn)Alembic的語(yǔ)法非常簡(jiǎn)單。Alembic不會(huì)捕捉所有可能的變更,比如,它不會(huì)記錄SQL索引的變化。讀者在每次遷移記錄后,應(yīng)該去檢查一下遷移記錄文件,并進(jìn)行必要的修正。我們不會(huì)直接使用Alembic,而是會(huì)使用Flask-Migrate,這是為SQLAlchemey專門創(chuàng)建的一個(gè)擴(kuò)展,并且可以跟FlaskScript一起使用。下面在pip中進(jìn)行安裝:$pipinstallFlask-Migrate
在使用前需要把命令加到manage.py文件中:fromflask.ext.scriptimportManager,Server
fromflask.ext.migrateimportMigrate,MigrateCommand
frommainimportapp,db,User,Post,Tag
migrate=Migrate(app,db)
manager=Manager(app)
manager.add_command("server",Server())
manager.add_command('db',MigrateCommand)
@manager.shell
defmake_shell_context():
returndict(app=app,db=db,User=User,Post=Post,Tag=Tag)
if__name__=="__main__":
manager.run()
我們通過(guò)app對(duì)象和SQLAlchemy的實(shí)例初始化了Migrate對(duì)象,然后讓遷移命令可以通過(guò)manage.pydb來(lái)調(diào)用。運(yùn)行下面的命令可以看到可用命令列表:$pythonmanage.pydb
要開(kāi)始跟蹤我們的數(shù)據(jù)庫(kù)變更,則可使用init命令$pythonmanage.pydbinit
這會(huì)在項(xiàng)目目錄中創(chuàng)建一個(gè)叫作migrations的文件夾,所有的記錄文件會(huì)被保存在里面?,F(xiàn)在我們可以開(kāi)始進(jìn)行首次遷移:$pythonmanage.pydbmigrate-m"initialmigration"
這個(gè)命令會(huì)讓Alembic掃描我們所有的SQLAlchemy對(duì)象,找到在此之前沒(méi)有被記錄過(guò)的所有表和列,由于這是第1次提交,所以遷移記錄文件會(huì)比較大。確保使用了-m參數(shù)來(lái)保存提交信息,通過(guò)提交信息尋找所需的遷移記錄版本是最容易的辦法。每個(gè)遷移記錄文件都被保存在migrations/versions/文件夾中。執(zhí)行下面的命令,就可以把遷移記錄應(yīng)用到數(shù)據(jù)庫(kù)上,并改變數(shù)據(jù)庫(kù)的結(jié)構(gòu):$pythonmanage.pydbupgrade
要返回以前的版本,則可以根據(jù)history命令找到版本號(hào),然后傳給downgrade命令:$pythonmanage.pydbhistory
<base>->7ded34bc4fb(head),initialmigration
$pythonmanage.pydbdowngrade7ded34bc4fb同Git一樣,每個(gè)遷移記錄都由一個(gè)哈希值來(lái)表示。這是Alembic的重要功能,但只用于它的表層。你也可以嘗試把遷移記錄和你的Git提交記錄對(duì)應(yīng)起來(lái),這樣當(dāng)你把代碼回滾到Git中的某個(gè)版本時(shí),也能很容易地升級(jí)或降級(jí)數(shù)據(jù)庫(kù)結(jié)構(gòu)??偨Y(jié)現(xiàn)在我們已經(jīng)能輕松地操縱數(shù)據(jù)了,接下來(lái)可以在應(yīng)用中顯示這些數(shù)據(jù)。第3章會(huì)告訴你如何基于數(shù)據(jù)模型動(dòng)態(tài)地創(chuàng)建HTML,以及如何通過(guò)網(wǎng)頁(yè)來(lái)添加新數(shù)據(jù)。
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 老師工作總結(jié)模板
- 旅游可行性報(bào)告
- 2025年其它核材料及相關(guān)特殊材料項(xiàng)目發(fā)展計(jì)劃
- 面對(duì)精神病人護(hù)理小講課比賽
- 街道財(cái)務(wù)管理制度講解
- 企業(yè)信息化系統(tǒng)安全維護(hù)協(xié)議
- 項(xiàng)目季度工作總結(jié)與經(jīng)驗(yàn)分享報(bào)告
- 高新技術(shù)開(kāi)發(fā)協(xié)議書
- 智能倉(cāng)儲(chǔ)管理流程優(yōu)化項(xiàng)目
- 航空鉚接工藝的基本知識(shí)
- 中考英語(yǔ)688高頻詞大綱詞頻表
- 計(jì)算機(jī)基礎(chǔ)教程電子版
- 關(guān)于如何做好清單招標(biāo)控制價(jià)的幾點(diǎn)建議
- 2024陜西西安事業(yè)單位歷年公開(kāi)引進(jìn)高層次人才和急需緊缺人才筆試參考題庫(kù)(共500題)答案詳解版
- 2024年湖南水利水電職業(yè)技術(shù)學(xué)院?jiǎn)握新殬I(yè)技能測(cè)試題庫(kù)及答案解析
- 有限空間安全檢查表
- 初中生注意力訓(xùn)練注意力
- 充電設(shè)施運(yùn)營(yíng)管理制度文件范文
- 2022年成都地鐵值班員資格考前復(fù)習(xí)題庫(kù)
- 2024年山東省春季高考技能考試-汽車專業(yè)備考試題庫(kù)(濃縮500題)
- 外賣報(bào)告數(shù)據(jù)分析
評(píng)論
0/150
提交評(píng)論