Flask 表单

485 阅读12分钟

基础表单

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>****
</head>
<body>
    <form method="post" action="">
        <label for="username">Username</label>
        <input type="text" name="username" placeholder="Hector Rivera"><br>
        <label for="password">Password</label>
        <input type="password" name="password" placeholder="19001130"><br>
        <input id="remember" name="remember" type="checkbox" checked>
        <label for="remember"><small>Remember me</small></label><br>
        <input type="submit" name="submit" value="Log in">
    </form>
</body>
</html>

使用Flask-WTF处理表单

pip install flask-wtf

Flask-WTF默认为每个表单启动CSRF保护,会自动生成和验证CSRF令牌

app.secret_key = 'secret key'

定义表单

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<div align="center">
<h2>User Management</h2>
    {% if message %} {{message}} {% endif %}
    <form method="POST">
        username:{{ form.username }}
        <br>
        password:{{ form.password }}
        <br>
        <!-- <input type="submit" value="Submit"> -->
        <br>
        {{ form.submit }}
        <input type="reset" value="reset">
    </form>
</div>

</body>
</html>

---------------------------------------------------
---------------------------------------------------
from flask_wtf import FlaskForm
from wtforms import Form

# 使用WTForms创建表单
class LoginForm(Form):
# 使用Flask-WTF创建表单
class LoginForm(FlaskForm):
    username = StringField('username', validators=[DataRequired()])
    password = PasswordField('password', validators=[DataRequired(),Length(8,128)])
    remember = BooleanField('remember me')
    submit = SubmitField('Log in')
    
@app.route('/login_base',methods=["GET","POST"])
def login_base():
    login_form = LoginForm()
    if request.method == 'POST':
        return render_template('flash.html')
    return render_template('login.html',form = login_form)

常用的WTForm字段

字段类说明对应的HTML表示
BooleanField复选框,值会被处理为True或False<input type="checkbox">
DateField文本字段,值会被处理为datetime.data对象<input type="text">
DateTimeField文本字段,值会被处理为datetime.datetime对象<input type="text">
FileField文件上传字段<input type="file">
FloatField浮点数字段,值会被处理为浮点型<input type="text">
IntegerField整数字段,值会被处理为整型<input type="text">
RadioField一组单选按钮<input type="radio">
SelectField下拉列表<select><option></option></select>
SelectMultipleField多选下拉列表<select multiple><option></option></select>
SubmitField提交按钮<input type="submit">
StringField文本字段<input type="text">
HiddenField隐藏文本字段<input type="hidden">
PasswordField密码文本字段<input type="password">
TextAreaField多行文本字段<textarea></textarea>
username = StringField('username', validators=[])

实例化字段常用参数

参数说明
label字段标签 <label> 的值,也就是渲染后显示在输入字段前的文字
render_kw一个字典,用来设置对应的HTML<input> 标签的属性,比如传入 {'placeholder':'Your Name'},渲染后HTML代码会将<input>标签的placeholder属性设置为Your Name
validators一个列表, 包含一系列验证器,会在表单提交后被逐一调用验证表单数据
default字符串或可调用对象,用来为表单字段设置默认值

常用的WTFforms验证器(wtforms.validators模块导入)

验证器说明
DataRequired(message=None)验证数据是否有效
Email(message=None)验证Email地址
EqualTo(fieldname, message=None)验证两个字段值是否相同
InputRequired(message = None)验证两个字段值是否相同
InputRequired(message=None)验证是否有数据
Length(min=1, max=-1, message=None)验证输入值长度是否在给定范围内
NumberRange(min=None, max=None,message=None)验证输入数字是否在给定范围内
Optional(strip_whitespace = True)允许输入值为空,并跳过其他验证
Regexp(regex, flags=0, message=None)使用正则表达式验证输入值
URL(require_tld=True, message=None)验证URL
AnyOf(values.message=None, values_formatter=None)确保输入值在可选值列表中
NoneOf(values, message=None, values_formatter=None)确保输入值不在可选值列表中

