(二十八)Flask之wtforms库【上手使用篇】

news2024/12/23 20:06:41

目录:

  • 每篇前言:
  • 用户登录验证:
  • 用户注册验证:
    • 使用示例:
  • 抽象解读使用wtforms编写的类:
    • 简单谈一嘴:
    • 开始抽象:

每篇前言:

  • 🏆🏆作者介绍:【孤寒者】—CSDN全栈领域优质创作者、HDZ核心组成员、华为云享专家Python全栈领域博主、CSDN原力计划作者

  • 🔥🔥本文已收录于Flask框架从入门到实战专栏:《Flask框架从入门到实战》
  • 🔥🔥热门专栏推荐:《Python全栈系列教程》、《爬虫从入门到精通系列教程》、《爬虫进阶+实战系列教程》、《Scrapy框架从入门到实战》、《Flask框架从入门到实战》、《Django框架从入门到实战》、《Tornado框架从入门到实战》、《前端系列教程》。
  • 📝​📝本专栏面向广大程序猿,为的是大家都做到Python全栈技术从入门到精通,穿插有很多实战优化点。
  • 🎉🎉订阅专栏后可私聊进一千多人Python全栈交流群(手把手教学,问题解答); 进群可领取Python全栈教程视频 + 多得数不过来的计算机书籍:基础、Web、爬虫、数据分析、可视化、机器学习、深度学习、人工智能、算法、面试题等。
  • 🚀🚀加入我一起学习进步,一个人可以走的很快,一群人才能走的更远!

在这里插入图片描述

WTForms 是一个用于处理Web表单的Python库。它设计简单,易于使用,广泛用于Web应用程序的表单处理,特别是与Flask等框架一起使用。

wtforms依照功能类别来说有以下几个类别:

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

官方文档:

  • https://wtforms.readthedocs.io/en/stable/index.html#

用户登录验证:

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

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

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

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

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


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

    pwd = simple.PasswordField(
        label='密码',
        validators=[
            validators.DataRequired(message='密码不能为空'),
            validators.Length(min=8, message=f'密码必须大于{min}'),
            # validators.Regexp(regex=r'^(?=.*[A-Z])'  # 至少一个大写字母
            #                         r'(?=.*[a-z])'  # 至少一个小写字母
            #                         r'(?=.*\d)'  # 至少一个数字
            #                         r'(?=.*[@$!%*?&])'  # 至少一个特殊字符
            #                         r'[A-Za-z\d@$!%*?&]{8,}$',  # 总长度至少8个字符
            #                   message='密码至少8个字符,至少一个大写字母,一个小写字母,一个数字和一个特殊字符')
        ],
        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)

    form = LoginForm(formdata=request.form)
    if not form.validate():
        return render_template('login.html', form=form)
	# 对用户提交数据进行校验,form.data是校验完成后的数据字典
    if form.data['user'] == '1234567' and form.data['pwd'] == '123456789':
        print('用户提交的数据通过格式验证,提交的值为:', form.data)
        return 'Login OK~'
    else:
        return render_template('login.html',msg='用户名或密码错误', form=form)


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

login.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户登录</title>
</head>
<body>
<form action="" method="post">
    {{form.user}} {{form.user.errors[0]}}
    {{form.pwd}} {{form.pwd.errors[0]}}
    <input type="submit" value="提交">{{msg}}
</form>
</body>
</html>

用户注册验证:

  • 注册页面需要让用户输入:用户名、密码、确认密码、性别、爱好…

来一个实战例子,底下会拆分详讲:

from flask import Flask, render_template, request, redirect
from wtforms import Form
from wtforms.fields import core, simple, html5
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='GuHanZhe'                         # 页面输入框默认值
    )

    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='两次密码输入不一致')    # EqualTo作用是比较当前字段和指定字段名的字段值是否相等
        ],
        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 __int__(self, *args, **kwargs):
        super(RegisterForm, self).__init__(*args, **kwargs)
        self.favor.choices = ((1, '篮球'), (2, '足球'), (3, '羽毛球'))

    def validate_pwd_confirm(self, field):
        """
        自定义pwd_confirm字段规则,例:与pwd字段是否一致
        """
        # 最开始初始化时,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)

    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()

  1. 有关于上述代码中coerce=int的作用:

    在这里插入图片描述

    wtforms库中,coerce参数是用来强制转换字段值的参数。coerce=int的作用是将选项中的值强制转换为整数类型

    RadioField中,choices参数定义了可选的值,它是一个元组,其中包含了每个选项的值和标签。在上图中,每个选项的值是1和2,而标签是’男’和’女’。由于HTTP表单提交的数据通常是字符串形式,使用coerce=int告诉wtforms将用户提交的值强制转换为整数类型。

    这对于确保表单数据的类型与后端处理代码的期望类型一致非常有用。在这个例子中,gender字段的值将被强制转换为整数,而不是保持为字符串。这样,在处理表单数据时就可以直接使用整数类型,而不需要手动进行类型转换。

  2. 上述我定义了这么多的字段,难道在写前端register.html代码的时候要一个个敲吗???

    肯定不是的!

    wtforms支持我们使用for循环~

    回想一下:一个类的实例如何才能支持for循环?

    在《Python全栈系列教程》专栏里讲过,只要一个类内部实现了iter魔法方法,且这个方法返回了一个迭代器,那么这个类的实例就支持for循环。

    在这里插入图片描述

    所以来看下wtforms源码,确认一下:

    进Form —> 进BaseForm:

    在这里插入图片描述

使用示例:

from flask import Flask, render_template, request, redirect
from wtforms import Form
from wtforms.fields import core, simple, html5
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='GuHanZhe'                         # 页面输入框默认值
    )

    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='两次密码输入不一致')    # EqualTo作用是比较当前字段和指定字段名的字段值是否相等
        ],
        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]
    )


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

    form = RegisterForm(formdata=request.form)
    if form.validate():
        print('用户提交数据通过格式验证,提交的值为:', form.data)
    else:
        print(form.errors)
    return '登录成功~'


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

register.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户注册</title>
</head>
<body>
<form action="" method="post">
    {% for item in form %}
    <p>{{ item.label }}: {{item}} {{item.errors[0]}}</p>
    {% endfor %}
    <input type="submit" value="提交">
</form>
</body>
</html>
  • 问题引入:

    实际生产中,可能有些下拉框的值是从数据库中取出展示的。此处以city这个为例:

        city = core.SelectField(
            label='城市',
            choices=SQLHelper.fetch_all('select id, name from city_info', {})
        )
    

    如果直接运行访问,这个下拉框是没有任何问题的。

    但是实际生产中可能会遇到的一个问题是:Flask服务没关,但是往数据库这张表加了几条数据,那么,不管怎样刷新页面,这个下拉框都不会出现这些新加的数据。

    但是将Flask服务重启一下就OK 了。

    原因很简单——因为在RegisterForm类中这些字段都是静态字段,运行的时候只执行一次!

  • 解决方法就是:

    重写RegisterForm类的构造方法:让每次实例化这个类的时候都执行一次sql查询语句并更新对应字段值:

        def __int__(self, *args, **kwargs):
            super(RegisterForm, self).__init__(*args, **kwargs)
            self.city.choices = SQLHelper.fetch_all('select id, name from city_info', {})
    
    

如何自定义校验规则:

【方法名以validate_开头,后面是对应需要校验的字段名】

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

抽象解读使用wtforms编写的类:

简单谈一嘴:

比如LoginForm类中的user字段,很容易知道user是一个实例(可以点进去StringField发现它是一个类):

在这里插入图片描述

在视图函数中,实例化form后将其传给了前端:

在这里插入图片描述

而前端就相当于执行了print(form.user)

那么这就执行对应类StringField的str魔法方法,这个玩意返回什么,页面就看到什么~

开始抽象:

class LoginForm(Form):
	user = 类(正则, 插件)
	字段 = 类(正则, 插件)
	字段 = 类(正则, 插件)
	字段 = 类(正则, 插件)


form = LoginForm(Form)
# 生成html标签
print(form.user)   ——>   类.__str__   ——>   插件.xx方法

