Web後端學習筆記 Flask(8) WTForms 表單驗證,文件上傳

Flask-WTF是簡化了WTForms操作的一個第三方庫。WTForms表單的兩個主要功能是驗證用戶提交數據的合法性以及渲染模板。同時還包含一些其他的功能。例如CSRF保護,文件上傳等功能,安裝flask-wtf也會默認安裝WTForms,通過pip方式安裝:

pip install flask-wtf

表單驗證

1. 自定義一個表單類,繼承自wtform.Form類。

2. 定義好需要驗證的字段,字段的名字必須和模板中input標籤的name字段名字相同

3. 在需要驗證的字段上,需要指定好具體的數據類型

4. 在相關的字段上,指定好驗證器

5. 在視圖中就只需要表單類的對象,進行表單驗證。

from wtforms import Form, StringField
from wtforms.validators import Length, EqualTo

# 表單驗證類
class RegisterForm(Form):
    # 導入驗證器
    # message爲驗證錯誤時拋出的錯誤信息
    username = StringField(validators=[Length(min=3, max=10,
                                              message="用戶名長度必須3到10位")])
    password = StringField(validators=[Length(min=6, max=10,
                                              message="密碼長度必須6到10位")])
    repeat_password = StringField(validators=[Length(min=6, max=10, message="密碼錯誤"),
                                              EqualTo("password")])

視圖函數的驗證:

@app.route('/register/', methods=["GET", "POST"])
def register():
    """
    表單驗證
    :return:
    """
    if request.method == "GET":
        return render_template("html/register.html")
    else:
        form = RegisterForm(request.form)
        if form.validate():
            return "success"
        else:
            print(form.errors)    # 獲取具體的錯誤信息
            return "Fail"

WTForm常用的驗證器:
Email:驗證上傳的數據是否爲郵箱

EqualTo:驗證上傳的數據是否和另一個字段相等

InputRequired:原始數據的需要驗證,如果不是特殊情況,應該使用InputRequired,即指定這個字段必須是要傳的,否則會報錯

Length:長度限制

NumberRange:數字的區間

Regexp:自定義正則表達式

URL:必須要是url的形式

UUID:驗證uuid

# -*- coding: utf-8 -*-
from wtforms import Form, StringField, IntegerField
from wtforms.validators import Length, EqualTo, InputRequired, Email, NumberRange, URL, UUID, Regexp


# 表單驗證類
class RegisterForm(Form):
    # 導入驗證器
    # message爲驗證錯誤時拋出的錯誤信息
    username = StringField(validators=[Length(min=3, max=10,
                                              message="用戶名長度必須3到10位")])
    password = StringField(validators=[Length(min=6, max=10,
                                              message="密碼長度必須6到10位")])
    repeat_password = StringField(validators=[Length(min=6, max=10, message="密碼錯誤"),
                                              EqualTo("password")])


# 表單驗證類
class LoginForm(Form):
    # email = StringField(validators=[Email(message="郵箱格式錯誤")])
    username = StringField(validators=[InputRequired(message="用戶名不能爲空")])
    age = IntegerField(validators=[NumberRange(20, 55)])
    phone = StringField(validators=[Regexp(r"1[38745]\d{9}")])

自定義驗證器:

當WTForm中的驗證器不能滿足業務需求的時候,用戶可以自定義自己的驗證器:

例如自定義驗證碼的驗證器:

1. 定義一個方法,方法的命名規則是 validate_字段名(self,field)

2. 在方法中使用field.data可以獲取到字段的具體值

3. 如果驗證成功,那麼可以什麼都不做

4. 如果驗證失敗,需要拋出一個異常

# 表單驗證類
class LoginForm(Form):
    # email = StringField(validators=[Email(message="郵箱格式錯誤")])
    # username = StringField(validators=[InputRequired(message="用戶名不能爲空")])
    # age = IntegerField(validators=[NumberRange(20, 55)])
    # phone = StringField(validators=[Regexp(r"1[38745]\d{9}")])
    captcha = StringField(validators=[Length(4, 4)])  # 首先驗證長度

    # 假設驗證碼爲1234, 自定義captcha驗證器,函數名必須以validate開頭
    # 所以在驗證captcha的時候,除了驗證Length,還會自動調用下面自定義的函數進行驗證
    def validate_captcha(self, field):   # 針對具體的字段做驗證
        # 通過field.data獲取數據
        if field.data == "1234":
            return "Success"
        else:
            raise ValidationError("驗證碼錯誤")

