自定義驗證器
WTForms中的驗證器指的是在定義字段時傳入validatiors參數列表的可調用對象。
行內驗證器
以下代碼中定義的validate_answer方法對answer進行驗證,如果標籤內填入的不是66,拋出異常‘Must be 66!’。
class AnotherForm(FlaskForm): answer = IntegerField('The Number', validators=[DataRequired()]) submit = SubmitField('提交') def validate_answer(self, field): if field.data != 66: raise ValidationError('Must be 66!')
當表單類中包含“validate_字段屬性名”這樣的方法時,在驗證字段時會同時調用這個方法來驗證對應字段,這也是爲什麼表單字段不能以validate開頭的原因。驗證方法接收兩個參數,就是這段代碼中的self和field,前者爲表單實例,後者爲字段對象,我們可以通過field.data來接收字段數據。ValidationError()用來拋出異常,需要從wtforms.validators模塊中導入。
演示流程如下:
編寫視函數圖
@app.route('/hang_nei_yan_zheng_qi', methods=['POST', 'GET']) def hang_nei_yan_zheng_qi(): form = AnotherForm() if form.validate_on_submit(): ##驗證成功返回3333333... return '33333333333' return render_template('hang_nei_yan_zheng_qi.html', form=form)
構造表單類
class AnotherForm(FlaskForm): answer = IntegerField('The Number', validators=[DataRequired()]) submit = SubmitField('提交') def validate_answer(self, field): if field.data != 66: raise ValidationError('Must be 66!')
編寫模板 hang_nei_yan_zheng_qi.html
<form method="post"> {{ form.csrf_token }} {{ form.answer.label }}{{ form.answer }}<br> {% for message in form.answer.errors %} ##顯示拋出的異常 {{ message }}<br> {% endfor %} {{ form.submit }}<br> </form>
全局驗證器
通過定義函數來實現一個全局通用的驗證器。如果這是一個不需要傳入參數的驗證器,要實現它非常簡單。以上面行內驗證器爲例,只需要把驗證器
def is_66(form, field): if field.data != 66: raise ValidationError('Must be 66!')
寫在外部,然後在需要的時候在validators裏傳入驗證器對象即可。
class AnotherForm(FlaskForm): answer = IntegerField('The Number', validators=[DataRequired(), is_66]) submit = SubmitField('提交')
如果需要一個可以接收參數的驗證器,需要把驗證函數實現爲一個工廠函數。
def is_66_or_sth_else(number=66): def _is_66(form, field): if field.data != number: raise ValidationError('Must be %d!'% number) return _is_66
修改成只有77才能通過驗證
class AnotherForm(FlaskForm): answer = IntegerField('The Number', validators=[DataRequired(), is_66_or_sth_else(77)]) submit = SubmitField('提交')
文件上傳
在HTML中,渲染一個文件上傳字段只需要將<input>標籤的type屬性設置爲file。在服務器端,可以和普通數據一樣上傳文件數據並保存,此外出於安全考慮或者其他原因,我們還需要注意驗證文件類型、驗證文件大小、過濾文件名。
定義上傳表單
在python表單類中創建文件上傳字段時,我們使用擴展Flask-WTF提供的FileField類,它繼承WTForms提供的上下文字段FlieField,添加了對Flask的集成。下面是一個包含文件上下文字段的表單
class UploadForm(FlaskForm): photo = FileField('Upload Image', validators=[FileRequired(), FileAllowed(['jpg', 'png', 'gif'])]) submit = SubmitField()
這裏用到了2個驗證器
FileRequired() 驗證是否包含文件對象
FileAllowed() 驗證文件類型
如果想限制上傳文件大小,需要修改Flask內置變量MAX_CONTENT_LENGTH。例如修改最大上傳爲3M
app.config['MAX_CONTENT_LENGTH'] = 3 * 1024 * 1024
當上傳超過限制,就會返回413錯誤響應。
渲染上傳表單
實例化表單類
@app.route('/upload', methods=['POST', 'GET']) def upload(): form = UploadForm() return render_template('upload.html', form=form)
傳入模板
<form method="post" enctype="multipart/form-data"> {{ form.csrf_token }} {{ form_field(form.photo) }} {{ form.submit }} </form>
需要注意的是,當有type爲file的input標籤時,需要將表單的enctype屬性設置爲"multipart/form-data",這會告訴瀏覽器將上傳數據發送到服務器,否則僅會把文件名作爲表單數據提交。
處理上傳文件
當包含文件上傳字段的表單提交後,上傳的文件需要在請求對象的files屬性(request.files)中獲取。手動處理時,要使用文件上傳字段的name屬性值作爲鍵獲取對應文件對象,例如
request.files.get('photo')
而Flask-WTF會自動幫我們獲取對應文件對象
@app.route('/upload', methods=['POST', 'GET']) def upload(): form = UploadForm() if form.validate_on_submit(): f = form.photo.data filename = random_filename(f.filename) f.save(os.path.join(app.config['UPLOAD_PATH'],filename)) flash('Upload success!') session['filenames'] = [filename] return redirect(url_for('show_images')) return render_template('upload.html', form=form)
當表單通過驗證後,通過form.photo.data獲取存儲上傳文件的FileStorage對象,接下來處理文件名。
通常有3種處理文件名的方法
1.使用原文件名
filename = f.name
2.使用過濾後的文件名
爲了防止攻擊者在文件名中添加惡意路徑,我們用secure_filename()函數來過濾文件名,這裏就不演示拉。
3.統一重命名
secure_filename()函數很方便,它會過濾掉所有非ASCII字符,如果文件名全由非ASCII碼構成,那麼就會返回一個空文件名。爲了避免這種狀況,最好的辦法就是統一對所有上傳的文件重命名。隨機文件名有很多辦法實現,下列函數使用Python內置uuid模塊來生成隨機文件名
def random_filename(filename): ext = os.path.splitext(filename)[1] new_filename = uuid.uuid4().hex + ext return new_filename
這個函數接收原文件名作爲參數,使用uuid模塊中的uuid4()方法生成新的文件名,並使用hex屬性獲取16進制字符串最後返回包含後綴的新文件名。
處理完文件名後,就該將文件保存到文件系統中了。我在目錄下創建了一個upload文件夾用來保存上傳的文件。指向這個文件夾的絕對路徑存儲在自定義配置變量PULOAD_PATH中
app.config['UPLOAD_PATH'] = os.path.join(app.root_path, 'uploads')
app.root_path存儲了程序實例所在腳本的絕對路徑。
爲了讓上傳後的文件能夠通過URL獲取,我們創建一個視圖來返回上傳後的圖片
@app.route('/uploads/<path:filename>') def get_file(filename): return send_from_directory(app.config['UPLOAD_PATH'],filename)
在upload視圖中保存文件後,使用flash()來提示保存成功,將文件名保存到session中,最後重定向到show_images視圖。show_images視圖返回的upload.html模板中將從session中獲取文件名,渲染出上傳的圖片。
flash('Upload success!') session['filenames'] = [filename] return redirect(url_for('show_images'))
upload.html如下
<head> {% from 'macros.html' import form_field %} </head> <form method="post" enctype="multipart/form-data"> {{ form.csrf_token }} {{ form_field(form.photo) }} {{ form.submit }} </form>
form_field是一個表單渲染宏,接收表單實例的字段屬性和附加的關鍵字參數,返回包含<label>標籤、表單字段、錯誤消息列表的HTML表單字段代碼。