深入理解 Flask(掌握用PHP創(chuàng)建強大動態(tài)Web應用的專項技術)_第1頁
深入理解 Flask(掌握用PHP創(chuàng)建強大動態(tài)Web應用的專項技術)_第2頁
深入理解 Flask(掌握用PHP創(chuàng)建強大動態(tài)Web應用的專項技術)_第3頁
深入理解 Flask(掌握用PHP創(chuàng)建強大動態(tài)Web應用的專項技術)_第4頁
深入理解 Flask(掌握用PHP創(chuàng)建強大動態(tài)Web應用的專項技術)_第5頁
已閱讀5頁,還剩292頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權,請進行舉報或認領

文檔簡介

深入理解Flask掌握用PHP創(chuàng)建強大動態(tài)Web應用的專項技術目錄TOC\h\h第1章入門\h使用Git進行版本控制\h安裝Git\hGit基礎\h使用pip管理Python包\h在Windows上安裝pip\h在MacOSX和Linux上安裝Python包管理器pip\hpip基本操作\h用virtualenv的沙盒管理依賴\hvirtualenv基礎\h開始我們的項目\h使用FlaskScript\h總結(jié)\h第2章使用SQLAlchemy創(chuàng)建數(shù)據(jù)模型\h設置SQLAlchemy\hPython安裝包\hFlaskSQLAlchemy\h我們的第1個數(shù)據(jù)模型\h創(chuàng)建user表\hCRUD\h新增數(shù)據(jù)\h讀取數(shù)據(jù)\h修改數(shù)據(jù)\h刪除數(shù)據(jù)\h數(shù)據(jù)模型之間的關聯(lián)\h一對多\h多對多\hSQLAlchemy會話對象的方便之處\h使用Alembic進行數(shù)據(jù)庫遷移\h總結(jié)\h第3章通過模板創(chuàng)建視圖\hJinja的語法\h過濾器\h注釋\hif語句\h循環(huán)\h宏\hFlask特有的變量和函數(shù)\h創(chuàng)建視圖\h視圖函數(shù)\h編寫和繼承模板\hFlaskWTForms\hWTForms基礎\h自定義檢驗器\h發(fā)布評論\h總結(jié)\h第4章使用藍圖創(chuàng)建控制器\h請求的構建和銷毀,以及全局變量\h錯誤頁面\h使用類描述視圖\h方法視圖\h藍圖\h總結(jié)\h第5章進階的應用結(jié)構\h模塊項目\h重構代碼\h應用的工廠模式\h總結(jié)\h第6章保護應用安全\h準備工作\h修改用戶模型\h創(chuàng)建表單\h創(chuàng)建視圖\h社交網(wǎng)絡登錄\h使用會話\h使用FlaskLogin\h用戶角色\h總結(jié)\h第7章在Flask中使用NoSQL數(shù)據(jù)庫\hNoSQL數(shù)據(jù)庫的種類\h鍵值數(shù)據(jù)庫\h文檔數(shù)據(jù)庫\h列式數(shù)據(jù)庫\h基于圖的數(shù)據(jù)庫\h關系型數(shù)據(jù)庫與NoSQL的比較\h關系型數(shù)據(jù)庫的優(yōu)勢\hNoSQL數(shù)據(jù)庫的優(yōu)勢\h在什么情況下用什么數(shù)據(jù)庫\h在Flask中使用MongoDB\h安裝MongoDB\h配置MongoEngine\h定義文檔\hCRUD\hNoSQL中的關聯(lián)關系\h利用NoSQL的強大能力\h總結(jié)\h第8章構建RESTfulAPI\hREST是什么\h構建RESTfulFlaskAPI\hGET請求\h格式化輸出\h請求中的參數(shù)\hPOST請求\h身份認證\hPUT請求\hDELETE請求\h總結(jié)\h第9章使用Celery編寫異步任務\hCelery是什么\h配置Celery和RabbitMQ\h在Celery中創(chuàng)建任務\h運行Celery任務\hCelery工作流\h監(jiān)控Celery\h在Flower中通過網(wǎng)頁進行監(jiān)控\h創(chuàng)建一個提醒應用\h生成每周摘要\h總結(jié)\h第10章有用的Flask擴展\hFlaskScript\hFlaskDebugToolbar\hFlaskCache\h緩存函數(shù)和視圖\h緩存帶參數(shù)的函數(shù)\h緩存帶有查詢參數(shù)的路徑\h使用Redis作為緩存后端\h使用memcached作為緩存后端\hFlaskAssets\hFlaskAdmin\h編寫基礎管理頁面\h編寫數(shù)據(jù)庫管理頁面\h增強文章管理功能\h編寫文件系統(tǒng)管理頁面\h保護FlaskAdmin的安全\hFlaskMail\h總結(jié)\h第11章構建你自己的擴展\h編寫一個YouTubeFlask擴展\h創(chuàng)建Python包\h通過Flask擴展修改響應數(shù)據(jù)\h總結(jié)\h第12章測試Flask應用\h什么是單元測試\h怎樣進行測試\h對應用進行單元測試\h測試路由函數(shù)\h用戶界面測試\h測試覆蓋率\h測試驅(qū)動的開發(fā)\h總結(jié)\h第13章部署Flask應用\h部署在你自己的服務器上\h使用fabric把代碼推送到服務器\h使用supervisor運行你的Web服務器\hGevent\hTornado\hNginx和uWSGI\hApache和uWSGI\h部署在Heroku上\h使用HerokuPostgres\h在Heroku中使用Celery\h在AWS上部署應用\h在AmazonElasticBeanstalk上使用Flask\h使用AmazonRelationalDatabaseService\h在AmazonSimpleQueueService中使用Celery第1章入門Python是一門靈活的語言,給予了程序員極大的自由,使程序員可以使用任意文件結(jié)構和開發(fā)環(huán)境。但這種自由帶來的一個危險后果是,也許程序員從一開始創(chuàng)建新項目的方式就不正確,導致在開發(fā)過程中可能出現(xiàn)一些問題。比如,可能寫到一半的時候,我們突然意識到現(xiàn)在需要的某些文件或部分代碼,在5天前就被自己刪掉了;或者想使用兩個Python包,發(fā)現(xiàn)它們需要依賴同一個底層的包,卻需要是不同版本的。如果不使用本章介紹的工具,則可能需要做一大堆額外的工作來處理這些問題。但其實對這些問題早就有解決方案了,在一開始做一點額外的準備,可以避免在將來浪費大量的時間。為此,我們需要安裝3個程序:Git、pip和virtualenv。使用Git進行版本控制為了防止項目遭到人為破壞,我們需要一個叫作Git的版本控制系統(tǒng)。版本控制系統(tǒng)是在文件更改過程中記錄變更的工具,這能夠使開發(fā)者看到代碼在歷史版本中是怎樣變化的,也可以把代碼回滾到過去的某個狀態(tài)。版本控制系統(tǒng)也讓協(xié)作變得比以往簡單,因為程序員之間可以分享變更,也可以快速合并變更到當前的版本中,而不需要手動去復制粘貼數(shù)百行代碼。簡而言之,版本管理系統(tǒng)就像代碼備份,不過要強大得多。安裝Git安裝Git非常簡單,只需要前往/downloads,單擊正在使用的操作系統(tǒng)(OperationSystem),就會下載一個安裝文件,并引導你完成基本的安裝流程。在Windows上使用GitGit最初是被開發(fā)出來僅供類Unix系統(tǒng)使用的(比如Linux、MacOSX),所以在Windows上不能完全無縫地直接使用Git。在安裝過程中,程序會詢問你是否希望在普通Windows命令行中使用Git,這時要選“否”。選擇默認的選項,程序會在你的系統(tǒng)中安裝一個叫作Bash的新命令行程序。這跟在Unix系統(tǒng)中使用的Bash是一樣的。Bash比Windows默認的命令行程序要強大得多,在本書的所有例子中都會用到。在/learning_the_shell.php#contents上有個很不錯的為初學者準備的Bash介紹。Git基礎Git是一個頗為復雜的工具。這里只介紹會在本書中用到的基礎部分。要學習更多內(nèi)容,請參考Git官方文檔/doc。Git不會自動跟蹤你的變更。為了讓Git正確工作,我們需要向它提供如下信息。要跟蹤哪個目錄。什么時候保存代碼的狀態(tài)。哪些變更需要被跟蹤,哪些不需要。在一切開始之前,首先讓Git在我們的目錄下創(chuàng)建一個git實例。從終端進入你的項目目錄下,運行下面的命令:$gitinit

