對Flask感興趣的,能夠看下這個視頻教程:http://study.163.com/course/courseLearn.htm?courseId=1004091002html
WTForms
是一個支持多 web
框架的一個插件,主要功能有兩個:第一個是作表單的驗證,驗證用戶提交上來的信息是否合法,第二個是模板渲染。前端
使用 WTForms
進行表單驗證,會更好的管理咱們的代碼和項目結構,還能夠大大提升開發項目時的效率。WTForms
功能強大,將表單定義成一個類,能夠實現對錶單字段的豐富限制。python
使用 WTForms
實現表單驗證的功能,主要有如下步驟:web
從 wtforms
中導入 Form
這個類,以及相關字段的數據類型正則表達式
from wtforms import From,StringField,IntegerField,FileField # Form 是一個基類,StringField 用來驗證 String 類型的數據
從 wrforms.validators
導入一些限制對象(如長度限制)json
from wrforms.validators import Length,EqualTo # # wrforms.vaildators 是一個驗證器,包含 Length 在內的多種驗證限制,Length 則專門對參數的長度進行驗證,EqualTo 指定必需要和某個值相等
建立表單類並繼承自 Form
,定義相關字段flask
class RegistForm(Form): # 該類用來驗證表單中傳遞的參數,屬性名和參數名必須一致 username = StringField(validators=[Length(min=3,max=10,message='用戶名長度必須在3到10位之間')]) # StringField 必須傳入關鍵字參數 validators,且 validators 是一個 List 類型(此處僅對長度做驗證) password = StringField(validators=[Length(min=6,max=16)]) password_repeat = StringField(validators=[Length(min=6,max=16),EqualTo('password')]) # 驗證長度和相等
在視圖函數中使用該 RegistForm後端
form = RegistForm(request.form) # request.form 會拿到全部提交的表單信息 if form.validate(): # form.validate() 方法會匹配表單信息並返回 True 或 False return '註冊成功!' else: return '註冊失敗!'
完整代碼以下:api
# regist.html <form action="" method="post"> <table> <tbody> <tr> <td>用戶名:</td> <td><input type="text" name="username"></td> </tr> <tr> <td>密碼:</td> <td><input type="password" name="password"></td> </tr> <tr> <td>確認密碼:</td> <td><input type="password" name="password_repeat"></td> </tr> <tr> <td></td> <td><input type="submit" value="點擊提交"></td> </tr> </tbody> </table> </form> # 後端程序 from wtforms import Form,StringField from wtforms.validators import Length,EqualTo class RegistForm(Form): username = StringField(validators=[Length(min=3,max=10,message='輸入的用戶名不符合長度規範')]) password = StringField(validators=[Length(min=6,max=16)]) password_repeat = StringField(validators=[Length(min=6,max=16),EqualTo('password')]) @app.route('/regist/',methods=['GET','POST']) def regist(): if request.method == 'GET': return render_template('regist.html') else: form = RegistForm(request.form) if form.validate(): return '註冊成功' else: print(form.errors) for message in form.errors: return '註冊成功'
除了上面使用到的兩個驗證器(StringField
和EqualTo
)外,WTForms 中還有不少經常使用的驗證器:瀏覽器
Email
:驗證上傳的數據是否爲郵箱(格式)
email = StringField(validators=[email()])
EqualTo
:驗證上傳的數據是否與另外一個字段相等,經常使用在註冊時的兩次密碼輸入上
password_repeat = StringField(validators=[Length(min=6,max=16),EqualTo('password')])
InputRequired
:該字段必須輸入參數,且只要輸入了,那麼該字段就是 True
。若是不是特數據狀況,應該使用 InputRequired
password = StringField(validators=[InputRequired()]) # 無論你的值是什麼,只要輸入了就是 True
Length
:長度限制,由 min
和 max
兩個值進行限制
password = StringField(validators=[Length(6,16)])
NumberRange
:數字的區間,由 min
和 max
兩個值進行限制(包括 min
和 max
)
age = IntegerField(validators=[NumberRange(12,100)])
Regexp
:自定義正則表達式,好比手機號碼的匹配
phone = StringField(validators=[Regexp(r'1[34578]\d{9}')])
URL
:必需要是 URL
的形式
homepage = StringField(validators=[URL()])
UUID
:驗證 UUID
uuid = StringField(validators=[UUID()])
注意在使用驗證器的時候,後面要加上 ()
。
若是以上介紹的驗證器不知足項目當中的需求,那麼還能夠根據需求自定義相關的驗證器。若是想要對錶單中的某個字段進行更加細緻的驗證,那麼能夠根據需求對該字段定進行單獨的驗證,步驟以下:
validate_字段名(self,field)
。field.data
獲取到用戶上傳到這個字段上的值。wtforms.validators.ValidationError
異常,並填入驗證失敗的緣由。示例代碼以下所示:
from wtforms import Form,StringField from wtforms.validators import Length,ValidationError class LoginForm(Form): captcha = StringField(validators=[Length(4,4)]) def validate_captcha(self,field): # 用 validate_captcha 來指定該驗證器是針對 captcha 字段的 if field.data != 'aw7e': raise ValidationError('驗證碼輸入錯誤!')
這個功能可讓咱們的前端代碼少寫一點點,可是實際上用處不大。主要使用方法以下:
在 forms
文件中定義一個表單類:
class SettingsForms(Form): username = StringField(validators=[Length(4,10)])
在視圖函數中返回模板時傳遞相關參數:
@app.route('/settings/',methods=['GET','POST']) def Settings(): if request.method == 'GET': form = SettingsForms() return render_template('settings.html',my_form=form) else: pass
在前端模板中調用
<form action="" method="post"> <table> <tbody> <tr> <td>{{ my_form.username.label }}</td> <td>{{ my_form.username() }}</td> </tr> <tr> <td></td> <td><input type="submit" value="提交"></td> </tr> </tbody> </table> </form>
其中,第五第六兩行至關於:
<td>用戶名:</td> <td><input type="text" name='username'></td>
實際上,這個功能在生產環境中幾乎沒有任何做用,很雞肋。
上傳文件時須要注意如下幾點:
在模板中,form
表單內,要指定 encotype='multipart/form-data'
才能實現文件的上傳:
<form action="" method="post" enctype="multipart/form-data"> ... </form>
在後臺獲取文件,須要使用 request.files.get('標籤名')
才能獲取到上傳的文件:
avatar = request.files.get('avatar')
保存文件使用 avatar.save(路徑)
實現,推薦在保存文件時先對文件進行安全封裝:
from werkzueg.utils import secure_filename import os UPLOAD_PATH = os.path.join(os.path.dirname(__file__),'images') # UPLOAD_PATH = 當前路徑/images avatar.save(UPLOAD_PATH,secure_filename(avatar.filename))
後臺完整代碼以下:
from werkzeug.utils import secure_filename import os UPLOAD_PATH = os.path.join(os.path.dirname(__file__),'images') # 定義文件保存路徑:UPLOAD_PATH = 當前路徑/images @app.route('/upload/',methods=['GET','POST']) def upload(): if request.method == 'GET': return render_template('upload.html') else: avatar = request.files.get('avatar') filename = secure_filename(avatar.filename) # 對文件名進行安全過濾 avatar.save(os.path.join(UPLOAD_PATH,filename)) desc = request.form.get('desc') print(desc) return '上傳成功!'
實現了文件上傳,那麼用戶確定會須要對文件進行訪問。在 Flask
中,實現文件的訪問必需要定義一個單獨的 url
與視圖函數的映射,而且要藉助 send_from_directory
方法返回文件給客戶端。
從 flask
導入 send_from_directory
from flask import send_from_directory
定義視圖函數並映射到文件的 url
UPLOAD_PATH = os.path.join(os.path.dirname(__file__),'images') @app.route('/getfile/<filename>/') def getfile(filename): return send_from_directory(UPLOAD_PATH,filename) # send_from_directory 要傳入路徑和文件名 # 用戶能夠訪問 http://domainname/filename 對文件進行訪問
在驗證文件的時候,一樣要定義一個驗證的類,而後用該驗證類去驗證上傳的文件。主要分爲如下幾個步驟:
導入 FileField
和文件驗證器:FileRequired
、FileAllowed
from forms import FileField from flask_wtf.file import FileRequired,FileAllowed # 注意這兩個針對文件的驗證器是從 flask_wtf_file 中導入的,而不是從以前的 wtforms.validators 中導入
定義表單類並繼承自 Form
,而後定義相關字段
class UpLoadForm(Form): avatar = FileField(validators=[FileRequired(),FileAllowed(['jpg','png','gif'])]) # FileRequired() 要求必須傳入文件,FileAllowed() 則指定了容許的文件類型 desc = StringField(validators=[InputRequired()])
在主 app
文件中引用
from werkzeug.datastructures import CombinedMultiDict # CombinedMultiDict 用來合併兩個不可變的 dict form =UpLoadForm(CombinedMultiDict([request.form,request.files])) # 傳入用戶提交的信息,其中 request.form 是表單中的信息,request.files 是上傳的文件
完整代碼以下:
# forms.py 文件 from wtforms import Form,StringField,FileField from flask_wtf.file import FileRequired,FileAllowed class UpLoadForm(Form): avatar = FileField(validators=[FileRequired(),FileAllowed(['jpg','png','gif'])]) desc = StringField(validators=[InputRequired()]) # 主 app 文件 from forms import UpLoadForm from werkzeug.utils import secure_filename from werkzeug.datastructures import CombinedMultiDict import os UPLOAD_PATH = os.path.join(os.path.dirname(__file__),'images') @app.route('/upload/',methods=['GET','POST']) def upload(): if request.method == 'GET': return render_template('upload.html') else: form =UpLoadForm(CombinedMultiDict([request.form,request.files])) if form.validate(): avatar = request.files.get('avatar') filename = secure_filename(avatar.filename) avatar.save(os.path.join(UPLOAD_PATH,filename)) desc = request.form.get('desc') print(desc) return '上傳成功!' else: return '上傳失敗!'
設置 Cookie
是 Response
類中有的方法,用法是:在視圖函數中
resp = Response('MYYD') # 建立一個 Response 對象,傳入的字符串會被顯示在網頁中 resp.set_cookie('username','myyd') return resp
其中,set_cookie
() 中的參數有:
key 鍵 value 值 max_age IE8 如下不支持,優先級比 expires 高 expires 幾乎全部瀏覽器都支持,必須傳入 datetime 的數據類型,而且默認加 8 個小時(由於咱們是東八區) path 生效的 URL,'/' 表明該域名下全部 URL 都生效,通常默認就好 domian 域名,若沒設置,則只能在當前域名下使用 secure 默認 False,若改成 True 則只能在 https 協議下使用 httponly 默認 False,若改成 True 則只能被瀏覽器所讀取,不能被 JavaScript 讀取(JavaScript能夠在前端處理一些簡單邏輯)
使用時依次傳入便可,若是有些選項要跳過則須要指定一下參數名。
完整代碼以下所示:
from flask import Flask,Response app = Flask(__name__) @app.route('/') def hello_world(): resp = Response('首頁') resp.set_cookie('username','MYYD') return resp if __name__ == '__main__': app.run()
刪除 Cookie
時須要另外指定一條 URL
和視圖函數,也是使用 Response
來建立一個類,並使用 resp.delete_cookie()
來完成這個需求。代碼以下所示:
from flask import Flask,Response app = Flask(__name__) @app.route('/delCookie/') def delete_cookie(): resp = Response('刪除Cookie') resp.delete_cookie('username') return resp if __name__ == '__main__': app.run()
設置 Cookie
的有效期,能夠有兩種方法:使用 max_age
或 expires
。
使用 max_age
使用 max-age 時要注意,max-age 不支持 IE8 及如下版本的瀏覽器,而且只能相對於如今的時間日後進行推遲(單位是秒s),而不能指定具體的失效時間。使用方法以下代碼所示:
resp.set_cookie('username','myyd',max_age=60) # 設置該 cookie 60s 以後失效。
使用 expires
使用 expires
時要注意,必需要使用格林尼治時間,由於最後會自動加上 8 小時(中國是東八區)。expires
的兼容性要比 max_age
要好,儘管在新版的 http
協議中指明瞭 expires
要被廢棄,但如今幾乎全部的瀏覽器都支持 expires
。
expire
設置失效時間,能夠針對當前時間日後推移,也能夠指定某一個具體的失效時間。具體以下所示:
針對當前時間推移
from datetime import datetime,timedelta expires = datetime.now() + timedelta(days=30,hours=16) # 當下時間日後推移 31 天失效,注意這裏給的參數是減了 8 小時的 resp.set_cookie('username','MYYD',expires=expires)
指定具體日期
from datetime import datetime resp = Response('首頁') expires = datetime(year=2018,month=12,day=30,hour=10,minute=0,second=0) # 實際上的失效時間是 2018-12-30-18:0:0 resp.set_cookie('username','MYYD',expires=expires) return resp
其餘注意事項
此外,還要注意幾點:
- 當同時使用
max_age
和expires
的時候,會優先使用max_age
指定的失效時間- 若同時不使用
max_age
和expires
的時候,默認的cookie
失效時間爲瀏覽器關閉的時間(而不是窗口關閉的時間)expires
要設置爲格林尼治時間,同時導入datetime.datetime
和datetime.timedelta
防範 CSRF 攻擊的措施:
實現:在返回一些危險操做的頁面時,同時返回一個 csrf_token
的 cookie
信息,而且在返回的頁面表單中也返回一個帶有 csrf_token
值的 input
標籤。
原理:當用戶提交該表單時,若表單中 input
標籤的 csrf_token
值存在而且和 cookie
中的 csrf_token
值相等則容許操做;若不知足該條件,則操做不被容許。
緣由:由於 csrf_token
這個值是在返回危險操做頁面時隨機生成的,黑客是沒法僞造出相同的 csrf_token
值的,由於黑客不能操做非本身域名下的 cookie
,即不知道 cookie
中的 csrf_token
值的內容。
具體實現:
主app文件:
模板文件(表單中):
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> 注意這裏是要在全部危險操做頁面的表單內都須要加入。
瀏覽器:F12 -> Network -> Disable Cache
// 整個文檔加載完畢後纔會執行這個函數 $(function () { $('#submit').click(function (event) { // 阻止默認的表單提交行爲 // event.preventDefault(); var email = $('input[name=email]').val(); var password = $('input[name=password]').val(); var csrftoken = $('input[name=csrf_token]').val(); // $.post() 方法用來提交表單 $.post({ 'url':'/login/', 'data':{ 'email': email, 'password': password, 'csrftoken': csrftoken }, 'success':function (data) { console.log(data); }, 'fail':function (error) { console.log(error); } }); }) });
Restful API
是用於在前端與後臺進行通訊時使用的一套傳輸規範,這些規範可使後臺開發變得更加輕鬆。
其採用的協議是 http
或 https
。
傳輸數據格式採用 json
而不是 xml
。使用 json
傳輸數據會變得更加簡單高效,而不是像 xml
那樣伴隨有衆多的固定代碼(相似於 html
的格式),即每次傳輸時 xml
佔的資源更多。
而且其url 連接中,不能包含動詞,只能包含名詞;而且對於名詞,若出現複數,則必須加上 s
。
HTTP 的請求方法主要有如下 5
種,但實際上 get
和 post
就夠用了。
get
:獲取服務器上的一個資源post
:在服務器上建立一個紫愛雲put
:在服務器上更新資源(客戶端須要提交更新後的全部數據)patch
:在服務器上更新資源(客戶端只須要提交所更新的數據)delete
:在服務器上刪除一個資源安裝
Flask-Restful
須要在 Flask 0.8
以上版本運行,在 python 2.6
以上版本運行,經過 pip install flask-restful
便可安裝。
使用
使用以前必須從 flask_restful
中導入 Api
和 Resource
;而後用 Api
將初始化的 app
綁定起來;再定義一個類視圖,定義類視圖必須繼承自 Resource
;最後用 add_resource
方法將接口(URL
)與視圖綁定起來。完整代碼以下:
from flask import Flask from flask_restful import Api,Resource # Api 用來綁定 app,Resource 用來建立類視圖 app = Flask(__name__) api = Api(app) class LoginView(Resource): def post(self): # 定義了什麼樣的方法,才能用什麼樣的請求 return {'username':'MYYD'} # 能夠直接返回字典類型的數據(由於字典數據已經自動轉換成Json格式了) api.add_resource(LoginView,'/login/',endpoint='login') # 映射類視圖和接口,endpoint 用來指定 url_for 反轉到類視圖時的關鍵字 if __name__ == '__main__': app.run()
注意事項:
- 映射類視圖和接口時不指定
endpoint
,則進行url_for
反轉時默認使用視圖名稱的小寫,即上例中的loginview
。
add_resource
方法的第二個參數,用來指定訪問這個類視圖的接口,與以前不一樣的是,這個地方能夠傳入多個接口。
基本使用
Flask-Restful
插件爲咱們提供了相似以前的 WTForm
表單驗證的包,能夠用來驗證提交的數據是否合法,叫作 reqparse
。基本用法以下(3步驟):
parser = reqparse.RequestParser() # 初始化一個 RequestParser 對象 parser.add_argument('password',type=int,help='password input error') # 指定驗證的參數名稱,類型以及驗證不經過時的提示信息 args = parser.parse_args() # 執行驗證
完整代碼以下:
from flask_restful import Api,Resource,reqparse class LoginView(Resource): def post(self): # post 方法提交數據時傳入的 username 和 password,這裏不須要定義 parser = reqparse.RequestParser() parser.add_argument('username',type=str,help='用戶名格式錯誤') # 若是提交數據時沒傳入,默認爲 None parser.add_argument('age',type=int,help='密碼錯誤') args = parser.parse_args() print(args) return {'username':'MYYD'}
add_argument
解析
在使用 add_argument
對上傳的數據進行驗證時,能夠根據需求使用不一樣的選項進行驗證,經常使用的選項有:
default
:默認值,若是沒有傳入該參數,則使用default
爲該參數指定默認的值。required
:置爲True
時(默認爲False
),該參數必須傳入值,不然拋出異常。type
:指定該參數的類型,並進行強制轉換,若強制轉換失敗則拋出異常。choices
:至關於枚舉類型,即該傳入的參數只能爲choices
列表中指定的值。help
:當驗證失敗時拋出的異常信息。trim
:置爲True
時對上傳的數據進行去空格處理(只去掉字符串先後的空格,不去掉字符串之間的空格)。
其中,type
選項除了能夠指定 python
自帶的一些數據類型外,還能夠指定 flask_restful.inputs
下的一些特定類型來進行強制轉換。經常使用的類型以下:
url
:會判斷上傳的這個參數是否是一個url
,若不是則拋出異常。regex
:會判斷上傳的這個參數是否符合正則表達式中的格式,若不符合則拋出異常。date
:將上傳的這個參數強制轉換成datetime.date
類型,若轉換不成功則拋出異常。
在使用 type
指定 flask_restful.inputs
數據類型時的用法以下:
parser.add_argument('birthday',type=inputs.date,help='日期輸入錯誤')
返回數據時候可使用最原始的方法,返回一個字典。可是 Restful
推薦咱們使用 Restful
方法,以下:
marshal_with
(字典名) 傳入字典名稱最後返回數據就好了,以下:
from flask_restful import Api,Resource,fields,marshal_with api = Api(app) class Article(object): def __init__(self,title,content): self.title = title self.content = content artilce = Article('MYYD','wuba luba dub dub') class LoginView(Resource): resource_field = { 'title': fields.String, 'content': fields.String } @marshal_with(resource_field) def get(self): return artilce # 能夠直接返回 Article 的實例,會拿到 article 對象的兩個屬性並返回 api.add_resource(LoginView,'/login/',endpoint='login')
這樣作的好處是:
article
對象只有 title
屬性而沒有 content
屬性,也會返回 content
的值,只不過該值被置爲 None
。對於一個類視圖,能夠指定好一些數據字段用於返回。指定的這些數據字段,在此後使用 ORM
模型或者自定義模型時,會自動獲取模型中的相應字段,生成 Json
數據,並返回給客戶端。對於擁有子屬性的字段而言,若想成功獲取其屬性並返回給客戶端,須要引用 fields.Nested
並在其中定義子屬性的字段。整個例子以下:
模型關係
class User(db.Model): __tablename__ = 'user' id = db.Column(db.Integer,primary_key=True) username = db.Column(db.String(50),nullable=False) email = db.Column(db.String(50),nullable=False) article_tag_table = db.Table( 'article_tag', db.Column('article_id',db.Integer,db.ForeignKey("article.id"),primary_key=True), db.Column('tag_id',db.Integer,db.ForeignKey("tag.id"),primary_key=True) ) class Article(db.Model): __tablename__ = 'article' id = db.Column(db.Integer,primary_key=True) title = db.Column(db.String(50),nullable=False) content = db.Column(db.Text) author_id = db.Column(db.Integer,db.ForeignKey('user.id')) author = db.relationship('User',backref='articles') tags = db.relationship('Tag',secondary=article_tag_table,backref='articles') class Tag(db.Model): __tablename__ = 'tag' id = db.Column(db.Integer,primary_key=True) name = db.Column(db.String(50),nullable=False)
返回時定義的數據字段
注意這裏有三點必須實現:
- 導入相關包並初始化
app
- 定義返回數據的字段
- 使用裝飾器
marshal_with
傳入定義的數據字段
from flask_restful import Api,Resource,fields,marshal_with api = Api(app) class ArticleView(Resource): article_detail = { 'article_title': fields.String(attribute='title'), 'content': fields.String, 'author': fields.Nested({ # 返回有子屬性的字段時要用 fields.Nested() 'username': fields.String, 'email': fields.String, 'age': fields.Integer(default=1) }), 'tags': fields.Nested({ # 返回有子屬性的字段時要用 fields.Nested() 'name': fields.String }) } @marshal_with(article_detail) def get(self,article_id): article = Article.query.filter_by(id=article_id).first() return article
重命名屬性很簡單,就是返回的時候使用不一樣於模型自己的字段名稱,此操做須要藉助 attribute
選項。以下所示代碼:
article_detail = { 'article_title': fields.String(attribute='title') }
Article
模型中的屬性本來是 title
,可是要返回的字段想要命名爲 article_title
。若是不使用 attribute
選項,則在返回時會去 Article
模型中找 article_title
屬性,很明顯是找不到的,這樣以來要返回的 article_title
字段會被置爲 Null
。使用 attribute
選項後,當返回 article_title
字段時,會去 Article
模型中找 attribute
選項指定的 title
屬性,這樣就能夠成功返回了。
當要返回的字段沒有值時,會被置爲 Null
,若是不想置爲 Null
,則須要指定一個默認的值,此操做須要藉助 default
選項。以下代碼所示:
article_detail = { 'article_title': fields.String(attribute='title') 'readed_number': fields.Integer(default=0) }
當想要返回一篇文章的閱讀量時,若沒有從模型中獲取到該字段的值,若不使用 default
選項則該字段會被置爲 Null
;若使用了該選項,則該字段會被置爲 0
。
實際上,flask-restful
還能夠嵌套在藍圖中使用,也能返回一個 html
模板文件。
嵌套藍圖使用
搭配藍圖使用時,在註冊 api
時就不須要使用 app
了,而是使用藍圖的名稱,以下:
article_bp = Blueprint('article',__name__,url_prefix='/article') api = Api(article_bp)
其餘的和以前同樣,不過要在主 app 文件中註冊一下藍圖。
渲染模板
若是想使用 flask-restful
返回 html
模板,則必須使用 api.representation()
裝飾器來轉換返回數據的類型,並根據該裝飾器定義一個函數,用於返回該模板,以下:
from flask import render_template,make_response @api.representation('text/html') def outPrintListForArticle(data,code,headers): # 這裏要傳入這三個參數 resp = make_response(data) # 其中,data 就是模板的 html 代碼 return resp class ListView(Resource): def get(self): return render_template('list.html') api.add_resource(ListView,'/list/',endpoint='list')