Flask 10 天開發一個網站

obs_23 8年前發布 | 95K 次閱讀 Flask MongoDB Web框架

pkyx是一個用Flask+MongoDB開發的比較(維基)網站。

Day 1:配置遠程開發環境

首先在 Paralles Desktop下安裝了64位的Ubuntu 15.04版本,里面配置了nginx和virtualenv。

  1. 在Ubuntu中新建一個目錄,用virtualenv創建好虛擬環境,用pip安裝flask,接著測試一下。

  2. 在Pycharm下新建一個項目,編譯器選擇虛擬機中用virtualenv里的python解釋器。

    在虛擬機中使用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 前綴和(或)子域的參數 成為藍圖中所有視圖的通用視圖參數(缺省情況下)。

    在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。

  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 作為參數,并且返回相應的用戶對象。

  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部署好項目的容器環境。

最后就可以在生產的服務器上測試運行項目了。

 

來自:http://www.jianshu.com/p/1278c7ee0cc7

 

 本文由用戶 obs_23 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!