Git會開始在你的項目中跟蹤變更。既然git已經(jīng)開始追蹤這些文件,那么我們可以通過以下命令來顯示被跟蹤的文件狀態(tài),同時會顯示所有沒有被跟蹤的文件:$gitstatus

現(xiàn)在可以保存我們的第1個提交(commit),它實際上就是在你執(zhí)行commit命令時,所有代碼的一個快照。#在Bash中,注釋語句用#標示,就跟在Python里一樣

#把所有你希望提交的變更添加進暫存區(qū)

$gitaddmain.py

#提交這些變更,使用-m參數(shù)加入提交信息(commitmessage)

$gitcommit-m"Ourfirstcommit"

將來我們可以在項目中回退到現(xiàn)在的這個時間點。這一操作在Git中叫作暫存。如果你已經(jīng)準備好提交文件,則記得先把它們暫存起來。注意,即使暫存了一個被修改的文件,這個文件的后繼修改也不會自動進入暫存區(qū)。下面是一個稍有難度的Git用例,使用文本編輯器把一些文本加到你的main.py文件中,然后執(zhí)行下面的命令:#看一下從上一個提交起,代碼有什么變化

$gitdiff

#查看你的提交歷史

$gitlog

#作為一個例子,我們先暫存main.py

#然后從暫存區(qū)移除所有被添加進來的文件

$gitaddmain.py

$gitstatus

$gitresetHEADmain.py

#在每次復雜的更改之后,記得跑一下status

#來確保所有的東西都沒錯

$gitstatus

#我們現(xiàn)在刪除main.py上的所有更改,回滾到它上次被提交的狀態(tài)

#這只能對沒有被暫存的文件使用

$gitcheckout--main.py

你的終端看起來應該如下所示。這里用到了Git系統(tǒng)的checkout命令,對于我們的Git簡介來說,似乎有點過于高級。它可以用來改變Git系統(tǒng)中HEAD指針的當前狀態(tài),也就是在項目歷史中我們的代碼目前的位置。在下個例子中我們會進一步了解?,F(xiàn)在,要查看代碼在上一個提交中的狀態(tài),首先運行:$gitlog

FriJan2319:16:432015-0500f01d1e2Ourfirstcommit[JackStouffer]

在提交信息之后的那串字符f01d1e2,是這個提交的唯一標識,被稱為這個提交的哈希值。使用此哈希值,可以讓項目代碼回到這個時刻的狀態(tài)。運行下面的命令就可以做到:$gitcheckoutf01d1e2

你的Git項目現(xiàn)在進入了一種特殊的狀態(tài),在此狀態(tài)下,你的任何改動和提交既不會被保存,也不會影響你檢出的這個提交之后的任何提交。這種狀態(tài)只用來查看老代碼。要回到普通的模式,可運行:$gitcheckoutmaster

使用pip管理Python包在開發(fā)Python項目時,我們可以從其他程序員那里下載庫文件來擴展Python標準庫的功能。正如你所知道的,在使用Flask時,可以從社區(qū)創(chuàng)建的大量Python庫中獲取我們所需要的各種功能。不過,要完全正確地使用第三方庫,卻并不容易。比如你想安裝一個包X,很簡單,下載Zip文件,執(zhí)行一下setup.py,對吧?其實這樣可能有問題。如果包X依賴包Y,包Y又依賴包Z和包Q,這些信息都沒有在包X的網(wǎng)站上列出來,若要安裝X并讓它正確工作,則你又必須一個個地找到這些包并安裝,并且期望你正在安裝的這些包沒有再依賴別的包。為了自動化這個流程,我們使用pip——Python的包管理器。在Windows上安裝pip如果你用的是Windows操作系統(tǒng),而且安裝了最新版本的Python,那么你已經(jīng)有pip了!如果你的Python不是最新版本,那么最簡單的辦法就是重新安裝它,可以從/downloads/上下載Python的Windows安裝包。Windows中的環(huán)境變量path,決定了在命令行里能夠使用哪些程序。為了在path里包含Python和pip,我們可以把C:\Python27和C:\Python27\tools添加進去。在Windows菜單中可以編輯path,用鼠標右鍵單擊我的電腦,選擇屬性,在系統(tǒng)高級設置中,單擊環(huán)境變量…,往下滾動,找到Path,雙擊它,在末尾添加;C:\Python27;C:\Python27\Tools。你可以關閉并重新打開終端,在命令行中輸入如下命令,確認path已經(jīng)被正確地修改:pip--help

