PythonWEB框架之Flask
前言:
Python 主流 WEB 框架对比
Django:1 个重武器,包含了 web 开发中常用的功能、组件的框架;(ORM、Session、Form、Admin、分页、中间件、信号、缓存、ContenType....);
Tornado:2 大特性就是异步非阻塞、原生支持 WebSocket 协议;
Flask:封装功能不及 Django 完善,性能不及 Tornado,但是 Flask 可扩展性强,因为 flask 的第三方开源组件丰富;
Bottle:比较简单;
结论
都不是我写的!!!不论优劣,不同的工具而已;
小型 web 应用设计的功能点不多使用 Flask;
大型 web 应用设计的功能点比较多使用的组件也会比较多,使用 Django(自带功能多不用去找插件);
如果追求性能可以考虑 Tornado;
WSGI 介绍
Django 使用 wsgiref 模块 Flask 使用 Werkzeug 模块实现了 WSGI 协议。
HTTP 协议是建立在 TCP 协议之上的,实现 UWSGI 协议本质是实现 Socket 服务端,也就是 TCP 层实现 HTTP 请求的接收、 对请求内容进行预处理如分割 HTPP 请求内容的 Header 和 body,然后再触发后面的 Django/Flask 框架。
Flask 是一个基于 Python 开发并且依赖 jinja2 模板和 Werkzeug WSGI 服务的一个微型框架
pip3 install flask #安装 flask
Flask 框架是基于 werkzeug 模块实现的。
from werkzeug.wrappers import Request,Response from werkzeug.serving import run_simple @Request.application def hello(request): return Response("Hello World")if name == 'main':
#请求一旦到来,执行第 3 个参数,hello(上下文)
run_simple('localhost', 4000, hello)
Flask 简单使用
from flask import Flaskapp=Flask(name) #创建 1 个 Flask 实例
@app.route('/') #路由系统生成 视图对应 url,1. decorator=app.route()2. decorator(first_flask)
def first_flask(): #视图函数
return 'Hello World' #responseif name == 'main':
app.run() #启动 socket
一、配置文件
app=Flask(__name__,template_folder='templates',static_url_path='/static/',static_path='/zhanggen')
模板路径: template_folder='templates'
静态文件路径:static_url_path='/static/'
静态文件引入别名:static_path='/zhanggen'
设置为调试环境:app.debug=True (代码修改自动更新)
设置 json 编码格式 如果为 False 就不使用 ascii 编码:app.config['JSON_AS_ASCII']=False
设置响应头信息 Content-Type app.config['JSONIFY_MIMETYPE'] ="application/json;charset=utf-8" (注意 ;charset=utf-8)
二、路由系统
1. 动态路由(url 传参)
@app.route('/user/<name>')
from flask import Flaskapp=Flask(name)
@app.route('/<name>') #设置 url 传参数 http://127.0.0.1:5000/zhanggen
def first_flask(name): #视图必须有对应接收参数
print(name)
return 'Hello World' #responseif name == 'main':
app.run()
@app.route('/post/<int:age>')
#接收整型数字参数 app=Flask(__name__) @app.route('/<int:age>/') #设置 url 传参数 http://127.0.0.1:5000/18/ def first_flask(age): #视图必须有对应接收参数 print(age) return 'Hello World' #responseif name == 'main':
app.run()
@app.route('/post/<float:salary>')
#接收浮点型型数字参数 app=Flask(__name__) @app.route('/<float:salary>/') #设置 url 传参数 http://127.0.0.1:5000/2345555.8889/ def first_flask(salary): #视图必须有对应接收参数 print(salary) return 'Hello World' #responseif name == 'main':
app.run()
@app.route('/post/<path:path>')
# 接收 URL 链接类型参数 app=Flask(__name__) @app.route('/<path:url>/') #设置 url 传参数:http://127.0.0.1:5000/http://www.baiu.com/ def first_flask(url): #视图必须有对应接收参数 print(url) return 'Hello World' #responseif name == 'main':
app.run()
2、指定允许的请求方法
@app.route('/login', methods=['GET', 'POST'])
# 指定允许的请求方法 app=Flask(__name__) @app.route('/<path:url>/',methods=['get']) #只允许 get 请求 def first_flask(url): print(url) return 'Hello World' #responseif name == 'main':
app.run()
3、通过别名反向生成 url
#反向生成 url from flask import Flask,url_for app=Flask(__name__) @app.route('/<path:url>',endpoint='name1') def first_flask(url): print(url_for('name1',url=url)) #如果设置了 url 参数,url_for(别名, 加参数) return 'Hello World'if name == 'main':
app.run()
4、通过 app.add_url_rule() 调用路由
#方式 2 通过 app.add_url_rule() 方法的方式调用路由 app=Flask(__name__)def first_flask():
return 'Hello World'app.add_url_rule(rule='/index/',endpoint='name1',view_func=first_flask,methods=['GET'])
#app.add_url_rule(rule= 访问的 url,endpoint= 路由别名,view_func= 视图名称,methods=[ 允许访问的方法])
if name == 'main':
app.run()
5、扩展路由功能:正则匹配 url
如果需要一些复杂的匹配规则可以自定义正则匹配 url
from flask import Flask, views, url_for from werkzeug.routing import BaseConverterapp </span>= Flask(import_name=<span style="color: rgba(128, 0, 128, 1)">__name__</span><span style="color: rgba(0, 0, 0, 1)">) </span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> RegexConverter(BaseConverter): </span><span style="color: rgba(128, 0, 0, 1)">"""</span><span style="color: rgba(128, 0, 0, 1)"> 自定义URL匹配正则表达式 </span><span style="color: rgba(128, 0, 0, 1)">"""</span> <span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__init__</span><span style="color: rgba(0, 0, 0, 1)">(self, map, regex): super(RegexConverter, self).</span><span style="color: rgba(128, 0, 128, 1)">__init__</span><span style="color: rgba(0, 0, 0, 1)">(map) self.regex </span>=<span style="color: rgba(0, 0, 0, 1)"> regex </span><span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> to_python(self, value): </span><span style="color: rgba(128, 0, 0, 1)">"""</span><span style="color: rgba(128, 0, 0, 1)"> 路由匹配时,匹配成功后传递给视图函数中参数的值 :param value: :return: </span><span style="color: rgba(128, 0, 0, 1)">"""</span> <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> int(value) </span><span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> to_url(self, value): </span><span style="color: rgba(128, 0, 0, 1)">"""</span><span style="color: rgba(128, 0, 0, 1)"> 使用url_for反向生成URL时,传递的参数经过该方法处理,返回的值用于生成URL中的参数 :param value: :return: </span><span style="color: rgba(128, 0, 0, 1)">"""</span><span style="color: rgba(0, 0, 0, 1)"> val </span>=<span style="color: rgba(0, 0, 0, 1)"> super(RegexConverter, self).to_url(value) </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> val </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 添加到flask中</span> app.url_map.converters[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">regex</span><span style="color: rgba(128, 0, 0, 1)">'</span>] =<span style="color: rgba(0, 0, 0, 1)"> RegexConverter @app.route(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">/index/<regex("\d+"):nid></span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">) </span><span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> index(nid): </span><span style="color: rgba(0, 0, 255, 1)">print</span>(url_for(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">index</span><span style="color: rgba(128, 0, 0, 1)">'</span>, nid=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">888</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)) </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Index</span><span style="color: rgba(128, 0, 0, 1)">'</span> <span style="color: rgba(0, 0, 255, 1)">if</span> <span style="color: rgba(128, 0, 128, 1)">__name__</span> == <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">__main__</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">: app.run()</span></pre>
四、视图
1、给 Flask 视图函数加装饰器
注意如果要给视图函数加装饰器增加新功能,一点要加在路由装饰器下面,才会被路由装饰器装饰,才能生生成 url 关系;
#给 Flask 视图加装饰器 #1、定义 1 个装饰器def auth(func):
print('我在上面')
def inner(*args,kwargs):
return func(*args,kwargs)
return innerapp=Flask(name)
@app.route('/',methods=['GET'])
@auth #注意如果要给视图函数加装饰器,一点要加在路由装饰器下面,才会被路由装饰器装饰
def first_flask():
print('ffff')
return 'Hello World'if name == 'main':
app.run()
2、request 和 response
a. 请求相关信息
request.method: 获取请求方法
request.json
request.json.get("json_key"):获取 json 数据 ** 较常用
request.argsget('name') :获取 get 请求参数
request.form.get('name') :获取 POST 请求参数
request.form.getlist('name_list'):获取 POST 请求参数列表(多个)
request.values.get('age') :获取 GET 和 POST 请求携带的所有参数(GET/POST 通用)
request.cookies.get('name'):获取 cookies 信息
request.headers.get('Host'):获取请求头相关信息
request.path:获取用户访问的 url 地址,例如(/,/login/,/ index/);
request.full_path:获取用户访问的完整 url 地址 + 参数 例如 (/login/?age=18)
request.script_root: 抱歉,暂未理解其含义;
request.url:获取访问 url 地址,例如 http://127.0.0.1:5000/?age=18;
request.base_url:获取访问 url 地址,例如 http://127.0.0.1:5000/;
request.url_root
request.host_url
request.host:获取主机地址
request.files:获取用户上传的文件
obj = request.files['the_file_name']
obj.save('/var/www/uploads/' + secure_filename(f.filename)) 直接保存
b、响应相关信息
return "字符串" :响应字符串
return render_template('html 模板路径',**{}):响应模板
return redirect('/index.html'):跳转页面
响应 json 数据
方式 1: return jsonify(user_list)
app.config['JSON_AS_ASCII']=False #指定 json 编码格式 如果为 False 就不使用 ascii 编码, app.config['JSONIFY_MIMETYPE'] ="application/json;charset=utf-8" #指定浏览器渲染的文件类型,和解码格式;
方式 2:
return Response(data,mimetype="application/json;charset=utf-8",)
如果需要设置响应头就需要借助 make_response() 方法
from flask import Flask,request,make_response
response = make_response(render_template('index.html'))
response 是 flask.wrappers.Response 类型
response.delete_cookie('key')
response.set_cookie('key', 'value')
response.headers['X-Something'] = 'A value'
return respons
3 、Flask 之 CBV 视图
#CBV 视图 from flask import Flask,url_for,views #----------------------------------------------------- app=Flask(__name__) #装饰器def auth(func):
print('我在上面')
def inner(*args,kwargs):
return func(*args,kwargs)
return inner
#--------------------------------------------------------
class IndexView(views.MethodView): #CBV 视图
methods=['GET'] #允许的 http 请求方法(改 CBV 只允许 GET 方法)
decorators = [auth,] #每次请求过来都加 auth 装饰器<span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> get(self): </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Index.GET</span><span style="color: rgba(128, 0, 0, 1)">'</span> <span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> post(self): </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Index.POST</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
app.add_url_rule('/index/',view_func=IndexView.as_view(name='name1')) #(name='name1' 反向生成 url 别名
if name == 'main':
app.run()
五、模板语言
Flask 使用的是 Jinja2 模板,所以其语法和 Django 无差别(Django 的模板语言参考 Jinja2)
1. 引用静态文件
方式 1:别名引入
<link rel="stylesheet" href="/zhanggen/commons.css">
方式 2:url_for() 方法引入
<link rel="stylesheet" href="{{url_for('static',filename='commons.css') }}">
2. 模板语言引用上下文对象
变量
<h1>{{user_list}}</h1> <!-- 变量 -->
循环、索引取值
<ul> {% for user in user_list %} <!-- 循环 --> <li>{{user}}</li> {% endfor %}{{user_list.0}} <!-- 索引取值 -->
</ul>
Flask 的 Jinjia2 可以通过 Context 把视图中的函数传递把模板语言中执行,这就是 Django 中的 simple_tag 和 simple_fifter;
simple_tag(只能传 2 个参数, 支持 for、if)
@app.template_global() #simple_tag def foo(arg): return '<input type="text">'
<h1>{{foo(1)|safe}}</h1> <!--Flask 的模板语言支持 simple_tag-->
simple_fifter(对参数个数无限制,不支持 for、if)
@app.template_filter() #simple_fifter def foo1(arg1,arg2,arg3): return arg1+arg2+arg3
<h1> {{'alex'|foo1('s','b',) }} </h1> <!-- simple_fifter -->
3.wtform(flask 表单验证插件)
3.0. 简介
wtforms WTForms 是一个支持多个 web 框架的 form 组件,主要对用户请求数据 进行表单验证。
3.1. 安装
pip install wtforms #安装 wtfroms 插件
3.2. 简单使用
wtforms 和 Django 自带的 form 验证插件功能相同,使用起来大同小异;
用户登录页面验证
#!/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 widgetsapp=Flask(name,template_folder='templates') #知道模板文件
app.debug=True#登录验证实例
class LoginForm(Form):)</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">不同的字段 内部包含正则表达式 html5.EmailField | html5.DateTimeField...</span> name=<span style="color: rgba(0, 0, 0, 1)">simple.StringField( label</span>=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">用户名</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, validators</span>=[ <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">验证规则和错误提示信息</span> validators.DataRequired(message=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">用户名不能为空.</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">), validators.Length(min</span>=6, max=18, message=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">用户名长度必须大于%(min)d且小于%(max)d</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">) ], widget</span>=widgets.TextInput(), <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">前端页面显示的插件.TextArea</span> render_kw={<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">class</span><span style="color: rgba(128, 0, 0, 1)">'</span>: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">form-control</span><span style="color: rgba(128, 0, 0, 1)">'</span>} <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">设置form标签的class信息</span>
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 不同的字段 内部包含正则表达式 html5.EmailField | html5.DateTimeField...</span> pwd =<span style="color: rgba(0, 0, 0, 1)"> simple.PasswordField( label</span>=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">密码</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, validators</span>=<span style="color: rgba(0, 0, 0, 1)">[ validators.DataRequired(message</span>=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">密码不能为空.</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">), validators.Length(min</span>=8, message=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">用户名长度必须大于%(min)d</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">), </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">自定义验证规则</span> validators.Regexp(regex=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]{8,}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, message</span>=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">) ], widget</span>=<span style="color: rgba(0, 0, 0, 1)">widgets.PasswordInput(), render_kw</span>={<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">class</span><span style="color: rgba(128, 0, 0, 1)">'</span>: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">form-control</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">} )
@app.route('/login/', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
form = LoginForm() #实例化 form 验证类
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()
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>登录</h1> <form method="post" novalidate> <!--<input type="text" name="name">--> <p>{{form.name.label}} {{form.name}} {{form.name.errors[0] }}</p><span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"><input type="password" name="pwd"></span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">p</span><span style="color: rgba(0, 0, 255, 1)">></span>{{form.pwd.label}} {{form.pwd}} {{form.pwd.errors[0] }}<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">p</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">input </span><span style="color: rgba(255, 0, 0, 1)">type</span><span style="color: rgba(0, 0, 255, 1)">="submit"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="提交"</span><span style="color: rgba(0, 0, 255, 1)">></span>
</form>
</body>
</html>login.html
用户注册页面验证
#用户注册 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 widgetsapp = Flask(name, template_folder='templates')
app.debug = Trueclass RegisterForm(Form):
name = simple.StringField(
label='用户名',
validators=[
validators.DataRequired()
],
widget=widgets.TextInput(),
render_kw={'class': 'form-control'},
default='张根' #设置 input 标签中默认值
)pwd </span>=<span style="color: rgba(0, 0, 0, 1)"> simple.PasswordField( label</span>=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">密码</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, validators</span>=<span style="color: rgba(0, 0, 0, 1)">[ validators.DataRequired(message</span>=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">密码不能为空.</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">) ], widget</span>=<span style="color: rgba(0, 0, 0, 1)">widgets.PasswordInput(), render_kw</span>={<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">class</span><span style="color: rgba(128, 0, 0, 1)">'</span>: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">form-control</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">} ) pwd_confirm </span>= simple.PasswordField( <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">第二次输入密码</span> label=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">重复密码</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, validators</span>=<span style="color: rgba(0, 0, 0, 1)">[ validators.DataRequired(message</span>=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">重复密码不能为空.</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">), validators.EqualTo(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">pwd</span><span style="color: rgba(128, 0, 0, 1)">'</span>, message=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">两次密码输入不一致</span><span style="color: rgba(128, 0, 0, 1)">"</span>) <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">验证2次输入的密码是否一致?</span>
],
widget=widgets.PasswordInput(),
render_kw={'class': 'form-control'}
)email </span>=<span style="color: rgba(0, 0, 0, 1)"> html5.EmailField( label</span>=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">邮箱</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, validators</span>=<span style="color: rgba(0, 0, 0, 1)">[ validators.DataRequired(message</span>=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">邮箱不能为空.</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">), validators.Email(message</span>=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">邮箱格式错误</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">) ], widget</span>=widgets.TextInput(input_type=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">email</span><span style="color: rgba(128, 0, 0, 1)">'</span>), <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">生成email input标签</span> render_kw={<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">class</span><span style="color: rgba(128, 0, 0, 1)">'</span>: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">form-control</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">} ) gender </span>=<span style="color: rgba(0, 0, 0, 1)"> core.RadioField( label</span>=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">性别</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, choices</span>=( <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">choice radio选项</span> (1, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">男</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">), (</span>2, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">女</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">), ), coerce</span>=int <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">讲用户提交过来的 '4' 强制转成 int 4</span>
)
city = core.SelectField(
label='城市',
choices=(
('bj', '北京'),
('sh', '上海'),
)
)hobby </span>= core.SelectMultipleField( <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">select 下拉框多选框</span> label=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">爱好</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, choices</span>=<span style="color: rgba(0, 0, 0, 1)">( (</span>1, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">篮球</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">), (</span>2, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">足球</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">), ), coerce</span>=<span style="color: rgba(0, 0, 0, 1)">int ) favor </span>=<span style="color: rgba(0, 0, 0, 1)"> core.SelectMultipleField( label</span>=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">喜好</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, choices</span>=<span style="color: rgba(0, 0, 0, 1)">( (</span>1, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">篮球</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">), (</span>2, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">足球</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">), ), widget</span>=widgets.ListWidget(prefix_label=False), <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">生成Checkbox 多选框</span> option_widget=<span style="color: rgba(0, 0, 0, 1)">widgets.CheckboxInput(), coerce</span>=<span style="color: rgba(0, 0, 0, 1)">int, default</span>=[1, 2<span style="color: rgba(0, 0, 0, 1)">] ) </span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__init__</span>(self, *args, **kwargs): <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">重写form验证类的__init__方法可以实时同步数据中数据</span> super(RegisterForm, self).<span style="color: rgba(128, 0, 128, 1)">__init__</span>(*args, **<span style="color: rgba(0, 0, 0, 1)">kwargs) self.favor.choices </span>= ((1, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">篮球</span><span style="color: rgba(128, 0, 0, 1)">'</span>), (2, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">足球</span><span style="color: rgba(128, 0, 0, 1)">'</span>), (3, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">羽毛球</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)) </span><span style="color: rgba(0, 0, 255, 1)">def</span> validate_pwd_confirm(self, field): <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">wtforms验证 钩子函数</span> <span style="color: rgba(128, 0, 0, 1)">"""</span><span style="color: rgba(128, 0, 0, 1)"> 自定义pwd_confirm字段规则,例:与pwd字段是否一致 :param field: :return: </span><span style="color: rgba(128, 0, 0, 1)">"""</span> <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 最开始初始化时,self.data中已经有所有的值</span> <span style="color: rgba(0, 0, 255, 1)">if</span> field.data != self.data[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">pwd</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">]: </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> raise validators.ValidationError("密码不一致") # 继续后续验证</span> <span style="color: rgba(0, 0, 255, 1)">raise</span> validators.StopValidation(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">密码不一致</span><span style="color: rgba(128, 0, 0, 1)">"</span>) <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 不再继续后续验证</span>
@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()
<!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>
3.2.wtforms 源码 猜想....
A. 自动生成 html 标签
先来分析一下 form 验证类的结构
LoginForm 类中包含了 2 个字段: name 和 pwd,而 name / pwd 字段 = 对象,所以 LoginForm 类包含了 2 个对象;
如果实例化了 obj=LoginForm() 就等于 在 这 1 个对象中嵌套了 2 个对象;
前端使用 Form 验证插件:
那如果在前端 for 循环 LoginForm 对象,就等于调用 LoginForm 对象的 __iter__ 方法,把每个字段(对象)封装的数据 返回
如果前端 {{obj}}= 直接调用了字段对象的 __str__ 方法;
class InputText(object): #插件 def __str__(self): return '<input type="text" />'class InputPassword(object):
def str(self):
return '<input type="password" />'#-----------------------------------------------------------
class StringField(object): #字段
def init(self,wg):
self.widget=wg</span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__str__</span>(self): <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">调用插件的__str__</span> <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> str(self.widget)
class DateField(object):
def init(self, wg):
self.widget = wg</span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__str__</span><span style="color: rgba(0, 0, 0, 1)">(self): </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> str(self.widget)
#--------------------------------------------------------------
class LoginForm(object): #统一 灵活接口 (对象嵌套对象,多层封装)
name=StringField(wg=InputText()) #wg=InputText()对象 StringField(wg=InputText()) 对象
pwd=DateField(wg=InputPassword())l_obj=LoginForm()
print(l_obj.name)
print(l_obj.pwd)
B. 数据校验
后台定义好正则
用户发来数据
对数据进行校验
3.3. 源码流程
生成 HTML 标签并显示
1. 验证类(LogibForm)生成
1.1. 由于 metaclass=FormMeta,所以LoginForm 是由 FormMeta 创建的
''' class BaseForm(): passclass NewBase(BaseForm,metaclass=FormMeta,):
passclass Form(NewBase):
passclass LoginForm(Form):
pass'''
class Form(with_metaclass(FormMeta,BaseForm)):
1.2. 执行 FormMeta 的 __init__ 方法,在 LoginForm 中添加 2 个静态字段
class FormMeta(type): """ The metaclass for `Form` and any subclasses of `Form`.`FormMeta`'s responsibility is to create the `_unbound_fields` list, which is a list of `UnboundField` instances sorted by their order of instantiation. The list is created at the first instantiation of the form. If any fields are added/removed from the form, the list is cleared to be re-generated on the next instantiation. Any properties which begin with an underscore or are not `UnboundField` instances are ignored by the metaclass. </span><span style="color: rgba(128, 0, 0, 1)">"""</span> <span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__init__</span><span style="color: rgba(0, 0, 0, 1)">(cls, name, bases, attrs): type.</span><span style="color: rgba(128, 0, 128, 1)">__init__</span>(cls, name, bases, attrs) <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">继承type的功能</span> cls._unbound_fields = None <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">在LoginForm中添加1个静态字段</span> cls._wtforms_meta = None <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">在LoginForm中添加1个静态字段</span></pre>
1.3. 开始解释 LoginForm 中的 实例化字段对象name=simple.StringField()simple.PasswordField()
StringField/PasswordField 开始实例化 (提到实例化就应该想到:指定元类的 __call__、自己 / 父类的 __new__、__init__):
StringField/PasswordField 是默认元类,自己没有 __new__ 和 __init__ 方法;
但父类 Field 类中有 __new__ 方法,所以执行父类的 __new__(Field.__new__)返回 UnboundField 对象
def new(cls, *args, **kwargs):#执行 __new__ 方法
if '_form' in kwargs and '_name' in kwargs:
return super(Field, cls).new(cls)
else:
#我 x 没想到 ! __new__ 既然返回了 1 个 UnboundField() 而不是 StringField/PasswordField 对象;狸猫换了太子 ?
return UnboundField(cls, *args, **kwargs)
由于 Field.__new__ 方法返回了 1 个 UnboundField 对象,来看 UnboundField 的 __init__ 方法
class UnboundField(object): _formfield = True creation_counter = 0 #静态字段 设置计数器UnboundField.creation_counter += 1 #每实例化 1 个 UnboundField 对象 计数器 +1 self.field_class = field_class self.args = args self.kwargs = kwargs #{'label': '用户名', 'validators': [<wtforms.validators.DataRequired object at 0x00000000038EF080>, <wtforms.validators.Length object at 0x00000000038EF0F0>], 'widget': <wtforms.widgets.core.TextInput object at 0x00000000038EF0B8>, 'render_kw': {'class': 'form-control'}} self.creation_counter = UnboundField.creation_counter# ''' print(self.__dict__) { 'field_class': <class 'wtforms.fields.simple.PasswordField'>, 'args': (), 'kwargs': {'label': '密码', 'validators': [<wtforms.validators.DataRequired object at 0x00000000038EF198>, <wtforms.validators.Length object at 0x00000000038EF1D0>, <wtforms.validators.Regexp object at 0x00000000038EF208>], 'widget': <wtforms.widgets.core.PasswordInput object at 0x00000000038EF2B0>, 'render_kw': {'class': 'form-control'}}, 'creation_counter': 2 }<span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__init__</span>(self, field_class, *args, **kwargs): <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">field_class=.StringField / PasswordField</span> <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">获取到field_class 的 参数封装到 UnboundField对象中,并且设置 排序 'creation_counter': 2</span>
</span><span style="color: rgba(128, 0, 0, 1)">'''</span></pre>
UnboundField 的 __init__ 方法在 UnboundField 对象中封装了 Field 类的参数和计数器,所以现在 LoginForml 类中封装数据如下
""" print(LoginForm.__dict__) LoginForm ={ '__module__': '__main__', 'name': <1 UnboundField(StringField, (),{'creation_counter': 1, 'label': '用户名', 'validators': [<wtforms.validators.DataRequired object at 0x00000000037DAEB8>, <wtforms.validators.Length object at 0x000000000382B048>], 'widget': <wtforms.widgets.core.TextInput object at 0x000000000382B080>, 'render_kw': {'class': 'form-control'} })>, 'pwd': <2 UnboundField(PasswordField, (),{'creation_counter': 2,'label': '密码', 'validators': [<wtforms.validators.DataRequired object at 0x000000000382B0F0>, <wtforms.validators.Length object at 0x000000000382B128>, <wtforms.validators.Regexp object at 0x000000000382B160>], 'widget': <wtforms.widgets.core.PasswordInput object at 0x000000000382B208>, 'render_kw': {'class': 'form-control'}})>, '__doc__': None, '_unbound_fields': None, '_wtforms_meta': None, } """
启发:
不一定要把代码都写在当前类中,如过多个类和类之间有同性方法、属性可以抽出来集中到父类之中;子类继承父类所以子类实例化对象之后,继承享有 2 者的属性和方法;所以看源码遇到继承一点要注意 观察父类;
每个对象实例化(在排除 MetaClass 的情况下)都会执行 父类的 __new__ 方法,再去执行 __init__ 方法;而 __new__ 实质性决定了实例化出来的对象是神马?
class Queen(object): def __new__(cls, *args, **kwargs): #类中 __new__ 方法决定了类(),实例化出什么对象; return Cat('狸猫','男','太子')</span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__init__</span>(self,name): <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">由于__nwe__方法返回了其他对象,所以不会执行Queen的__init__方法</span> <span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">ok</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">) self.name</span>=<span style="color: rgba(0, 0, 0, 1)">name
Prince=Queen('王子')
print(Prince.name)
print(Prince.gender)
print(Prince.identity)
2.LoginForm 实例化
谈到类实例化应该先检查该类是否指定了 Meta 类,如果指定了 Meta 类, 就需要先执行 (指定元类的 __call__、自己 / 父类的 __new__、__init__)
21. 执行 FormMeta 的 __call__ 方法,赋值 LoginForm 的 _unbound_fields 和 _wtforms_meta 属性;
根据 unbound 对象的 creation_counter 属性对 LoginForm 中的字段进行排序,并填充到 LoginForm 的 _unbound_fields 属性中
根据 LoginForm 的 __mro__ 继承顺序:获取当前类(FormLogin)所有父类, 并在每个父类中 提取 Meta 属性添加到列表,转成元组,最后创建 Meta 类让其继承,赋值 LoginForm._wtforms_meta 属性
def __call__(cls, *args, **kwargs): if cls._unbound_fields is None: #在创建类时 已经设置 LoginForm 的 _unbound_fields 为空 fields = [] # 获取 LoginForm 类中,中所有属性的 key:['_get_translations', '_unbound_fields', '_wtforms_met,'name','populate_obj','process','pwd','validate'.....] for name in dir(cls): if not name.startswith('_'): #排除 __ 下划线的私有属性 name. pwd unbound_field = getattr(cls, name) #cls =LoginForm 类 #根据 key 获取 unbound_field 对象 if hasattr(unbound_field, '_formfield'): #检查 unbound_field 对象是否包含 _formfield = True fields.append((name, unbound_field)) # ''' # fields = [ # (name,name 的 unbound 对象), # (pwd,pwd 的 unbound 对象), # ] # ''' #对 fields 按照定义顺序 进行排序 fields.sort(key=lambda x: (x[1].creation_counter, x[0])) #根据 unbound 对象的 creation_counter 进行字段排序 cls._unbound_fields = fields</span><span style="color: rgba(0, 0, 255, 1)">if</span> cls._wtforms_meta <span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> None: bases </span>= [] <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">bases = [DefaultMeta],</span> <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 按照继承顺序:获取当前类(FormLogin)所有父类</span> <span style="color: rgba(0, 0, 255, 1)">for</span> mro_class <span style="color: rgba(0, 0, 255, 1)">in</span> cls.<span style="color: rgba(128, 0, 128, 1)">__mro__</span><span style="color: rgba(0, 0, 0, 1)">: </span><span style="color: rgba(0, 0, 255, 1)">if</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Meta</span><span style="color: rgba(128, 0, 0, 1)">'</span> <span style="color: rgba(0, 0, 255, 1)">in</span> mro_class.<span style="color: rgba(128, 0, 128, 1)">__dict__</span>: <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">去每个父类(mro_class)获取 Meta = DefaultMeta</span>
bases.append(mro_class.Meta) </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">bases = [DefaultMeta],</span> <span style="color: rgba(128, 0, 0, 1)">'''</span><span style="color: rgba(128, 0, 0, 1)"> class Meta(DefaultMeta): pass </span><span style="color: rgba(128, 0, 0, 1)">'''</span><span style="color: rgba(0, 0, 0, 1)"> cls._wtforms_meta </span>= type(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Meta</span><span style="color: rgba(128, 0, 0, 1)">'</span>, tuple(bases), {}) <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">cls._wtforms_meta=Meta(DefaultMeta)类:</span> <span style="color: rgba(0, 0, 255, 1)">return</span> type.<span style="color: rgba(128, 0, 128, 1)">__call__</span>(cls, *args, **kwargs)</pre>
执行完了指定元类 FormMeta.__call__() 方法之后的 LoginForm 类中封装的数据
print(LoginForm.__dict__) LoginForm ={ '__module__': '__main__', 'name': <1 UnboundField(StringField, (),{'creation_counter': 1, 'label': '用户名', 'validators': [<wtforms.validators.DataRequired object at 0x00000000037DAEB8>, <wtforms.validators.Length object at 0x000000000382B048>], 'widget': <wtforms.widgets.core.TextInput object at 0x000000000382B080>, 'render_kw': {'class': 'form-control'} })>, 'pwd': <2 UnboundField(PasswordField, (),{'creation_counter': 2,'label': '密码', 'validators': [<wtforms.validators.DataRequired object at 0x000000000382B0F0>, <wtforms.validators.Length object at 0x000000000382B128>, <wtforms.validators.Regexp object at 0x000000000382B160>], 'widget': <wtforms.widgets.core.PasswordInput object at 0x000000000382B208>, 'render_kw': {'class': 'form-control'}})>, '__doc__': None,</span><span style="color: rgba(255, 0, 0, 1)">'_unbound_fields': [ (name, UnboundField对象(1,simple.StringField,参数)), (pwd, UnboundField对象(2,simple.PasswordField,参数)), ],, '_wtforms_meta': Meta(DefaultMeta)类,
}
"""
启发:
#sort 排序 v1=[ (11,'Martin11',18), (121,'Martin121',19), (311,'Martin311',25), (311, 'Martin311', 26) #按元素 1 排序,如果元素 1 相同按照 元素 3 排序 ]v1.sort(key=lambda x:(x[0],x[2])) #列表的 sort 方法,根据 列表中的元组元素 进行排序
print(v1)
'''[(11, 'Martin11', 18), (121, 'Martin121', 19), (311, 'Martin311', 25), (311, 'Martin311', 26)]
'''
class F1(object): pass class F2(object): pass class F3(F1): passclass F4(F2,F3):
passprint(F4.mro) #打印 F4 的继承关系
'''
(
<class 'main.F4'>,
<class 'main.F2'>,
<class 'main.F3'>,
<class 'main.F1'>,
<class 'object'>)'''
2.2. 执行 LoginForm 的 __new__ 方法
没有 __new__ 方法 pass
2.3. 执行 LoginForm 的 __init__ 方法实例化 form 对象
def __init__(self, formdata=None, obj=None, prefix='', data=None, meta=None, **kwargs):super(Form, self).__init__(self._unbound_fields, meta=meta_obj, prefix=prefix) #给 form 对象 中的 _fields 字段赋值如下; ''' _fields: {name: StringField 对象 (), pwd: PasswordField 对象 (),} name: StringField 对象 (widget=widgets.TextInput()), pwd: PasswordField 对象 (widget=widgets.PasswordInput())</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 实例化LoginForm中封装的 Meta类进行实例化,以后用于生成CSRF Tocken 标签</span> meta_obj =<span style="color: rgba(0, 0, 0, 1)"> self._wtforms_meta() </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">meta是 form = LoginForm(meta={'csrf':'true'})传过来的参数,封装到meta_obj中</span> <span style="color: rgba(0, 0, 255, 1)">if</span> meta <span style="color: rgba(0, 0, 255, 1)">is</span> <span style="color: rgba(0, 0, 255, 1)">not</span> None <span style="color: rgba(0, 0, 255, 1)">and</span><span style="color: rgba(0, 0, 0, 1)"> isinstance(meta, dict): meta_obj.update_values(meta) </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">执行父类的构造方法,参数</span> <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> self._unbound_fields</span> <span style="color: rgba(128, 0, 0, 1)">'''</span><span style="color: rgba(128, 0, 0, 1)"> '_unbound_fields'=[ (name, UnboundField对象(1,simple.StringField,参数)), (pwd, UnboundField对象(2,simple.PasswordField,参数)), ], </span><span style="color: rgba(128, 0, 0, 1)">'''</span> <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> meta_ob=Meta(DefaultMeta)对象</span>
</span><span style="color: rgba(128, 0, 0, 1)">'''</span> <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">循环form对象 中的_fields字段(字典),给form对象赋值 form.name/form.pwd</span> <span style="color: rgba(0, 0, 255, 1)">for</span> name, field <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> iteritems(self._fields): setattr(self, name, field) </span><span style="color: rgba(128, 0, 0, 1)">'''</span><span style="color: rgba(128, 0, 0, 1)"> _fields: { name: StringField对象(), pwd: PasswordField对象(), } name: StringField对象(widget=widgets.TextInput()), pwd: PasswordField对象(widget=widgets.PasswordInput()) </span><span style="color: rgba(128, 0, 0, 1)">'''</span><span style="color: rgba(0, 0, 0, 1)"> self.process(formdata, obj, data</span>=data, **kwargs)</pre>
执行 Form 父类 BaseForm.__init__ 方法,把 UnboundField 对象转换成 StringField 对象,并赋值到 form 对象的 _fields:{} 字典中;
class BaseForm(object): def __init__(self, fields, prefix='', meta=DefaultMeta()): ''' 参数 fields=[(name, UnboundField 对象(1,simple.StringField,参数)), (pwd, UnboundField 对象(2,simple.PasswordField,参数)), ],meta=Meta(DefaultMeta)对象 </span><span style="color: rgba(128, 0, 0, 1)">'''</span> <span style="color: rgba(0, 0, 255, 1)">if</span> prefix <span style="color: rgba(0, 0, 255, 1)">and</span> prefix[-1] <span style="color: rgba(0, 0, 255, 1)">not</span> <span style="color: rgba(0, 0, 255, 1)">in</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">-_;:/.</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">: prefix </span>+= <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">-</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)"> self.meta </span>=<span style="color: rgba(0, 0, 0, 1)"> meta self._prefix </span>=<span style="color: rgba(0, 0, 0, 1)"> prefix self._errors </span>=<span style="color: rgba(0, 0, 0, 1)"> None self._fields </span>=<span style="color: rgba(0, 0, 0, 1)"> OrderedDict() </span><span style="color: rgba(0, 0, 255, 1)">if</span> hasattr(fields, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">items</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">): fields </span>=<span style="color: rgba(0, 0, 0, 1)"> fields.items() translations </span>=<span style="color: rgba(0, 0, 0, 1)"> self._get_translations() extra_fields </span>=<span style="color: rgba(0, 0, 0, 1)"> [] </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">------------------------------------------------------</span> <span style="color: rgba(0, 0, 255, 1)">if</span> meta.csrf: <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">生成 CSRF tocken隐藏标签</span> self._csrf =<span style="color: rgba(0, 0, 0, 1)"> meta.build_csrf(self) extra_fields.extend(self._csrf.setup_form(self)) </span><span style="color: rgba(0, 0, 255, 1)">for</span> name, unbound_field <span style="color: rgba(0, 0, 255, 1)">in</span> itertools.chain(fields, extra_fields):<span style="color: rgba(0, 128, 0, 1)">#
#(name, UnboundField 对象(1,simple.StringField,参数))
#(pwd, UnboundField 对象(2,simple.PasswordField,参数))
options = dict(name=name, prefix=prefix, translations=translations)
#(name, UnboundField 对象(1,simple.StringField,参数)) #真正实例化 simple.StringField(参数)
field = meta.bind_field(self, unbound_field, options)
#UnboundField 对象转换成 StringField 对象
self._fields[name] = field
form = {
_fields: {name: StringField 对象 (),
pwd: PasswordField 对象 (),}
循环 form 对象 中的 _fields 字段(字典),分别赋值到 form 对象,这样就可以通过 form.name/form.pwd 直接获取到 Field 对象了
,无需 form._fields['name'] / form._fields['name']
代码:
for name, field in iteritems(self._fields): setattr(self, name, field)
form 对象封装数据就变成以下内容喽
form = { _fields: {name: StringField 对象 (), pwd: PasswordField 对象 (),} name: StringField 对象 (widget=widgets.TextInput()), pwd: PasswordField 对象 (widget=widgets.PasswordInput()) }
3. 当 form 对象生成之后 print(form.name) = 执行StringField 对象的 __str__ 方法;
StringField 类中没有 __str__ 方法,所以去执行基类 Field 的,Field.__str__ 方法返回了: self() = StringFieldObj.__call__()
def __str__(self): return self() #执行 LoginForm 的 __call__ 方法
StringField 没有 __call__ 所以执行其基类 Field.__call__ 方法,调用了 self.meta.render_field(self, kwargs)
def __call__(self, **kwargs): # self=StringField 对象 return self.meta.render_field(self, kwargs) #把 StringField 对象传传入 meta.render_field 方法
下面来看 self.meta.render_field(self, kwargs) 做了什么?
def render_field(self, field, render_kw):other_kw </span>= getattr(field, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">render_kw</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, None) </span><span style="color: rgba(0, 0, 255, 1)">if</span> other_kw <span style="color: rgba(0, 0, 255, 1)">is</span> <span style="color: rgba(0, 0, 255, 1)">not</span><span style="color: rgba(0, 0, 0, 1)"> None: render_kw </span>= dict(other_kw, **<span style="color: rgba(0, 0, 0, 1)">render_kw) </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> StringField对象.widget(field, **render_kw)</span> <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">插件.__call__()</span> <span style="color: rgba(128, 0, 0, 1)">'''</span><span style="color: rgba(128, 0, 0, 1)"> #field =StringField对象 StringField对象.widget对象()=调用widget对象的.__call__方法 </span><span style="color: rgba(128, 0, 0, 1)">'''</span> <span style="color: rgba(0, 0, 255, 1)">return</span> field.widget(field, **render_kw)</pre>
来看 widget 对象 =TextInput() 的 __call__ 方法,最终打印了 obj.name 的结果
def __call__(self, field, **kwargs): kwargs.setdefault('id', field.id) kwargs.setdefault('type', self.input_type) if 'value' not in kwargs: kwargs['value'] = field._value() if 'required' not in kwargs and 'required' in getattr(field, 'flags', []): kwargs['required'] = True return HTMLString('<input %s>' % self.html_params(name=field.name, **kwargs))
""" 0. Form.__iter__: 返回所有字段对象 1. StringField 对象.__str__ 2. StringField 对象.__call__ 3. meta.render_field(StringField 对象,) 4. StringField 对象.widget(field, **render_kw) 5. 插件.__call__() """
4. 执行 for iteam in form 对象的执行流程
执行 form 对象基类 BaseForm 的 __inter__ 方法,变量 self._fields 字典中的内容
def __iter__(self): """Iterate form fields in creation order.""" return iter(itervalues(self._fields))
_fields: {name: StringField 对象 (),
pwd: PasswordField 对象 (),}
用户输入数据的校验验证流程 form = LoginForm(formdata=request.form)
# 请求发过来的值 form = LoginForm(formdata=request.form) # 值.getlist('name')<span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 实例:编辑</span> <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> # 从数据库对象</span> <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> form = LoginForm(obj='值') # 值.name/值.pwd</span> <span style="color: rgba(0, 128, 0, 1)">#
# # 字典 {}
# form = LoginForm(data=request.form) # 值 ['name']# 1. 循环所有的字段
# 2. 获取每个字段的钩子函数
# 3. 为每个字段执行他的验证流程 字段.validate(钩子函数 + 内置验证规则)
六、session 功能
1. Flask 自带的 session 功能
from flask import session import json app=Flask(__name__,template_folder='templates',static_path='/static/',static_url_path='/static/') app.debug=True app.secret_key='sjehfjeefrjewth43u' #设置 session 加密 app.config['JSON_AS_ASCII']=False #指定 json 编码格式 如果为 False 就不使用 ascii 编码, app.config['JSONIFY_MIMETYPE'] ="application/json;charset=utf-8" #指定浏览器渲染的文件类型,和解码格式; @app.route('/login/',methods=['GET','POST']) def login(): msg = '' if request.method=='POST': name=request.values.get('user') pwd=request.values.get('pwd') if name =='zhanggen' and pwd=='123.com': session['user']=name #设置 session 的 key value return redirect('/index/') else: msg='用户名或者密码错误' return render_template('login.html',msg=msg)@app.route('/index/',methods=['GET','POST'])
def index():
user_list = ['张根', 'egon', 'eric']
user=session.get('user') #获取 session
if user:
user=['alex','egon','eric']
return jsonify(user_list)
else:
return redirect('/login/')if name == 'main':
app.run()
2. 第三方 session 组件(Session)
安装 pip install flask-session
from flask import session, Flask,request,make_response,render_template,redirect,jsonify,Responsefrom flask.ext.session import Session #引入第三方 session
import json
app=Flask(name,template_folder='templates',static_path='/static/',static_url_path='/static/')
app.debug=True
app.secret_key='sjehfjeefrjewth43u' #设置 session 加密
app.config['JSON_AS_ASCII']=False #指定 json 编码格式 如果为 False 就不使用 ascii 编码,
app.config['JSONIFY_MIMETYPE'] ="application/json;charset=utf-8" #指定浏览器渲染的文件类型,和解码格式;
app.config['SESSION_TYPE']='redis'from redis import Redis #引入连接 redis 模块
app.config['SESSION_REDIS']=Redis(host='192.168.0.94',port=6379) #连接 redis
Session(app)@app.route('/login/',methods=['GET','POST'])
def login():
msg = ''
if request.method=='POST':
name=request.values.get('user')
pwd=request.values.get('pwd')
if name =='zhanggen' and pwd=='123.com':
session['user']=name #设置 session 的 key value
return redirect('/index/')
else:
msg='用户名或者密码错误'
return render_template('login.html',msg=msg)@app.route('/index/',methods=['GET','POST'])
def index():
user_list = ['张根', 'egon', 'eric']
user=session.get('user') #获取 session
if user:
user=['alex','egon','eric']
return jsonify(user_list)
else:
return redirect('/login/')if name == 'main':
app.run()
不仅可以把 session 存放到 redis 还可放到文件、内存、memcache...
def _get_interface(self, app): config = app.config.copy() config.setdefault('SESSION_TYPE', 'null') config.setdefault('SESSION_PERMANENT', True) config.setdefault('SESSION_USE_SIGNER', False) config.setdefault('SESSION_KEY_PREFIX', 'session:') config.setdefault('SESSION_REDIS', None) config.setdefault('SESSION_MEMCACHED', None) config.setdefault('SESSION_FILE_DIR', os.path.join(os.getcwd(), 'flask_session')) config.setdefault('SESSION_FILE_THRESHOLD', 500) config.setdefault('SESSION_FILE_MODE', 384) config.setdefault('SESSION_MONGODB', None) config.setdefault('SESSION_MONGODB_DB', 'flask_session') config.setdefault('SESSION_MONGODB_COLLECT', 'sessions') config.setdefault('SESSION_SQLALCHEMY', None) config.setdefault('SESSION_SQLALCHEMY_TABLE', 'sessions')</span><span style="color: rgba(0, 0, 255, 1)">if</span> config[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_TYPE</span><span style="color: rgba(128, 0, 0, 1)">'</span>] == <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">redis</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">: session_interface </span>=<span style="color: rgba(0, 0, 0, 1)"> RedisSessionInterface( config[</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_REDIS</span><span style="color: rgba(128, 0, 0, 1)">'</span>], config[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_KEY_PREFIX</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">], config[</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_USE_SIGNER</span><span style="color: rgba(128, 0, 0, 1)">'</span>], config[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_PERMANENT</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">]) </span><span style="color: rgba(0, 0, 255, 1)">elif</span> config[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_TYPE</span><span style="color: rgba(128, 0, 0, 1)">'</span>] == <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">memcached</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">: session_interface </span>=<span style="color: rgba(0, 0, 0, 1)"> MemcachedSessionInterface( config[</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_MEMCACHED</span><span style="color: rgba(128, 0, 0, 1)">'</span>], config[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_KEY_PREFIX</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">], config[</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_USE_SIGNER</span><span style="color: rgba(128, 0, 0, 1)">'</span>], config[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_PERMANENT</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">]) </span><span style="color: rgba(0, 0, 255, 1)">elif</span> config[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_TYPE</span><span style="color: rgba(128, 0, 0, 1)">'</span>] == <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">filesystem</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">: session_interface </span>=<span style="color: rgba(0, 0, 0, 1)"> FileSystemSessionInterface( config[</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_FILE_DIR</span><span style="color: rgba(128, 0, 0, 1)">'</span>], config[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_FILE_THRESHOLD</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">], config[</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_FILE_MODE</span><span style="color: rgba(128, 0, 0, 1)">'</span>], config[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_KEY_PREFIX</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">], config[</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_USE_SIGNER</span><span style="color: rgba(128, 0, 0, 1)">'</span>], config[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_PERMANENT</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">]) </span><span style="color: rgba(0, 0, 255, 1)">elif</span> config[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_TYPE</span><span style="color: rgba(128, 0, 0, 1)">'</span>] == <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">mongodb</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">: session_interface </span>=<span style="color: rgba(0, 0, 0, 1)"> MongoDBSessionInterface( config[</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_MONGODB</span><span style="color: rgba(128, 0, 0, 1)">'</span>], config[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_MONGODB_DB</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">], config[</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_MONGODB_COLLECT</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">], config[</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_KEY_PREFIX</span><span style="color: rgba(128, 0, 0, 1)">'</span>], config[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_USE_SIGNER</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">], config[</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_PERMANENT</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">]) </span><span style="color: rgba(0, 0, 255, 1)">elif</span> config[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_TYPE</span><span style="color: rgba(128, 0, 0, 1)">'</span>] == <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">sqlalchemy</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">: session_interface </span>=<span style="color: rgba(0, 0, 0, 1)"> SqlAlchemySessionInterface( app, config[</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_SQLALCHEMY</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">], config[</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_SQLALCHEMY_TABLE</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">], config[</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_KEY_PREFIX</span><span style="color: rgba(128, 0, 0, 1)">'</span>], config[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_USE_SIGNER</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">], config[</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">SESSION_PERMANENT</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">]) </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">: session_interface </span>=<span style="color: rgba(0, 0, 0, 1)"> NullSessionInterface() </span><span style="color: rgba(0, 0, 255, 1)">return</span> session_interface</pre>
3. 自定义 session 组件
#!/usr/bin/env python # -*- coding:utf-8 -*- import uuid import json from flask.sessions import SessionInterface from flask.sessions import SessionMixin from itsdangerous import Signer, BadSignature, want_bytesclass MySession(dict, SessionMixin):
def init(self, initial=None, sid=None):
self.sid = sid
self.initial = initial
super(MySession, self).init(initial or ())</span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__setitem__</span><span style="color: rgba(0, 0, 0, 1)">(self, key, value): super(MySession, self).</span><span style="color: rgba(128, 0, 128, 1)">__setitem__</span><span style="color: rgba(0, 0, 0, 1)">(key, value) </span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__getitem__</span><span style="color: rgba(0, 0, 0, 1)">(self, item): </span><span style="color: rgba(0, 0, 255, 1)">return</span> super(MySession, self).<span style="color: rgba(128, 0, 128, 1)">__getitem__</span><span style="color: rgba(0, 0, 0, 1)">(item) </span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__delitem__</span><span style="color: rgba(0, 0, 0, 1)">(self, key): super(MySession, self).</span><span style="color: rgba(128, 0, 128, 1)">__delitem__</span><span style="color: rgba(0, 0, 0, 1)">(key)
class MySessionInterface(SessionInterface):
session_class = MySession
container = {}</span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__init__</span><span style="color: rgba(0, 0, 0, 1)">(self): </span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> redis self.redis </span>=<span style="color: rgba(0, 0, 0, 1)"> redis.Redis() </span><span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> _generate_sid(self): </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> str(uuid.uuid4()) </span><span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> _get_signer(self, app): </span><span style="color: rgba(0, 0, 255, 1)">if</span> <span style="color: rgba(0, 0, 255, 1)">not</span><span style="color: rgba(0, 0, 0, 1)"> app.secret_key: </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> None </span><span style="color: rgba(0, 0, 255, 1)">return</span> Signer(app.secret_key, salt=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">flask-session</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, key_derivation</span>=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">hmac</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">) </span><span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> open_session(self, app, request): </span><span style="color: rgba(128, 0, 0, 1)">"""</span><span style="color: rgba(128, 0, 0, 1)"> 程序刚启动时执行,需要返回一个session对象 </span><span style="color: rgba(128, 0, 0, 1)">"""</span><span style="color: rgba(0, 0, 0, 1)"> sid </span>=<span style="color: rgba(0, 0, 0, 1)"> request.cookies.get(app.session_cookie_name) </span><span style="color: rgba(0, 0, 255, 1)">if</span> <span style="color: rgba(0, 0, 255, 1)">not</span><span style="color: rgba(0, 0, 0, 1)"> sid: sid </span>=<span style="color: rgba(0, 0, 0, 1)"> self._generate_sid() </span><span style="color: rgba(0, 0, 255, 1)">return</span> self.session_class(sid=<span style="color: rgba(0, 0, 0, 1)">sid) signer </span>=<span style="color: rgba(0, 0, 0, 1)"> self._get_signer(app) </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">: sid_as_bytes </span>=<span style="color: rgba(0, 0, 0, 1)"> signer.unsign(sid) sid </span>=<span style="color: rgba(0, 0, 0, 1)"> sid_as_bytes.decode() </span><span style="color: rgba(0, 0, 255, 1)">except</span><span style="color: rgba(0, 0, 0, 1)"> BadSignature: sid </span>=<span style="color: rgba(0, 0, 0, 1)"> self._generate_sid() </span><span style="color: rgba(0, 0, 255, 1)">return</span> self.session_class(sid=<span style="color: rgba(0, 0, 0, 1)">sid) </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> session保存在redis中</span> <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> val = self.redis.get(sid)</span> <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> session保存在内存中</span> val =<span style="color: rgba(0, 0, 0, 1)"> self.container.get(sid) </span><span style="color: rgba(0, 0, 255, 1)">if</span> val <span style="color: rgba(0, 0, 255, 1)">is</span> <span style="color: rgba(0, 0, 255, 1)">not</span><span style="color: rgba(0, 0, 0, 1)"> None: </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">: data </span>=<span style="color: rgba(0, 0, 0, 1)"> json.loads(val) </span><span style="color: rgba(0, 0, 255, 1)">return</span> self.session_class(data, sid=<span style="color: rgba(0, 0, 0, 1)">sid) </span><span style="color: rgba(0, 0, 255, 1)">except</span><span style="color: rgba(0, 0, 0, 1)">: </span><span style="color: rgba(0, 0, 255, 1)">return</span> self.session_class(sid=<span style="color: rgba(0, 0, 0, 1)">sid) </span><span style="color: rgba(0, 0, 255, 1)">return</span> self.session_class(sid=<span style="color: rgba(0, 0, 0, 1)">sid) </span><span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> save_session(self, app, session, response): </span><span style="color: rgba(128, 0, 0, 1)">"""</span><span style="color: rgba(128, 0, 0, 1)"> 程序结束前执行,可以保存session中所有的值 如: 保存到resit 写入到用户cookie </span><span style="color: rgba(128, 0, 0, 1)">"""</span><span style="color: rgba(0, 0, 0, 1)"> domain </span>=<span style="color: rgba(0, 0, 0, 1)"> self.get_cookie_domain(app) path </span>=<span style="color: rgba(0, 0, 0, 1)"> self.get_cookie_path(app) httponly </span>=<span style="color: rgba(0, 0, 0, 1)"> self.get_cookie_httponly(app) secure </span>=<span style="color: rgba(0, 0, 0, 1)"> self.get_cookie_secure(app) expires </span>=<span style="color: rgba(0, 0, 0, 1)"> self.get_expiration_time(app, session) val </span>=<span style="color: rgba(0, 0, 0, 1)"> json.dumps(dict(session)) </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> session保存在redis中</span> <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> self.redis.setex(name=session.sid, value=val, time=app.permanent_session_lifetime)</span> <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> session保存在内存中</span>
self.container.setdefault(session.sid, val)
session_id </span>=<span style="color: rgba(0, 0, 0, 1)"> self._get_signer(app).sign(want_bytes(session.sid)) response.set_cookie(app.session_cookie_name, session_id, expires</span>=expires, httponly=<span style="color: rgba(0, 0, 0, 1)">httponly, domain</span>=domain, path=path, secure=secure)</pre>
from flask import Flask from flask import session from my_session import MySessionInterfaceapp = Flask(name)
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
app.session_interface = MySessionInterface()@app.route('/login/', methods=['GET', "POST"])
def login():
print(session)
session['user1'] = 'alex'
session['user2'] = 'alex'
del session['user2']</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">内容</span><span style="color: rgba(128, 0, 0, 1)">"</span>
if name == 'main':
app.run()
七、蓝图
from flask import Blueprint
Flask 的 Blueprint 功能称为蓝图,它的功能就像Django 中的 include 或者 Gin 的路由分组,旨在为 web 框架的路由进行合理拆分。
from flask import Blueprint #把名称为 test_zhanggen 的蓝图 register 到 app 中 test_zhanggen= Blueprint('oss_api_zhanggen',__name__) @test_zhanggen.route('/index/', methods=['GET', 'POST']) def index(): """功能项辅助审核""" return "测试 / 张根 /Index"@test_zhanggen.route('/home/', methods=['GET', 'POST'])
def home():
"""功能项辅助审核"""
return "测试 / 张根 /Home"
在 app 注册已经生成的蓝图
#把 api_zhanggen 文件中 test_zhanggen 蓝图注册到 flask appp app.register_blueprint(blueprint=api_zhanggen.test_zhanggen, url_prefix='/test/zhanggen')
八、message (闪现)
message 是一个基于 Session 实现的用于保存数据的集合,其特点是:一次性。
特点:和 labada 匿名函数一样不长期占用内存
from flask import Flask,request,flash,get_flashed_messagesapp = Flask(name)
app.secret_key = 'some_secret'@app.route('/set/')
def index2():
flash('Disposable') #在 message 中设置 1 个个值
return 'ok'#---------------------------------------------------------------------------------
@app.route('/')
def index1():
messages = get_flashed_messages() #获取 message 中设置的值,只能获取 1 次。(1 次性)
print(messages)
return "Index1"if name == "main":
app.run()
九、中间件
flask 也有中间件功能和 Django 类似,不同的是使用的是使用 3 个装饰器来实现的;
1.@app.before_first_request : 请求第 1 次到来执行 1 次,之后都不执行;
2.@app.before_request:请求到达视图之前执行;(改函数不能有返回值,否则直接在当前返回)
3.@app.after_request:请求 经过视图之后执行;(最下面的先执行)
#!/usr/bin/env python # -*- coding:utf-8 -*- from flask import Flask, Request, render_templateapp = Flask(name, template_folder='templates')
app.debug = True@app.before_first_request #第 1 个请求到来执行
def before_first_request1():
print('before_first_request1')@app.before_request #中间件 2
def before_request1():
Request.nnn = 123
print('before_request1') #不能有返回值,一旦有返回值在当前返回@app.before_request
def before_request2():
print('before_request2')@app.errorhandler(404)
def page_not_found(error):
return 'This page does not exist', 404@app.route('/')
def hello_world():
return "Hello World"@app.after_request #中间件 执行视图之后
def after_request1(response):
print('after_request1', response)
return response@app.after_request #中间件 执行视图之后 先执行 after_request2
def after_request2(response):
print('after_request2', response)
return responseif name == 'main':
app.run()
十、Flask 相关组件
2、flask-script 组件
flask-script 组件:用于通过脚本的形式,启动 flask;(实现类似 Django 的 python manager.py runserver 0.0.0.0:8001)
pip install flask-script #安装
#!/usr/bin/env python # -*- coding:utf-8 -*-from sansa import create_app
from flask_script import Manager #导入
app = create_app()manager=Manager(app) #实例化 Manager 对象
if name == 'main':
manager.run()
python run.py runserver -h 0.0.0.0 -p 8001
* Running on http://0.0.0.0:8001/ (Press CTRL+C to quit)
3.flask-migrate 组件
在线修改、迁移数据库(Django 的 migrate 。
#!/usr/bin/env python # -*- coding:utf-8 -*-from sansa import create_app,db
from flask_script import Manager #导入
from flask_migrate import Migrate,MigrateCommandapp = create_app()
manager=Manager(app) #实例化 Manager 对象
migrate=Migrate(app,db)manager.add_command('db',MigrateCommand) #注册命令
if name == 'main':
manager.run()
pip install flask-migrate #安装
3.1. 初始化数据库:python run.py db init
3.2. 迁移数据: python run.py db migrate
3.3. 生成表: python run.py db upgrade
ps: 修改表结构 first 直接注释静态字段代码,second 执行 python run.py db upgrade.
D:\Flask 练习 \sansa>python run.py db init Creating directory D:\Flask 练习 \sansa\migrations ... done Creating directory D:\Flask 练习 \sansa\migrations\versions ... done Generating D:\Flask 练习 \sansa\migrations\alembic.ini ... done Generating D:\Flask 练习 \sansa\migrations\env.py ... done Generating D:\Flask 练习 \sansa\migrations\README ... done Generating D:\Flask 练习 \sansa\migrations\script.py.mako ... done Please edit configuration/connection/logging settings in 'D:\\Flask 练习 \\sansa\\migrations\\alembic.ini' before proceeding.D:\Flask 练习 \sansa>python run.py db migrate
INFO [alembic.runtime.migration] Context impl MySQLImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added table 'users666'
Generating D:\Flask 练习 \sansa\migrations\versions\a7f412a8146f_.py ... doneD:\Flask 练习 \sansa>python run.py db upgrade
INFO [alembic.runtime.migration] Context impl MySQLImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running upgrade -> a7f412a8146f, empty messageD:\Flask 练习 \sansa>
文档: http://docs.jinkan.org/docs/flask/quickstart.html
银角大王:http://www.cnblogs.com/wupeiqi/articles/7552008.html
银角大王:http://www.cnblogs.com/wupeiqi/articles/5713330.html(执行原生 SQL 模块 pymsql ,ORM 框架 SQLAchemy)