wtformsinflask(wtforms在flask中的应用)(代码片段)

TreeSir TreeSir     2022-11-07     641

关键词:

WTForms

WTForms是一个支持多个web框架的form组件,主要用于对用户请求数据进行验证。

安装wtforms : pip3/pip install wtforms

用户登录/注册示例

项目目录结构

flask-wtforms-example
    │  app.py
    │  
    └─templates
            add_user.html
            index.html
            login.html
            register.html
            users.html

启动项目

用户登录

当用户登录时候,需要对用户提交的用户名和密码进行多种格式校验。

如:

  用户不能为空;用户长度必须大于6;

  密码不能为空;密码长度必须大于12;密码必须包含 字母、数字、特殊字符等(自定义正则);

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from flask import Flask, render_template, request, redirect
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


class LoginForm(Form):
    name = simple.StringField(
        label=\'用户名\',
        validators=[
            validators.DataRequired(message=\'用户名不能为空.\'),
            validators.Length(min=6, max=18, message=\'用户名长度必须大于%(min)d且小于%(max)d\')
        ],
        widget=widgets.TextInput(),
        render_kw=\'class\': \'form-control\'

    )
    pwd = simple.PasswordField(
        label=\'密码\',
        validators=[
            validators.DataRequired(message=\'密码不能为空.\'),
            validators.Length(min=8, message=\'用户名长度必须大于%(min)d\'),
            validators.Regexp(regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[$@$!%*?&])[A-Za-z\\d$@$!%*?&]8,",
                              message=\'密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符\')

        ],
        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():
            print(\'用户提交数据通过格式验证,提交的值为:\', form.data)
        else:
            print(form.errors)
        return render_template(\'login.html\', form=form)

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

app.py
app.py
<!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>
login.html

代码下载地址

用户注册

注册页面需要让用户输入:用户名、密码、密码重复、性别、爱好等。

from flask import Flask, render_template, request, redirect
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



class RegisterForm(Form):
    name = simple.StringField(
        label=\'用户名\',
        validators=[
            validators.DataRequired()
        ],
        widget=widgets.TextInput(),
        render_kw=\'class\': \'form-control\',
        default=\'alex\'
    )

    pwd = simple.PasswordField(
        label=\'密码\',
        validators=[
            validators.DataRequired(message=\'密码不能为空.\')
        ],
        widget=widgets.PasswordInput(),
        render_kw=\'class\': \'form-control\'
    )

    pwd_confirm = simple.PasswordField(
        label=\'重复密码\',
        validators=[
            validators.DataRequired(message=\'重复密码不能为空.\'),
            validators.EqualTo(\'pwd\', message="两次密码输入不一致")
        ],
        widget=widgets.PasswordInput(),
        render_kw=\'class\': \'form-control\'
    )

    email = html5.EmailField(
        label=\'邮箱\',
        validators=[
            validators.DataRequired(message=\'邮箱不能为空.\'),
            validators.Email(message=\'邮箱格式错误\')
        ],
        widget=widgets.TextInput(input_type=\'email\'),
        render_kw=\'class\': \'form-control\'
    )

    gender = core.RadioField(
        label=\'性别\',
        choices=(
            (1, \'\'),
            (2, \'\'),
        ),
        coerce=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):
        super(RegisterForm, self).__init__(*args, **kwargs)
        self.favor.choices = ((1, \'篮球\'), (2, \'足球\'), (3, \'羽毛球\'))

    def validate_pwd_confirm(self, field):
        """
        自定义pwd_confirm字段规则,例:与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)
        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()

app.py
app.py
<!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>

register.html
register.py

meta用法

#!/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()
Meta示例

Metaclass示例

示例一

class MyType(type):
    def __init__(self, *args, **kwargs):
        print(\'MyType创建类\',self)
        super(MyType, self).__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        obj = super(MyType, self).__call__(*args, **kwargs)
        print(\'类创建对象\', self, obj)
        return obj


class Foo(object,metaclass=MyType):
    user = \'wupeiqi\'
    age = 18

obj = Foo()

示例二

class MyType(type):
    def __init__(self, *args, **kwargs):
        super(MyType, self).__init__(*args, **kwargs)

    def __call__(cls, *args, **kwargs):
        v = dir(cls)
        obj = super(MyType, cls).__call__(*args, **kwargs)
        return obj


class Foo(MyType(\'MyType\', (object,), )):
    user = \'wupeiqi\'
    age = 18


obj = Foo()

示例三

class MyType(type):
    def __init__(self, *args, **kwargs):
        super(MyType, self).__init__(*args, **kwargs)

    def __call__(cls, *args, **kwargs):
        v = dir(cls)
        obj = super(MyType, cls).__call__(*args, **kwargs)
        return obj


def with_metaclass(arg,base):
    return MyType(\'MyType\', (base,), )


class Foo(with_metaclass(MyType,object)):
    user = \'wupeiqi\'
    age = 18


obj = Foo()

实例化流程分析

    # 源码流程
    1. 执行type的 __call__ 方法,读取字段到静态字段 cls._unbound_fields 中; meta类读取到cls._wtforms_meta中
    2. 执行构造方法
        
        a. 循环cls._unbound_fields中的字段,并执行字段的bind方法,然后将返回值添加到 self._fields[name] 中。
            即:
                _fields = 
                    name: wtforms.fields.core.StringField(),
                
                
            PS:由于字段中的__new__方法,实例化时:name = simple.StringField(label=\'用户名\'),创建的是UnboundField(cls, *args, **kwargs),当执行完bind之后,才变成执行 wtforms.fields.core.StringField()
        
        b. 循环_fields,为对象设置属性
            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)
        c. 执行process,为字段设置默认值:self.process(formdata, obj, data=data, **kwargs)
            优先级:obj,data,formdata;
            
            再循环执行每个字段的process方法,为每个字段设置值:
            for name, field, in iteritems(self._fields):
                if obj is not None and hasattr(obj, name):
                    field.process(formdata, getattr(obj, name))
                elif name in kwargs:
                    field.process(formdata, kwargs[name])
                else:
                    field.process(formdata)
            
            执行每个字段的process方法,为字段的data和字段的raw_data赋值
            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:
                    try:
                        if self.name in formdata:
                            self.raw_data = formdata.getlist(self.name)
                        else:
                            self.raw_data = []
                        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])
                
        d. 页面上执行print(form.name) 时,打印标签
            
            因为执行了:
                字段的 __str__ 方法
                字符的 __call__ 方法
                self.meta.render_field(self, kwargs)
                    def render_field(self, field, render_kw):
                        other_kw = getattr(field, \'render_kw\', None)
                        if other_kw is not None:
                            render_kw = dict(other_kw, **render_kw)
                        return field.widget(field, **render_kw)
                执行字段的插件对象的 __call__ 方法,返回标签字符串
分析

验证流程分析

查看详情

WTForms 自定义验证器 - 需要在 RadioField 上

】WTForms自定义验证器-需要在RadioField上【英文标题】:WTFormscustomvalidator-RequiredIfonaRadioField【发布时间】:2018-11-2104:58:39【问题描述】:我借用了一个验证器,如果另一个字段的值为某个值,则要求用户输入数据:classRequiredIf(obje... 查看详情

在烧瓶 wtforms jinja select 上设置动态数据属性

】在烧瓶wtformsjinjaselect上设置动态数据属性【英文标题】:settingadynamicdataattributeonflaskwtformsjinjaselect【发布时间】:2021-12-1403:44:39【问题描述】:有一个烧瓶wtforms选择字段并尝试合并htmxajax调用,该调用在数据属性中有破折号,... 查看详情

WTForms 得到错误

】WTForms得到错误【英文标题】:WTFormsgettingtheerrors【发布时间】:2011-09-2117:24:46【问题描述】:目前在WTForms中要访问错误,您必须像这样遍历字段错误:forerrorinform.username.errors:printerror由于我正在构建一个不使用表单视图的rest... 查看详情

使用 wtforms 动态添加输入字段

】使用wtforms动态添加输入字段【英文标题】:Addinputfieldsdynamicallywithwtforms【发布时间】:2015-04-0704:09:49【问题描述】:我不太确定如何处理这件事。我希望我能到达那里。例如,我在一个页面上有一个充满地址的表格。这些计... 查看详情

WTForms 不会呈现字段

】WTForms不会呈现字段【英文标题】:WTFormswon\'trenderfields【发布时间】:2011-07-1305:38:07【问题描述】:我在使用WTForms呈现表单字段时遇到问题。我将它与GAE中的webapp框架和Django模板一起使用。我做了一个简单的项目作为测试,它... 查看详情

确定在 Flask 视图中按下了哪个 WTForms 按钮

】确定在Flask视图中按下了哪个WTForms按钮【英文标题】:DeterminewhichWTFormsbuttonwaspressedinaFlaskview【发布时间】:2016-06-1621:21:54【问题描述】:我有一个包含多个链接的页面,用于将用户重定向到不同的页面。我认为使用表单会更... 查看详情

使用来自 SQLAlchemy 对象的数据在烧瓶中预填充 WTforms

】使用来自SQLAlchemy对象的数据在烧瓶中预填充WTforms【英文标题】:Pre-PopulateaWTformsinflask,withdatafromaSQLAlchemyobject【发布时间】:2014-07-0522:50:36【问题描述】:我对烧瓶框架相当陌生,正在为一个门户网站创建一个编辑个人资料页... 查看详情

在网站的主要布局模板中使用 Flask-wtforms 的困难

】在网站的主要布局模板中使用Flask-wtforms的困难【英文标题】:DifficultieswithhavingaFlask-wtformsinthemainlayouttemplateofthewebsite【发布时间】:2021-11-1714:20:31【问题描述】:我过去一直在使用烧瓶、python和引导程序在网站上工作。我在我... 查看详情

如何使用 wtforms 使用 SelectMultipleField

】如何使用wtforms使用SelectMultipleField【英文标题】:HowtouseSelectMultipleFieldusingwtforms【发布时间】:2021-10-0703:28:51【问题描述】:我无法理解SelectMultipleField的documentation。是这么写的"您需要为选择字段指定HTML大小属性渲染时。”我... 查看详情

不是动态选择字段 WTFORMS 的有效选择

】不是动态选择字段WTFORMS的有效选择【英文标题】:NotaValidChoiceforDynamicSelectFieldWTFORMS【发布时间】:2012-12-0711:59:43【问题描述】:我目前正在使用WTFORMS创建一个动态选择字段,但是它从不提交并且验证失败并出现以下错误。Not... 查看详情

如何在带有 FormField 的 Flask / WTForms 中使用 populate_obj?

】如何在带有FormField的Flask/WTForms中使用populate_obj?【英文标题】:Howtousepopulate_objinFlask/WTFormswithaFormField?【发布时间】:2016-04-1217:52:35【问题描述】:我有一个有位置的用户。正如概念证明一样,Location是CombinedForm中的一个FormFie... 查看详情

Wtforms.fields.html5 DateField 在 Flask 中不能作为普通的旧 DateField 工作

】Wtforms.fields.html5DateField在Flask中不能作为普通的旧DateField工作【英文标题】:Wtforms.fields.html5DateFieldnotworkingasplainoldDateFieldinFlask【发布时间】:2017-01-2010:29:09【问题描述】:我在让HTML5Datepicker使用WTF-Forms为Flask中的表单提供值时... 查看详情

从 WTForms 字段获取上传的文件

】从WTForms字段获取上传的文件【英文标题】:GetanuploadedfilefromaWTFormsfield【发布时间】:2015-07-2813:22:00【问题描述】:在Flask文档中,文件上传示例使用&lt;inputtype="file"name="file"&gt;然后request.files[\'file\']来获取... 查看详情

如何使用 FormFields 的 WTForms FieldList?

】如何使用FormFields的WTFormsFieldList?【英文标题】:HowtouseaWTFormsFieldListofFormFields?【发布时间】:2015-07-1905:56:39【问题描述】:我正在使用Flask构建一个网站,其中我使用WTForms。在表单中,我现在想使用FormFields的FieldList,如下所... 查看详情

将 Select2 与烧瓶 wtforms 一起使用

】将Select2与烧瓶wtforms一起使用【英文标题】:UsingSelect2withflask-wtforms【发布时间】:2019-01-0108:22:22【问题描述】:在select2操作下拉字段后,form.owner_id.data的常规使用会产生None。如何从select2字段中提取所选选项。如果我禁用selec... 查看详情

用数据填充 WTForms FormField FieldList 会在字段中生成 HTML

】用数据填充WTFormsFormFieldFieldList会在字段中生成HTML【英文标题】:FillingWTFormsFormFieldFieldListwithdataresultsinHTMLinfields【发布时间】:2015-06-1310:04:38【问题描述】:我有一个Flask应用程序,我可以在其中通过上传CSV文件来填充表单数... 查看详情

Flask WTForms 动态表单依赖项 selectField 未填充在编辑页面中

】FlaskWTForms动态表单依赖项selectField未填充在编辑页面中【英文标题】:FlaskWTFormsdynamicformsdependenciesselecFieldnotpopulateineditpage【发布时间】:2019-12-1916:46:35【问题描述】:我有要在编辑页面中重新填充的表单,在我的表单中,我有... 查看详情