下載示例代碼文件若你在上購買了博文視點的書,那么你可以從你的賬戶中下載示例代碼文件。如果你是在其他地方購買的書,則可以在上注冊,按照提示完成下載。pip會打印出它的使用說明,就像下面這個截屏顯示的一樣。在MacOSX和Linux上安裝Python包管理器pipLinux上的某些Python版本沒有同時安裝pip,MacOSX也不會默認安裝pip。要安裝pip,可以從/pypa/pip/master/contrib/get-pip.py下載get-pip.py。在下載完畢后,可使用管理員權限執(zhí)行該腳本,代碼如下:$sudopythonget-pip.py

pip會被自動安裝到系統(tǒng)中。pip基本操作用pip安裝一個包很簡單,代碼如下:$pipinstall[包名]

在Mac和Linux上,因為你要在用戶目錄之外安裝程序,所以必須在安裝命令之前添加sudo。要安裝Flask,命令非常簡單:$pipinstallflask

這樣就會為你安裝Flask及其所有依賴。如果你希望移除一個不再需要的包,則可運行:$pipuninstall[包名]

如果你想查找一個還不清楚其確切名字的包,則可以使用搜索命令:$pipsearch[搜索關鍵詞]

現(xiàn)在我們已經(jīng)安裝了一些包,按照Python社區(qū)的慣例,我們需要創(chuàng)建一個列表文件,來指明運行這個項目需要依賴哪些包。這也為你的項目新成員提供了便利,使他們能夠快速上手并運行你的代碼??梢允褂胮ip執(zhí)行以下命令,來生成這個列表:$pipfreeze>requirements.txt

這個命令具體做了什么?pipfreeze命令自己會打印一個列表,包括了已經(jīng)安裝的包,以及它們的版本號,如下所示:Flask==0.10.1

itsdangerous==0.24

Jinja2==2.7.3

MarkupSafe==0.23

Werkzeug==0.10.4

wheel==0.24.0

大于操作符>會告訴Bash把它之前的最后一個命令輸出的內(nèi)容寫入文件中。查看項目文件夾,你會發(fā)現(xiàn)一個叫作requirements.txt的新文件,里面包含了pipfreeze輸出的內(nèi)容。要安裝這個文件指定的所有包,新的項目維護者需要運行如下命令:$pipinstall-rrequirements.txt

這會讓pip讀取requirements.txt列出的所有包,并且安裝它們。用virtualenv的沙盒管理依賴現(xiàn)在你已經(jīng)安裝了新項目所需要的所有包,太棒了!但是,如果你接下來要開發(fā)第2個項目,會用到一些同樣的包,版本卻更新了,那么會發(fā)生什么?如果一個包依賴了你在之前的項目開發(fā)中安裝過的另一個包,但這次它依賴一個更老的版本,那么又會發(fā)生什么?當依賴包的新版本包含了與老版本不兼容的改動時,要升級這些包,就意味著必須對老項目做一些額外的開發(fā),你可能會不樂意。幸運的是,我們還有virtualenv,一個能把Python項目沙盒化的工具。virtualenv的秘密在于,它讓你的電腦從項目目錄而不是系統(tǒng)全局的Python主目錄下查找和安裝包,這樣就可以把它們的環(huán)境完全隔離開了。既然我們已經(jīng)有了pip,要安裝virtualenv,則只需運行:$pipinstallvirtualenv

virtualenv基礎首先,用virtualenv來初始化你的項目:$virtualenvenv

后面這個env告訴virtualenv,把所有的包都裝在一個叫作env的文件夾里。接下來,virtualenv需要你激活沙盒環(huán)境,這樣就可以對你的項目進行沙盒化。$sourceenv/bin/activate

#你的提示符可能會變成下面這樣

(env)$

這個source命令會讓Bash在當前目錄中運行腳本env/bin/activate。現(xiàn)在我們可以在新的沙盒環(huán)境中重新安裝Flask:#這次不需要sudo了

(env)$pipinstallflask

#退出沙盒,返回全局的Python環(huán)境

(env)$deactivate

另外,我們要避免跟蹤第三方庫的代碼變更,因為跟蹤不屬于你的代碼,是和Git最佳實踐相沖突的。為了忽略我們項目中的特定文件,需要創(chuàng)建一個gitignore文件:$touch.gitignore

touch是用來創(chuàng)建文件的Bash指令。文件名開頭的點會告訴Bash,不要把這個文件顯示出來,除非特意要求它顯示隱藏文件?,F(xiàn)在我們來編寫一個簡單的gitignore文件:env/

*.pyc

這會告訴Git忽略整個env文件夾及所有以.pyc結(jié)尾的文件(Python編譯生成的文件)。當我們這樣寫的時候,“*”符號叫作通配符(wildcard)。開始我們的項目終于,我們開始了第1個Flask項目。為了在本書的最后實現(xiàn)一個復雜的項目,我們需要一個簡單的項目作為開始。在config.py文件中添加如下內(nèi)容:classConfig(object):

pass

classProdConfig(Config):

pass

classDevConfig(Config):

DEBUG=True

現(xiàn)在,在另一個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()

對于了解一些FlaskAPI的讀者來說,這個程序非?;A,它只是在我們訪問:5000的時候,在瀏覽器中顯示一行“HelloWorld!”。另外,F(xiàn)lask用戶可能不很熟悉的一個地方是,這里使用了config.from_object,而不是app.config['DEBUG']。使用from_object是因為未來我們會加入很多配置項,如果要在不同的配置集之間切換,那么手動去改每個變量是一件煩瑣乏味的事情。記得在Git中提交這些改動:#--all標志會告訴Git把你的所有改動全部加入暫存

#包括刪除的和新增的文件

$gitadd--all

