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

目录:

  • 每篇前言:
  • 用户登录验证:
  • 用户注册验证:
    • 使用示例:
  • 抽象解读使用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 + 用户发过来的对应的数据   ——>    正则校验

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

L3 【哈工大_操作系统】操作系统启动

本节要点&#xff1a; 1、理解 OS 启动过程发生了什么&#xff0c;理解 OS 与 硬件 与 应用 之间的关系 2、本节讲解了 setup 模块 和 system 模块实现的功能 1、计算机上电时&#xff0c;操作系统在硬盘&#xff08;磁盘&#xff09;上&#xff0c;为了“取指执行”&#xff0…

Vite多环境配置与打包:灵活高效的Vue开发工作流

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

京东商品详情接口可以获取到那些数据?商品属性价格sku主图

京东商品详情接口可以获取到关于商品的丰富数据&#xff0c;包括但不限于以下内容&#xff1a; 商品基本信息&#xff1a;例如商品标题、价格、销量等。商品详情描述&#xff1a;这包括商品的详细描述、规格参数、包装清单等。商品评价信息&#xff1a;比如商品的好评率、评价…

图神经网络

图的性质 聚类系数 C i E i T i C_i \frac{E_i}{T_i} Ci​Ti​Ei​​ E i E_i Ei​表示节点 i i i的邻居实际存在的边的数量&#xff0c; T i T_i Ti​表示节点 i i i的邻居可能&#xff08;最多&#xff09;存在的边的数量 理论溯源 聚类系数这一概念首先源于论文“Colle…

OpenCV的查找命中或未命中

返回:OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇:OpenCV4.9更多形态转换 下一篇:OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 目标 在本教程中&#xff0c;您将学习如何使用 Hit-or-Miss 转换&#xff08;也称为 Hit-and-Miss 转…

已解决:前端直传阿里oss报错跨域问题,“No ‘Access-Control-Allow-Origin‘”,这个错误基本就是在阿里的开放平台没做规则配置(附我封装的上传源码)

解决方案&#xff08;我封装的上传代码在后面“封装上传”部分&#xff09;&#xff1a; 就直接上阿里oss管理后台去增加一个跨域规则&#xff1a;见图片&#xff0c;特详细 配置成这样点确定就好了&#xff0c;就这么简单 案发背景&#xff1a; 标题其实就已经是答案了&…

2024年第十五届蓝桥杯C/C++B组复盘(持续更新)

&#x1f525;博客主页&#xff1a; 小羊失眠啦. &#x1f3a5;系列专栏&#xff1a;《C语言》 《数据结构》 《C》 《Linux》 《Cpolar》 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 文章目录 试题A&#xff1a;握手问题问题描述思路 试题B&#xff1a;小球反弹问题描述思路…

【算法】字符串

个人主页 &#xff1a; zxctscl 如有转载请先通知 题目 1. 14. 最长公共前缀1.1 分析1.2 代码 2. 5. 最长回文子串2.1 分析2.2 代码 3. 67. 二进制求和3.1 分析3.2 代码 4. 43. 字符串相乘4.1 分析4.2 代码 1. 14. 最长公共前缀 1.1 分析 从第一个字符串开始两两比较&#xff…

LlamaIndex 文档 2

文章目录 一、构建 LLM 应用构建LLM 应用的关键步骤 二、使用LLM可用的LLM使用本地LLM Prompts 三、加载数据&#xff08;提取&#xff09;Loaders1、使用 SimpleDirectoryReader 加载2、使用 LlamaHub 的 Readers3、直接创建文档 转换 Transformations1、高级转换 API2、较低级…

Unity URP PBR_Cook-Torrance模型

Cook-Torrance模型是一个微表面光照模型&#xff0c;认为物体的表面可以看作是由许多个理想的镜面反射体微小平面组成的。 单点反射镜面反射漫反射占比*漫反射 漫反射 基础色/Π 镜面反射DFG/4(NV)(NL) D代表微平面分布函数&#xff0c;描述的是法线与半角向量normalize(L…

自编译支持CUDA硬解的OPENCV和FFMPEG

1 整体思路 查阅opencv的官方文档&#xff0c;可看到有个cudacodec扩展&#xff0c;用他可方便的进行编解码。唯一麻烦的是需要自行编译opencv。 同时&#xff0c;为了考虑后续方便&#xff0c;顺手编译了FFMPEG&#xff0c;并将其与OPENCV绑定。 在之前的博文“鲲鹏主机昇腾A…

帆软查询按钮,获取组件值。

【查询】按钮增加点击事件&#xff0c;通过_g().parameterEl.getWidgetByName(‘组件名’).getValue(); 获取组件值。 js脚本示例: var bm _g().parameterEl.getWidgetByName(bm).getValue(); if(!bm || bm.length 0 ) {alert ("没有选择部门&#xff0c;查询速度会很…

解决PyCharm安装第三方库时出现“Error updating package list: Connect timed out”问题

在使用PyCharm开发Python项目时&#xff0c;有时会遇到在安装第三方库时出现“Error updating package list: Connect timed out”的错误。这通常是由于网络连接不稳定或PyPI官方源访问速度较慢导致的。为解决此类问题&#xff0c;本文将介绍以下几种策略&#xff1a; 2. 设置P…

【练习】位运算思想

&#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f525;个人专栏&#xff1a;算法(Java)&#x1f4d5;格言&#xff1a;吾愚多不敏&#xff0c;而愿加学欢迎大家&#x1f44d;点赞✍评论⭐收藏 目录 1.判断字符串是否唯一 题目描述 讲解 代码实现 2.丢失的数字 题目描述…

重学Java 12 JavaBean

一、JavaBean的使用 1.标准javaBean JavaBean是Java语言编写类的一种标准规范&#xff0c;符合JavaBean的类&#xff0c;要求&#xff1a; ①类必须是具体的&#xff08;非抽象 abstract&#xff09;和公共的&#xff0c;public class 类名 ②并且具有无参数的构造方法&#x…

C#泛型,利用反射创建和普通创建泛型

泛型,利用反射创建和普通创建 反射 var input Activator.CreateInstance(typeof(Input<>).MakeGenericType(typeof(T))) as dynamic;typeof(T)这个位置可以塞入不同的类型 Activator.CreateInstance 反射动态创建实例&#xff1a; 这种方式使用 Activator.CreateIns…

Android Studio 之 Intent及其参数传递

一、Intent 显式Intent&#xff1a;通过组件名指定启动的目标组件,比如startActivity(new Intent(A.this,B.class)); 每次启动的组件只有一个~隐式Intent:不指定组件名,而指定Intent的Action,Data,或Category,当我们启动组件时, 会去匹配AndroidManifest.xml相关组件的Intent-…

《6G数据面架构研究》

目录 一、数据服务的定义二、6G数据服务驱动力及面临的挑战6G数据服务的业务驱动6G数据服务的技术驱动6G数据服务的网络内在驱动6G数据面面临的挑战 三、6G数据服务典型场景自动化网络运维用户体验提升通信感知数据服务 四、6G数据面架构研究数据面架构视图功能定义说明&#x…

在Windows上安装Go编译器并配置Golang开发环境

文章目录 1、安装Go语言编译程序1.1、下载GoLang编译器1.2、安装GoLang编译器 2、配置Golang IDE运行环境2.1、配置GO编译器2.1.1、GOROOT 概述2.1.2、GOROOT 作用2.1.2、配置 GOROOT 2.2、配置GO依赖管理2.2.1、Module管理依赖2.2.2、GOPATH 管理依赖 2.3、运行GO程序2.3.1、创…

CMake基础语法

目录 概述一、示例引入二、语法规则三、变量四、控制结构4.1 条件判断4.2 循环语句4.2.1 foreach循环4.2.2 while循环4.2.3 break、continue 五、函数六、文件操作七、环境配置7.1 设置交叉编译7.2 作用域7.3 属性 八、补充8.1 数学运算math 概述 首先我们都知道Makefile带来的…