message、re 参数用来传入自定义错误信息,如果没传则使用默认的错误信息(英文)。

实例化字段参数

  • 使用render_kw属性render_kw={"placeholder":"You username"}
username = StringField('Username', render_kw={"placeholder":"You username"})
  • 调用字段时传入
form.username(style='width:200px;',class='bar')

u'<input class="bar" id="username" name="username" style="width: 200px;" type="text">'

在模板中渲染表单

templates/login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<div align="center">
<h2>User Management</h2>
    {% if message %} {{message}} {% endif %}
    <form method="POST">
        **{{form.username.label}}**:**{{ form.username }}**
        <br>
        {{form.password.label}}:{{ form.password }}
        <br>
        <!-- <input type="submit" value="Submit"> -->
        <br>
        {{form.remember.label}}:{{ form.remember }}
        {{ form.submit }}
        **{{form.csrf_token}} # 包含了自动生成的scrf令牌值,在提交表单后悔自动被验证。**
    </form>
</div>

</body>
</html>
===========================================
app.py
from wtforms import StringField, Form, PasswordField,BooleanField,SubmitField
from wtforms.validators import DataRequired, Length
from flask_wtf import FlaskForm

class LoginForm(FlaskForm):
    username = StringField('username', validators=[DataRequired()])
    password = PasswordField('password', validators=[DataRequired(),Length(8,128)])
    remember = BooleanField('remember me')
    submit = SubmitField('Log in')

@app.route('/login_base',methods=["GET","POST"])
def login_base():
    login_form = LoginForm()
    if request.method == 'POST':
        return render_template('flash.html')
    return render_template('login.html',form = login_form)

# 在浏览器中请求 "http://127.0.0.1:5000/login_base" 就可以看到表单页面

处理表单数据:(流程)

表单数据的处理涉及很多内容,除去表单提交不说,从获取数据到保存数据大致会经历以下步骤: 1)解析请求,获取表单数据。 2)对数据进行必要的转换,比如将勾选框的值转换成Python的布尔值。 3)验证数据是否符合要求,同时验证CSRF令牌。 4)如果验证未通过则需要生成错误消息,并在模板中显示错误消息。 5)如果通过验证,就把数据保存到数据库或做进一步处理。 除非是简单的程序,否则手动处理不太现实,使用Flask-WTF和WTForms可以极大地简化这些步骤。

HTML中表单控制提交的行为属性默认值说明
action当前URL,即页面对应的URL表单提交时发送请求的目标URL
methodget提交表单的HTTP请求方法,目前仅仅支持使用GET和POST方法
enctypeapplication/x-www-form-urlencoded表单数据的编码类型,当表单中包含文件上传字段时,需要设为multipart/form-data, 还可以设为纯文本类型text/plain

GET请求:

http://localhost:5000/basic?username=greyli&password=12345

POST请求:

POST /login HTTP/1.0
Content-Type: application/x-www-from-urlencoded
Content-Length: 30

username=wuhan&password=12345

Flask为路由设置默认监听方法为GET,要是接收提交表单的POST接口的话,就需要进行指定。

@app.route(methods=['GET','POST'])

表单验证机制

客户端验证

{{ form.username(required='') }} #验证是否输入可以使用这种方式

服务端验证

  • WTForms验证机制
    • 在实例化表单类时传入表单数据,然后对其调用validate() 方法进行验证。(返回布尔值)