$gitcommit-m"createdthebaseapplication"之后我們將不會再提示什么時候把代碼提交到Git。讀者自己應該培養(yǎng)這樣的習慣,并且自己決定在何時可以暫停工作、提交代碼。我們也假設你一直在虛擬環(huán)境中操作,因此命令行提示符前的(env)前綴將不會被打印出來。使用FlaskScript為了讓讀者更容易學習第2章,我們會在眾多Flask擴展(用來擴充Flask功能的包)中,首先選用一個叫作FlaskScript的擴展。使用FlaskScript可以創(chuàng)建命令,并在Flask的應用上下文(ApplicationContext)中執(zhí)行,因為這樣才能對Flask對象進行修改。FlaskScript自帶了一些默認的命令,可以運行服務器或者開啟帶應用上下文的Python命令行。下面使用pip安裝FlaskScript:$pipinstallflask-script

在第10章中會介紹更多關于FlaskScript的高級用法,現(xiàn)在先從創(chuàng)建一個簡單的manage.py腳本開始。首先導入FlaskScript的對象,代碼如下:fromflask.ext.scriptimportManager,Server

frommainimportapp

然后把你的app傳給Manager對象,以初始化FlaskScript:manager=Manager(app)

現(xiàn)在我們來添加一些命令。這里運行的服務器跟通過main.py運行的普通開發(fā)服務器是一樣的。make_shell_context函數(shù)會創(chuàng)建一個Python命令行,并且在應用上下文中執(zhí)行。返回的字典會告訴FlaskScript在打開命令行時進行一些默認的導入工作。manager.add_command("server",Server())

@manager.shell

defmake_shell_context():

returndict(app=app)通過manage.py運行命令行在將來會十分必要,因為一些Flask擴展只有在Flask應用對象被創(chuàng)建之后才會被初始化。直接運行默認的Python命令行會令這些擴展返回錯誤。然后,在文件結(jié)尾添加如下代碼,這是Python的標準方式,用來限制僅在用戶直接運行文件的時候,才執(zhí)行上面的代碼:if__name__=="__main__":

manager.run()

你現(xiàn)在可以這樣來運行開發(fā)環(huán)境服務器:$pythonmanage.pyserver

以及使用命令行:$pythonmanage.pyshell

#我們來看下app有沒有被正確導入

>>>app

<Flask‘main'>

總結(jié)現(xiàn)在我們已經(jīng)搭建了開發(fā)環(huán)境,可以繼續(xù)在Flask里面實現(xiàn)更高級的特性了。我們在編寫可以顯示的內(nèi)容之前,首先要準備一些用以展示的內(nèi)容。在第2章中,你會理解并熟練地掌握如何在Flask中使用數(shù)據(jù)庫。第2章使用SQLAlchemy創(chuàng)建數(shù)據(jù)模型如前所述,模型(models)是對數(shù)據(jù)抽象并提供通用訪問接口的一種方式。在大多數(shù)網(wǎng)絡應用中,數(shù)據(jù)會被存儲在一個關系數(shù)據(jù)庫管理系統(tǒng)(RDBMS)中,也就是把數(shù)據(jù)格式化存儲在由行與列組成的表格中,且能夠跨表對數(shù)據(jù)進行比較。例如MySQL、Postgres、Oracle和MSSQL。為了基于數(shù)據(jù)庫抽象出數(shù)據(jù)模型,我們需要使用一個叫作SQLAlchemy的Python包。SQLAlchemy在最底層包裝了數(shù)據(jù)庫操作接口,在最上層提供了對象關系映射(ORM)。ORM是在基于不同的數(shù)據(jù)結(jié)構和系統(tǒng)類型的數(shù)據(jù)源之間傳遞和轉(zhuǎn)換數(shù)據(jù)的技術。在這里,它用來把大量的不同類型的數(shù)據(jù)庫中的數(shù)據(jù),轉(zhuǎn)換成Python對象的集合。同時,像Python這樣的語言,允許你在不同的對象之間建立引用,讀取和設置它們的屬性;而SQLAlchemy這樣的ORM,能為你將對象操作轉(zhuǎn)換為傳統(tǒng)的數(shù)據(jù)庫操作。為了把SQLAlchemy綁定到我們的應用上下文中,我們可以使用FlaskSQLAlchemy。FlaskSQLAlchemy在SQLAlchemy上提供了一層包裝,這樣就可以結(jié)合Flask的一些特性來方便地調(diào)用SQLAlchemy的功能。如果你對SQLAlchemy已經(jīng)很熟悉,那么你可以單獨地使用它,而無須和FlaskSQLAlchemy一起使用。在本章的最后,我們會為博客應用準備完整的數(shù)據(jù)庫結(jié)構,以及與之交互的數(shù)據(jù)模型。設置SQLAlchemy為了跟上第1章的內(nèi)容,如果你還沒有數(shù)據(jù)庫,那么你需要先選擇一個。如果沒安裝過數(shù)據(jù)庫或者沒什么偏好,那么SQLite會是初學者的最佳選擇。SQLite是無須運行服務的SQL數(shù)據(jù)庫。它的運行速度很快,所有數(shù)據(jù)都包含在一個文件中,而且支持Python。如果你選擇了SQLite,那么將在我們的第1個數(shù)據(jù)模型一節(jié)中,為你創(chuàng)建一個SQLite數(shù)據(jù)庫。Python安裝包使用pip安裝FlaskSQLAlchemy,運行如下命令:$pipinstallflask-sqlalchemy

我們還需要安裝一些特定的包,作為SQLAlchemy與你所選擇的數(shù)據(jù)庫之間的連接器。SQLite用戶可以跳過這一步:#MySQL

$pipinstallPyMySQL

#Postgres

$pipinstallpsycopg2

#MSSQL

$pipinstallpyodbc

#Oracle

$pipinstallcx_Oracle

FlaskSQLAlchemy在開始抽象數(shù)據(jù)結(jié)構之前,我們需要先設置FlaskSQLAlchemy。AQLAlchemy通過一個特殊的數(shù)據(jù)庫URI來創(chuàng)建數(shù)據(jù)庫連接,這個URI是一個類似于URL的字符串,包含了SQLAlchemy建立連接所需要的所有信息。下面是它的一般形式:databasetype+driver://user:password@ip:port/db_name

對于你在之前安裝的每個驅(qū)動程序來說,對應的URI會是:#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個數(shù)據(jù)模型你可能已經(jīng)注意到,我們還沒有真正進入數(shù)據(jù)庫中去創(chuàng)建任何表結(jié)構。這是因為SQLAlchemy不但允許我們根據(jù)數(shù)據(jù)庫表結(jié)構創(chuàng)建數(shù)據(jù)模型(model),也允許我們根據(jù)數(shù)據(jù)模型創(chuàng)建數(shù)據(jù)庫表結(jié)構。所以當我們把第1個模型創(chuàng)建出來之后,表結(jié)構也就有了。首先,要在main.py文件中將我們的app對象傳給SQLAlchemy,將SQLAlchemy初始化:fromflask.ext.sqlalchemyimportSQLAlchemy

app=Flask(__name__)

app.config.from_object(DevConfig)

db=SQLAlchemy(app)

SQLAlchemy會從app的配置中讀取信息,自動連接到數(shù)據(jù)庫。我們首先在main.py中創(chuàng)建一個User模型,它會跟相應的一個user表進行交互。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)這段代碼做了什么呢?實際上,我們已經(jīng)得到了User模型,它基于一個user表,該表擁有3個字段。當我們繼承db.Model時,與數(shù)據(jù)庫連接和通信的工作已經(jīng)自動完成了。User的某些類的屬性值是db.Column類的實例,每個這樣的屬性都代表了數(shù)據(jù)庫表里的一個字段。在db.Column的構造函數(shù)里,第1個參數(shù)是可選的,通過這個參數(shù),我們可以指定該屬性在數(shù)據(jù)庫中的字段名。如果沒有指定,則SQLAlchemy會認為字段名與這個屬性的名字是一樣的。如果要指定這個可選參數(shù),則可以這樣寫:username=db.Column('user_name',db.String(255))

傳給db.Column的第2個參數(shù)會告訴SQLAlchemy,應該把這個字段作為什么類型來處理。我們在書中將會用到的主要類型有:db.Stringdb.Textdb.Integerdb.Floatdb.Booleandb.Datedb.DateTimedb.Time每種類型的含義都很簡單。String和Text類型會接收Python的字符串,并且把它們轉(zhuǎn)為varchar和text類型的字段。Integer和Float類型則會接收Python的任意數(shù)值類型,把它們分別轉(zhuǎn)換為對應的正確類型,再插入數(shù)據(jù)庫中。Boolean類型會接收Python的True或False值,如果數(shù)據(jù)庫支持boolean類型的字段,則直接把Python的布爾值轉(zhuǎn)換成Boolean類型的字段;如果數(shù)據(jù)庫不支持boolean類型的字段,則SQLAlchemy會自動把Python的布爾值轉(zhuǎn)換為0和1保存在數(shù)據(jù)庫中。Date、DateTime和Time類型使用了Python的datetime原生包中的同名類,并把它們轉(zhuǎn)換后保存到數(shù)據(jù)庫中。String、Integer和Float類型都會接收一個額外的參數(shù),來告訴SQLAlchemy該字段的存儲長度限制。如果你希望真正理解SQLAlchemy是怎么把你的代碼翻譯成SQL查詢語句的,則可以在DevConfig文件中加入:SQLALCHEMY_ECHO=True

這樣就會把生成的查詢語句打印到終端。但當你繼續(xù)學習本書的后續(xù)章節(jié)時,則可能會想把這個特性關掉,因為你每打開一個頁面,終端都會打印出大量的查詢語句。primary_key參數(shù)會告訴SQLAlchemy,這個字段需要做主鍵索引(primarykeyindex)。每個SQLAlchemy模型類都必須有一個主鍵才能正常工作。SQLAlchemy會假設你的表名就是模型類名的小寫版本。但是,如果我們想給表起個別的名字,不叫作user,則應該怎么做呢?要告訴SQLAlchemy使用指定的表名,可以添加叫作__tablename__的類屬性。另外,通過采用這種方式,你也可以使用在數(shù)據(jù)庫中已經(jīng)存在的表,只需把表名設為該屬性的值: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__方法,如果我們沒有定義,則SQLAlchemy會自動創(chuàng)建__init__方法。你定義的所有字段名將會成為此方法所接收的關鍵字的參數(shù)名。創(chuàng)建user表現(xiàn)在有SQLAlchemy來完成繁重的勞動,我們就可以輕松地在數(shù)據(jù)庫中創(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)在開始,我們每新增一個數(shù)據(jù)模型,都會在這個地方把它導入,并添加到返回的dict中。這樣就能夠在命令行中使用我們的模型了。現(xiàn)在可以運行命令行,并用db.create_all()來創(chuàng)建所有的表:$pythonmanage.pyshell

>>>db.create_all()

你現(xiàn)在應該能在數(shù)據(jù)庫中找到一個叫作users的表,該表中有你所指定的那些字段。同樣,如果你使用的是SQLite,則也會在你的目錄結(jié)構中找到一個叫作database.db的文件。CRUD在每種數(shù)據(jù)存儲策略中,都存在4個基本功能類型:添加、讀取、修改和刪除(CRUD)。CRUD提供了在我們的網(wǎng)絡應用中需要的所有操作和檢視數(shù)據(jù)的基礎功能。要使用這些功能,我們需要在數(shù)據(jù)庫中用到一個叫作會話(session)的對象。會話的含義會在本章稍后解釋,但現(xiàn)在可以先把它們看作保存對數(shù)據(jù)庫的改動的地方。新增數(shù)據(jù)要使用我們的數(shù)據(jù)模型在數(shù)據(jù)庫中新增一條記錄,可以把數(shù)據(jù)添加到會話對象中,并將其提交(commit)。在會話中添加(add)一個對象,這個改動將在會話中被標記為待保存。而提交則可以把這個會話的改動保存進數(shù)據(jù)庫。代碼如下:>>>user=User(username='fake_name')

>>>db.session.add(user)

>>>mit()

在我們的表里添加一行新數(shù)據(jù)就是這么簡單。讀取數(shù)據(jù)把數(shù)據(jù)添加進數(shù)據(jù)庫后,SQLAlchemy可以通過Model.query方法對數(shù)據(jù)進行查詢。Model.query是.db.session.query(Model)的簡寫。下面是第1個例子,使用all()獲取數(shù)據(jù)庫中的所有行,并作為列表返回。>>>users=User.query.all()

