Flask_8

自定義驗證器

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表單字段代碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章