# 验证
form = LoginForm(formdata=request.form)
if form.validate():
	# 内部找到所有的字段:
	#                  比如:user + 用户发过来的对应的数据   ——>    正则校验

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1605363.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

多任务学习,在共享层,究竟在共享什么?

在多任务学习中&#xff0c;共享层所共享的主要是网络结构和参数。具体来说&#xff0c;当多个任务在共享层进行参数硬共享时&#xff0c;它们使用的是相同的网络结构&#xff08;例如三层全连接神经网络&#xff09;&#xff0c;并且这些网络层的权重&#xff08;weights&…

Java工具类:批量发送邮件(带附件)

​ 不好用请移至评论区揍我 原创代码,请勿转载,谢谢! 一、介绍 用于给用户发送特定的邮件内容,支持附件、批量发送邮箱账号必须要开启 SMTP 服务(具体见下文教程)本文邮箱设置示例以”网易邮箱“为例,其他如qq邮箱或企业邮箱均可,只要在设置中对应开启SMTP及授权码等操…

css中设置元素大小的属性block-size

block-size 是 CSS 中的一个属性&#xff0c;它用于设置元素的块级尺寸&#xff08;即元素的高度&#xff09;。这个属性是 height 和 max-height 的逻辑组合&#xff0c;允许你同时设置元素的最小和最大高度。 这些属性旨在让布局不再依赖于传统的物理方向&#xff08;如上下左…

爬虫 | 基于 Python 实现有道翻译工具

Hi&#xff0c;大家好&#xff0c;我是半亩花海。本项目旨在利用 Python 语言实现一个简单的有道翻译工具。有道翻译是一款常用的在线翻译服务&#xff0c;能够实现多种语言的互译&#xff0c;提供高质量的翻译结果。 目录 一、项目功能 二、注意事项 三、代码解析 1. 导入…

eclipse配置SVN和Maven插件

3、 安装SVN插件 使用如下方法安装 Help–Install New Software 注意&#xff1a;目前只能安装1.8.x这个版本的SVN&#xff0c;如果使用高版本的SVN&#xff0c;在安装SVN和maven整合插件的时候就会报错&#xff0c;这应该是插件的bug。 点击Add name: subclipse location…

隐式/动态游标的创建与使用

目录 将 emp 数据表中部门 10 的员工工资增加 100 元&#xff0c;然后使用隐式游标的 %ROWCOUNT 属性输出涉及的员工数量 动态游标的定义 声明游标变量 打开游标变量 检索游标变量 关闭游标变量 定义动态游标&#xff0c;输出 emp 中部门 10 的所有员工的工号和姓名 Orac…

编程入门(四)【计算机网络基础(由一根网线连接两个电脑开始)】

读者大大们好呀&#xff01;&#xff01;!☀️☀️☀️ &#x1f525; 欢迎来到我的博客 &#x1f440;期待大大的关注哦❗️❗️❗️ &#x1f680;欢迎收看我的主页文章➡️寻至善的主页 文章目录 前言两个电脑如何互连呢&#xff1f;集线器、交换机与路由器总结 前言 当你有…

【IC前端虚拟项目】接口分析与agent组件生成

【IC前端虚拟项目】数据搬运指令处理模块前端实现虚拟项目说明-CSDN博客 到目前为止关于环境的准备工作都已经完成了,甚至验证环境的大体结构我们也已经画好了,再来看一下: 于是乎呢就可以大张旗鼓的开始咱们验证环境的搭建了!看上面这个结构图,里面除了mvu作为DUT,其他…

【C语言】冒泡排序算法详解

目录 一、算法原理二、算法分析时间复杂度空间复杂度稳定性 三、C语言实现四、Python实现 冒泡排序&#xff08;Bubble Sort&#xff09;是一种基础的排序算法。它重复地遍历要排序的数列&#xff0c;一次比较两个元素&#xff0c;如果他们的顺序错误就把他们交换过来。遍历数列…

HackMyVM-BaseME

目录 信息收集 arp nmap WEB web信息收集 gobuster hydra 目录检索 ssh 提权 get user sudo base64提权 get root 信息收集 arp ┌─[rootparrot]─[~/HackMyVM] └──╼ #arp-scan -l Interface: enp0s3, type: EN10MB, MAC: 08:00:27:16:3d:f8, IPv4: 192.168…

