简介

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

作用

  • 生成HTML标签

  • form表单验证

使用

- 用户登录示例
- 用户注册示例
- 数据库获取数据实时更新(重写构造方法) (跟django的form一样)

注意:字段不能以下划线开头。

安装

  1. pip3 install wtforms

用户登录注册示例

1. 用户登录

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

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

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

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. from flask import Flask, render_template, request, redirect
  4. from wtforms import Form
  5. from wtforms.fields import core
  6. from wtforms.fields import html5
  7. from wtforms.fields import simple
  8. from wtforms import validators
  9. from wtforms import widgets
  10.  
  11. app = Flask(__name__, template_folder='templates')
  12. app.debug = True
  13.  
  14. class LoginForm(Form):
  15. name = simple.StringField(
  16. label='用户名',
  17. validators=[
  18. validators.DataRequired(message='用户名不能为空.'),
  19. validators.Length(min=6, max=18, message='用户名长度必须大于%(min)d且小于%(max)d')
  20. ],
  21. widget=widgets.TextInput(),
  22. render_kw={'class': 'form-control'}
  23.  
  24. )
  25. pwd = simple.PasswordField(
  26. label='密码',
  27. validators=[
  28. validators.DataRequired(message='密码不能为空.'),
  29. validators.Length(min=8, message='用户名长度必须大于%(min)d'),
  30. validators.Regexp(regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]{8,}",
  31. message='密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符')
  32.  
  33. ],
  34. widget=widgets.PasswordInput(),
  35. render_kw={'class': 'form-control'}
  36. )
  37.  
  38. @app.route('/login', methods=['GET', 'POST'])
  39. def login():
  40. if request.method == 'GET':
  41. form = LoginForm()
  42. return render_template('login.html', form=form)
  43. else:
  44. form = LoginForm(formdata=request.form)
  45. if form.validate():
  46. print('用户提交数据通过格式验证,提交的值为:', form.data)
  47. else:
  48. print(form.errors)
  49. return render_template('login.html', form=form)
  50.  
  51. if __name__ == '__main__':
  52. app.run()

app.py

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <h1>登录</h1>
  9. <form method="post">
  10. <!--<input type="text" name="name">-->
  11. <p>{{form.name.label}} {{form.name}} {{form.name.errors[0] }}</p>
  12.  
  13. <!--<input type="password" name="pwd">-->
  14. <p>{{form.pwd.label}} {{form.pwd}} {{form.pwd.errors[0] }}</p>
  15. <input type="submit" value="提交">
  16. </form>
  17. </body>
  18. </html>

login.html