>>>users

[<User'fake_name'>]

當數(shù)據(jù)庫中的記錄數(shù)量越來越多時,查詢操作就會變慢。同使用SQL一樣,在SQLAlchemy里,我們可以使用limit函數(shù)來指定希望返回的總行數(shù):>>>users=User.query.limit(10).all()

在默認情況下,SQLAlchemy會根據(jù)主鍵排序并返回記錄。要控制排序方式,我們可以使用order_by函數(shù),使用方式如下:#正向排序

>>>users=User.query.order_by(User.username).all()

#逆向排序

>>>users=User.query.order_by(User.username.desc()).all()

如果想只返回一行數(shù)據(jù),則可以使用first()來替代all():>>>user=User.query.first()

>>>user.username

fake_name

要通過主鍵取得一行數(shù)據(jù),可使用query.get():>>>user=User.query.get(1)

>>>user.username

fake_name

所有的這些函數(shù)都是可以鏈式調(diào)用的,也就是說,可以把它們追加在一起,來修改最終的返回結(jié)果。我們?nèi)绻↗avaScript,則會對這樣的語法非常熟悉。>>>users=User.query.order_by(

User.username.desc()

).limit(10).first()

first()和all()方法會返回結(jié)果,并且終止鏈式調(diào)用。另外,還存在一個FlaskSQLAlchemy專有的方法,叫作pagination(分頁),可以用來替代first()和all()。這個方法是專門設計用來實現(xiàn)分頁功能的,大多數(shù)網(wǎng)站都會用分頁的方式來展示長列表。第1個參數(shù)指示查詢應該返回第幾頁的內(nèi)容,第2個參數(shù)是每頁展示的對象數(shù)量。所以,如果我們傳入1和10作為參數(shù),則會獲得前10個對象作為返回。如果我們傳入2和10,則會得到第11~20個對象,以此類推。pagination方法跟first()和all()方法有不同之處,因為它返回的是一個pagination對象,而不是數(shù)據(jù)模型對象的列表。比如,我們想得到前10個(虛構的)Post對象,并將其顯示在博客的第1頁上:>>>Post.query.paginate(1,10)

<flask_sqlalchemy.Paginationat0x105118f50>

這個對象有幾個有用的屬性:>>>page=User.query.paginate(1,10)

#返回這一頁包含的數(shù)據(jù)對象

>>>page.items

[<User'fake_name'>]

#返回這一頁的頁數(shù)

>>>page.page

1

#返回總頁數(shù)

>>>page.pages

1

#上一頁和下一頁是否有對象可以顯示

>>>page.has_prev,page.has_next

(False,False)

#返回上一頁和下一頁的pagination對象

#如果不存在的話則返回當前頁

>>>page.prev(),page.next()

(<flask_sqlalchemy.Paginationat0x10812da50>,

<flask_sqlalchemy.Paginationat0x1081985d0>)

條件查詢現(xiàn)在我們來看SQL最擅長的事情,根據(jù)一些條件的集合獲得過濾后的數(shù)據(jù)。要得到滿足一系列等式條件的數(shù)據(jù)列表,則我們可以使用query.filter_by過濾器。query.filter_by過濾器接收關鍵字參數(shù),并把接收到的參數(shù)作為我們想要在數(shù)據(jù)庫里查詢的字段名值對。比如,要得到用戶名為fake_name的用戶列表,則可以這樣:>>>users=User.query.filter_by(username='fake_name').all()

這個例子只基于一個值進行過濾,但filter_by過濾器也可以接收多個值進行過濾。跟我們之前的函數(shù)類似,filter_by也是可鏈式調(diào)用的。>>>users=User.query.order_by(User.username.desc())

.filter_by(username='fake_name')

.limit(2)

.all()

query.filter_by只有在你確切地知道要查找的值時,才能夠工作。使用query.filter則可以避免這一不便之處,你可以把一個比較大小的Python表達式傳給它:>>>user=User.query.filter(

User.id>1

).all()