>>> from wtforms import Form, StringField, PasswordField, BooleanField 
>>> from wtforms.validators import DataRequired, Length                
>>> class LoginForm(Form):                                             
...     username = StringField('Username', **validators=[DataRequired()]**)
...     password = PasswordField('Password', validators=[DataRequired()
, Length(8, 128)])
>>> form = LoginForm(username='', password='123')                      
>>> form.data  # 表单数据字典
{'username': '', 'password': '123'}
>>> **form.validate()**
False
>>> **form.errors ** # 错误消息字典
{'username': [u'This field is required.'], 'password': [u'Field must be
 between 8 and 128 characters long.']}
>>> form2 = LoginForm(username='greyli', password='12345678')            
>>> form2.data
{'username': 'greyli', 'password': '12345678'}
>>> form2.validate()
True
>>> form2.errors
{}													

在视图函数中验证表单 - if request.method == 'POST' and login_form.validate(): 提交表单和验证分开

@app.route('/login_base',methods=["GET","POST"])
def login_base():
    login_form = LoginForm()
    if request.method == 'POST' **and login_form.validate()**:
        return render_template('flash.html')
    return render_template('login.html',form = login_form)
  • validate_on_submit():提交表单和验证合并
@app.route('/login_base',methods=["GET","POST"])
def login_base():
    login_form = LoginForm()
    **if ****form.validate_on_submit():**
        return render_template('flash.html')
    return render_template('login.html',form = login_form)

表单验证与获取数据

获取数据方式: from.字段名.data

from flask import Flask, render_template, redirect, url_for, flash
...
@app.route('/basic', methods=['GET', 'POST'])
def basic():
    form = LoginForm()
    if form.validate_on_submit(): # 表单验证
        username = form.username.data # 获取数据
        flash('Welcome home, %s!' % username)
        return redirect(url_for('index'))
    return render_template('basic.html', form=form)

在浏览器中,当单击F5刷新/重载时的默认行为是发送上一个请求。如果上一个请求是POST请求,那么就会弹出一个确认窗口,询问用户是否再次提交表单。为了避免出现这个容易让人产生困惑的提示,我们尽量不要让提交表单的POST请求作为最后一个请求。这就是为什么我们在处理表单后返回一个重定向响应,这会让浏览器重新发送一个新的GET请求到重定向的目标URL。最终,最后一个请求就变成了GET请求。这种用来防止重复提交表单的技术称为PRG(Post/Redirect/Get)模式,即通过对提交表单的POST请求返回重定向响应将最后一个请求转换为GET请求。

在模板中渲染错误消息

使用form.validate_on_submit() 返回Fasle 之后,验证没通过,此时WTForms会吧错误消息添加到errors中、然后我们在模版中就可以取出这个errors列表(遍历)form.字段名.errors

<form method="post">
    {{ form.csrf_token }}
    {{ form.username.label }}<br>
    {{ form.username }}<br>
    **{% for message in form.username.errors %}  # 因为特性原因所以验证的时候可以输入 空格 来验证报错**
        <small class="error">{{ message }}</small><br>
    {% endfor %}
    {{ form.password.label }}<br>
 <form method="post">
    {{ form.csrf_token }}
    {{ form.username.label }}<br>
    {{ form.username }}<br>
    {% for message in form.username.errors %}
        <small class="error">{{ message }}</small><br>
    {% endfor %}
    {{ form.password.label }}<br>


使用内置错误消息为中文

# 设置内置的错误消息语言为Z洪文

app.config['WTF_I18N_ENABLED'] = False
class MyBaseForm(FlaskForm):
    class Meta:
        locales = ['zh']

class HelloForm(MyBaseForm):
    name = StringField("Name", validators=[DataRequired()])
    submit = SubmitField()
===========================
也可以在实例化表单时通过meta关键字传入locales值
form = MyForm(meta={'locales':['zh','zh_TW']})

使用 宏 来渲染表单

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    {% macro qux(amount=1) %}
        {% if amount == 1%}
            I am qux
        {% elif amount > 1 %}
            We are quxs.
        {% endif %}
    {% endmacro %}
    
# 渲染表单的宏
    {% macro get_field(field)%}
        {{ field.label }}
        {{ field(**kwargs) }}
        {% if field.errors %}
            {% for error in field.errors %}
                <small class="error">{{ error }}</small>
            {% endfor %}
        {% endif %}
    {% endmacro %}
    
</body>
</html>

在html文件中使用

{% from 'macro.html' import get_field %}
        
{{ get_field(form.username) }}<br/>
{{ get_field(form.password) }}<br/>
{{ form.submit(class='btn btn-primary') }}

自定义验证器

  • 使用validate_字段属性名 形式命名的方法时(自动调用),提交表单的时候会自动进行验证。
from wtforms import StringField, Form, PasswordField, \
    BooleanField,SubmitField,IntegerField
from wtforms.validators import DataRequired, Length, \
    ValidationError
from flask_wtf import FlaskForm
class FortyTwoForm(FlaskForm):
    answer = IntegerField('The Number')
    submit = SubmitField()

    **def validate_answer(form, field):  # (行内验证器[in-line validator])
    # validate_answer 这个名字需要与上面定义的字段名一致
        if field.data != 42:  # field.data 获取字段数据
            raise ValidationError("must be 42.")**

@app.route('/vali_login', methods= ['GET','POST'])
def vali_login():
    forty = FortyTwoForm()
    if forty.validate_on_submit():
        return render_template('flash.html')
    return render_template('vali_login.html', form = forty)

============================================
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <form method="POST">
        {% from 'macro.html' import get_field %}
        {{form.csrf_token}}
        {{ get_field(form.answer) }}<br/>
        {{ form.submit(class='btn btn-primary') }}
    
    </form>
    
</body>
</html>

  • 全局验证器
from wtforms.validators import ValidationError

def is_42(form, field):
  if field.data != 42:
    raise ValidationError('Must be 42.')
    
class FortyTwoForm(FlaskForm):
    answer = IntegerField('The Number',validators = [is_42])
    submit = SubmitField()

  • 工厂函数形式的全局验证器
from wtforms.validators import ValidationError

def is_42(message=None):
    if message is None:
        message = 'Must be 42.'

    def _is_42(form, field):
        if field.data != 42:
            raise ValidationError(message)

    return _is_42

class FortyTwoForm(FlaskForm):
    answer = IntegerField('The Number', validators=[is_42()])
    submit = SubmitField()

文件上传

定义上传表单

  • FileFIeld

# 上传文件
from flask_wtf.file import FileField, FileRequired, FileAllowed
class UploadForm(FlaskForm):
    photo = **FileField("Upload File", validators=[FileRequired(), FileAllowed(['jpg','jpeg','png','gif'])])**
    submit = SubmitField()
    #**TODO**  增加一个验证文件类型

@app.route("/file_test", methods=['GET','POST'])
def file_test():
    f_test = UploadForm()
    if f_test.validate_on_submit():
        return render_template('flash.html')
    return render_template('upload.html', form = f_test)
 =========================
 <form method="post" **enctype="multipart/form-data"**>
        {{form.csrf_token}}
        {{ form.photo }}
        {{ form.submit }}
 </form>

Flask-WTF提供的上传文件验证器

验证器说明
FileRequired(message=None)验证是否包含文件对象
FileAllowed(upload_set, message=None)用来验证文件类型,upload_set 参数用来传入允许的文件后缀名列表
  • 设置文件的最大长度(单位为字节(byte))
app.config['MAX_CONTENT_LENGTH'] = 3 * 1024 * 1024 # 大小限制为3M

处理上传的文件

from flask_wtf.file import FileField, FileRequired, FileAllowed
class UploadForm(FlaskForm):
    photo = FileField("Upload File", validators=[FileRequired(), FileAllowed(['jpg','jpeg','png','gif'])])
    submit = SubmitField()
    
import uuid
def random_filename(filename):
    ext = os.path.splitext(filename)[1]
    new_filename = uuid.uuid4().hex + ext
    return new_filename

@app.route('/upload', methods=['GET', 'POST'])
def upload():
    form = UploadForm()
    if form.validate_on_submit():
        f = form.photo.data 
        print("===>", f) # ===> <FileStorage: 'test.png' ('image/png')>
        # filename = random_filename(f.filename)
        filename = f.filename # 原文件名
        f.save(os.path.join(app.config['UPLOAD_PATH'], filename))
        
        flash('Upload success.')
        session['filenames'] = [filename]
        return redirect(url_for('upload'))
    
    return render_template('upload.html', form=form)

定义 判断上传文件类型的函数

app.config['ALLOWED_EXTENSIONS'] = ['png','jpg','jpeg','gif']
def allowed_file(filename):
    return '.' in filename and filename.rsplit('.',1 )[1].lower() in app.config['ALLOWED_EXTENSIONS']

上传文件

# 上传文件

import os

app.config['UPLOAD_PATH'] = os.path.join(app.root_path, 'static/upload')

import uuid
def random_filename(filename):
    ext = os.path.splitext(filename)[1]
    new_filename = uuid.uuid4().hex + ext
    return new_filename

from flask_wtf.csrf import validate_csrf

@app.route('/upload', methods=['GET', 'POST'])
def upload():
    form = UploadForm()
    if request.method == 'POST':
        filenames = []
        # 验证CSRF有没有进行验证
        try:
            validate_csrf(form.csrf_token.data)
        except ValidationError:
            flash('CSRF token error.')
            return redirect(url_for('upload')) 
        # 验证文件是否存在
        if 'photo' not in request.files:
            flash("This field  is required")
            return redirect(url_for('upload'))
        # 验证文件类型
        for f in request.files.getlist('photo'):
            if f and allowed_file(f.filename):
                f1 = form.photo.data 
                print("===>", f1) # ===> <FileStorage: 'test.png' ('image/png')>
                filename = random_filename(f.filename)
                f1.save(os.path.join(app.config['UPLOAD_PATH'], filename))
                filenames.append(filename)
            else:
                flash("Invalid file type.")
                return redirect(url_for('upload'))

        flash('Upload success.')
        session['filenames'] = [filename]
        return redirect(url_for('upload'))
    
    return render_template('upload.html', form=form)

  • 获取对应文件对象
request.files.get('photo')
  • 获取上传文件的FileStorage对象
form.photo.data
  • 处理文件名
    • 使用原文件名
filename = f.filename
  • 使用过滤后的文件名
from werkzeug.utils import secure_filename

secure_filename('avator@&(^&(##^&(#&.jpg')
-> avator.jpg
  • 统一重命名
def random_filename(filename):
  ext = os.path.splitext(filaname)[1]
  new_filename = uuid.uuid4().hex + ext
  return new_filename

  • 规定下载文件路径
app.config['UPLOAD_PATH'] = os.path.join(app.root_path, 'uploads')
  • 保存下载文件
f.save(os.path.join(app.config['UPLOAD_PATH'], filename))
  • 获取上传后的文件 send_from_directory():用来获取文件,传入文件得路径和文件名
@app.route('/upload/<path:filename>')
def get_file(filename):
  return **send_from_directory****(**app.config['UPLOAD_PATH'], filename)
  • 多文件上传
# 多文件上传
from wtforms import MultipleFileField
class MultiUploadForm(FlaskForm):
    photo = MultipleFileField('Upload image', validators=[DataRequired()])
    submit = SubmitField()

@app.route('/multi-upload', methods=['GET','POST'])
def multi_upload():
    form = MultiUploadForm()
    if request.method == 'POST':
        filenames = []
        # 验证CSRF令牌 
        try:
            validate_csrf(form.csrf_token.data)
        except:
            flash('CSRF token error.')
            return redirect(url_for('multi-upload'))
        # 验证文件是否存在
        if 'photo' not in request.files:
            flash('This filed is required.')
            return redirect(url_for('multi_upload'))
        # 验证文件类型是否正确
        for f in request.files.getlist('photo'):
            if f and allowed_file(f.filename):
                filename = random_filename(f.filename)
                f.save(os.path.join(app.config['UPLOAD_PATH'], filename))
                filenames.append(filename)
            else:
                flash('Invalid file type.')
                return redirect(url_for('multi_upload'))
        flash("Upload Success.")
        session['filenames'] = filenames
        return redirect(url_for('multi_upload'))
    return render_template('multi-upload.html', form=form)
                
获取图片路径

from flask import send_from_directory
@app.route('/upload/<path:filename>')
def get_file(filename):
    return send_from_directory(app.config['UPLOAD_PATH'], filename)
展示图片用
@app.route('/uploaded-images')
def show_images():
    return render_template('uploaded.html')
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <form method="post" action="" enctype="multipart/form-data">
    **{% if session.filenames %}
    {% for filename in session.filenames %}
    <a href="{{ url_for('get_file', filename=filename) }}" target="_blank"> # 使用get_file来拼接路径
      <img src="{{ url_for('get_file', filename=filename) }}">
    </a>
    {% endfor %}
    {% endif %}**
</body>
</html>

在请求方法为POST时,我们对上传数据进行手动验证,主要包含下面几步: 1)手动调用flask_wtf.csrf.validate_csrf验证CSRF令牌,传入表单中csrf_token隐藏字段的值。如果抛出wtforms.ValidationError异常则表明验证未通过。 2)其中if'photo'not in request.files用来确保字段中包含文件数据(相当于FileRequired验证器),如果用户没有选择文件就提交表单则request.files将为空。 3)if f用来确保文件对象存在,这里也可以检查f是否是FileStorage实例。 4)allowed_file(f.filename)调用了allowed_file()函数,传入文件名。这个函数相当于FileAllowed验证器,用来验证文件类型,返回布尔值,如代码清单4-17所示。

  • 验证文件类型
