Flask框架 之 wtforms
简介
WTForms是一个支持多个web框架的form组件,主要用于对用户请求数据进行验证。
作用
生成HTML标签
form表单验证
使用
- 用户登录示例
- 用户注册示例
- 数据库获取数据实时更新(重写构造方法) (跟django的form一样)
注意:字段不能以下划线开头。
安装
- pip3 install wtforms
用户登录注册示例
1. 用户登录
当用户登录时候,需要对用户提交的用户名和密码进行多种格式校验。如:
密码不能为空;密码长度必须大于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
- <!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
2、用户注册
注册页面需要让用户输入:用户名、密码、密码重复、性别、爱好等。
- 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
- <!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
3、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()
py
问题
form对象为什么可以被for循环?
- 答:变为可迭代对象。
- class Foo(object):
- '''
- 要想成为迭代器,必须要有__iter__方法,而且返回迭代器(生成器是特殊的迭代器)
- '''
- # def __iter__(self):
- # return iter([11,22,33])
- def __iter__(self):
- yield 1
- yield 2
- yield 3
- obj = Foo()
- for item in obj:
- print(item)
new方法的返回值决定对象到底是什么?
- class Bar(object):
- pass
- class Foo(object):
- def __new__(cls, *args, **kwargs):
- # return super(Foo,cls).__new__(cls,*args, **kwargs)
- return Bar()
- obj = Foo()
- print(obj)
metaclass
类创建的两种方式
- #方式一
- class Foo(object):
- a1 = 123
- def func(self):
- return 666
- #方式二
- Foo = type("Foo",(object,),{'a1':123,'func':lambda self:666})
对象是由类创建的,类是由type创建的。
metaclass作用是指定当前类由谁来创建。
- - 创建类时,先执行type的__init__。
- - 类的实例化时,执行type的__call__,__call__方法的的返回值就是实例化的对象。
- __call__内部调用:
- - 类.__new__,创建对象
- - 类.__init__,对象的初始化
- __call__内部调用:
- class MyType(type):
- def __init__(self,*args,**kwargs):
- super(MyType,self).__init__(*args,**kwargs)
- def __call__(cls, *args, **kwargs):
- obj = cls.__new__(cls)
- cls.__init__(obj,*args, **kwargs)
- return obj
- class Foo(object,metaclass=MyType):
- a1 = 123
- def __init__(self):
- pass
- def __new__(cls, *args, **kwargs):
- return object.__new__(cls)
- def func(self):
- return 666
- # Foo是类
- # Foo是MyType的一个对象
- obj = Foo()
- 源码:
- - 类的创建
- type.__init__
- - 对象的创建
- type.__call__
- - 类.__new__
- - 类.__init__
- type.__call__
其他
1、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()
示例三
2、实例化流程分析
- # 源码流程
- 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__ 方法,返回标签字符串
实例化流程分析
3、验证流程分析
- a. 执行form的validate方法,获取钩子方法
- def validate(self):
- extra = {}
- for name in self._fields:
- inline = getattr(self.__class__, 'validate_%s' % name, None)
- if inline is not None:
- extra[name] = [inline]
- return super(Form, self).validate(extra)
- b. 循环每一个字段,执行字段的 validate 方法进行校验(参数传递了钩子函数)
- def validate(self, extra_validators=None):
- self._errors = None
- success = True
- for name, field in iteritems(self._fields):
- if extra_validators is not None and name in extra_validators:
- extra = extra_validators[name]
- else:
- extra = tuple()
- if not field.validate(self, extra):
- success = False
- return success
- c. 每个字段进行验证时候
- 字段的pre_validate 【预留的扩展】
- 字段的_run_validation_chain,对正则和字段的钩子函数进行校验
- 字段的post_validate【预留的扩展】
验证流程分析
Flask框架 之 wtforms的更多相关文章
- flask框架----flask中的wtforms使用
一.简单介绍flask中的wtforms WTForms是一个支持多个web框架的form组件,主要用于对用户请求数据进行验证. 安装: pip3 install wtforms 二.简单使用wtfo ...
- Flask框架 (四)—— 请求上下文源码分析、g对象、第三方插件(flask_session、flask_script、wtforms)、信号
Flask框架 (四)—— 请求上下文源码分析.g对象.第三方插件(flask_session.flask_script.wtforms).信号 目录 请求上下文源码分析.g对象.第三方插件(flas ...
- Flask 框架入门
Flask Flask是一个使用 Python 编写的轻量级 Web 应用框架.其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 . 安装 Flask 依赖两个外部库, We ...
- flask中的wtforms使用
一.简单介绍flask中的wtforms WTForms是一个支持多个web框架的form组件,主要用于对用户请求数据进行验证. 安装: pip3 install wtforms 二.简单使用wtfo ...
- Flask学习【第7篇】:Flask中的wtforms使用
简介flask中的wtforms WTForms是一个支持多个web框架的form组件,主要用于对用户请求数据进行验证. 安装 pip3 install wtforms 简单使用wtforms组件 用 ...
- 使用 Flask 框架写用户登录功能的Demo时碰到的各种坑(五)——实现注册功能
使用 Flask 框架写用户登录功能的Demo时碰到的各种坑(一)——创建应用 使用 Flask 框架写用户登录功能的Demo时碰到的各种坑(二)——使用蓝图功能进行模块化 使用 Flask 框架写用 ...
- Flask系列(七)Flask中的wtforms使用
一.简单介绍flask中的wtforms WTForms是一个支持多个web框架的form组件,主要用于对用户请求数据进行验证. 安装: pip3 install wtforms 二.简单使用wtfo ...
- python高级之Flask框架
目录: Flask基本使用 Flask配置文件 Flask路由系统 Flask模版 Flask请求与响应 Flask之Session Flask之蓝图 Flask之message 中间件 Flask插 ...
- 使用 Flask 框架写用户登录功能的Demo时碰到的各种坑(三)——使用Flask-Login库实现登录功能
使用 Flask 框架写用户登录功能的Demo时碰到的各种坑(一)——创建应用 使用 Flask 框架写用户登录功能的Demo时碰到的各种坑(二)——使用蓝图功能进行模块化 使用 Flask 框架写用 ...
随机推荐
- python RabbitMQ队列使用
python RabbitMQ队列使用 关于python的queue介绍 关于python的队列,内置的有两种,一种是线程queue,另一种是进程queue,但是这两种queue都是只能在同一个进程下 ...
- OpenSSH 使用技巧
1. 取消 OpenSSH 初次连接 yes 确认 在脚本中有时会使用ssh进行远程连接操作,如果是第一次 ssh 连接往往会提示你是否确认连接并要求你输入yes, 才能继续.如何才能避免这个步骤呢? ...
- AGC006 C Rabbit Exercise——思路(置换)
题目:https://agc006.contest.atcoder.jp/tasks/agc006_c 选了 i 位置后 x[ i ] = x[ i-1 ] + x[ i+1 ] - x[ i ] . ...
- Could not find class 'org.ksoap2.serialization.SoapObject
Could not find class 'org.ksoap2.serialization.SoapObject工程编译没问题,一在模拟器运行就报错! 这是由于ADT版本过高引发的问题,解决办法: ...
- delphi xe5 安卓 配置sqlite
本篇我们介绍一下在android手机上怎样使用sqlite数据库,这里用Navigator实现 增删改查. 1.新建firemonkey mobile application 2.选择blank ap ...
- 第2章 深入分析java I/O的工作机制(上)
java的I/O操作类在包java.io下,大致分成4组: 所有文件的存储都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再存储这些字节到磁盘.在读取文件时,也是一个 ...
- python's eleventh day for me
python2 中没有nonlocal. 函数名是什么? 函数名就是函数的名字, 本质:变量,特殊的变量. 1.单独打印函数名: def func(): print(666) print(func) ...
- java的IO流初探
DEMO代码: /* * 文件IO流的简单演示 */ package com.IO; import java.io.*; public class Demo_IO_1 { /** * @param a ...
- 简单 JS 弹出层 背景变暗
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- Cause: java.sql.SQLException: 无效的列索引
今天调试代码发现“Cause: java.sql.SQLException: 无效的列索引”,查资料得出结论如下: 1.sql串的?号用''括了起来. 例如:select* from user t ...