机器人的非接触式充电和无线充电有什么区别?

文 | BFT机器人 在日新月异的技术浪潮中&#xff0c;接触式与非接触式无线充电之间的微妙差异变得愈发重要&#xff0c;这如同在纷繁复杂的迷雾中增添了一层难以捉摸的迷离。而今&#xff0c;一些所谓的“无线”充电站纷纷涌入市场&#xff0c;它们自诩为无需线缆束缚的新时代…

在线预约家政服务小程序上门服务源码系统 带完整的安装代码包以及搭建教程

随着互联网的快速发展&#xff0c;家政服务行业也逐渐向线上化、智能化转型。为了满足广大用户的需求&#xff0c;罗峰给大家分享一款在线预约家政服务小程序上门服务源码系统。该系统不仅功能完善&#xff0c;而且操作简单&#xff0c;是您打造高效、便捷的家政服务平台的首选…

【uniapp踩坑记】——微信小程序转发保存图片

关于微信小程序转发&保存图片 微信小程序图片转发保存简单说明网络图片的转发保存base64流形式图片转发保存 已经好多年没写博客了&#xff0c;最近使用在用uniapp开发一个移动版管理后台&#xff0c;记录下自己踩过的一些坑 吃相别太难看&#xff0c;搞一堆下头僵尸号来点…

Elasticsearch Windows上安装

系列文章目录 文章目录 系列文章目录前言前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能…

python二级题目-仅使用 Python 基本语法,即不使用任何模块,编写 Python 程序计算下列数学表达式的结果并输出,小数点后保留 3 位。

x(((3**4)5*(6**7))/8)**0.5 .format 用法一&#xff1a; print({}.format(1)) 1 print(这个是format的用法{}。。。.format(3)) 这个是format的用法3 ’大括号1:{},大括号2:{},大括号3:{}‘.format(3,4,5) print(’大括号1:{},大括号2:{},大括号3:{}‘.form…

github,raw.githubusercontent.com 等网址登陆不上不去的设置方法

目录 提示域名解析错误&#xff1a; 出现的现象&#xff1a; 解决办法&#xff1a;修改host host改完不生效 解决方案1&#xff1a; 解决方案2&#xff1a; 提示域名解析错误&#xff1a; 出现的现象&#xff1a; 登陆github&#xff0c;raw.githubusercontent.com 等网…

Vue接收接口返回的mp3格式数据并支持在页面播放音频

一、背景简介 在实际工作中需要开发一个转音频工具&#xff0c;并且能够在平台页面点击播放按钮播放音频 二、相关知识介绍 2.1 JS内置对象Blob Blob对象通常用于处理大量的二进制数据&#xff0c;可以读取/写入/操作文件、音视频等二进制数据流。Blob表示了一段不可变的二…

第21天:信息打点-公众号服务Github监控供应链网盘泄漏证书图标邮箱资产

第二十一天 一、开发泄漏-Github监控 1.短期查看 1.密码搜索 根据攻击目标的域名在GitHub上进行搜索密码&#xff0c;如果目标网站的文件与搜索到的源码相关&#xff0c;那就可以联想目标网站是否使用这套源码进行开发 原理就是开发者在上传文件的时候忘记更改敏感文件或者…

Arduino源代码(ino)在Proteus中调试总结

一、前言 基于BluePill Plus开发板&#xff08;该板是毕设网红板&#xff09; BluePill Plus / WeAct Studio 微行工作室 出品 BluePill-Plus/README-zh.md at master WeActStudio/BluePill-Plus GitHub 首页-WeAct Studio-淘宝网 (taobao.com) 在Proteus中对应的例子是&…

解决vue启动项目报错:npm ERR! Missing script: “serve“【详细清晰版】

目录 问题描述问题分析和解决情况一解决方法情况二&#xff08;常见于vue3&#xff09;解决方法情况三解决方法 问题描述 在启动vue项目时通常在控制台输入npm run serve 但是此时出现如下报错&#xff1a; npm ERR! Missing script: "serve" npm ERR! npm ERR! T…