這只是個簡單的例子,實際上query.filter可以接收任何Python的比較表達式。對于Python的常規(guī)類型,比如整數(shù)(integers)、字符串(strings)和日期(dates),你可以使用==操作符來表示相等的比較。對于類型為整數(shù)(integer)、浮點(float)或者日期(date)的列,還可以用>、<、<=和>=操作符來表示不等的比較。另外,一些復雜的SQL查詢也可以轉(zhuǎn)為用SQLAlchemy的函數(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()

#這些方法都可以被組合起來

>>>user=User.query.filter(

or_(not_(User.password==None),User.id>=1)

).first()

在SQLAlchemy中,與None的比較會被翻譯成與NULL的比較。修改數(shù)據(jù)在使用first()或者all()等方法返回數(shù)據(jù)之前,調(diào)用update方法可以修改已存在的數(shù)據(jù)的值。>>>User.query.filter_by(username='fake_name').update({

'password':'test'

})

#對數(shù)據(jù)模型的修改已被自動加入session中

>>>mit()

刪除數(shù)據(jù)如果我們要從數(shù)據(jù)庫中刪除一行數(shù)據(jù),則可以:>>>user=User.query.filter_by(username='fake_name').first()

>>>db.session.delete(user)

>>>mit()

數(shù)據(jù)模型之間的關聯(lián)數(shù)據(jù)模型之間的關聯(lián)在SQLAlchemy里表現(xiàn)為兩個或更多模型之間的鏈接,模型之間可以互相建立引用。這使得相關聯(lián)的數(shù)據(jù)能夠很容易地從數(shù)據(jù)庫中取出,例如文章和它的評論,這就是關系數(shù)據(jù)庫管理系統(tǒng)(RDBMS)中“關系”(Relational)的含義,它給這類數(shù)據(jù)庫帶來了強大的功能?,F(xiàn)在讓我們來創(chuàng)建第1個關聯(lián)關系。在我們的博客網(wǎng)站上會有一些博客文章,每篇文章都有一個特定的作者。通過把每個作者的文章跟這個作者建立關聯(lián),可以方便地獲取這個作者的所有文章,這顯然是合理的做法。這就是一對多關系的一個范例。一對多我們先建立一個數(shù)據(jù)模型,用來表示網(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字段,對關系數(shù)據(jù)庫熟悉的讀者立刻會明白,它表示了一個外鍵約束(ForeignKeyConstraint)。外鍵約束是數(shù)據(jù)庫中的一種約束規(guī)則,在這里,它強制要求user_id字段的值存在于user表的id列中。這是數(shù)據(jù)庫進行的一項檢查,用來保證每個Post對象都會對應到一個已有的user。傳給db.ForeignKey的參數(shù),是一個用來代表user表id列的字符串。如果你要用__tablename__自定義表名,則需要同時修改這個字符串。之所以直接用表名,而不是使用User.id引用,是因為在SQLAlchemy初始化期間,User對象可能還沒有被創(chuàng)建出來。user_id字段還不足以讓SQLAlchemy建立我們想要的關聯(lián),我們還需要這樣修改User對象: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)建了一個虛擬的列,它會和我們的Post對象中的db.ForeignKey建立聯(lián)系。待會兒我們再來講backref的含義,不過lazy參數(shù)又是什么?lazy參數(shù)會告訴SQLAlchemy如何去加載我們指定的關聯(lián)對象。如果設為子查詢方式(subquery),則會在加載完Post對象的時候,就立即加載與其關聯(lián)的對象。這樣會讓總查詢數(shù)量減少,但如果返回的條目數(shù)量很多,就會比較慢。另外,也可以設置為動態(tài)方式(dynamic),這樣關聯(lián)對象會在被使用的時候再進行加載,并且在返回前進行過濾。如果返回的對象數(shù)很多,或者未來會變得很多,那最好采用這種方式。我們現(xiàn)在可以使用User.posts屬性來得到一個posts列表,其中每項的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'>]

可以從上面的例子中注意到,在把新增變更提交進數(shù)據(jù)庫之前,是無法通過關聯(lián)對象獲取新的Post對象的。backref參數(shù)則可以使我們通過Post.user屬性對User對象進行讀取和修改。例如:>>>second_post=Post('SecondTitle')

>>>second_post.user=user

>>>db.session.add(second_post)

>>>mit()

>>>user.posts

[<Post'PostTitle'>,<Post'SecondTitle'>]

由于user.posts是一個列表,所以我們也可以通過把Post對象直接添加進這個列表,來自動保存它:>>>second_post=Post('SecondTitle')

>>>user.posts.append(second_post)

>>>db.session.add(user)

>>>mit()

>>>user.posts

[<Post'PostTitle'>,<Post'SecondTitle'>]

由于backref選項被設置為動態(tài)方式,所以我們既可以把這個關聯(lián)字段看作列表,也可以把它看作一個查詢對象:>>>user.posts

[<Post'PostTitle'>,<Post'SecondTitle'>]

>>>user.posts.order_by(Post.publish_date.desc()).all()

[<Post'SecondTitle'>,<Post'PostTitle'>]

在開始學習下一種關聯(lián)類型之前,我們再創(chuàng)建一個數(shù)據(jù)模型,用來實現(xiàn)用戶評論,并加上一對多的關聯(lián),稍后在書中將會用到: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])

多對多如果我們有兩個數(shù)據(jù)模型,它們不但可以互相引用,而且其中的每個對象都可以引用多個對應的對象,那應該怎么做呢?比如,我們的博客文章需要加上標簽,這樣用戶就能輕松地把相似的文章分組。每個標簽都對應了多篇文章,而每篇文章同時對應了多個標簽。這樣的關聯(liá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對象對數(shù)據(jù)庫的操作比db.Model更底層。db.Model是基于db.Table提供的一種對象化包裝方式,用來表示數(shù)據(jù)庫表里的某行記錄。這里之所以使用了db.Table,正是因為我們不需要專門讀取這個表的某行記錄。我們用tags變量來代表post_tags表,這個表有兩個字段:一個表示博客文章的id,另一個表示某個標簽的id。下面的例子演示了這種用法,如果表中有如下數(shù)據(jù):post_idtag_id

11

13

23

24

25

31

32則SQLAlchemy會將其翻譯成:id為1的文章?lián)碛衖d為1和3的標簽。id為2的文章?lián)碛衖d為3、4和5的標簽。id為3的文章?lián)碛衖d為1和2的標簽。你可以把這組數(shù)據(jù)簡單地理解為標簽和文章的關聯(lián)關系。在上面的程序中我們又使用了db.relationship函數(shù)來設置所需的關聯(lián),但這次多傳了一個secondary(次級)參數(shù),secondary參數(shù)會告知SQLAlchemy該關聯(lián)被保存在tags表里。讓我們在下面的代碼中體會一下這種用法:>>>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()

在設置一對多的關聯(lián)時,主關聯(lián)字段實際上是一個列表?,F(xiàn)在主要的不同之處在于,backref也變成了一個列表。由于它是個列表,所以我們也可以像這樣把文章加到標簽里:>>>tag_one.posts.append(post_one)

[<Post'PostTitle'>,<Post'SecondTitle'>]

>>>post_one.tags

[<Tag'SQLAlchemy'>,<Tag'Python'>]

>>>db.session.add(tag_one)

>>>mit()