2、用户注册

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

  1. from flask import Flask, render_template, request, redirect
  2. from wtforms import Form
  3. from wtforms.fields import core
  4. from wtforms.fields import html5
  5. from wtforms.fields import simple
  6. from wtforms import validators
  7. from wtforms import widgets
  8.  
  9. app = Flask(__name__, template_folder='templates')
  10. app.debug = True
  11.  
  12. class RegisterForm(Form):
  13. name = simple.StringField(
  14. label='用户名',
  15. validators=[
  16. validators.DataRequired()
  17. ],
  18. widget=widgets.TextInput(),
  19. render_kw={'class': 'form-control'},
  20. default='alex'
  21. )
  22.  
  23. pwd = simple.PasswordField(
  24. label='密码',
  25. validators=[
  26. validators.DataRequired(message='密码不能为空.')
  27. ],
  28. widget=widgets.PasswordInput(),
  29. render_kw={'class': 'form-control'}
  30. )
  31.  
  32. pwd_confirm = simple.PasswordField(
  33. label='重复密码',
  34. validators=[
  35. validators.DataRequired(message='重复密码不能为空.'),
  36. validators.EqualTo('pwd', message="两次密码输入不一致")
  37. ],
  38. widget=widgets.PasswordInput(),
  39. render_kw={'class': 'form-control'}
  40. )
  41.  
  42. email = html5.EmailField(
  43. label='邮箱',
  44. validators=[
  45. validators.DataRequired(message='邮箱不能为空.'),
  46. validators.Email(message='邮箱格式错误')
  47. ],
  48. widget=widgets.TextInput(input_type='email'),
  49. render_kw={'class': 'form-control'}
  50. )
  51.  
  52. gender = core.RadioField(
  53. label='性别',
  54. choices=(
  55. (1, '男'),
  56. (2, '女'),
  57. ),
  58. coerce=int
  59. )
  60. city = core.SelectField(
  61. label='城市',
  62. choices=(
  63. ('bj', '北京'),
  64. ('sh', '上海'),
  65. )
  66. )
  67.  
  68. hobby = core.SelectMultipleField(
  69. label='爱好',
  70. choices=(
  71. (1, '篮球'),
  72. (2, '足球'),
  73. ),
  74. coerce=int
  75. )
  76.  
  77. favor = core.SelectMultipleField(
  78. label='喜好',
  79. choices=(
  80. (1, '篮球'),
  81. (2, '足球'),
  82. ),
  83. widget=widgets.ListWidget(prefix_label=False),
  84. option_widget=widgets.CheckboxInput(),
  85. coerce=int,
  86. default=[1, 2]
  87. )
  88.  
  89. def __init__(self, *args, **kwargs):
  90. super(RegisterForm, self).__init__(*args, **kwargs)
  91. self.favor.choices = ((1, '篮球'), (2, '足球'), (3, '羽毛球'))
  92.  
  93. def validate_pwd_confirm(self, field):
  94. """
  95. 自定义pwd_confirm字段规则,例:与pwd字段是否一致
  96. :param field:
  97. :return:
  98. """
  99. # 最开始初始化时,self.data中已经有所有的值
  100.  
  101. if field.data != self.data['pwd']:
  102. # raise validators.ValidationError("密码不一致") # 继续后续验证
  103. raise validators.StopValidation("密码不一致") # 不再继续后续验证
  104.  
  105. @app.route('/register', methods=['GET', 'POST'])
  106. def register():
  107. if request.method == 'GET':
  108. form = RegisterForm(data={'gender': 1})
  109. return render_template('register.html', form=form)
  110. else:
  111. form = RegisterForm(formdata=request.form)
  112. if form.validate():
  113. print('用户提交数据通过格式验证,提交的值为:', form.data)
  114. else:
  115. print(form.errors)
  116. return render_template('register.html', form=form)
  117.  
  118. if __name__ == '__main__':
  119. app.run()

app.py

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <h1>用户注册</h1>
  9. <form method="post" novalidate style="padding:0 50px">
  10. {% for item in form %}
  11. <p>{{item.label}}: {{item}} {{item.errors[0] }}</p>
  12. {% endfor %}
  13. <input type="submit" value="提交">
  14. </form>
  15. </body>
  16. </html>

register.html

3、meta

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. from flask import Flask, render_template, request, redirect, session
  4. from wtforms import Form
  5. from wtforms.csrf.core import CSRF
  6. from wtforms.fields import core
  7. from wtforms.fields import html5
  8. from wtforms.fields import simple
  9. from wtforms import validators
  10. from wtforms import widgets
  11. from hashlib import md5
  12.  
  13. app = Flask(__name__, template_folder='templates')
  14. app.debug = True
  15.  
  16. class MyCSRF(CSRF):
  17. """
  18. Generate a CSRF token based on the user's IP. I am probably not very
  19. secure, so don't use me.
  20. """
  21.  
  22. def setup_form(self, form):
  23. self.csrf_context = form.meta.csrf_context()
  24. self.csrf_secret = form.meta.csrf_secret
  25. return super(MyCSRF, self).setup_form(form)
  26.  
  27. def generate_csrf_token(self, csrf_token):
  28. gid = self.csrf_secret + self.csrf_context
  29. token = md5(gid.encode('utf-8')).hexdigest()
  30. return token
  31.  
  32. def validate_csrf_token(self, form, field):
  33. print(field.data, field.current_token)
  34. if field.data != field.current_token:
  35. raise ValueError('Invalid CSRF')
  36.  
  37. class TestForm(Form):
  38. name = html5.EmailField(label='用户名')
  39. pwd = simple.StringField(label='密码')
  40.  
  41. class Meta:
  42. # -- CSRF
  43. # 是否自动生成CSRF标签
  44. csrf = True
  45. # 生成CSRF标签name
  46. csrf_field_name = 'csrf_token'
  47.  
  48. # 自动生成标签的值,加密用的csrf_secret
  49. csrf_secret = 'xxxxxx'
  50. # 自动生成标签的值,加密用的csrf_context
  51. csrf_context = lambda x: request.url
  52. # 生成和比较csrf标签
  53. csrf_class = MyCSRF
  54.  
  55. # -- i18n
  56. # 是否支持本地化
  57. # locales = False
  58. locales = ('zh', 'en')
  59. # 是否对本地化进行缓存
  60. cache_translations = True
  61. # 保存本地化缓存信息的字段
  62. translations_cache = {}
  63.  
  64. @app.route('/index/', methods=['GET', 'POST'])
  65. def index():
  66. if request.method == 'GET':
  67. form = TestForm()
  68. else:
  69. form = TestForm(formdata=request.form)
  70. if form.validate():
  71. print(form)
  72. return render_template('index.html', form=form)
  73.  
  74. if __name__ == '__main__':
  75. app.run()