app.config['ALLOWED_EXTENSIONS'] = ['png','jpg','jpeg','gif']

def allowed_file(filename):
  return '.' in filename and filename.rsplit('.',1)[1].lower() in app.config['ALLOWED_EXTENSIONS']

使用FLask-CKEditor集成富文本编辑器

pip install flask-ckeditor

from flask_ckeditor import CKEditor
ckeditor = CKEditor(app)

Flask-CKEditor常用配置

配置键默认值说明
CKEDITOR_SERVE_LOCALFalse设为True会使用内置的本地资源
CKEDITOR_PKG_TYPE'standard'CKEditor包类型,可选值为basic,standard和full
CKEDITOR_LANGUAGE''界面语言,传入 ISO 639格式的语言吗
CKEDITOR_HEIGHT''编辑器高度
CKEDITOR_WIDTH''编辑器宽度

re:app.config['CKEDITOR_SERVE_LOCAL'] = True

# 富文本编辑器
from flask_ckeditor import CKEditor, CKEditorField

app = Flask(__name__)
app.config['CKEDITOR_SERVE_LOCAL'] = True
app.config['CKEDITOR_HEIGHT'] = 400
app.secret_key = 'secret string'

**ckeditor = CKEditor(app)**


class PostForm(FlaskForm):
    title = StringField('Title')
    **body = CKEditorField('Body', validators=[DataRequired()])**
    submit = SubmitField('Submit')


