GVKun编程网logo

表格对WTForms永远无效(表格无效怎么回事)

31

在本文中,我们将带你了解表格对WTForms永远无效在这篇文章中,我们将为您详细介绍表格对WTForms永远无效的方方面面,并解答表格无效怎么回事常见的疑惑,同时我们还将给您一些技巧,以帮助您实现更有

在本文中,我们将带你了解表格对WTForms永远无效在这篇文章中,我们将为您详细介绍表格对WTForms永远无效的方方面面,并解答表格无效怎么回事常见的疑惑,同时我们还将给您一些技巧,以帮助您实现更有效的6.Flask-WTForms、Flask - WTF 和 WTForms 创建表单、flask web 表单验证 WTForms、flask wtforms 组件详解

本文目录一览:

表格对WTForms永远无效(表格无效怎么回事)

表格对WTForms永远无效(表格无效怎么回事)

我有一个Flask-WTF表单登录。显然,该表单永远无效,无论我输入什么“成功”,都永远不会打印。为什么我的表格无法验证?

class loginForm(Form):    email = EmailField(''email'', validators=[InputRequired("Please enter your email address."), Email("Please enter a valid email address.")])    password = PasswordField(''password'', validators=[InputRequired("Please enter your password.")])@app.route(''/sign-in'', methods=[''POST'', ''GET''])def signIn():    form = loginForm(request.form)    if form.validate_on_submit():        print ''success''        return redirect(''/'')    return render_template(''signIn.html'')<form method="POST" action="/sign-in">    {{ form.email(placeholder=''Email'',) }}    {{ form.password(placeholder=''Password'',) }}    <button onclick="submit()">Sign In</button></form>

答案1

小编典典

Flask-WTF添加了CSRF保护字段。如果不存在,CSRF验证将失败,并且该表格将无效。用于form.hidden_tag()在表单中包括所有隐藏字段(包括CSRF字段)。

<form method="post">    {{ form.hidden_tag() }}    ...

通常,如果表单未通过验证,则应form.errors在致电后validate进行检查以了解问题所在。

您没有看到错误,因为您没有渲染该字段(在这种情况下也没有渲染任何字段的错误,但这对解决此问题没有帮助)。如果您在调试器中运行并进行了检查form.errors,您将看到确实存在“
CSRF令牌丢失”错误。

6.Flask-WTForms

6.Flask-WTForms

 Flask-WTF是简化了WTForms操作的一个第三方库。WTForms表单的两个主要功能是验证用户提交数据的合法性以及渲染模板。还有其它一些功能:CSRF保护,

文件上传等。安装方法:pip install flask-wtf

1.1.WTForms简单验证

from flask import Flask,request,render_template
from wtforms import Form,StringField
from wtforms.validators import Length,EqualTo

app = Flask(__name__)

class RegistForm(Form):
    username = StringField(validators=[Length(min=3,max=10,message=''用户名必须在3到10位之间'')])
    password = StringField(validators=[Length(min=6,max=10,message=''密码必须6到10位之间'')])
    password_repeat = StringField(validators=[Length(min=6,max=10),
                                              EqualTo("password",message=''密码不一致'')])

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

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

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

 

 1.2.WTForms常用验证器和自定义验证器

 常用的验证器

  • Email:验证上传的数据是否为邮箱格式
  • EqualTo:两个字段是否相等(密码和重复密码)
  • InputRequired:原始数据的需要验证
  • Length:长度限制,有mix和max两个值
  • NumberRange:数字的区间,有mix和max两个值,如果在两个值之间则满足
  • Regexp:自定义正则表达式
  • URL:必须url格式
  • UUID:uuid格式
from wtforms import Form,StringField,IntegerField
from wtforms.validators import Length,EqualTo,Email,InputRequired,NumberRange
from wtforms.validators import Regexp,URL,ValidationError


class LoginForm(Form):
    email = StringField(validators=[Email(message=''邮箱格式不正确'')])
    username = StringField(validators=[InputRequired(message=''这个字段必须要填'')])
    age = IntegerField(validators=[NumberRange(min=18,max=100)])
    phone = StringField(validators=[Regexp(r''1[38745]\d{9}'')])
    homepage = StringField(validators=[URL()])
    captcha = StringField(validators=[Length(4,4)])

    # 自定义验证器
    def validate_captcha(self,field):
        if field.data != ''1234'':      #field.data:用户提交过来的数据
            raise ValidationError(''验证码错误'')          #如果验证失败,就抛出验证失败的异常

 

1.3.使用wtforms渲染模板

 forms.py

class SettingsForm(Form):
    username = StringField(label="用户名:",validators=[InputRequired(message=''这个字段必须要填'')])
    age = IntegerField(''年龄:'',validators=[NumberRange(min=18, max=100)])
    remeber = BooleanField(''记住我'')
    tags = SelectField(''标签'',choices=[(1,''python''),(2,''django'')])

flask_wtforms_demo.py

@app.route(''/settings/'',methods=[''GET'',''POST''])
def settings():
    if request.method == ''GET'':
        form = SettingsForm()
        return render_template(''settings.html'',form=form)
    else:
        pass

settings.html