SQLAlchemy會話對象的方便之處現(xiàn)在你了解了SQLAlchemy的好處,也就應該能了解SQLAlchemy的會話對象是什么,以及為什么開發(fā)網(wǎng)絡應用少不了它們。如之前所說,會話可以被簡單地描述為用來跟蹤數(shù)據(jù)模型變化的對象,它還可以根據(jù)我們的指令將這些變化提交進數(shù)據(jù)庫。不過,它的作用遠不止這些。首先,會話可以用來控制事務。事務是一組變更集,在提交的時候被一起寫入數(shù)據(jù)庫。事務提供了很多看不見的功能。首先,當對象之間有關聯(lián)的時候,事務會自動決定保存的先后順序。在上一節(jié)我們保存標簽的時候你可能已經(jīng)注意到了這一點,當我們把新標簽關聯(lián)到文章的時候,會話對象會自動先把標簽保存進來,盡管我們沒有專門告訴它要提交標簽對象。如果我們直接使用底層的數(shù)據(jù)庫連接和SQL查詢進行開發(fā),就必須格外小心,對于哪些記錄跟哪些記錄有關聯(lián),需要自己記錄下來,以避免在保存外鍵時指向了不存在的對象。事務還會在數(shù)據(jù)庫發(fā)生變更的時候,將當前數(shù)據(jù)標記為舊數(shù)據(jù),當我們下次讀取這項數(shù)據(jù)的時候,它就會先向數(shù)據(jù)庫發(fā)送一條查詢,以更新當前數(shù)據(jù)。這些工作都是在背后自動進行的。如果沒有使用SQLAlchemy,則我們必須手工記錄哪些數(shù)據(jù)行需要被更新,并且只更新那些必須更新的數(shù)據(jù)行,以高效地使用數(shù)據(jù)庫資源。其次,事務會避免出現(xiàn)兩個不同的引用指向數(shù)據(jù)庫中的同一行記錄的情況。這都歸功于查詢是在會話中進行的(Model.query實際上是db.session.query(Model)),如果事務中的一個數(shù)據(jù)行已經(jīng)被查詢過,則會直接返回指向這個數(shù)據(jù)對象的引用,而不會創(chuàng)建一個新的對象。如果沒有這樣的檢查,則可能會出現(xiàn)兩個表示同一行數(shù)據(jù)的不同對象,分別把不同的修改提交到數(shù)據(jù)庫,這會造成很難發(fā)現(xiàn)和捕捉的隱性問題。要注意,F(xiàn)laskSQLAlchemy會為每一個request創(chuàng)建一個新的會話對象,在request處理結(jié)束的時候,會丟棄沒有提交的所有更改。因此一定要記得把工作保存下來。SQLAlchemy的作者MikeBayer于2012年在加拿大的PyCon上做過一個演講,演講題目是TheSQLAlchemySession-InDepth,如果想要深入了解會話,則在這里可以查看這個演講/watch?v=PKAdehPHOMo。使用Alembic進行數(shù)據(jù)庫遷移一個網(wǎng)絡應用的功能總會不斷地發(fā)生改變,增加新功能的時候,我們通常需要修改數(shù)據(jù)庫結(jié)構。不論你是增刪字段還是創(chuàng)建新表,數(shù)據(jù)模型的修改會貫穿你的應用開發(fā)的始終。但是,當數(shù)據(jù)庫更改變得頻繁后,你會很快面臨一個問題:當把這些更改從開發(fā)環(huán)境遷移到生產(chǎn)環(huán)境時,如果不人工對數(shù)據(jù)模型和對應表的每一行修改進行仔細比較,那么你怎樣才能保證所有的更改都會被遷移過去?又比如,要是你想把開發(fā)環(huán)境的代碼回滾到Git中的某個歷史版本,用來嘗試復現(xiàn)目前的生產(chǎn)環(huán)境中該版本代碼出現(xiàn)的某個問題,那么你應該怎樣把你的數(shù)據(jù)庫結(jié)構調(diào)整到該版本對應的狀態(tài),而無須做大量的額外工作呢?作為程序員,我們痛恨除開發(fā)外的額外工作。還好有個工具可以解決這個問題,這個工具是Alembic,可以根據(jù)我們的SQLAlchemy模型的變化,自動創(chuàng)建數(shù)據(jù)庫遷移記錄。數(shù)據(jù)庫遷移記錄(Databasemigration)保存了我們的數(shù)據(jù)庫結(jié)構變化的歷史信息。Alembic讓我們可以把數(shù)據(jù)庫升級或者降級到某個已保存的特定版本,而跨越好幾個版本之間的升級或者降級,則會執(zhí)行這兩個選定版本之間的所有歷史記錄文件。Alembic最棒的地方在于,這些歷史文件本身就是Python程序文件。下面,我們創(chuàng)建第1個數(shù)據(jù)庫遷移記錄,你會發(fā)現(xiàn)Alembic的語法非常簡單。Alembic不會捕捉所有可能的變更,比如,它不會記錄SQL索引的變化。讀者在每次遷移記錄后,應該去檢查一下遷移記錄文件,并進行必要的修正。我們不會直接使用Alembic,而是會使用Flask-Migrate,這是為SQLAlchemey專門創(chuàng)建的一個擴展,并且可以跟FlaskScript一起使用。下面在pip中進行安裝:$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()

我們通過app對象和SQLAlchemy的實例初始化了Migrate對象,然后讓遷移命令可以通過manage.pydb來調(diào)用。運行下面的命令可以看到可用命令列表:$pythonmanage.pydb

要開始跟蹤我們的數(shù)據(jù)庫變更,則可使用init命令$pythonmanage.pydbinit

這會在項目目錄中創(chuàng)建一個叫作migrations的文件夾,所有的記錄文件會被保存在里面。現(xiàn)在我們可以開始進行首次遷移:$pythonmanage.pydbmigrate-m"initialmigration"

這個命令會讓Alembic掃描我們所有的SQLAlchemy對象,找到在此之前沒有被記錄過的所有表和列,由于這是第1次提交,所以遷移記錄文件會比較大。確保使用了-m參數(shù)來保存提交信息,通過提交信息尋找所需的遷移記錄版本是最容易的辦法。每個遷移記錄文件都被保存在migrations/versions/文件夾中。執(zhí)行下面的命令,就可以把遷移記錄應用到數(shù)據(jù)庫上,并改變數(shù)據(jù)庫的結(jié)構:$pythonmanage.pydbupgrade

要返回以前的版本,則可以根據(jù)history命令找到版本號,然后傳給downgrade命令:$pythonmanage.pydbhistory

<base>->7ded34bc4fb(head),initialmigration

$pythonmanage.pydbdowngrade7ded34bc4fb同Git一樣,每個遷移記錄都由一個哈希值來表示。這是Alembic的重要功能,但只用于它的表層。你也可以嘗試把遷移記錄和你的Git提交記錄對應起來,這樣當你把代碼回滾到Git中的某個版本時,也能很容易地升級或降級數(shù)據(jù)庫結(jié)構??偨Y(jié)現(xiàn)在我們已經(jīng)能輕松地操縱數(shù)據(jù)了,接下來可以在應用中顯示這些數(shù)據(jù)。第3章會告訴你如何基于數(shù)據(jù)模型動態(tài)地創(chuàng)建HTML,以及如何通過網(wǎng)頁來添加新數(shù)據(jù)。

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論