@app.route('/index1', methods=['GET', 'POST'])
def index1():
    form = PostForm()
    if form.validate_on_submit():
        title = form.title.data
        body = form.body.data
        # WARNING: use bleach or something similar to clean the data (escape JavaScript code)
        # You may need to store the data in database here
        return render_template('post.html', title=title, body=body)
    return render_template('index.html', form=form)
{% extends 'base.html' %}
{% from 'macro.html' import get_field %}

{% block content %}
<h1>Integrate CKEditor with Flask-CKEditor</h1>
<form method="post">
    {{ form.csrf_token }}
    {{ get_field(form.title) }}
    {{ get_field(form.body) }}
    {{ form.submit }}
</form>
{% endblock %}

{% block scripts %}
**{{ super() }}
{{ ckeditor.load() }}**
{% endblock %}

单个表单多个提交按钮

场景:文章用户编辑完成之后, 有保存草稿发布按钮

class NewPostFrom(FlaskForm):
    title = StringField("Title", validators=[DataRequired(), Length(1, 20)])
    body = TextAreaField('Body', validators=[DataRequired()])
    save = SubmitField('Save')
    publish = SubmitField('Publish')

@app.route('/two-submits', methods = ['GET','POST'])
def two_submits():
  form = NewPostForm()
  if form.validate_on_submit():
    if form.save.data:
      # do_something
      flash("这是保存按钮")
     elif form.publish.data:
       # do_something
       flash("这是提交按钮")
    return redirect(url_for('index'))
   return render_template('two_submit.html')