<form action="" method="post">
{#    括号里面可以添加样式#}
    <p>{{ form.username.label }} {{ form.username() }}</p>
    <p>{{ form.age.label }} {{ form.age() }}</p>
    <p>{{ form.remeber.label }} {{ form.remeber() }}</p>
    <p>{{ form.tags.label }} {{ form.tags() }}</p>
    <p><input type="submit" value="提交"></p>
</form>

 

Flask - WTF 和 WTForms 创建表单

Flask - WTF 和 WTForms 创建表单

[TOC]

Flask - WTF 和 WTForms 创建表单

一. Flask-WTF

Flask-WTF 是集成 WTForms,并带有 csrf 令牌的安全表单和全局的 csrf 保护的功能。 每次我们在建立表单所创建的类都是继承与 flask_wtf 中的 FlaskForm,而 FlaskForm 是继承 WTForms 中 forms。

1. 创建基础表单

class LoginForm(FlaskForm):
    username = StringField()
    password = PasswordField()
    remember_me = BooleanField(label=''Keep me logged in'')

2.CSRF 保护

任何使用 FlaskForm 创建的表单发送请求,都会有 CSRF 的全部保护,在对应的 template 中 HTML 渲染表单时,可以加入 form.csrf_token:

<form method="post">
    {{ form.csrf_token }}
</form>

但是如果模板中没有表单,则可以使用一个隐藏的 input 标签加入 csrf_token。

<form method="post">
    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
</form>

3. 验证表单

在视图处理程序中验证请求:

def login():
    form = LoginForm()
    if form.validate_on_submit():
        return redirect(''/success'')
    return render_template(''login.html'', form=form)

# 使用validate_on_submit 来检查是否是一个 POST 请求并且请求是否有效。

或者下面这种方式:

new_lf = LoginForm(formdata=request.form)
        if new_lf.validate():
            return "登陆成功"
        else:
            return render_template("login.html", lf=new_lf)

4. 文件上传

Flask-WTF 提供 FileField 来处理文件上传,它在表单提交后,自动从 flask.request.files 中抽取数据。FileField 的 data 属性是一个 Werkzeug FileStorage 实例。

from werkzeug import secure_filename
from flask_wtf.file import FileField

class PhotoForm(Form):
    photo = FileField(''Your photo'')

@app.route(''/upload/'', methods=(''GET'', ''POST''))
def upload():
    form = PhotoForm()
    if form.validate_on_submit():
        filename = secure_filename(form.photo.data.filename)
        form.photo.data.save(''uploads/'' + filename)
    else:
        filename = None
    return render_template(''upload.html'', form=form, filename=filename)

注意:在 HTML 表单的 enctype 设置成 multipart/form-data,如下:

5. 验证码

Flask-WTF 通过 RecaptchaField 也提供对验证码的支持:

rom flask_wtf import Form, RecaptchaField
from wtforms import TextField

class SignupForm(Form):
    username = TextField(''Username'')
    recaptcha = RecaptchaField()

还需要配置一下信息:

# 字段					# 配置

RECAPTCHA_PUBLIC_KEY	# 必须公钥

RECAPTCHA_PRIVATE_KEY	# 必须私钥

RECAPTCHA_API_SERVER	# 可选验证码API服务器

RECAPTCHA_PARAMETERS	# 可选 一个 JavaScript(api.js)参数的字典

RECAPTCHA_DATA_ATTRS	# 可选 一个数据属性项列表 https://developers.google.com/recaptcha/docs/display

二. WTForms

WTForms 是一个 Flask 集成的框架,或者是说库。用于处理浏览器表单提交的数据。它在 Flask-WTF 的基础上扩展并添加了一些随手即得的精巧的帮助函数,这些函数将会使在 Flask 里使用表单更加有趣。

1. field 字段

WTForms 支持 HTML 字段:

字段类型:			说明:

StringFidle			文本字段, 相当于type类型为text的input标签

TextAreaField		多行文本字段

PasswordField		密码文本字段

HiddenField			隐藏文本字段

DateField			文本字段, 值为datetime.date格式

DateTimeFieeld		文本字段, 值为datetime.datetime格式

IntegerField		文本字段, 值为整数

DecimalField		文本字段, 值为decimal.Decimal

FloatField			文本字段, 值为浮点数

BooleanField		复选框, 值为True 和 False

RadioField			一组单选框

SelectField			下拉列表

SelectMultipleField	下拉列表, 可选择多个值

FileField			文件上传字段

SubmitField			表单提交按钮

FormFiled			把表单作为字段嵌入另一个表单

FieldList			子组指定类型的字段

2.Validators 验证器

WTForms 可以支持很多表单的验证函数:

Email
验证是电子邮件地址


EqualTo
比较两个字段的值; 常用于要求输入两次密钥进行确认的情况


IPAddress
验证IPv4网络地址


Length
验证输入字符串的长度


NumberRange
验证输入的值在数字范围内


Optional
无输入值时跳过其它验证函数


DataRequired
确保字段中有数据


Regexp
使用正则表达式验证输入值


URL
验证url


AnyOf
确保输入值在可选值列表中


NoneOf
确保输入值不在可选列表中

3. 自定义 Validators 验证器

第一种: in-line validator(内联验证器)

也就是自定义一个验证函数,在定义表单类的时候,在对应的字段中加入该函数进行认证。下面的 my_length_check 函数就是用于判 name 字段长度不能超过 50.

def my_length_check(form, field):
    if len(field.data) > 50:
        raise ValidationError(''Field must be less than 50 characters'')

class MyForm(Form):
    name = StringField(''Name'', [InputRequired(), my_length_check])

第二种:通用且可重用的验证函数

一般是以 validate 开头,加上下划线再加上对应的 field 字段(validate_filed),浏览器在提交表单数据时,会自动识别对应字段所有的验证器,然后执行验证器进行判断。

class RegistrationForm(FlaskForm):
    email = StringField(''Email'', validators=[DataRequired(), Length(1, 60), Email()])
    username = StringField(''Username'', validators=[DataRequired(), Length(1, 60),
        Regexp(''^[A-Za-z][A-Za-z0-9_.]*$'', 0, ''username must have only letters, numbers dots or underscores'')])
    password = PasswordField(''Password'', validators=[DataRequired(), EqualTo(''password2'', message=''password must match'')])
    password2 = PasswordField(''Confirm password'', validators=[DataRequired()])

    def validate_email(self, field):
        if User.objects.filter(email=field.data).count() > 0:
            raise ValidationError(''Email already registered'')

    def validate_username(self, field):
        if User.objects.filter(username=field.data).count() > 0:
            raise ValidationError(''Username has exist'')

第三种:比较高级的 validators

class Length(object):
    def __init__(self, min=-1, max=-1, message=None):
        self.min = min
        self.max = max
        if not message:
            message = u''Field must be between %i and %i characters long.'' % (min, max)
        self.message = message

    def __call__(self, form, field):
        l = field.data and len(field.data) or 0
        if l < self.min or self.max != -1 and l > self.max:
            raise ValidationError(self.message)

length = Length

4.Widget 组件

下面可以以登录界面为实例:

login.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from flask import Flask, render_template, request, redirect
from wtforms import Form
from wtforms import validators
from wtforms import widgets

class LoginForm(Form):
    name = simple.StringField(
        label=''用户名'',
        validators=[
            validators.DataRequired(message=''用户名不能为空.''),
        ],
        widget=widgets.TextInput(),
        render_kw={''class'': ''form-control''}
    )
    pwd = simple.PasswordField(
        label=''密码'',
        validators=[
            validators.DataRequired(message=''密码不能为空.''),
        ],
        widget=widgets.PasswordInput(),
        render_kw={''class'': ''form-control''}
    )

三。简单的登录验证表单实例

from flask import Flask, request, render_template, redirect
from wtforms.fields import simple, core
from wtforms import Form, validators

app = Flask(__name__)
app.config["DEBUG"] = True


class LoginForm(Form):
    username = simple.StringField(
        label="用户名:",
        validators=[
            validators.DataRequired(message="用户名不能为空"),
            validators.Length(min=4, max=10, message="用户名不能小于%(min)d,不能大于%(max)d")
        ],  # 声明校验方式
        id="username",
        # widget=widgets.FileInput(),
        render_kw={"class": "my_class"},
    )

    password = simple.PasswordField(
        label="密码:",
        validators=[
            validators.Length(max=10, min=6, message="password不能小于%(min)d,不能大于%(max)d"),
            validators.Regexp("\d+", message="密码只能是数字")
        ],
        id="pwd",
        render_kw={"style": "width:300px;"}
    )

    sub = simple.SubmitField(
        label="登陆",
        render_kw={"class": "bs"}
    )


@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "GET":
        lf = LoginForm()
        return render_template("login.html", lf=lf)
    else:
        new_lf = LoginForm(formdata=request.form)
        if new_lf.validate():
            return "登陆成功"
        else:
            return render_template("login.html", lf=new_lf)


class RegForm(Form):
    username = simple.StringField(
        label="用户名:",
        validators=[
            validators.Length(min=4, max=10, message="用户名不能小于%(min)d,不能大于%(max)d")
        ]
    )

    password = simple.PasswordField(
        label="密码:",
        validators=[
            validators.Length(max=10, min=6, message="password不能小于%(min)d,不能大于%(max)d")
        ]
    )

    repassword = simple.PasswordField(
        label="确认眼神:",
        validators=[
            validators.EqualTo("password", message="眼神未确认")
        ]
    )

    email = simple.StringField(
        label="邮箱",
        validators=[
            validators.Email(message="邮箱格式不符")
        ]
    )

    gender = core.RadioField(
        label="性别:",
        choices=[
            (1, "女"),
            (2, "男")
        ],
        coerce=int
    )

    hobby = core.SelectMultipleField(
        label="嗜好",
        choices=[
            (1, "小姐姐"),
            (2, "小萝莉"),
            (3, "小正太"),
            (4, "小哥哥")
        ],
        coerce=int
    )

    sub = simple.SubmitField(
        label="登陆",
        render_kw={"class": "bs"}
    )


@app.route("/reg", methods=["GET", "POST"])
def reg():
    if request.method == "GET":
        regf = RegForm()
        return render_template("reg.html", regf=regf)

    else:
        new_regf = RegForm(formdata=request.form)
        if new_regf.validate():
            print(new_regf.data.get("gender"))
            print(new_regf.data.get("hobby"))
            return new_regf.data.get("username")
        else:
            return render_template("reg.html", regf=new_regf)


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

参考链接:

1.https://wtforms.readthedocs.io/en/stable/

2.https://flask-wtf.readthedocs.io/en/stable/

flask web 表单验证 WTForms

flask web 表单验证 WTForms

简介

WTForms 是一个 flask 集成框架,或者说是库,用于处理浏览器表单提交的数据,它在 flask-WTF 的基础上扩展并添加了一些随手可得的精巧帮助函数,这些函数将会是在 flask 里使用表单更加有趣.

用法:

1.field 字段

WTForms 支持 HTML 字段

  字段类型 说明 StringField 文本字段,相当于 type 类型为 text 的 input 标签 TextAreaField 多行文本字段 PasswordField 密码文本字段 HiddenField 隐藏文本字段 DateField 文本字段, 值为 datetime.date 格式 DateTimeField 文本字段, 值为 datetime.datetime 格式 IntegerField 文本字段, 值为整数 DecimalField 文本字段, 值为 decimal.Decimal FloatField 文本字段, 值为浮点数 BooleanField 复选框, 值为 True 和 False RadioField 一组单选框 SelectField 下拉列表 SelectMultipleField 下拉列表, 可选择多个值 FileField 文件上传字段 SubmitField 表单提交按钮  FormFiled  把表单作为字段嵌入另一个表单  FieldList  子组指定类型的字段

 2.Validators 验证器

WTForms 可以支持很多表单的验证函数:

验证函数 说明 Email 验证电子邮件地址 EqualTo 比较两个字段的值;常用于要求输入两次秘钥进行确认的情况 IPAddress 验证 IPv4 网络地址 Length 验证输入字符串的长度 NumberRange 验证输入的值在数字范围内 Optional 无输入值时跳过其他验证函数 DateRequired 确保字段中有数据 Regexp 使用正则表达式验证输入值 URL 验证 URL AnyOf 确保输入值在可选值列表中 NoneOf 确保输入值不在可选列表中

3. 自定义 Validators 验证器

第一种:in-line validator (内联验证器)

也就是自定义一个验证函数,在定义表单类的时候,在对应的字段中加入该函数进行认证。下面的 my_length_check 函数就是用于判断 name 字段长度不能超过 50

def my_length_check(form, field):
    if len(field.data) > 50:
        raise ValidationError(''Field must be less than 50 characters'')

class MyForm(Form):
    name = StringField(''Name'', [InputRequired(), my_length_check])

 

第二种:通用且可重用的验证函数

一般是以 validate 开头,加上下划线在加上对应的 field 字段 (validate_field), 浏览器在提交表单数据时,会自动识别对应字段所有的验证器,然后执行验证器进行判断.

class RegistrationForm(FlaskForm):
    email = StringField(''Email'', validators=[DataRequired(), Length(1, 60), Email()])
    username = StringField(''Username'', validators=[DataRequired(), Length(1, 60),
        Regexp(''^[A-Za-z][A-Za-z0-9_.]*$'', 0, ''username must have only letters, numbers dots or underscores'')])
    password = PasswordField(''Password'', validators=[DataRequired(), EqualTo(''password2'', message=''password must match'')])
    password2 = PasswordField(''Confirm password'', validators=[DataRequired()])

    def validate_email(self, field):
        if User.objects.filter(email=field.data).count() > 0:
            raise ValidationError(''Email already registered'')

    def validate_username(self, field):
        if User.objects.filter(username=field.data).count() > 0:
            raise ValidationError(''Username has exist'')

 

第三种:比较高级的 validators

class Length(object):
    def __init__(self, min=-1, max=-1, message=None):
        self.min = min
        self.max = max
        if not message:
            message = u''Field must be between %i and %i characters long.'' % (min, max)
        self.message = message

    def __call__(self, form, field):
        l = field.data and len(field.data) or 0
        if l < self.min or self.max != -1 and l > self.max:
            raise ValidationError(self.message)

length = Length

4.widget 组件

下面可以以登录界面为实例:

login.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from flask import Flask, render_template, request, redirect
from wtforms import Form
from wtforms import validators
from wtforms import widgets

class LoginForm(Form):
    name = simple.StringField(
        label=''用户名'',
        validators=[
            validators.DataRequired(message=''用户名不能为空.''),
        ],
        widget=widgets.TextInput(),
        render_kw={''class'': ''form-control''}
    )
    pwd = simple.PasswordField(
        label=''密码'',
        validators=[
            validators.DataRequired(message=''密码不能为空.''),
        ],
        widget=widgets.PasswordInput(),
        render_kw={''class'': ''form-control''}
    )

 

转自:https://www.jianshu.com/p/7e16877757f8

 

flask wtforms 组件详解

flask wtforms 组件详解

一、简介

  在 flask 内部并没有提供全面的表单验证,所以当我们不借助第三方插件来处理时候代码会显得混乱,而官方推荐的一个表单验证插件就是 wtforms。wtfroms 是一个支持多种 web 框架的 form 组件,主要用于对用户请求数据的进行验证,其的验证流程与 django 中的 form 表单验证由些许类似,本文将介绍 wtforms 组件使用方法以及验证流程。  

wtforms 依照功能类别来说 wtforms 分别由以下几个类别:
  • Forms: 主要用于表单验证、字段定义、HTML 生成,并把各种验证流程聚集在一起进行验证。
  • Fields: 主要负责渲染 (生成 HTML) 和数据转换。
  • Validator: 主要用于验证用户输入的数据的合法性。比如 Length 验证器可以用于验证输入数据的长度。
  • Widgets:html 插件,允许使用者在字段中通过该字典自定义 html 小部件。
  • Meta:用于使用者自定义 wtforms 功能,例如 csrf 功能开启。
  • Extensions:丰富的扩展库,可以与其他框架结合使用,例如 django。

二、安装使用

官方文档: https://wtforms.readthedocs.io/en/stable/index.html#
安装:
pip3 install wtforms

定义 Forms

简单登陆验证

app:

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Author:wd

from  flask import Flask,render_template,request
from wtforms.fields import simple
from wtforms import Form
from wtforms import validators
from wtforms import widgets
app = Flask(__name__,template_folder="templates")

class LoginForm(Form):
    ''''''Form''''''
    name = simple.StringField(
        label="用户名",
        widget=widgets.TextInput(),
        validators=[
            validators.DataRequired(message="用户名不能为空"),
            validators.Length(max=8,min=3,message="用户名长度必须大于%(max)d且小于%(min)d")
        ],
        render_kw={"class":"form-control"}  #设置属性生成的html属性
    )

    pwd = simple.PasswordField(
        label="密码",
        validators=[
            validators.DataRequired(message="密码不能为空"),
            validators.Length(max=18,min=4,message="密码长度必须大于%(max)d且小于%(min)d"),
            validators.Regexp(regex="\d+",message="密码必须是数字"),
        ],
        widget=widgets.PasswordInput(),
        render_kw={"class":"form-control"}
    )



@app.route(''/login'',methods=["GET","POST"])
def login():
    if request.method =="GET":
        form = LoginForm()
        return render_template("login.html",form=form)
    else:
        form = LoginForm(formdata=request.form)
        if form.validate():  # 对用户提交数据进行校验,form.data是校验完成后的数据字典
            print("用户提交的数据用过格式验证,值为:%s"%form.data)
            return "登录成功"
        else:
            print(form.errors,"错误信息")
        return render_template("login.html",form=form)


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

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>登录</h1>
<form method="post">
    <!--<input type="text" name="name">-->
    <p>{{form.name.label}} {{form.name}} {{form.name.errors[0] }}</p>

    <!--<input type="password" name="pwd">-->
    <p>{{form.pwd.label}} {{form.pwd}} {{form.pwd.errors[0] }}</p>
    <input type="submit" value="提交">
</form>
</body>
</html>

Form 类实例化参数:

  • formdata:需要被验证的 form 表单数据。
  • obj: 当 formdata 参数为提供时候,可以使用对象,也就是会是有 obj. 字段的值进行验证或设置默认值。
  • prefix: 字段前缀匹配,当传入该参数时,所有验证字段必须以这个开头 (无太大意义)。
  • data: 当 formdata 参数和 obj 参数都有时候,可以使用该参数传入字典格式的待验证数据或者生成 html 的默认值,列如:{''usernam'':''admin’}。
  • meta:用于覆盖当前已经定义的 form 类的 meta 配置,参数格式为字典。 

自定义验证规则

#定义
class Myvalidators(object):
    ''''''自定义验证规则''''''
    def __init__(self,message):
        self.message = message
    def __call__(self, form, field):
        print(field.data,"用户输入的信息")
        if field.data == "admin":
            raise validators.ValidationError(self.message)


#使用
class LoginForm(Form):
    ''''''Form''''''
    name = simple.StringField(
        label="用户名",
        widget=widgets.TextInput(),
        validators=[ Myvalidators(message=''用户名不能是admin''),]#自定义验证类
        render_kw={"class":"form-control"}  #设置属性
    )

字段介绍

wtforms 中的 Field 类主要用于数据验证和字段渲染 (生成 html),以下是比较常见的字段:

  •  StringField    字符串字段,生成 input 要求字符串
  • PasswordField  密码字段,自动将输入转化为小黑点
  • DateField  日期字段,格式要求为 datetime.date 一样
  • IntergerField  整型字段,格式要求是整数
  • FloatField  文本字段,值是浮点数
  • BooleanField  复选框,值为 True 或者 False
  • RadioField  一组单选框
  • SelectField  下拉列表,需要注意一下的是 choices 参数确定了下拉选项,但是和 HTML 中的 <select> 标签一样。
  • MultipleSelectField  多选字段,可选多个值的下拉列表
  • ...

字段参数:

  • label:字段别名,在页面中可以通过字段.label 展示;
  • validators:验证规则列表;
  • filters:过氯器列表,用于对提交数据进行过滤;
  • description:描述信息,通常用于生成帮助信息;
  • id:表示在 form 类定义时候字段的位置,通常你不需要定义它,默认会按照定义的先后顺序排序。
  • default:默认值
  • widget:html 插件,通过该插件可以覆盖默认的插件,更多通过用户自定义;
  • render_kw:自定义 html 属性;
  • choices:复选类型的选项;

示例:

from flask import Flask,render_template,redirect,request
from wtforms import Form
from wtforms.fields import core
from wtforms.fields import html5
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgets

app = Flask(__name__,template_folder="templates")
app.debug = True

=======================simple===========================
class RegisterForm(Form):
    name = simple.StringField(
        label="用户名",
        validators=[
            validators.DataRequired()
        ],
        widget=widgets.TextInput(),
        render_kw={"class":"form-control"},
        default="wd"
    )
    pwd = simple.PasswordField(
        label="密码",
        validators=[
            validators.DataRequired(message="密码不能为空")
        ]
    )
    pwd_confim = simple.PasswordField(
        label="重复密码",
        validators=[
            validators.DataRequired(message=''重复密码不能为空.''),
            validators.EqualTo(''pwd'',message="两次密码不一致")
        ],
        widget=widgets.PasswordInput(),
        render_kw={''class'': ''form-control''}
    )

  ========================html5============================
    email = html5.EmailField(  #注意这里用的是html5.EmailField
        label=''邮箱'',
        validators=[
            validators.DataRequired(message=''邮箱不能为空.''),
            validators.Email(message=''邮箱格式错误'')
        ],
        widget=widgets.TextInput(input_type=''email''),
        render_kw={''class'': ''form-control''}
    )

  ===================以下是用core来调用的=======================
    gender = core.RadioField(
        label="性别",
        choices=(
            (1,""),
            (1,""),
        ),
        coerce=int  #限制是int类型的
    )
    city = core.SelectField(
        label="城市",
        choices=(
            ("bj","北京"),
            ("sh","上海"),
        )
    )
    hobby = core.SelectMultipleField(
        label=''爱好'',
        choices=(
            (1, ''篮球''),
            (2, ''足球''),
        ),
        coerce=int
    )
    favor = core.SelectMultipleField(
        label="喜好",
        choices=(
            (1, ''篮球''),
            (2, ''足球''),
        ),
        widget = widgets.ListWidget(prefix_label=False),
        option_widget = widgets.CheckboxInput(),
        coerce = int,
        default = [1, 2]
    )

    def __init__(self,*args,**kwargs):  #这里的self是一个RegisterForm对象
        ''''''重写__init__方法''''''
        super(RegisterForm,self).__init__(*args, **kwargs)  #继承父类的init方法
        self.favor.choices =((1, ''篮球''), (2, ''足球''), (3, ''羽毛球''))  #把RegisterForm这个类里面的favor重新赋值,实现动态改变复选框中的选项

    def validate_pwd_confim(self,field,):
        ''''''
        自定义pwd_config字段规则,例:与pwd字段是否一致
        :param field:
        :return:
        ''''''
        # 最开始初始化时,self.data中已经有所有的值
        if field.data != self.data[''pwd'']:
            # raise validators.ValidationError("密码不一致") # 继续后续验证
            raise validators.StopValidation("密码不一致")  # 不再继续后续验证

@app.route(''/register'',methods=["GET","POST"])
def register():
    if request.method=="GET":
        form = RegisterForm(data={''gender'': 1})  #默认是1,
        return render_template("register.html",form=form)
    else:
        form = RegisterForm(formdata=request.form)
        if form.validate():  #判断是否验证成功
            print(''用户提交数据通过格式验证,提交的值为:'', form.data)  #所有的正确信息
        else:
            print(form.errors)  #所有的错误信息
        return render_template(''register.html'', form=form)

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

register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>用户注册</h1>
<form method="post" novalidate style="padding:0  50px">
    {% for item in form %}
    <p>{{item.label}}: {{item}} {{item.errors[0] }}</p>
    {% endfor %}
    <input type="submit" value="提交">
</form>
</body>
</html>

Meta

  Meta 主要用于自定义 wtforms 的功能,大多都是配置选项,以下是配置参数:

csrf = True   # 是否自动生成CSRF标签
csrf_field_name = ''csrf_token''   # 生成CSRF标签name
csrf_secret = ''adwadada''     # 自动生成标签的值,加密用的csrf_secret
csrf_context = lambda x: request.url  # 自动生成标签的值,加密用的csrf_context
csrf_class = MyCSRF         # 生成和比较csrf标签     
locales = False      # 是否支持翻译
locales = (''zh'', ''en'')  # 设置默认语言环境
cache_translations = True  # 是否对本地化进行缓存
translations_cache = {}       # 保存本地化缓存信息的字段

示例:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from flask import Flask, render_template, request, redirect, session
from wtforms import Form
from wtforms.csrf.core import CSRF
from wtforms.fields import core
from wtforms.fields import html5
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgets
from hashlib import md5

app = Flask(__name__, template_folder=''templates'')
app.debug = True


class MyCSRF(CSRF):
    """
    Generate a CSRF token based on the user''s IP. I am probably not very
    secure, so don''t use me.
    """

    def setup_form(self, form):
        self.csrf_context = form.meta.csrf_context()
        self.csrf_secret = form.meta.csrf_secret
        return super(MyCSRF, self).setup_form(form)

    def generate_csrf_token(self, csrf_token):
        gid = self.csrf_secret + self.csrf_context
        token = md5(gid.encode(''utf-8'')).hexdigest()
        return token

    def validate_csrf_token(self, form, field):
        print(field.data, field.current_token)
        if field.data != field.current_token:
            raise ValueError(''Invalid CSRF'')


class TestForm(Form):
    name = html5.EmailField(label=''用户名'')
    pwd = simple.StringField(label=''密码'')

    class Meta:
        # -- CSRF
        # 是否自动生成CSRF标签
        csrf = True
        # 生成CSRF标签name
        csrf_field_name = ''csrf_token''

        # 自动生成标签的值,加密用的csrf_secret
        csrf_secret = ''xxxxxx''
        # 自动生成标签的值,加密用的csrf_context
        csrf_context = lambda x: request.url
        # 生成和比较csrf标签
        csrf_class = MyCSRF

        # -- i18n
        # 是否支持本地化
        # locales = False
        locales = (''zh'', ''en'')
        # 是否对本地化进行缓存
        cache_translations = True
        # 保存本地化缓存信息的字段
        translations_cache = {}


@app.route(''/index/'', methods=[''GET'', ''POST''])
def index():
    if request.method == ''GET'':
        form = TestForm()
    else:
        form = TestForm(formdata=request.form)
        if form.validate():
            print(form)
    return render_template(''index.html'', form=form)


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

三、实现原理

   wtforms 实现原理这里主要从三个方面进行说明:form 类创建过程、实例化过程、验证过程。从整体看其实现原理实则就是将每个类别的功能 (如 Filed、validate、meta 等) 通过 form 进行组织、封装,在 form 类中调用每个类别对象的方法实现数据的验证和 html 的渲染。这里先总结下验证流程:

  1. for 循环每个字段;
  2. 执行该字段的 pre_validate 钩子函数;
  3. 执行该字段参数的 validators 中的验证方法和 validate_字段名钩子函数 (如果有);
  4. 执行该字段的 post_validate 钩子函数;
  5. 完成当前字段的验证,循环下一个字段,接着走该字段的 2、3、4 流程,直到所有字段验证完成;

Form 类创建过程

以示例中的 RegisterForm 为例子,它继承了 Form:

class Form(with_metaclass(FormMeta, BaseForm)):
    Meta = DefaultMeta

    def __init__(self, formdata=None, obj=None, prefix='''', data=None, meta=None, **kwargs):
        meta_obj = self._wtforms_meta()
        if meta is not None and isinstance(meta, dict):
            meta_obj.update_values(meta)
        super(Form, self).__init__(self._unbound_fields, meta=meta_obj, prefix=prefix)

        for name, field in iteritems(self._fields):
            # Set all the fields to attributes so that they obscure the class
            # attributes with the same names.
            setattr(self, name, field)
        self.process(formdata, obj, data=data, **kwargs)

    def __setitem__(self, name, value):
        raise TypeError(''Fields may not be added to Form instances, only classes.'')

    def __delitem__(self, name):
        del self._fields[name]
        setattr(self, name, None)

    def __delattr__(self, name):
        if name in self._fields:
            self.__delitem__(name)
        else:
            # This is done for idempotency, if we have a name which is a field,
            # we want to mask it by setting the value to None.
            unbound_field = getattr(self.__class__, name, None)
            if unbound_field is not None and hasattr(unbound_field, ''_formfield''):
                setattr(self, name, None)
            else:
                super(Form, self).__delattr__(name)

    def validate(self):
        """
        Validates the form by calling `validate` on each field, passing any
        extra `Form.validate_<fieldname>` validators to the field validator.
        """
        extra = {}
        for name in self._fields:
            inline = getattr(self.__class__, ''validate_%s'' % name, None)
            if inline is not None:
                extra[name] = [inline]

        return super(Form, self).validate(extra)

其中 with_metaclass (FormMeta, BaseForm):

def with_metaclass(meta, base=object):
    return meta("NewBase", (base,), {})

这几段代码就等价于:

class Newbase(BaseForm,metaclass=FormMeta):
    pass

class Form(Newbase):
    pass

也就是说 RegisterForm 继承 Form—》Form 继承 Newbase—》Newbase 继承 BaseForm,因此当解释器解释道 class RegisterForm 会执行 FormMeta 的__init__方法用于生成 RegisterForm 类:

class FormMeta(type):
    def __init__(cls, name, bases, attrs):
        type.__init__(cls, name, bases, attrs)
        cls._unbound_fields = None
        cls._wtforms_meta = None

由其__init__方法可以知道生成的 RegisterForm 中含有字段_unbound_fields 和_wtforms_meta 并且也包含了我们自己定义的验证字段 (name、pwd...), 并且这些字段保存了每个 Field 实例化的对象,以下拿 name 说明:

name = simple.StringField(
        label="用户名",
        validators=[
            validators.DataRequired()
        ],
        widget=widgets.TextInput(),
        render_kw={"class":"form-control"},
        default="wd"
    )

实例化 StringField 会先执行其__new__方法在执行__init__方法,而 StringField 继承了 Field:

class Field(object):
    """
    Field base class
    """
    errors = tuple()
    process_errors = tuple()
    raw_data = None
    validators = tuple()
    widget = None
    _formfield = True
    _translations = DummyTranslations()
    do_not_call_in_templates = True  # Allow Django 1.4 traversal

    def __new__(cls, *args, **kwargs):
        if ''_form'' in kwargs and ''_name'' in kwargs:
            return super(Field, cls).__new__(cls)
        else:
            return UnboundField(cls, *args, **kwargs)

    def __init__(self, label=None, validators=None, filters=tuple(),
                 description='''', id=None, default=None, widget=None,
                 render_kw=None, _form=None, _name=None, _prefix='''',
                 _translations=None, _meta=None):

也就是这里会执行 Field 的__new__方法,在这里的__new__方法中,判断_form 和_name 是否在参数中,刚开始 kwargs 里面是 label、validators 这些参数,所以这里返回 UnboundField (cls, *args, **kwargs), 也就是这里的 RegisterForm.name=UnboundField (), 其他的字段也是类似,实际上这个对象是为了让我们定义的字段由顺序而存在的,如下:

class UnboundField(object):
    _formfield = True
    creation_counter = 0

    def __init__(self, field_class, *args, **kwargs):
        UnboundField.creation_counter + 1
        self.field_class = field_class
        self.args = args
        self.kwargs = kwargs
        self.creation_counter = UnboundField.creation_counter

实例化该对象时候,会对每个对象实例化的时候计数,第一个对象是 1,下一个 + 1,并保存在每个对象的 creation_counter 中。最后的 RegisterForm 中就保存了 {’name’:UnboundField (1,simple.StringField, 参数),’pwd’:UnboundField (2,simple.StringField, 参数)…}。

Form 类实例化过程

同样在 RegisterForm 实例化时候先执行__new__方法在执行__init__方法,这里父类中没也重写__new__也就是看__init__方法:

class Form(with_metaclass(FormMeta, BaseForm)):
    Meta = DefaultMeta

    def __init__(self, formdata=None, obj=None, prefix='''', data=None, meta=None, **kwargs):
        meta_obj = self._wtforms_meta()  # 实例化meta
        if meta is not None and isinstance(meta, dict): # 判断meta是否存在且为字典
            meta_obj.update_values(meta) # 覆盖原meta的配置
            # 执行父类的构造方法
        super(Form, self).__init__(self._unbound_fields, meta=meta_obj, prefix=prefix)

        for name, field in iteritems(self._fields):
            # Set all the fields to attributes so that they obscure the class
            # attributes with the same names.
            setattr(self, name, field)
        self.process(formdata, obj, data=data, **kwargs)

构造方法中先实例化默认的 meta,在判断是否传递类 meta 参数,传递则更新原 meta 的配置,接着执行父类的构造方法,父类是 BaseForm:

class BaseForm(object):
    """
    Base Form Class.  Provides core behaviour like field construction,
    validation, and data and error proxying.
    """

    def __init__(self, fields, prefix='''', meta=DefaultMeta()):
        if prefix and prefix[-1] not in ''-_;:/.'':
            prefix += ''-''

        self.meta = meta  
        self._prefix = prefix
        self._errors = None
        self._fields = OrderedDict()

        if hasattr(fields, ''items''):
            fields = fields.items()

        translations = self._get_translations()
        extra_fields = []
        if meta.csrf:  #判断csrf配置是否为true,用于生成csrf的input框
            self._csrf = meta.build_csrf(self)
            extra_fields.extend(self._csrf.setup_form(self))
        #循环RegisterForm中的字段,并对每个字段进行实例化
        for name, unbound_field in itertools.chain(fields, extra_fields):
            options = dict(name=name, prefix=prefix, translations=translations)
            field = meta.bind_field(self, unbound_field, options)
            self._fields[name] = field

在这里的 for 循环中执行 meta.bind_field 方法对每个字段进行实例化,并以 k,v 的形式放入了 self._fields 属性中。并且实例化传递来参数_form 和_name,也就是在执行 BaseForm 时候判断的两个属性,这里传递了就走正常的实例化过程。

def bind_field(self, form, unbound_field, options):
    """
    bind_field allows potential customization of how fields are bound.

    The default implementation simply passes the options to
    :meth:`UnboundField.bind`.

    :param form: The form.
    :param unbound_field: The unbound field.
    :param options:
        A dictionary of options which are typically passed to the field.

    :return: A bound field
    """
    return unbound_field.bind(form=form, **options)



def bind(self, form, name, prefix='''', translations=None, **kwargs):
    kw = dict(
        self.kwargs,
        _form=form,  #传递_form 
        _prefix=prefix,
        _name=name, # 传递_name
        _translations=translations,
        **kwargs
    )
    return self.field_class(*self.args, **kw)

继续看 Form 类中的__init__方法,接着循环:

for name, field in iteritems(self._fields):
            # Set all the fields to attributes so that they obscure the class
            # attributes with the same names.
            setattr(self, name, field)
self.process(formdata, obj, data=data, **kwargs)

此时的 self._fields 已经包含了每个实例化字段的对象,调用 setattr 为对象设置属性,为了方便获取字段,例如没有该语句获取字段时候通过 RegisterForm ()._fields [’name’], 有了它直接通过 RegisterForm ().name 获取,继续执行 self.process (formdata, obj, data=data, **kwargs) 方法,改方法用于验证的过程,因为此时的 formdata、obj 都是 None,所以执行了该方法无影响。

 

验证流程

  当 form 对用户提交的数据验证时候,同样以上述注册为例子,这次请求是 post,同样会走 form = RegisterForm (formdata=request.form),但是这次不同的是 formdata 已经有值,让我们来看看 process 方法:

def process(self, formdata=None, obj=None, data=None, **kwargs):
    formdata = self.meta.wrap_formdata(self, formdata) 

    if data is not None: #判断data参数
        # XXX we want to eventually process ''data'' as a new entity.
        #     Temporarily, this can simply be merged with kwargs.
        kwargs = dict(data, **kwargs),更新kwargs参数

    for name, field, in iteritems(self._fields):#循环每个字段
        if obj is not None and hasattr(obj, name):# 判断是否有obj参数
            field.process(formdata, getattr(obj, name)) 
        elif name in kwargs:
            field.process(formdata, kwargs[name])
        else:
            field.process(formdata)

首先对用户提交的数据进行清洗变成 k,v 格式,接着判断 data 参数,如果不为空则将其值更新到 kwargs 中,然后循环 self._fields(也就是我们定义的字段),并执行字段的 process 方法:

def process(self, formdata, data=unset_value):
    self.process_errors = []
    if data is unset_value:
        try:
            data = self.default()
        except TypeError:
            data = self.default

    self.object_data = data

    try:
        self.process_data(data)
    except ValueError as e:
        self.process_errors.append(e.args[0])

    if formdata is not None:
        if self.name in formdata:
            self.raw_data = formdata.getlist(self.name)
        else:
            self.raw_data = []

        try:
            self.process_formdata(self.raw_data)
        except ValueError as e:
            self.process_errors.append(e.args[0])

    try:
        for filter in self.filters:
            self.data = filter(self.data)
    except ValueError as e:
        self.process_errors.append(e.args[0])

def process_data(self, value):
    self.data = value

该方法作用是将用户的提交的数据存放到 data 属性中,接下来就是使用 validate () 方法开始验证:

def validate(self):
    """
    Validates the form by calling `validate` on each field, passing any
    extra `Form.validate_<fieldname>` validators to the field validator.
    """
    extra = {}
    for name in self._fields: # 循环每个field 
        #寻找当前类中以validate_’字段名匹配的方法’,例如pwd字段就寻找validate_pwd,也就是钩子函数
        inline = getattr(self.__class__, ''validate_%s'' % name, None) 
        if inline is not None:
            extra[name] = [inline] #把钩子函数放到extra字典中
    return super(Form, self).validate(extra) #接着调用父类的validate方法

验证时候先获取所有每个字段定义的 validate_+'' 字段名 '' 匹配的方法,并保存在 extra 字典中,在执行父类的 validate 方法:

def validate(self, extra_validators=None):
        self._errors = None
        success = True
        for name, field in iteritems(self._fields):  # 循环字段的名称和对象
            if extra_validators is not None and name in extra_validators: # 判断该字段是否有钩子函数
                extra = extra_validators[name] # 获取到钩子函数
            else:
                extra = tuple()
            if not field.validate(self, extra): # 执行字段的validate方法
                success = False
        return success

该方法主要用于和需要验证的字段进行匹配,然后在执行每个字段的 validate 方法:

def validate(self, form, extra_validators=tuple()):
        self.errors = list(self.process_errors)
        stop_validation = False

        # Call pre_validate
        try:
            self.pre_validate(form)      # 先执行字段字段中的pre_validate方法,这是一个自定义钩子函数
        except StopValidation as e:
            if e.args and e.args[0]:
                self.errors.append(e.args[0])
            stop_validation = True
        except ValueError as e:
            self.errors.append(e.args[0])

        # Run validators
        if not stop_validation:     
            chain = itertools.chain(self.validators, extra_validators)     # 拼接字段中的validator和validate_+''字段名''验证
            stop_validation = self._run_validation_chain(form, chain)  # 执行每一个验证规则,self.validators先执行

        # Call post_validate
        try:
            self.post_validate(form, stop_validation)
        except ValueError as e:
            self.errors.append(e.args[0])

        return len(self.errors) == 0

在该方法中,先会执行内部预留给用户自定义的字段的 pre_validate 方法,在将字段中的验证规则(validator 也就是我们定义的 validators=[validators.DataRequired ()],)和钩子函数(validate_+'' 字段名 '')拼接在一起执行,注意这里的 validator 先执行而字段的钩子函数后执行,我们来看怎么执行的:

def _run_validation_chain(self, form, validators):
       
        for validator in validators:  # 循环每个验证规则
            try:
                validator(form, self)   # 传入提交数据并执行,如果是对象执行__call__,如果是函数直接调用
            except StopValidation as e:
                if e.args and e.args[0]:    
                    self.errors.append(e.args[0])   # 如果有错误,追加到整体错误中
                return True
            except ValueError as e:
                self.errors.append(e.args[0])

        return False

很明显就是循环每一个验证规则,并执行,有错误追加到整体错误中,接着我们回到 validate 方法中,接着又会执行 post_validate,这也是内置钩子函数,允许用户自己定义,最后这个字段的数据验证完成了,而在开始的 for 循环,循环结束意味着整个验证过程结束。
def post_validate(self, form, validation_stopped):
        """
        Override if you need to run any field-level validation tasks after
        normal validation. This shouldn''t be needed in most cases.

        :param form: The form the field belongs to.
        :param validation_stopped:
            `True` if any validator raised StopValidation.
        """
        pass

 

 

今天关于表格对WTForms永远无效表格无效怎么回事的介绍到此结束,谢谢您的阅读,有关6.Flask-WTForms、Flask - WTF 和 WTForms 创建表单、flask web 表单验证 WTForms、flask wtforms 组件详解等更多相关知识的信息可以在本站进行查询。

本文标签: