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

2024-04-27 1069阅读

目录:

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

        每篇前言:

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

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

          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:

                
                
                    
                    用户登录
                
                
                
                    {{form.user}} {{form.user.errors[0]}}
                    {{form.pwd}} {{form.pwd.errors[0]}}
                    {{msg}}
                
                
                
                

                用户注册验证:

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

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

                  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的作用:

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

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

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

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

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

                    肯定不是的!

                    wtforms支持我们使用for循环~

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

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

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

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

                    进Form —> 进BaseForm:

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

                  使用示例:

                  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:

                  
                  
                      
                      用户注册
                  
                  
                  
                      {% for item in form %}
                      

                  {{ item.label }}: {{item}} {{item.errors[0]}}

                  {% endfor %}
                  • 问题引入:

                    实际生产中,可能有些下拉框的值是从数据库中取出展示的。此处以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发现它是一个类):

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

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

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

                    而前端就相当于执行了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 + 用户发过来的对应的数据   ——>    正则校验
                    
VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]