单个页面多个表单

  1. 单视图处理
class SigninForm(FlaskForm):
    username = StringField("Username", validates=[DataRequired(), Length(1,20)])
    password = PasswordField("Password", validates = [DataRequired(), Lenght(8, 128)])
    submit1 = SubmitField("Sign in")
 class RegisterForm(FlaskForm):
    username = StringField("Username", validates=[DataRequired(), Length(1,20)])
    email = StringField("Email", validates = [DataRequired(), Email(),Lenght(8, 128)])
    password = PasswordField("Password", validates = [DataRequired(), Lenght(8, 128)])
    submit2 = SubmitField("Register")
@app.route('/multi-form', methods = ['GET','POST'])
def multi_form():
  signin_form = SigninForm()
  register_form = RegisterForm()
  
  if signin_form.submit1.data and signin_form.validate():
    username = signin_form.username.data
    flash("%s , you just submit the Signin Form." % username)
    return redirect(url_for('index'))
      
  if register_form.submit2.data and register_form.validate():
    username = register_form.username.data
    flash("%s , you just submit the Register Form." % username) 
    return redirect(url_for('index'))
  return render_template("2form.html", signin_form=signin_form, register_form = register_form)
<form method="POST">
  {{ signin_form.csrf_token }}
  {{ form_field(signin_form.username) }}
  {{ form.field(signin_form.password) }}
  {{ signin_form.submit1 }}