WTForm渲染模板(不推薦使用)

WTForm可以結合jinja2模板渲染表單元素,例如可以渲染各種input, radio等標籤

1. 首先,在form.py文件中定義相關的表單類,定義相關的字段

class SettingForm(Form):
    username = StringField("用戶名", validators=[InputRequired()])
    age = IntegerField("年齡", validators=[NumberRange(20, 60)])
    remember = BooleanField("記住我: ")
    tags = SelectField("標籤", choices=[("1", "Java"), ("2", "C++"), ("3", "Android")]

2. 在視圖函數中,將實例化的form對象作爲參數,傳遞到模板文件中:

@app.route('/settings/', methods=["GET", "POST"])
def setting():
    if request.method == "GET":
        form = SettingForm()     # 利用form渲染模板
        return render_template("html/settings.html", form=form)   # 傳遞參數
    else:
        form = SettingForm(request.form)
        pass

3. 在模板文件中,可以通過變量綁定來使用,同時,可以對相應的字段傳入樣式參數,id, class

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>設置</title>
    <style>
        .username-input
        {
            background: blue;
        }
        #age-input
        {
            background: pink;
        }
    </style>
</head>
<body>
    <form action="" method="post">
        <table>
            <tbody>
                <tr>
                    <!--利用form渲染頁面-->
                    <td>{{ form.username.label }}</td>
{#                    <td>用戶名</td>#}
{#                    <td><input type="text" name="username"></td>#}
                    <td>{{ form.username(class="username-input") }}</td>
                </tr>
                <tr>
                    <!--傳入參數-->
                    <td>{{ form.age.label }}</td>
                    <td>{{ form.age(id="age-input") }}</td>
                </tr>
                <tr>
                    <td>{{ form.remember.label }}</td>
                    <td>{{ form.remember() }}</td>
                </tr>
                <tr>
                    <td>{{ form.tags.label }}</td>
                    <td>{{ form.tags() }}</td>
                </tr>
                <tr>
                    <td></td>
                    <td><input type="submit" value="提交"></td>
                </tr>
            </tbody>
        </table>
    </form>
</body>
</html>

渲染的效果:

文件上傳

1. 在模板中,需要指定input標籤的enctype才能上傳文件。upload.html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>上傳</title>
</head>
<body>
    <form action="" method="post" enctype="multipart/form-data">   <!--上傳文件要寫enctype-->
        <table>
            <tbody>
                <tr>
                    <td>頭像: </td>
                    <td><input type="file" name="avatar"></td>
                </tr>
                <tr>
                    <td>描述: </td>
                    <td><input type="text" name="desc"></td>
                </tr>
                <tr>
                    <td></td>
                    <td><input type="submit" value="提交"></td>
                </tr>
            </tbody>
        </table>
    </form>
</body>
</html>

完成上傳界面:

2. 在後臺如果想要獲取上傳的文件,那麼應該使用request.files.get(name)獲取上傳的文件,這裏的name值表示的是對應的input標籤的name屬性的值。

@app.route('/upload/', methods=["GET", "POST"])
def upload():
    if request.method == "GET":
        return render_template("html/upload.html")
    else:
        # 接受用戶上傳的數據
        desc = request.form.get("desc")
        avatar = request.files.get("avatar")        # 獲取文件
        # 保存文件
        file_name = secure_filename(avatar.filename)  # 對文件名進行安全檢測
        avatar.save("uploaded/" + file_name)
        print(desc)
        return "文件上傳成功"

3. 獲取到上傳的文件後,可以使用save方法保存文件

【注】: 在保存文件之前,需要使用secure_filename對文件名進行過濾,消除安全隱患:

from werkzeug.utils import secure_filename    # 對文件名進行包裝

4. 在服務器上讀取文件,應該定義一個url來獲取指定文件,在視圖函數中使用send_from_directory(文件目錄,文件名)來獲取服務器上的文件:

@app.route('/show/<file_name>/')
def show_image(file_name):
    return send_from_directory("uploaded/", filename=file_name)

完整的app.py文件如下:

from flask import Flask, url_for, request, render_template
import config
from exts import db
from forms import RegisterForm, LoginForm, SettingForm
import os
from werkzeug.utils import secure_filename    # 對文件名進行包裝
from flask import send_from_directory

app = Flask(__name__)
app.config.from_object(config)
db.init_app(app)    # db獲取app中數據庫的連接方式


@app.route('/')
def hello_world():
    return 'Hello World!'


@app.route('/profile/')
def profile():
    return "profile page"


@app.route('/register/', methods=["GET", "POST"])
def register():
    """
    表單驗證
    :return:
    """
    if request.method == "GET":
        return render_template("html/register.html")
    else:
        form = RegisterForm(request.form)
        if form.validate():
            return "success"
        else:
            print(form.errors)    # 獲取具體的錯誤信息
            return "Fail"


@app.route('/login/', methods=["GET", "POST"])
def login():
    if request.method == "GET":
        return render_template("html/login.html")
    else:
        form = LoginForm(request.form)
        if form.validate():
            return "success"
        else:
            print(form.errors)
            return "Failed"


@app.route('/settings/', methods=["GET", "POST"])
def setting():
    if request.method == "GET":
        form = SettingForm()     # 利用form渲染模板
        return render_template("html/settings.html", form=form)
    else:
        form = SettingForm(request.form)
        pass


@app.route('/upload/', methods=["GET", "POST"])
def upload():
    if request.method == "GET":
        return render_template("html/upload.html")
    else:
        # 接受用戶上傳的數據
        desc = request.form.get("desc")
        avatar = request.files.get("avatar")        # 獲取文件
        # 保存文件
        file_name = secure_filename(avatar.filename)  # 對文件名進行安全檢測
        avatar.save("uploaded/" + file_name)
        print(desc)
        return "文件上傳成功"


@app.route('/show/<file_name>/')
def show_image(file_name):
    return send_from_directory("uploaded/", filename=file_name)


if __name__ == '__main__':
    app.run()

利用Flask_wtf驗證上傳的文件:

1. 對上傳的問價使用表單驗證,在定義表單的時候,對文件的字段需要採用FileField進行驗證

2. 驗證器應該從flask_wtf.file中導入,需要使用的是flask_wtf.file.FileRequired驗證文件是否爲空,flask_wtf.file.FileAllowed

驗證文件的格式是否滿足要求。

3. 在進行驗證的時候,因爲上傳的字符串需要使用request.form進行獲取,上傳的文件需要使用request.file進行獲取,所以在進行驗證的時候,需要對這兩個數據進行組合,這兩個數據都是immutableDict,不可變字典的形式,所以在視圖函數中使用CombineMultiDict將字符串和文件組合在一起,作爲表單驗證的輸入參數。

示例代碼:

form.py中定義的驗證表單

from flask_wtf.file import FileRequired, FileAllowed    # 驗證文件必須上傳,以及上傳的類型
# 定義上傳表單驗證
class UploadForm(Form):
    avatar = FileField(validators=[FileRequired(), FileAllowed(["jpg", "png", "gif"])])
    desc = StringField(validators=[InputRequired()])

視圖函數:

from werkzeug.datastructures import CombinedMultiDict   # 將兩個不可變的字典組合到一起

@app.route('/upload/', methods=["GET", "POST"])
def upload():
    if request.method == "GET":
        return render_template("html/upload.html")
    else:
        # 接受用戶上傳的數據
        # form = UploadForm(request.form)   # 因爲這裏字符串是根據request.form獲取的 文件時根據request.form獲取的,需要組合
        form = UploadForm(CombinedMultiDict([request.form, request.files]))
        if form.validate():
            desc = request.form.get("desc")
            avatar = request.files.get("avatar")  # 獲取文件
            
            # 另一種數據獲取方法
            # desc_ = form.desc.data
            # avatar_ = form.avatar.data
            
            # 保存文件
            file_name = secure_filename(avatar.filename)  # 對文件名進行安全檢測
            avatar.save("uploaded/" + file_name)
            print(desc)
            return "文件上傳成功"
        else:
            print(form.errors)
            return "文件上傳失敗"

 

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