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 Flask

app=Flask(name) #创建 1 个 Flask 实例

@app.route(
'/') #路由系统生成 视图对应 url,1. decorator=app.route()2. decorator(first_flask)
def first_flask(): #视图函数
return 'Hello World' #response

if name == 'main':
app.run()
#启动 socket

View Code

 

 

一、配置文件

 

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 Flask

app=Flask(name)

@app.route('/<name>') #设置 url 传参数 http://127.0.0.1:5000/zhanggen
def first_flask(name): #视图必须有对应接收参数
print(name)
return 'Hello World' #response

if 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'  #response

if 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'  #response

if 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'  #response

if name == 'main':
app.run()

接收 URL 链接类型参数

 

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'  #response

if 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()

url_for() 反向生成 url

 

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()

app.add_url_rule() 方法调用路由

 

5、扩展路由功能:正则匹配 url

如果需要一些复杂的匹配规则可以自定义正则匹配 url

    from flask import Flask, views, url_for
            from werkzeug.routing import BaseConverter
        app </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/&lt;regex("\d+"):nid&gt;</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 inner

app=Flask(name)

@app.route('/',methods=['GET'])
@auth
#注意如果要给视图函数加装饰器,一点要加在路由装饰器下面,才会被路由装饰器装饰
def first_flask():
print('ffff')
return 'Hello World'

if name == 'main':
app.run()

View Code

 

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()

CBV 视图

 

 

五、模板语言

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> <!-- 变量 -->
View Code

 

循环、索引取值

<ul>
{% for user in user_list %}   <!-- 循环 -->
    <li>{{user}}</li>
{% endfor %}

{{user_list.0}} <!-- 索引取值 -->

</ul>

View Code

 

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. 简介

 wtformWTForms 是一个支持多个 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 widgets

app=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)(?=.*[$@$!%*?&amp;])[A-Za-z\d$@$!%*?&amp;]{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()

app01.py
<!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)">&lt;!--</span><span style="color: rgba(0, 128, 0, 1)">&lt;input type="password" name="pwd"&gt;</span><span style="color: rgba(0, 128, 0, 1)">--&gt;</span>
<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">p</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>{{form.pwd.label}} {{form.pwd}} {{form.pwd.errors[0] }}<span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">p</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
<span style="color: rgba(0, 0, 255, 1)">&lt;</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)">&gt;</span>

</form>
</body>
</html>

login.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 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
='张根' #设置 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()

app02.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.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():
    pass

class NewBase(BaseForm,metaclass=FormMeta,):
pass

class Form(NewBase):
pass

class LoginForm(Form):
pass

'''

class Form(with_metaclass(FormMeta,BaseForm)):

View Code

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>
View Code

 

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__()

 

由于 Field.__new__ 方法返回了 1 个 UnboundField 对象,来看 UnboundField 的 __init__ 方法

class UnboundField(object):
    _formfield = True
    creation_counter = 0                   #静态字段 设置计数器
<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>
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><span style="color: rgba(128, 0, 0, 1)">'''</span></pre>
UnboundField.__init__

 

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__

 

 执行完了指定元类 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)]
'''

列表的 sort()
class F1(object):
    pass
class F2(object):
    pass
class F3(F1):
    pass

class F4(F2,F3):
pass

print(F4.mro) #打印 F4 的继承关系

'''
(
<class 'main.F4'>,
<class 'main.F2'>,
<class 'main.F3'>,
<class 'main.F1'>,
<class 'object'>)

'''

__mro__ 获取当前对象的继承顺序

 

 

 

 

2.2. 执行 LoginForm 的 __new__ 方法

没有 __new__ 方法 pass

 

 

2.3. 执行 LoginForm 的 __init__ 方法实例化 form 对象

    def __init__(self, formdata=None, obj=None, prefix='', data=None, meta=None, **kwargs):
    </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>
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(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.__init__

执行 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

BaseForm. __init__
 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__ 方法
Field.__str__ 方法

 

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()

Flask 自带 session 功能

 

2. 第三方 session 组件(Session)

安装 pip install flask-session

from flask import session, Flask,request,make_response,render_template,redirect,jsonify,Response

from 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

不仅可以把 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>
View Code

 

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_bytes

class 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 MySessionInterface

app = 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_messages

app = 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_message

 

 

九、中间件

 

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_template

app = 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 response

if name == 'main':
app.run()

View Code

 

 

十、Flask 相关组件

1、flask-sqlchemy

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()

run.py

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,MigrateCommand

app = create_app()
manager
=Manager(app) #实例化 Manager 对象
migrate=Migrate(app,db)

manager.add_command('db',MigrateCommand) #注册命令

if name == 'main':
manager.run()

run.py
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 ... done

D:\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 message

D:\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)