py

问题

form对象为什么可以被for循环?

  1. 答:变为可迭代对象。
  2. class Foo(object):
  3. '''
  4. 要想成为迭代器,必须要有__iter__方法,而且返回迭代器(生成器是特殊的迭代器)
  5. '''
  6. # def __iter__(self):
  7. # return iter([11,22,33])
  8.  
  9. def __iter__(self):
  10. yield 1
  11. yield 2
  12. yield 3
  13.  
  14. obj = Foo()
  15. for item in obj:
  16. print(item)

new方法的返回值决定对象到底是什么?

  1. class Bar(object):
  2. pass
  3.  
  4. class Foo(object):
  5.  
  6. def __new__(cls, *args, **kwargs):
  7. # return super(Foo,cls).__new__(cls,*args, **kwargs)
  8. return Bar()
  9. obj = Foo()
  10. print(obj)

metaclass 

类创建的两种方式 

  1. #方式一
  2. class Foo(object):
  3. a1 = 123
  4. def func(self):
  5. return 666
  6. #方式二
  7. Foo = type("Foo",(object,),{'a1':123,'func':lambda self:666})

对象是由类创建的,类是由type创建的。

metaclass作用是指定当前类由谁来创建。

  • - 创建类时,先执行type的__init__。
  • - 类的实例化时,执行type的__call__,__call__方法的的返回值就是实例化的对象。
    • __call__内部调用:

      • - 类.__new__,创建对象
      • - 类.__init__,对象的初始化
  1. class MyType(type):
  2. def __init__(self,*args,**kwargs):
  3. super(MyType,self).__init__(*args,**kwargs)
  4.  
  5. def __call__(cls, *args, **kwargs):
  6. obj = cls.__new__(cls)
  7.  
  8. cls.__init__(obj,*args, **kwargs)
  9.  
  10. return obj
  11.  
  12. class Foo(object,metaclass=MyType):
  13. a1 = 123
  14. def __init__(self):
  15. pass
  16.  
  17. def __new__(cls, *args, **kwargs):
  18. return object.__new__(cls)
  19.  
  20. def func(self):
  21. return 666
  22.  
  23. # Foo是类
  24. # Foo是MyType的一个对象
  25.  
  26. obj = Foo()

- 源码:

  • - 类的创建

    • type.__init__
  • - 对象的创建
    • type.__call__

      • - 类.__new__
      • - 类.__init__  

其他

1、metaclass

  1. class MyType(type):
  2. def __init__(self, *args, **kwargs):
  3. print('MyType创建类',self)
  4. super(MyType, self).__init__(*args, **kwargs)
  5.  
  6. def __call__(self, *args, **kwargs):
  7. obj = super(MyType, self).__call__(*args, **kwargs)
  8. print('类创建对象', self, obj)
  9. return obj
  10.  
  11. class Foo(object,metaclass=MyType):
  12. user = 'wupeiqi'
  13. age = 18
  14.  
  15. obj = Foo()

示例一

  1. class MyType(type):
  2. def __init__(self, *args, **kwargs):
  3. super(MyType, self).__init__(*args, **kwargs)
  4.  
  5. def __call__(cls, *args, **kwargs):
  6. v = dir(cls)
  7. obj = super(MyType, cls).__call__(*args, **kwargs)
  8. return obj
  9.  
  10. class Foo(MyType('MyType', (object,), {})):
  11. user = 'wupeiqi'
  12. age = 18
  13.  
  14. obj = Foo()

示例二

  1. class MyType(type):
  2. def __init__(self, *args, **kwargs):
  3. super(MyType, self).__init__(*args, **kwargs)
  4.  
  5. def __call__(cls, *args, **kwargs):
  6. v = dir(cls)
  7. obj = super(MyType, cls).__call__(*args, **kwargs)
  8. return obj
  9.  
  10. def with_metaclass(arg,base):
  11. return MyType('MyType', (base,), {})
  12.  
  13. class Foo(with_metaclass(MyType,object)):
  14. user = 'wupeiqi'
  15. age = 18
  16.  
  17. obj = Foo()

示例三

2、实例化流程分析

  1. # 源码流程
  2. 1. 执行type __call__ 方法,读取字段到静态字段 cls._unbound_fields 中; meta类读取到cls._wtforms_meta
  3. 2. 执行构造方法
  4.  
  5. a. 循环cls._unbound_fields中的字段,并执行字段的bind方法,然后将返回值添加到 self._fields[name] 中。
  6. 即:
  7. _fields = {
  8. name: wtforms.fields.core.StringField(),
  9. }
  10.  
  11. PS:由于字段中的__new__方法,实例化时:name = simple.StringField(label='用户名'),创建的是UnboundField(cls, *args, **kwargs),当执行完bind之后,才变成执行 wtforms.fields.core.StringField()
  12.  
  13. b. 循环_fields,为对象设置属性
  14. for name, field in iteritems(self._fields):
  15. # Set all the fields to attributes so that they obscure the class
  16. # attributes with the same names.
  17. setattr(self, name, field)
  18. c. 执行process,为字段设置默认值:self.process(formdata, obj, data=data, **kwargs)
  19. 优先级:obj,data,formdata;
  20.  
  21. 再循环执行每个字段的process方法,为每个字段设置值:
  22. for name, field, in iteritems(self._fields):
  23. if obj is not None and hasattr(obj, name):
  24. field.process(formdata, getattr(obj, name))
  25. elif name in kwargs:
  26. field.process(formdata, kwargs[name])
  27. else:
  28. field.process(formdata)
  29.  
  30. 执行每个字段的process方法,为字段的data和字段的raw_data赋值
  31. def process(self, formdata, data=unset_value):
  32. self.process_errors = []
  33. if data is unset_value:
  34. try:
  35. data = self.default()
  36. except TypeError:
  37. data = self.default
  38.  
  39. self.object_data = data
  40.  
  41. try:
  42. self.process_data(data)
  43. except ValueError as e:
  44. self.process_errors.append(e.args[0])
  45.  
  46. if formdata:
  47. try:
  48. if self.name in formdata:
  49. self.raw_data = formdata.getlist(self.name)
  50. else:
  51. self.raw_data = []
  52. self.process_formdata(self.raw_data)
  53. except ValueError as e:
  54. self.process_errors.append(e.args[0])
  55.  
  56. try:
  57. for filter in self.filters:
  58. self.data = filter(self.data)
  59. except ValueError as e:
  60. self.process_errors.append(e.args[0])
  61.  
  62. d. 页面上执行print(form.name) 时,打印标签
  63.  
  64. 因为执行了:
  65. 字段的 __str__ 方法
  66. 字符的 __call__ 方法
  67. self.meta.render_field(self, kwargs)
  68. def render_field(self, field, render_kw):
  69. other_kw = getattr(field, 'render_kw', None)
  70. if other_kw is not None:
  71. render_kw = dict(other_kw, **render_kw)
  72. return field.widget(field, **render_kw)
  73. 执行字段的插件对象的 __call__ 方法,返回标签字符串

实例化流程分析

3、验证流程分析

  1. a. 执行formvalidate方法,获取钩子方法
  2. def validate(self):
  3. extra = {}
  4. for name in self._fields:
  5. inline = getattr(self.__class__, 'validate_%s' % name, None)
  6. if inline is not None:
  7. extra[name] = [inline]
  8.  
  9. return super(Form, self).validate(extra)
  10. b. 循环每一个字段,执行字段的 validate 方法进行校验(参数传递了钩子函数)
  11. def validate(self, extra_validators=None):
  12. self._errors = None
  13. success = True
  14. for name, field in iteritems(self._fields):
  15. if extra_validators is not None and name in extra_validators:
  16. extra = extra_validators[name]
  17. else:
  18. extra = tuple()
  19. if not field.validate(self, extra):
  20. success = False
  21. return success
  22. c. 每个字段进行验证时候
  23. 字段的pre_validate 【预留的扩展】
  24. 字段的_run_validation_chain,对正则和字段的钩子函数进行校验
  25. 字段的post_validate【预留的扩展】

验证流程分析