</form>
<form method="POST">
  {{ register_form.csrf_token }}
  {{ form_field(register_form.username) }}
  {{ form.field(register_form.password) }}
  {{ register_form.submit2 }}
</form>
  1. 多视图处理
  2. 把同一模板页面得表单 分成多个视图函数来处理
# 多视图处理
@app.route('/multi-form-multi-view')
def multi_form_multi_view():
    """这部分只处理GET请求"""
    signin_form = SigninForm()
    register_form = RegisterForm()
    return render_template('multi_form.html', register_form=register_form, signin_form=signin_form)

# 这部分各自处理各自的请求
@app.route('/handle_signin', methods=['POST'])
def handle_signin():
    signin_form = SigninForm()
    register_form = RegisterForm()
    
    if signin_form.validate_on_submit():
        username = signin_form.username.data
        
        flash("%s, you just submit the Signin Form." % username)
        return redirect(url_for('index1'))
    return render_template('multi_form.html', register_form=register_form,signin_form=signin_form)

@app.route('/handle_register', methods=['POST'])
def handle_register():
    signin_form = SigninForm()
    register_form = RegisterForm()
    
    if signin_form.validate_on_submit():
        username = signin_form.username.data
        
        flash("%s, you just submit the Register Form." % username)
        return redirect(url_for('index1'))
    return render_template('multi_form.html', register_form=register_form,signin_form=signin_form)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  {% from 'macro.html' import get_field%}
  <h3>Login Form</h3>
  <form method="post" action="{{ url_for('handle_signin') }}">
    {{ signin_form.csrf_token }}
    {{ get_field(signin_form.username) }}
    {{ get_field(signin_form.password) }}
    {{ signin_form.submit1 }}
  </form>
  <h3>Register Form</h3>
  <form method="post" action="{{ url_for('handle_register') }}">
    {{ register_form.csrf_token }}
    {{ get_field(register_form.username) }}
    {{ get_field(register_form.email) }}
    {{ get_field(register_form.password) }}
    {{ register_form.submit2 }}
  </form>
</body>
</html>

处理错误信息

def flash_errors(form):
    for field, errors in form.errors.items():
        for error in errors:
            flash(u"Error in the %s field - %s" % (
                getattr(form, field).label.text,
                error
            ))

  • 以下思考点
    • 保存状态下 应该是可以先保存到session中然后在读取
    • if session['body']: form.body.data = session['body']