Flask 10天開發一個網站
偶然找到一篇兩年半前大二時寫的文章,記錄了十天內是怎麼完成一個網站的,很多內容現在看起來都過時了,文風和代碼也有各種槽點(切勿模仿!),但還是想發布出來,畢竟記錄了一個曾經奮鬥的自己:)
pkyx是一個用Flask+MongoDB開發的比較(維基)網站。
Day 1:配置遠程開發環境
首先在 Paralles Desktop下安裝了64位的Ubuntu 15.04版本,裡面配置了nginx和virtualenv。
1.在Ubuntu中新建一個目錄,用virtualenv創建好虛擬環境,用pip安裝flask,接著測試一下。
2.在Pycharm下新建一個項目,編譯器選擇虛擬機中用virtualenv里的python解釋器。
3.在虛擬機中使用ifconfig命令查看ip地址,然後配置好ssh的選項,連接前記得已經在虛擬機中安裝好ssh-server,因為默認它是只有ssh-client而沒有ssh的伺服器的,用apt-get install openssh-server安裝之,用戶和密碼為虛擬機中的用戶名和密碼。
3.創建好項目後,在Tools-Deployment-Configuation中配置sftp選項,在這裡要注意Root Path是指在遠程主機(ubuntu)中的頂層路徑。
在Mapping選項卡中,配置項目路徑映射到遠程主機的項目路徑。
4.在Tools-Deployment-Option中配置Upload changed files automatically to the default server。這裡是選擇host的項目和遠程主機項目的同步時刻,Always是一直保持同步,Ctrl+s是指保存時同步。
5.編寫代碼,這裡寫了一個返回hello world的路由。
6.把代碼同步到遠程主機上,Upload to直接把代碼推送到虛擬機的項目路徑中,Sync with Deployed to..查看項目部署的文件狀態和選擇同步的文件。
到這裡基本上配置已經算完成了,下面直接Run運行代碼。
7.運行代碼。
可以看到,flask的程序已經開始啟動,但是這裡要注意,在本機是不能夠直接訪問虛擬機上的localhost的,所以這裡的http://127.0.0.1:5000/是指在虛擬機的服務,而在host這邊是無法通過此路徑查看的。那怎麼辦?之前我們說過在虛擬機中配置了nginx,此時它的作用就來了,總所周知,nginx的其中一個常見用途就是作反向代理,於是,在這裡我們也用nginx代理flask程序。
(其實這裡也有另外一種方法,就是在Paralles中的網路設置裡面配置埠映射,這樣就有辦法在host主機中訪問到虛擬機的localhost了。)
8.修改nginx配置文件(/etc/nginx/site-available/[conf])。
我們的nginx伺服器監聽了虛擬機的80埠,把跟路徑的request轉發到flask綁定的5000埠,而靜態文件路徑(/static)的請求則繞過flask,直接訪問虛擬機上的文件目錄,這樣也有效地減輕了flask程序的負荷。
9.獲取虛擬機的ip地址,通過瀏覽器訪問web程序。
perfect
10.別忘了把項目同步到git上了。
Day 2:編寫應用配置和視圖
在編寫配置和視圖之前,首先為應用規劃目錄結構。
code/pkyx/├── app(應用目錄)│ ├── config.py(配置文件)│ ├── forms.py(表單文件)│ ├── init.py│ ├── main(主體模塊)│ │ ├── errors.py(錯誤視圖)│ │ ├── init.py│ │ └── views.py(主體視圖)│ ├── static(靜態文件目錄)│ │ └── style.css│ ├── templates(模版文件目錄)│ │ ├── 404.html│ │ ├── 500.html│ │ ├── index.html│ │ └── pk.html│ └── users(用戶模塊)│ ├── init.py│ └── views.py├── manage.py
接下來我們再做一些準備工作,用pip安裝如下幾個擴展。
- Flask-Mail:Flask的郵件擴展,利用它可以方便快捷地給用戶發送郵件。
- Flask-WTF:Flask的表單擴展,用它可以在代碼中編寫表單類和基礎屬性,在模版中渲染表單。
- Flask-PyMongo:Flask的基於Pymongo的擴展,在為Flask應用部署MongoDB的連接時更加的快捷方便。
1.接下來,編寫表單文件。
首先從WTF擴展中導入Form類,我們要定義的表單類會繼承到這個Form類,接下來簡單地為表單定義三個域,兩個文本輸入框,validators中加入了wtforms.validators中的DataRequired的實例,它將會把這兩個文本框設置為必填項。最後還有一個提交域,也就是提交該表單的按鈕。
2.編寫藍本文件。
藍本(Flask-Blueprint)有許多用途,其中一個常見的用途即是為應用的模塊做url的劃分。
在一個應用的 URL 前綴和(或)子域上註冊一個藍圖。 URL 前綴和(或)子域的參數 成為藍圖中所有視圖的通用視圖參數(預設情況下)。
關於藍本的詳細說明:http://dormousehole.readthedocs.org/en/latest/blueprints.html#blueprints
在Blueprint的參數中還可以指定模塊的靜態文件路徑以及模版文件路徑。
3.編寫錯誤響應視圖。
為應用編寫錯誤的響應視圖十分重要,這裡簡單地定義了兩個視圖,分別對應錯誤404和錯誤500的響應,注意這裡使用的main為上一個中定義的藍本對象,若在英語中註冊了藍本,被裝飾器包裝的視圖函數都會註冊到應用中,它會把 構建Blueprint時所使用的名稱(在本例為simple_page)作為函數端點 的前綴。
4.編寫主體視圖。
這裡定義了首頁(/)和比較頁(/pk)的對應視圖,在index中,我們把之前定義的PkForm表單實例化,作為渲染模版函數render_template的上下文參數傳入到模版中渲染。在pk視圖中,它接受來自index表單中post提交的數據,因此要在它的裝飾器的methods參數中加上"POST屬性(默認只有GET),注意要把request.form作為表單的構造函數的參數傳入,否則表單將不能接收到任何數據,然後我們用表單到validate_on_submit方法來判斷表單的數據是否合法,若正確,則把輸入框的兩個數據拿出,渲染到比較頁中,否則,便簡單地返回一個顯示錯誤的響應。
5.編寫模版。
這裡簡單地在模版定義一個表單,它指向之前定義的pk視圖,當然了,只有這麼簡單的顯示是遠遠不夠的,在static中定義css樣式文件,在頁面的頭部的link標籤的href屬性指定用url_for()反向解析得到的靜態文件路徑(這裡要感謝強大的jinjia2模版:))。
這裡的效果大概如下:
接著編寫比較頁的模版。
6.定義配置文件。
到這裡,應用的框架基本清晰,但我們需要更加靈活的啟動和運行應用,在app目錄下編寫全局的配置文件。
在BaseConfig中定義了應用的一些基本配置,例如秘鑰,郵箱配置等等,下面所有的其它配置都會繼承BaseConfig,擴展出的其它配置,這裡定義了一個DevConfig(開發配置),顧名思義是在開發中的配置,除此之外,還可以定義其它類型的配置(如生產配置,測試配置等),在DevConfig中我們擴展了關於MongoDB連接(Flask-PyMongo)的配置,以及一個靜態方法init_app,它會應用進行一些配置的初始化(如建立資料庫的連接)。
7.編寫創建應用的函數。
這裡編寫了一個創建和初始化應用的函數,它將負責為應用初始化傳入的配置,使用配置的init_app初始化自身,以及註冊前面編寫的藍本。
8.建立管理應用腳本(manage.py)。
最後應用還需要一個全局的管理腳本,這裡暫時只需要加上啟動應用的代碼。
9.啟動和運行應用。
完成上面的步驟,應用就能跑起來了。
Day 3:編寫RESTful API和測試資料庫
前面已經完成了視圖,模板,表單的工作,應用也已經可以運行了。
接下來開始編寫API了,REST(表現層狀態轉換)設計風格是當前最流行的設計模式,接下來將會為應用編寫REST風格的API。
關於RESTful
1.還是老規矩,新建API模塊的目錄,和init.py,定義Blueprint。
2.測試資料庫
在app模塊的初始化中,我們創建了MongoDB的連接實例,但這個實例是還沒有綁定到當前的應用上下文的,因此還要在create_app中為mongo實例用創建出來的app添加到連接實例的init_app方法,這時候,MongoDB就正式可以在應用中工作了,只需要在其他文件中導入app模塊的mongo實例( from app import mongo)
。
接下來,在mongo shell中新建一個存放條目的集合,插入一條數據。。
3.編寫工具函數(utils)。
這裡的兩個工具函數(bson_to_json,bson_obj_id)是待會在編寫API視圖的時候要用到的,其作用分別是把MongoDB的BSON(文檔的數據格式)轉換為JSON和把id轉換為MongoDB中的ObjectId形式,因為這兩個功能都難以用python內置的函數實現,而pymongo為我們提供的bson模塊提供了很好用的json_util,使得我們很方便地去實現MongoDB和Python之間的數據格式轉換。
一張圖就能解釋其中的流程。
4.編寫REST API。
終於到了重中之重的步驟了,在Python的Web框架中實現REST API不是一件難事,Python社區也有許多關於REST的包(EVE,REST framework等),而在Flask里,flask默認為開發者提供了可組裝視圖(Pluggable View),其中裡面有一個MethodView,就是專門為開發者設計REST風格的視圖的,這種類視圖有一個as_view()方法,使用它可以直接把類視圖轉換成平時使用的普通視圖,在有重用視圖的需求時,更是比普通函數視圖更加地靈活。
首先,編寫好一個API類,繼承MethodView,簡單地實現get、post、put、delete四個方法,分別對應四個HTTP方法對應的處理句柄。在代碼的最後加上路由的規則,映射到不同的方法中,注意get方法中有兩種情況,一種是提供id,只返回特定的資源,不提供id則返回所有(或前N條)資源,用add_url_route()方法動態地添加路由。
最後先簡單地實現一下get方法,用pymongo把資源從資料庫拉取,find()方法返回的是結果游標,注意這裡用到了bson_to_json()方法,前面說過了,這是把MongoDB的文檔格式從bson轉換為json格式,拿到存放json數據的列表之後,再用json.dumps()返回之。最後客戶端得到的就是一個json格式的對象數組了。
最後的最後要注意的是在find()方法中傳入了一個params字典,這個字典是存放GET請求後面帶的參數的鍵值對的,有了條件查詢,我們構建的API會更加靈活。
其他的方法就按照各自的條件實現。
5.測試REST API。
這裡用Postman向伺服器的api地址發送了一個GET請求,參數是之前插入到MongoDB中的文檔的id字元串,最後得到一條結果。(若不加參數的話,則得到所有結果)
再測試了另外一條GET請求,這次則是加上了數據的屬性與值作為query string,發送查詢請求,結果得到一條記錄。
Day 4:使用Supervisor和Gunicorn優化應用
Sueprvisor是Linux上的一個可以監控應用和進程的工具,我們用它來作為守護進程,自動化地啟動和停止應用。
首先在系統上用sudo apt-get install supervisor安裝它。
接著再用pip install gunicorn安裝gunicorn,gunicorn是用python實現的高性能wsgi伺服器,flask自帶的wsgi伺服器不適合在生產環境使用,我們使用gunicorn作為flask應用的伺服器,提高應用的吞吐量和響應速度。
接下來在應用的目錄下新建一個gunicorn的配置文件,裡面配置了四個工作進程(wokers)和綁定的埠(bind)。
除此之外還要創建應用專屬的supervisor的配置文件,其中主要的參數有幾個:
- [program:[app]]:指定應用的名稱
- command:指定啟動應用的命令
- directory:應用所處的工作目錄
- stdout_logfile:標準輸出日誌
- stderr_logfile:標準錯誤日誌
用supervisor啟動應用進程也非常簡單,只需要在supervisorctl的控制台里輸入對應的命令即可運行應用。
注意的是,在使用supervisor之前,要先要用supervisord -c [conf_path]和supervisorctl -c [conf_path]命令指定好supervisor自身的配置文件。
當然了,在開發調試環境下還是不太適宜用gunicorn和supervisor來啟動應用的,因為這樣做不便於查看應用的輸出和錯誤信息,而要在日誌中觀察應用的運行狀態。
Day 5:編寫用戶和認證模塊
使用Flask-Login擴展能夠很方便地為你的應用實現用戶的會話和登錄功能。
首先用pip install Flask-Login安裝之。
除此之外,在認證模塊要用到Flask-httpauth這個包,我們將在用戶的REST API用認證的方式來管理請求。
安裝方式:pip install Flask-httpauth。
1.說到用戶模塊,當然離不開登錄/註冊功能,那麼我們首先編寫登錄和註冊表單。
代碼包括了最常用的幾個域,這裡就沒什麼好說的了。
2.編寫用戶模型(Model)。
儘管我們的應用沒有採用ORM模型的形式,而是用pymongo來直接與MongoDB交互,但我們還是需要編寫一個通用的用戶模型,一是因為Flask-Login中要用到關於用戶的模型對象,二是方便在認證模塊中管理用戶。
新建一個models.py文件,定義一個User類,添加Flask-Login模塊里的UserMixin,這個Mixin會為我們定義的用戶類聲明一些通用的用戶狀態的property,如is_anonymous, is_authorized, is_active等等,混入UserMixin後,User類便能作為Flask-Login的用戶模型一樣被對待。
在這個用戶類定義了四個方法。
1)gen_passwd_hash(password)
返回用哈希演算法加密後的密碼,因為我們的密碼是不能讓它明文地保存在資料庫的,在這裡使用werkzeug.security中的generate_password_hash方法來加密密碼。
2)verify_passwd(passwd_hash, passwd)
把輸入的密碼與加密的哈希密碼做對比,驗證其正確性。
3)gen_auth_token(self, expiration)
生成一個帶有過期驗證的訪問令牌。
4)verify_auth_token(token)
驗證訪問令牌,若成功,返回用戶信息。
3.初始化LoginManager。
在應用模塊中新建Flask-Login的LoginManager的實例,用它當前綁定應用,指定用戶登錄的視圖(這裡是"users.login")。你必須提供一個 user_loader回調。這個回調用於從會話中存儲的用戶 ID 重新載入用戶對象。它應該接受一個用戶的ID 作為參數,並且返回相應的用戶對象。
https://flask-login.readthedocs.org/en/latest/
4.編寫用戶主要視圖。
這裡分別定義了register(註冊),login(登錄),profile(用戶資料),logout(註銷)四個視圖,這裡要注意的是錯誤的判斷以及用戶驗證流程,用flask-login中相應的login_user(user)和logout_user()來實現登錄/登出功能,最後記得在需要保護的視圖中添加上login_required裝飾器,防止未經登錄的訪問。
5.添加/修改登錄註冊模版。
6.測試登錄/註冊等功能。
前面已經實現了用戶的登錄功能,現在註冊一個用戶並登錄測試應用。
註冊。
接著登錄。
登錄成功。
7.編寫用戶認證模塊。
前面在定義用戶類的時候就已經寫好了一些認證的方法。
現在我們使用flask-httpauth來構建帶有用戶認證的REST API。
在api模塊目錄下新建users.py。
創建一個HTTPBasicAuth實例,定義核心的verify_password函數,它將完成用戶認證的功能,這裡需要用auth的verify_password裝飾這個函數。
verify_password中提供了兩種認證方式,首先是用token認證,如果不通過則用用戶+密碼的方式入庫驗證。
接著用login_requried包裝一個獲取token的視圖和一個資源視圖。
接著開始測試api。
如果不用認證的方式去訪問資源的話,會得到一個access denied的響應。
我們用郵箱(用戶名):密碼的方式訪問資源,成功返回一個資源。
然後用認證的方式請求token所在的視圖,獲取到帶有過期時間和token的json,下面不用用戶名跟密碼,而是用token代替去訪問應用資源,同樣正確地返回資源。
Day 6:用模版繼承組件化應用
今天來為應用完善之前寫好的模板。
在完善頁面前最好先為模板添加上一些樣式,不然頁面看起來不美觀,也沒有層次感,不便於調整頁面元素。因此在原來的基礎上,可以添加上自己寫的樣式,用link的方式導出到前端,或者直接使用開源的css框架,在這裡使用的是semantic-ui,用bower安裝到應用的資源目錄,然後就可以編寫模板了。
由於一個網站中通常會有一些重複的基礎組件(如導航欄,頂部,通用樣式等),這樣一來在每個頁面文件中我們都要把這些幾乎相同的代碼拷貝,而且當要改動元素時得每個文件都要進行修改,給開發帶來許多的不便,這時候便要用到jinjia2的模板繼承功能,使用模板,我們能把這些通用的部分封裝出來作為模板,需要替換的地方只需要在特定的位置添加一個塊,在繼承的子模板中填充塊的內容即可。
1.下面在模板目錄下新建一個基礎文件(base.html)作為需要繼承的通用模板。
在該目標中包含了一些基本樣式和腳本,定義了三個需要填充的塊,分別是head頭部,主體內容和js文件。
2.繼承父模板。
修改之前定義的首頁文件(index.html),用extends的方式繼承了父模板,然後只需要在相應的塊中補充內容,子模板在渲染的時候便會把塊中的內容導出到自身的對應塊中,構造和實現頁面,這裡還把頂部欄(header.html)以及登錄框(login.html)分別獨立出一個組件,只供特定的模板使用,這樣的導入方式會帶來更大的靈活性。
3.編寫組件。
可以把這裡的組件理解為頁面中的一部分,因此在寫代碼的時候只需吧對應部分的html元素完成即可。
頂部欄組件
登錄框(模態框)組件
4.整合模板。
現在處理過後的組件可以像積木一樣裝載在應用上了,這裡簡單地應用在幾個頁面,查看效果。
Day 7:編寫主功能
接著上次的地方開始做。
1.建立創建條目的頁面,一個表單搞定,server端向資料庫插入一條文檔,so easy。
2.創建條目之後,系統會重定向到條目的信息頁面,編寫信息頁面的布局。
如下所示,條目內容將會由一個表格來展示,剛創建的條目只有類型一個屬性,頁面底下會有一個添加屬性的按鈕,動態地向該條目插入屬性。
由於表格的內容是由python端進行渲染的,而一個條目的屬性可能有多種類型。如下圖所示:
這就帶來一個問題,因為屬性類型不一定是純文本,因此不能簡單地從資料庫取數據然後再直接渲染。
這裡要實現動態的渲染,要在ajax和server端之間定義一套規則,向條目添加屬性的時候會按照不同的類型來構造出特定的數據格式,然後python端在知道格式的情況下,用自定義的渲染器實現html的插入。
有了這個思路,馬上開始編寫代碼。
當點擊添加屬性按鈕後,底下會摺疊處一個標籤頁菜單,選擇不同的類型時,下面的輸入框也會相應地變化。
當然,js也要相應地配合,用ajax請求伺服器端,進行數據更新。
根據選擇類型獲取屬性值。
Ajax請求
3.編寫渲染器。
jinjia2的靈活性使得我們可以方便地在模板中使用python代碼,下面定義了一個簡單的類型渲染器,根據傳入的【屬性名,屬性值,屬性類型】構造出html。
然後在頁面中這樣調用,記得使用safe過濾器取消轉義。
4.增加編輯/刪除屬性方法。
添加屬性有了,怎麼可以沒有編輯和刪除屬性的方法呢?
一開始想用可編輯表格的方式對表格的數據進行即時修改,卻發現這樣做會帶來一些問題,最終放棄之。
改成了雙擊表格列,用模態框修改之。
修改成功之後會刷新頁面,看到修改後的表格。
5.完善表單驗證。
因為之前做的表單驗證實在是太簡陋了,出於安全考慮,一定要對錶單驗證(尤其是用戶信息方面)加以完善。
編輯表單文件,在之前的基礎上,我們改用一個validators字典來存放不同表單域的規則,在用戶名和密碼中都加上了長度以及正則表表達式加以限制。
在頁面中也要提示用戶怎麼填寫表單。
6.完善樣式。
最後再修飾一下頁面。
Day 8:使用GridFS實現文件上傳
文件上傳有許多種方法,一般用文件系統的io即可,這裡使用了mongodb的GridFS系統,mongodb推薦使用它來保存大型文件,
這裡先嘗鮮試用一下,用gridfs實現用戶頭像的上傳。
在flask端,用request.files來接收上傳的頭像圖片,判斷圖片的擴展名是否符合格式,如果合法,用werkzurg.utils的secure_filename方法來替換和過濾文件名的特殊字元,接下來實例化GridFS類,它接受一個database和集合作為參數,用fs對象的put方法上傳到GridFS,返回的Object Id指向的是db.avatar.files插入的文檔,把頭像id以及用戶的資料信息一併保存到資料庫。
在前端的頁面用url_for()方法反向解析出圖片的地址,首先要編寫好獲取頭像的路由,它接受頭像的Object Id作為參數,從fs系統中取出頭像圖片的數據,圖片的二進位數據會保存在db.avatar.chunks中,這裡獲取圖片之後,構造一個content-type為圖片格式的response,否則當打開url時,圖片數據不能正確被瀏覽器解析,在img的src屬性中填上路由即可顯示圖片。
用戶的資料可能有不全的情況,用jinja2的Environment Filter來編寫自定義的過濾器,使數據的顯示更加人性化。
最後編寫比較頁面,這裡要注意屬性的順序排置,實現正確地渲染。
到此網站頁面端初步完成。
Day 9:配置Celery&Redis運行後台任務
有些時候,我們的應用會執行一些後台任務,例如一些不會與用戶直接交互,實時性要求較低的動作。例如用戶註冊的時候,通常會發送一封帶有認證token鏈接的郵件到用戶的郵箱,因為發送郵件這個動作會比較耗時,如果同一時間有大量註冊的請求,就可能會出現阻塞,影響用戶瀏覽的體驗,這時候我們更希望把任務放到後台進行,那麼Celery會是一個合適的選擇,Celery是一個分散式的任務隊列,負責任務的執行與調度。
Celery的架構由三部分組成,消息中間件(message broker),任務執行單元(worker)和任務執行結果存儲(task result store)組成。
這裡用Redis作為Celery的Broker,負責傳遞通訊消息。
用pip install flask-celery-helper安裝Celery和它的Flask擴展,用pip install redis安裝Redis的python擴展。
安裝好之後要在配置文件中添加上celery和redis的相關配置。
首先重構目錄的結構,把擴展移到extensions文件下,在init中用工廠函數初始化應用。
1.在app目錄下新建一個tasks目錄,裡面放的就是Celery要處理的任務文件。
這裡新建一個非同步發送郵件的任務,用@celery.task裝飾之。
2.在視圖函數中封裝一個發送郵件的函數,以及編寫用戶認證的視圖。
認證的方式用帶有時間戳的token。
3.運行Celery。
加上-A參數後Celery會去識別用戶自定義的配置文件,後面接一個celery實例所在的模塊文件。
運行之後,去註冊一個賬號。
點擊郵件中的激活連接,驗證token:
Day 10:編寫Dockerfile
最後一步:編寫部署環境的腳本
首先把項目用到的配置文件都放在項目的conf目錄下,如下圖顯示了項目的supervisor的配置文件。
接著編寫Dockerfile文件,方便快速地用docker部署好項目的容器環境。
最後就可以在生產的伺服器上測試運行項目了。
推薦閱讀:
※Python 家族有多龐大
※Python數據分析及可視化實例之CentOS7.2+Python3x+Flask部署標準化配置流程
※Flask 實現小說網站 (二)