Flask框架 之 wtforms的更多相关文章

  1. flask框架----flask中的wtforms使用

    一.简单介绍flask中的wtforms WTForms是一个支持多个web框架的form组件,主要用于对用户请求数据进行验证. 安装: pip3 install wtforms 二.简单使用wtfo ...

  2. Flask框架 (四)—— 请求上下文源码分析、g对象、第三方插件(flask_session、flask_script、wtforms)、信号

    Flask框架 (四)—— 请求上下文源码分析.g对象.第三方插件(flask_session.flask_script.wtforms).信号 目录 请求上下文源码分析.g对象.第三方插件(flas ...

  3. Flask 框架入门

    Flask Flask是一个使用 Python 编写的轻量级 Web 应用框架.其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 . 安装 Flask 依赖两个外部库, We ...

  4. flask中的wtforms使用

    一.简单介绍flask中的wtforms WTForms是一个支持多个web框架的form组件,主要用于对用户请求数据进行验证. 安装: pip3 install wtforms 二.简单使用wtfo ...

  5. Flask学习【第7篇】:Flask中的wtforms使用

    简介flask中的wtforms WTForms是一个支持多个web框架的form组件,主要用于对用户请求数据进行验证. 安装 pip3 install wtforms 简单使用wtforms组件 用 ...

  6. 使用 Flask 框架写用户登录功能的Demo时碰到的各种坑(五)——实现注册功能

    使用 Flask 框架写用户登录功能的Demo时碰到的各种坑(一)——创建应用 使用 Flask 框架写用户登录功能的Demo时碰到的各种坑(二)——使用蓝图功能进行模块化 使用 Flask 框架写用 ...

  7. Flask系列(七)Flask中的wtforms使用

    一.简单介绍flask中的wtforms WTForms是一个支持多个web框架的form组件,主要用于对用户请求数据进行验证. 安装: pip3 install wtforms 二.简单使用wtfo ...

  8. python高级之Flask框架

    目录: Flask基本使用 Flask配置文件 Flask路由系统 Flask模版 Flask请求与响应 Flask之Session Flask之蓝图 Flask之message 中间件 Flask插 ...

  9. 使用 Flask 框架写用户登录功能的Demo时碰到的各种坑(三)——使用Flask-Login库实现登录功能

    使用 Flask 框架写用户登录功能的Demo时碰到的各种坑(一)——创建应用 使用 Flask 框架写用户登录功能的Demo时碰到的各种坑(二)——使用蓝图功能进行模块化 使用 Flask 框架写用 ...

随机推荐

  1. python RabbitMQ队列使用

    python RabbitMQ队列使用 关于python的queue介绍 关于python的队列,内置的有两种,一种是线程queue,另一种是进程queue,但是这两种queue都是只能在同一个进程下 ...

  2. OpenSSH 使用技巧

    1. 取消 OpenSSH 初次连接 yes 确认 在脚本中有时会使用ssh进行远程连接操作,如果是第一次 ssh 连接往往会提示你是否确认连接并要求你输入yes, 才能继续.如何才能避免这个步骤呢? ...

  3. AGC006 C Rabbit Exercise——思路(置换)

    题目:https://agc006.contest.atcoder.jp/tasks/agc006_c 选了 i 位置后 x[ i ] = x[ i-1 ] + x[ i+1 ] - x[ i ] . ...

  4. Could not find class 'org.ksoap2.serialization.SoapObject

    Could not find class 'org.ksoap2.serialization.SoapObject工程编译没问题,一在模拟器运行就报错! 这是由于ADT版本过高引发的问题,解决办法: ...

  5. delphi xe5 安卓 配置sqlite

    本篇我们介绍一下在android手机上怎样使用sqlite数据库,这里用Navigator实现 增删改查. 1.新建firemonkey mobile application 2.选择blank ap ...

  6. 第2章 深入分析java I/O的工作机制(上)

    java的I/O操作类在包java.io下,大致分成4组: 所有文件的存储都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再存储这些字节到磁盘.在读取文件时,也是一个 ...

  7. python's eleventh day for me

    python2 中没有nonlocal. 函数名是什么? 函数名就是函数的名字, 本质:变量,特殊的变量. 1.单独打印函数名: def func(): print(666) print(func) ...

  8. java的IO流初探

    DEMO代码: /* * 文件IO流的简单演示 */ package com.IO; import java.io.*; public class Demo_IO_1 { /** * @param a ...

  9. 简单 JS 弹出层 背景变暗

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  10. Cause: java.sql.SQLException: 无效的列索引

    今天调试代码发现“Cause: java.sql.SQLException: 无效的列索引”,查资料得出结论如下: 1.sql串的?号用''括了起来. 例如:select* from user t  ...