4. 表单

4.1 HTML表单

先看一个简单的HTML表单



4.2 使用Flask-wtf处理表单

$ pip install flask-wtf

Flask-wtf默认为每个表单启用CSRF保护,他会为我们自动生成和验证CSRF令牌,默认情况下 我们需要在程序内设置秘钥

app.secret_key = "secret string"

4.2.1 定义WTForms表单类

  • 常用的WTForms字段
字段类 说明 对应的HTML表示
BooleanField 复选框,值会被处理为True或False
DataField 文本字段,值会被处理为datetime.date对象
DateTimeField 文本字段,值会被处理为datetime.datetime对象
FielField 文件上传字段
FloatField 浮点数字段,值会被处理为浮点型
IntegerField 整数字段,值会被处理为整型
RadioField 一组单选按钮
SelectField 下拉列表
SelectMultipleField 多选下拉列表
SubmitFiled 提交按钮
StringField 文本字段
HiddenField 隐藏文本字段
PasswordFiled 密码文本字段
TextAreaField 多行文本字段

实例化字段类型常用参数

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

常用的WTForms验证器

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

**下面我们来定义一个表单类 forms.py"

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms,validators import DataRequired, Length

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

4.2.2 输出HTML代码

form = LoginForm()
form.username

''

form.submit()

''

form.username.label()

form.submit.label()

1.使用render_kw属性

username = StringField("Username", render_kw={"placeholder": "Your Username"})

输出的label

2.在调用字段时传入

  form.username(style="width:200px;', class_="bar")

class 是python的保留关键字 我们使用class_来替代class 但是我们不能修改name属性值

4.2.3 在模板中渲染表单

from forms import LoginForm

@app.route("/basic")
def basic():
  form = LoginForm()
  return render_template("login.html", form=form)

basic.html

{{ form.csrf_token }} {{ form.username.label }}{{ form.username }}
{{ form.password.label }}{{ form.password }}
{{ form.remember.label }}{{ form.remember }}
{{ form.submit }}

渲染Bootstrap风格表单

{{ form.csrf_token }}
{{ form.username.label }}{{ form.username(class="form-control") }}
{{ form.password.label }}{{ form.password(class="form-control") }}
{{ form.remember.label }}{{ form.remember(class="form-check-input") }}
{{ form.submit }}

处理表单数据

从获取数据到保存数据大致会经过以下步骤:

  • 解析请求,获取表单数据
  • 对数据进行必要的转换,比如将勾选框的值转换成Python的布尔值
  • 验证数据是否符合要求,同时验证CSRF令牌
  • 如果验证未通过则需要生产错误信息.并在模板中显示错误信息
  • 如果通过验证, 就把数据保存到数据库或做进一步验证

4.3.1 提交表单

HTML表单中控制提交行为的树心

属性 默认值 说明
action 当前URL,即对应的Url 表单提交时发送请求的目标url
method get 提交表单的http请求方法,目前仅支持GET和POST方法
enctype application/x-www-form-urlencoded 表单数据的编码类型,当表单中包含文件上传字段时,需要设为mutipart/form-data,还可以设为纯文本类型text/plain

4.3.2 验证表单数据

from flask import request

@app.route("/basic", methods=["GET", "POST"]
def basic():
  form = LoginForm()
  if request.method == "POST" and form.validate():
     ...  # 处理POST请求
  return render_template("forms/basic.html", form=form

flask-wtf提供了validate_on_submit()方法合并了两个操作 因此可以优化为
from flask import request

@app.route("/basic", methods=["GET", "POST"]
def basic():
form = LoginForm()
if form.validate_on_submit():
... # 处理POST请求
return render_template("forms/basic.html", form=form)

# 表单验证与数据获取
from flask import Flask, render_template, flash, redirect, url_for
...

@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("forms/basic.html", form=form)

4.3.3 在模板中渲染错误信息

{{ form.csrf_token }}
{{ form.username.label }}{{ form.username(class="form-control") }} {% for message in form.username.errors %} {{messgae""
{% endfor %}
{{ form.password.label }}{{ form.password(class="form-control") }} {% for message in form.password.errors %} {{messgae""
{% endfor %}
{{ form.remember.label }}{{ form.remember(class="form-check-input") }}
{{ form.submit }}

4.4 表单进阶实践

4.4.1 设置错误信息语言

from flask_wtf import FlaskForm
app = Flask(__name__)
app.config["WTF_I18N_ENABLED'] = False

class MyBaseForm(FlaskForm):
   class Meta:
      locales = ["zh"]

class HelloForm(MyBaseForm):
  name = StringField("Name", validtors=[DataRequired()])
  submit = SubmitField("提交")

我们需要将配置变量WTF_I18N_ENABLED设为False, 这会让Flask-WTF使用WTForms内置的错误信息翻译. 然后我们需要在自定义基类定义元类Meta.并在locales列表中加入中文。也可以在实例化表单类时通过meta关键字传入locales,比如:

form = MyForm(meta={"locales":["en_US", "en"]})

4.4.2 使用宏渲染表单

{% macro from_field(field) %}
{{ field.label }}
{{ field(**kwargs) }}
{% if field.errors %} {% for error in errors %} {{ error }} {% endfor %} {% endif %} {% endmacro %}

宏调用

{ % from 'macros.html' import form_field %}
...
{{ form.csrf_token }} {{ form_field(form.username) }} {{ form_field(form.password }}

4.4.3 自定义验证器

  • 1.行内验证器
from wtforms import IntegerField, SubmitField
from wtforms.validators import ValidationError

class FortywoForm(FlaskForm):
  answer = IntegerField("The Number")
  submit = SubmitField(“提交")
  
  def validate_answer(form, field):
    if field.data != 42:
      raise ValidationError("Must be 42.')

当表单类包含以”validate_字段属性名"形式命名的方法时,在验证字段数据时会同时调用这个方法来验证对应的字段,这也是为什么表单类的属性名不能以validate开头.验证方法接收两个位置参数,依次为form和field,前者为表单类实例,后者为字段对象,我们可以通过field.data获取字段数据,这2个参数将在验证表单时被调用传入. 验证出错时抛出从wtforms.validators模块导入的ValidationError异常,传入错误消息作为参数.因此这种方法仅用来验证特定的表单类字段,所以又称为行内验证器(in-line validator)。

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

def is_42(form, field):
  if field.data != 42:
    raise ValidationError("Must be 42")

class FortywoForm(FlaskForm):
  answer = IntegerField("The Number", validators=[is_42])
  submit = SubmitField(“提交")

我们通常需要让验证器支持传入参数来对验证器进行函数调用。这里我们使用闭包实现

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 FortywoForm(FlaskForm):
  answer = IntegerField("The Number", validators=[is_42()])
  submit = SubmitField(“提交")

4.4.4 文件上传

出于安全考虑.除了常规的CSRF防范,我们还需要重点注意下面的问题

  • 验证文件类型
  • 验证文件大小
  • 过滤文件名

1.定义上传表单

from flask_wtf.file import FileField, FileRequired, FileAllowed
from flask_wtf import FlaskForm

class UploadForm(FlaskForm):
  photo = FileField("Upload Image", validators=[FileRequired(), FileAllowed(['jpg', 'jpeg', 'png', 'gif'])])
  submit = SubmitField()
验证器 说明
FileRequired(message=None) 验证是否包含文件对象
FileAllowed(upload_set, meaasge=None) 用来验证文件类型,upload_set参数用来传入包含允许的文件后缀名列表

除了验证文件的类型 我们通常还需要对文件大小进行验证 通过FLASK的内置的配置变量MAX_CONTENT_LENGTH我们可以限制请求报文的最大长度 单位为字节(byte) 比如我们将最大长度限制为3M。
app.config["MAX_CONTENT_LENGTH"] = 3 * 1024 * 1024

你可能感兴趣的:(4. 表单)