python自动化(七)自动化测试平台开发:3.flask技术讲解上

一.后端开发框架flask

在这里插入图片描述

本次主要讲解flask框架

1.flask框架简介

在这里插入图片描述

flask框架是一款非常受欢迎的python web框架,它非常的灵活小巧。

2 flask框架的基本使用

from flask import Flask

# 传入__name__初始化一个Flask实例
app = Flask(__name__)


# 装饰器,将当前路由映射到指定函数
@app.route('/')
def hello_world():
    return 'hello world'


if __name__ == '__main__':
    app.run()

运行结果如下:

在这里插入图片描述
在这里插入图片描述

3.指定参数来启动flask

from flask import Flask

# 传入__name__初始化一个Flask实例
app = Flask(__name__)


# 装饰器,将当前路由映射到指定函数
@app.route('/')
def hello_world():
    return 'hello world'


if __name__ == '__main__':
    app.run(debug=True,host="0.0.0.0",port=5055)
    # 说明
    # debug=True,表示使用调试模式启动flask,这种模式下代码更新后不需要重新启动flask,系统会自动刷新。且当代码有问题时,会在终端输出异常信息
    # host="0.0.0.0",这种方式可以让其他电脑访问flask服务
    # port=5055,指定端口为5055

4.flask的配置与配置文件

flask项目的配置都是以app.config对象来进行配置的。

在Flask项目中,有四种方式进行项目的配置。

4.1 直接硬编码

app = Flask(__name__)
app.config['DEBUG'] = True # 设置项目以debug模式运行

这种方式不灵活,复用性太低

4.2 通过update()方式

因为app.config本质上是一个dict,使用可以使用update()来进行配置

app = Flask(__name__)

app.config.update(
    DEBUG=True,
    SECRET_KEY='....'
)

4.3.通过from_object()方法

如果项目的配置项特别多,那么我们可以把所有的配置项都放在一个模块中,然后通过加载模块的方式进行配置,假设有一个settings.py模块,专门用来存储配置项的,此你可以通过app.config.from_object()方法进行加载,并且该方法既可以接收模块的的字符串名称,也可以模块对象本身。
有两种形式:

# 1. 通过模块字符串
app.config.from_object('settings')
# 2. 通过模块对象
import settings
app.config.from_object(settings)

使用这种方式,添加配置文件后,将配置项都放入该文件中,其他文件直接引用该配置文件中的配置项,提高了代码的复用性、降低了耦合度,同时,在配置文件中修改了配置项时,其他代码中均不需要修改,从而提高了代码的灵活性。

例如:新建config.py文件,添加一些配置项如下:

# 设置Debug模式为True
DEBUG = True

# 指定HOST
HOST = '127.0.0.1'

在flask文件中导入:

from flask import Flask
import config

app = Flask(__name__)


# 装饰器,将当前路由映射到指定函数
@app.route('/')
def hello_world():
    return 'hello world'

if __name__ == '__main__':
    app.config.from_object(config)
    app.run()

再运行,也能开启Debug模式。
也可以通过字符串形式导入:

if __name__ == '__main__':
    app.config.from_object('config')
    app.run()

此时不需要再导入config模块。

4.4.通过from_pyfile()方法

app.config.from_pyfile()方法传入一个文件名,通常是以.py结尾的文件,但也不限于只使用.py后缀的文件。
通过导入Python文件的形式导入配置文件:

if __name__ == '__main__':
    app.config.from_pyfile('config.py')
    app.run()

from_pyfile()方法有一个silent参数,设置为True时,如果配置文件不存在也不会报错;
不仅支持Python格式的配置文件,也支持.ini等格式。

5.flask实现url与函数的映射

5.1实现基本映射

flask中使用@app.route('/')来实现函数与url之间的映射,app.route()中传入指定的url。

from flask import Flask

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)

# 装饰器,将当前路由映射到指定函数。当我们访问http://127.0.0.1:5055/app时,就会自动映射到hello_world()函数中。我们一般将这种函数称为视图函数
@app.route('/app')
def hello_world():
    return 'hello app'

if __name__ == '__main__':
    app.run(host="0.0.0.0",port=5055)

5.2 实现函数中使用url的参数

视图函数中可以获取到url中的参数。

from flask import Flask

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)
# 装饰器,将当前路由映射到指定函数,id为url中的可变参数
@app.route('/app/<id>')
def hello_world(id):
    return f'hello app{id}'

if __name__ == '__main__':
    app.run(host="0.0.0.0",port=5055)

效果如下:

在这里插入图片描述

说明:

  1. 如果要在url中设置可变参数,必须以<变量名称>的方式在app.route中设置路由
  2. 视图函数中需要传入对应变量作为参数。
  3. 可以指定变量的数据类型,格式为<类型:变量名>,常见的限制类型有:string(接受任何没有斜杠/的字符串,为flask中的默认格式),int(整数类型),float(浮点型),path(与string类似,但可以接受斜杠/),uuid(uuid类型的字符串),any(可以同时指定多种路径)
from flask import Flask

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)

# 装饰器,将当前路由映射到指定函数,id为url中的可变参数
@app.route('/app/<int:id>')
def hello_world(id):
    return f'hello app{id}'

if __name__ == '__main__':
    app.run(host="0.0.0.0",port=5055)

运行效果如下:

在这里插入图片描述

在这里插入图片描述

6.指定url的HTTP请求方式

app.route()中可以使用参数methods来指定使用的HTTP连接方式

from flask import Flask
from flask import request

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)

# 使用methods来指定HTTP方式,默认为GET
@app.route('/app',methods=['POST']) 
def hello_world():
    return 'hello app'

if __name__ == '__main__':
    app.run(debug=True,host="0.0.0.0",port=5055)

使用postman来访问效果如下:

在这里插入图片描述

7.使用url_for获取视图函数的映射路径

可以使用url_for(视图函数名称,函数参数1,函数参数2,…)来获取对应视图函数的url地址

from flask import Flask, url_for

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)

@app.route('/app/<id>')
def hello_world(id):
    return f'hello app{id}'

@app.route('/url_for')
def test_url_for():
    s = url_for('hello_world',id=3) # 根据视图函数获取对应的url
    return s

if __name__ == '__main__':
    app.run(debug=True,host="0.0.0.0",port=5055)

运行后效果如下:

在这里插入图片描述

8.页面重定向

页面重定向就是当我们访问一个页面时,自动跳转到另一个页面。一般分为两种重定向:

第一种:永久重定向。比如某个网站地址变为了另一个地址,则当我们访问这个网站时,会自动跳转到新的地址。例如输入www.jingdong.com的时候,会被重定向到www.jd.com,因为jingdong.com这个网址已经被废弃了,被改成jd.com,所以这种情况下应该用永久重定向。

第二种:临时重定向。比如某个页面需要登录权限,当你未登陆时访问,则会自动跳转到登录页面

from flask import Flask, url_for, redirect

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)

@app.route('/app')
def hello_world():
    return 'hello app'

@app.route('/redict')
def test_redict():
    return redirect('/app') # 重定向到‘/app’页面

if __name__ == '__main__':
    app.run(debug=True,host="0.0.0.0",port=5055)

说明:redirect(url地址,code=xxx),第一个参数为需要重定向的目标url,第二个参数code用来指定返回码,默认为302

9.函数的返回值 - 响应(Response)

视图函数中可以返回以下类型的值:

  • Response对象。
  • 字符串
    Flask是根据返回的字符串类型重新创建一个werkzeug.wrappers.Response对象,Response将该字符串作为主体,状态码为200,MIME类型为text/html,然后返回该Response对象。
  • 元组
    传入元组的格式是(response,status,headers),response为一个字符串,status值是状态码,headers是响应头。

如果不是以上三种类型,Flask会通过Response.force_type(rv,request.environ)转换为一个请求对象。

之前在函数中返回的都是字符串,现进行返回列表的测试:

from flask import Flask,url_for,request,redirect

app = Flask(__name__)

@app.route('/about')
def about():
    return ['123']

if __name__ == '__main__':
    app.run(debug=True)

访问http://127.0.0.1:5000/about,会报错:
flask response list test
提示返回的类型只能是string、dict、tuple、Response instance或WSGI callable,其他类型会报错,现换成字典测试:

from flask import Flask,url_for,request,redirect

app = Flask(__name__)

@app.route('/about')
def about():
    return {'name':'Corley'}

if __name__ == '__main__':
    app.run(debug=True)

显示:
flask response dict test
也可以返回元组,是元组时,只返回元组的第一个元素;
在一般情况下,返回元组的用法是return '关于我们',200,即return '字符串',状态码
返回Response测试:

from flask import Flask,url_for,request,redirect,Response

app = Flask(__name__)

@app.route('/about')
def about():
    return Response('关于我们')

if __name__ == '__main__':
    app.run(debug=True)

显示:
flask response response test
可以看到,给Response传入字符串参数后,返回内容和字符串是类似的;
Response('关于我们')相当于Response('关于我们',status=200,mimetype='text/html')
Response的用法是Response('字符串',状态码,mimetype='')
也可以用make_response()方法创建Response对象并返回,这个方法可以设置额外的数据,比如设置cookie、header等信息,测试如下:

from flask import Flask,url_for,request,redirect,Response,make_response

app = Flask(__name__)


@app.route('/about')
def about():
    return make_response('关于我们')

if __name__ == '__main__':
    app.run(debug=True)

效果与前者是一样的。

10.获取url的请求数据

视图函数中可以使用flask.request获取url中的请求数据

from flask import Flask, request

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)

@app.route('/app')
def hello_world():
    request_data = dict(request.args) # request.args来获取get请求中的数据
    return request_data

@app.route('/redict' ,methods=['POST'])
def test_redict():
    request_data = dict(request.json) # 使用request.json来获取post请求中的json数据;使用request.form来获取post请求中的form数据
    return request_data

if __name__ == '__main__':
    app.run(debug=True,host="0.0.0.0",port=5055)

11.Flask-Restful的概念和使用

11.1 Restful API规范

Restful API是用于在前端与后台进行通信的一套规范,使用这个规范可以让前后端开发变得更加简单。

(1)协议

需要采用http或者是采用https协议。

(2)数据传输格式

数据之间传输的格式应该使用json形式

(4)HTTP请求方法
方法含义举例
GET从服务器上获取资源/users/获取所有用户
/user/id/根据id获取一个用户
POST在服务器上新创建一个资源/user/新建一个用户
PUT在服务器上更新资源(客户端提供所有改变后的数据)/user/id/更新某个id的用户的信息(需要提供用户的所有信息)
PATCH在服务器上更新资源(客户端只提供需要改变的属性)/user/id/更新某个id的用户信息(只需要提供需要改变的信息)
DELETE从服务器上删除资源/user/id/删除一个用户
状态码描述含义
200OK服务器成功响应客户端的请求
400INVALID REQUEST用户发出的请求有错误,服务器没有进行新建或修改数据的操作
401Unauthorized用户没有权限访问这个请求
403Forbidden因为某些原因禁止访问这个请求
404NOT FOUND用户发送的请求的url不存在
406NOT Acceptable用户请求不被服务器接收(比如服务器期望客户端发送某个字段,但是没有发送)
500Internal server error服务器内部错误,比如出现了bug

12 Flask-Restful插件的基本使用

12.1 基本概念

Flask-Restful是Flask中专门用来实现Restful API的插件,使用它可以快速集成Restful API功能。
在普通的网站中,这个插件显得有些鸡肋,因为在普通的网页开发中,是需要去渲染HTML代码的,而Flask-Restful在每个请求中都返回json格式的数据。但是在前后端分离的项目中,Flask-Restful是flask非常好用的一个插件。

2.安装

使用pip install flask-restful命令安装。

3.基本使用

from flask import Flask, request
from flask_restful import Api , Resource

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)
api = Api(app)

# 视图类需要集成Resource
class IndexView(Resource):

    # get表示get请求
    def get(self):
        return {'username': 'Corley'}

    # post表示post请求
    def post(self):
        return {'info': 'Login Successfully!!'}

# 使用api.add_resource()添加路由
api.add_resource(IndexView, '/', endpoint='index')


if __name__ == '__main__':
    app.run(debug=True,host="0.0.0.0",port=5055)

运行效果如下:
在这里插入图片描述
在这里插入图片描述

说明:

  1. 使用Flask-Restful自定义视图类需要继承Resource基类,使用post,get方法来指定HTTP请求类型
  2. api.add_resource()来实现视图类与url的映射。第一个参数为视图类对象;第二个参数为url;第三个参数为可选对象,相当于给视图类自定义一个别名,用于url_for()反向获取url,如果不指定则默认为视图类名称的小写字符串

13. Restful获取请求中的参数

在flask_restful中与之前一样,也是使用request.args来获取get请求中的参数,使用request.json来获取post请求中的json数据参数,使用request.form来获取post请求中的form数据

from flask import Flask, request
from flask_restful import Api , Resource

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)
api = Api(app)

# 视图类需要集成Resource
class IndexView(Resource):

    # get表示get请求
    def get(self):
        return {'args': request.args}

    # post表示post请求
    def post(self):
        return {'args': request.json}

# 使用api.add_resource()添加路由
api.add_resource(IndexView, '/', endpoint='index')


if __name__ == '__main__':
    app.run(debug=True,host="0.0.0.0",port=5055)

运行后结果如下:

在这里插入图片描述

在这里插入图片描述

14.Flask-Restful对请求中的数据进行校验

Flask-Restful插件提供了类似WTForms来验证提交的数据是否合法的包,即reqparse
RequestParser对象的add_argument()方法可以指定字段的名字、数据类型等,常见的参数有:

  • default
    默认值,如果参数没有值,那么将使用这个参数指定的值。
  • required
    是否必须。
    默认为False;如果设置为True,则必须提交这个参数。
  • type
    参数的数据类型,如果指定,那么将使用指定的数据类型来强制转换提交得到的值。
  • choices
    选项,提交上来的值只有满足选项中的值才符合验证通过,否则验证不通过。
  • help
    错误信息,如果验证失败后,将会使用help参数指定的值作为错误信息。
  • trim
    是否要去掉前后的空格。
  • location
    请求的对象,要从(例如: location : args, form, json, headers, cookies等)中获取参数,可以是an迭代器,默认为(‘json’, ‘values’,)

练习如下:

from flask import Flask, request
from flask_restful import Api , Resource, reqparse, inputs

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)
api = Api(app)

# 视图类需要集成Resource
class IndexView(Resource):

    # get表示get请求
    def get(self):
        return {'args': request.args}

    # post表示post请求
    def post(self):
        parse = reqparse.RequestParser()
        parse.add_argument('username', type=str, help='用户名验证错误', required=True)
        parse.add_argument('password', type=str, help='密码验证错误', trim=True)
        parse.add_argument('age', type=int, help='年龄错误')
        parse.add_argument('gender', type=str, help='性别错误', choices=['male', 'female', 'secret'], default='secret')
        parse.add_argument('blog', type=inputs.url, help='博客地址错误')
        parse.add_argument('phone', type=inputs.regex(r'1[35789]\d{9}'), help='电话号码错误')

        args = parse.parse_args()
        return {'info': 'Register Successfully!!', 'args': args}

# 使用api.add_resource()添加路由
api.add_resource(IndexView, '/', endpoint='index')


if __name__ == '__main__':
    app.run(debug=True,host="0.0.0.0",port=5055)

显示:
在这里插入图片描述

15. Flask-Restful高级使用

1.自定义输出格式模板

flask_restful可以设置一个返回数据的json模板,只需要将数据传入到这个模板中就可以返回特定模式的数据
这需要导入flask_restful.marshal_with装饰器,在视图类中定义一个字典来指定需要返回的字段,以及该字段的数据类型。

测试如下:

from flask import Flask, request
from flask_restful import Api , Resource, reqparse, inputs,fields,marshal_with

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)
api = Api(app)

class IndexView(Resource):

    # 定义一个数据返回的模板,使用flask_restful.fields来指定数据的类型
    template_data = {
        'name': fields.String,  # 指定为一个字符串
        'age': fields.Integer,  # 指定为一个整数
        'vip': fields.Boolean  # 指定为一个布尔值
    }

    @marshal_with(template_data) # 将数据模板传入到装饰器中
    def get(self):
        return {'name': "zhangsan", "age":18}

# 使用api.add_resource()添加路由
api.add_resource(IndexView, '/', endpoint='index')

if __name__ == '__main__':
    app.run(debug=True,host="0.0.0.0",port=5055)

运行效果如下:

可以看到即便没传vip字段的,也会在网页中渲染该字段

在这里插入图片描述

有时候想要在返回的数据格式中,返回列表或者字典
要在一个字段中放置一个列表,可以使用fields.List
在一个字段下面又是一个字典,可以使用fields.Nested

练习如下:

from flask import Flask, request
from flask_restful import Api , Resource, reqparse, inputs,fields,marshal_with

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)
api = Api(app)

class IndexView(Resource):

    # 定义一个数据返回的模板
    template_data = {
        'name': fields.String,  # 指定为一个字符串
        'age': fields.Integer,  # 指定为一个整数
        'hehe': fields.Nested({'dict1':fields.String,'dict2':fields.String}) # 指定一个字典
        # 'haha': fields.List,  # 指定一个列表
        # 'hehe': fields.Nested  # 指定一个字典
    }

    @marshal_with(template_data) # 将数据模板传入到装饰器中
    def get(self):
        return {'name': "zhangsan", "age":18, 'hehe':{'dict1':'ss11','dict2':'ddfff'}}

# 使用api.add_resource()添加路由
api.add_resource(IndexView, '/', endpoint='index')


if __name__ == '__main__':
    app.run(debug=True,host="0.0.0.0",port=5055)

运行结果如下:

在这里插入图片描述

16.flask中cookie和session的使用

16.1 设置cookie

在Flask中操作cookie,是通过Response对象来设置cookie,具体实现为在response返回之前,通过response.set_cookie()方法来设置,这个方法有以下几个参数:

  • key
    cookie的键
  • value
    cookie的键对应的值。
  • max_age
    cookie的过期时间,如果不设置,则浏览器关闭后就会自动过期。
  • expires
    过期时间,时间戳的形式(1970到现在的时间)。
  • domain
    该cookie在哪个域名中有效,一般设置子域名,比如cms.example.com。
  • path
    该cookie在哪个路径下有效,即当前主域名。

例如:

from flask import Flask, Response

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
)

@app.route('/set_cookie')
def set_cookie():
    res = Response('Start set cookie')
    res.set_cookie(key='username',value='zhangsan')
    return res

if __name__ == '__main__':
    app.run(debug=True,host="0.0.0.0",port=5055)

运行效果如下:

在这里插入图片描述

16.2 设置session

设置session

flask中的session机制是将内容加密后储存在cookie中。

from datetime import timedelta
from flask import Flask, session

# 传入__name__初始化一个Flask实例
app = Flask(__name__)

app.config.update(
    DEBUG=True,
    SECRET_KEY='asfdsdgfdhyfgjguygjk', # 设置一个密匙用于session的加密
    PERMANENT_SESSION_LIFETIME = timedelta(hours=2) # 设置session的过期时间
)

@app.route('/set_session')
def set_session():
    session.permanent = True # 设置session持久化,默认过期时间为30天
    session['username'] = 'zhangsan'
    return 'start set session'

if __name__ == '__main__':
    app.run(debug=True,host="0.0.0.0",port=5055)

运行结果如下:
在这里插入图片描述

删除session

flask中可以使用session.clear()删除整个session,使用session.pop(key)删除指定的session值

17.flask的上下文

Flask项目中上下文包括两类:

  • 应用上下文
    用于记录和应用相关的数据,包括current_app和g。
  • 请求上下文
    用于记录和请求相关的数据,包括request和session。

请求上下文request和应用上下文current_app都是全局变量,所有请求都是共享的。
Flask中使用特殊的机制来保证每次请求的数据都是隔离的,即用户A请求所产生的数据不会影响到用户B的请求,所以直接导入request对象也不会被一些脏数据影响,并且不需要像Django一样在每个函数中使用request的时候传入request参数。
4类上下文对象如下:

  • request
    请求上下文对象,一般用来保存一些请求的变量,如method、args、form等。
  • session
    请求上下文对象,一般用来保存一些会话信息。
  • current_app
    应用上下文对象,返回当前的app。
  • g
    应用上下文对象,处理请求时用于临时存储的对象。

current_app使用示意如下:

from flask import Flask, current_app

app = Flask(__name__)


@app.route('/')
def index():
    return '这是首页'


@app.route('/login/')
def login():
    ca = current_app.name
    cfg = current_app.config['DEBUG']
    return '登录页面:' + ca + '-' + str(cfg)


if __name__ == '__main__':
    app.run(debug=True)

1234567891011121314151617181920

显示:
flask context current_app use
显然,得到了当前所处的app,即初始化Flask对象时传入的参数__name__,也就是当前Python文件名;
除了获取当前app名称,还可以获取绑定到app中的配置参数。

注意:current_app只能在视图函数中使用,在视图函数之外使用会报错。

一般情况测试:
新建utils.py如下:

'''
工具文件
'''


def log_a(username):
    print("Log A %s" % username)


def log_b(username):
    print("Log B %s" % username)

主程序flask_context.py如下:

from flask import Flask, session
from utils import log_a, log_b
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)


@app.route('/')
def index():
    username = session.get('username')
    log_a(username)
    log_b(username)
    return '这是首页'


@app.route('/login/')
def login():
    session['username'] = 'Corley'
    session['user_id'] = 1
    return '登录页面'


if __name__ == '__main__':
    app.run(debug=True)

1234567891011121314151617181920212223242526

运行后,先访问http://127.0.0.1:5000/,再访问http://127.0.0.1:5000/login/,再访问http://127.0.0.1:5000/,控制台打印如下:

127.0.0.1 - - [13/May/2020 11:57:59] "GET / HTTP/1.1" 200 -
Log A None
Log B None
127.0.0.1 - - [13/May/2020 11:58:14] "GET /login/ HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2020 11:58:23] "GET / HTTP/1.1" 200 -
Log A Corley
Log B Corley

12345678

显然,在访问登录页面之后,控制台打印出了username信息。

此时使用g对象进行改进:
主程序文件修改如下:

from flask import Flask, current_app, g, session
from utils import log_a, log_b
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)


@app.route('/')
def index():
    g.username = session.get('username')
    log_a()
    log_b()
    return '这是首页'


@app.route('/login/')
def login():
    session['username'] = 'Corley'
    session['user_id'] = 1
    return '登录页面'


if __name__ == '__main__':
    app.run(debug=True)

utils.py修改如下:

'''
工具文件
'''
from flask import g

def log_a():
    print("Log A %s" % g.username)


def log_b():
    print("Log B %s" % g.username)

123456789101112

运行之后,按之前的顺序访问,控制台打印:

127.0.0.1 - - [13/May/2020 12:04:34] "GET / HTTP/1.1" 200 -
Log A None
Log B None
127.0.0.1 - - [13/May/2020 12:04:42] "GET /login/ HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2020 12:04:52] "GET / HTTP/1.1" 200 -
Log A Corley
Log B Corley

12345678

显然,达到了同样的效果,并且此时不需要再在log_a()log_b()函数中传递参数;
每次刷新页面后,g对象都会被重置,是临时存储的对象;
g对象一般用于解决频繁传参的问题。

可以在一个视图函数中实现调用另一个函数,如下:

from flask import Flask, current_app, g, session
from utils import log_a, log_b
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)


@app.route('/')
def index():
    g.username = session.get('username')
    log_a()
    log_b()
    hello()
    return '这是首页'


def hello():
    print('Hello %s' % g.username)


@app.route('/login/')
def login():
    session['username'] = 'Corley'
    session['user_id'] = 1
    return '登录页面'


if __name__ == '__main__':
    app.run(debug=True)

按之前的顺序访问,控制台打印如下:

127.0.0.1 - - [13/May/2020 12:13:41] "GET / HTTP/1.1" 200 -
Log A None
Log B None
Hello None
127.0.0.1 - - [13/May/2020 12:13:48] "GET /login/ HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2020 12:13:52] "GET / HTTP/1.1" 200 -
Log A Corley
Log B Corley
Hello Corley

18.常用的钩子函数

钩子函数是指在执行函数和目标函数之间挂载的函数,框架开发者给调用方提供一个point即挂载点,至于挂载什么函数由调用方决定, 从而大大提高了灵活性

1.before_first_request

第一次请求之前执行。
练习如下:

from flask import Flask

app = Flask(__name__)


@app.route('/')
def index():
    print('这是首页')
    return '这是首页'


@app.before_first_request
def handle_first_request():
    print('在第一次请求之前执行')


if __name__ == '__main__':
    app.run(debug=True)

运行后,访问http://127.0.0.1:5000/,控制台打印:

127.0.0.1 - - [13/May/2020 12:39:01] "GET / HTTP/1.1" 200 -
在第一次请求之前执行
这是首页
123

2.before_request

在每次请求之前执行,通常可以用这个装饰器来给视图函数增加一些变量。

练习如下:

from flask import Flask

app = Flask(__name__)


@app.route('/')
def index():
    print('这是首页')
    return '这是首页'


@app.before_first_request
def handle_first_request():
    print('在第一次请求之前执行')


@app.before_request
def handle_before():
    print("在每一次请求之前执行")


if __name__ == '__main__':
    app.run(debug=True)

运行后,多次访问http://127.0.0.1:5000/,控制台打印:

在第一次请求之前执行
在每一次请求之前执行
这是首页
127.0.0.1 - - [13/May/2020 12:41:36] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页
127.0.0.1 - - [13/May/2020 12:41:38] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页
127.0.0.1 - - [13/May/2020 12:41:39] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页
127.0.0.1 - - [13/May/2020 12:41:40] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2020 12:41:41] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页

1234567891011121314151617

3.after_request

在每次请求之后执行。

练习如下:

from flask import Flask

app = Flask(__name__)


@app.route('/')
def index():
    print('这是首页')
    return '这是首页'


@app.before_first_request
def handle_first_request():
    print('在第一次请求之前执行')


@app.before_request
def handle_before():
    print("在每一次请求之前执行")


@app.after_request
def handle_after(response):
    print("在每一次请求之后执行")
    return response


if __name__ == '__main__':
    app.run(debug=True)

运行后,多次访问http://127.0.0.1:5000/,控制台打印:

127.0.0.1 - - [13/May/2020 12:51:05] "GET / HTTP/1.1" 200 -
在第一次请求之前执行
在每一次请求之前执行
这是首页
在每一次请求之后执行
在每一次请求之前执行
这是首页
在每一次请求之后执行
127.0.0.1 - - [13/May/2020 12:51:06] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页
在每一次请求之后执行
127.0.0.1 - - [13/May/2020 12:51:07] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页
在每一次请求之后执行
127.0.0.1 - - [13/May/2020 12:51:08] "GET / HTTP/1.1" 200 -
在每一次请求之前执行
这是首页
在每一次请求之后执行
127.0.0.1 - - [13/May/2020 12:51:09] "GET / HTTP/1.1" 200 -

4.teardown_appcontext

不管是否有异常,注册的函数都会在每次请求之后执行。

练习如下:

from flask import Flask

app = Flask(__name__)


@app.route('/')
def index():
    1/0
    print('这是首页')
    return '这是首页'


@app.before_first_request
def handle_first_request():
    print('在第一次请求之前执行')


@app.before_request
def handle_before():
    print("在每一次请求之前执行")


@app.after_request
def handle_after(response):
    print("在每一次请求之后执行")
    return response


@app.teardown_appcontext
def handle_teardown(response):
    print('teardown被执行')
    return response


if __name__ == '__main__':
    app.run()

运行后,访问http://127.0.0.1:5000/,显示:
flask hook teardown_appcontext
控制台打印:

在第一次请求之前执行
在每一次请求之前执行
在每一次请求之后执行
teardown被执行
[2020-05-13 12:58:48,206] ERROR in app: Exception on / [GET]
Traceback (most recent call last):
  File "XXX\Test-gftU5mTd\lib\site-packages\flask\app.py", line 2447, in wsgi_app
    response = self.full_dispatch_request()
  File "XXX\Test-gftU5mTd\lib\site-packages\flask\app.py", line 1952, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "XXX\Test-gftU5mTd\lib\site-packages\flask\app.py", line 1821, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "XXX\Test-gftU5mTd\lib\site-packages\flask\_compat.py", line 39, in reraise
    raise value
  File "XXX\Test-gftU5mTd\lib\site-packages\flask\app.py", line 1950, in full_dispatch_request
    rv = self.dispatch_request()
  File "XXX\Test-gftU5mTd\lib\site-packages\flask\app.py", line 1936, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "XXXX\flask_hook.py", line 8, in index
    1/0
ZeroDivisionError: division by zero
127.0.0.1 - - [13/May/2020 12:58:48] "GET / HTTP/1.1" 500 -

1234567891011121314151617181920212223

显然, 在出现异常的情况下也执行了handle_teardown()函数;
虚造关闭Debug模式才能有明显的现象,在Debug模式下是不能执行handle_teardown()函数的。

5.context_processor

上下文处理器,返回的字典中的键可以在模板上下文中使用。

假如在多个视图函数中渲染模板时都需要传入同样的参数,flask_hook.py如下:

from flask import Flask, render_template

app = Flask(__name__)


@app.route('/')
def index():
    return render_template('index.html', username='Corley')


@app.route('/list/')
def list():
    return render_template('list.html', username='Corley')


if __name__ == '__main__':
    app.run(debug=True)

123456789101112131415161718

在模板目录templates中新建index.html和list.html,index.html如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
    <p>这是首页</p>
    <p>{{ username }}</p>
</body>
</html>
1234567891011

list.html如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>列表</title>
</head>
<body>
    <p>这是列表</p>
    <p>{{ username }}</p>
</body>
</html>
1234567891011

显示:
flask hook nocontext_processor
显然,达到了效果,但是在每个视图函数中都要传入参数,很麻烦冗余,可以用context_processor装饰器定义钩子函数传递公共参数:

from flask import Flask, render_template

app = Flask(__name__)


@app.route('/')
def index():
    return render_template('index.html')


@app.route('/list/')
def list():
    return render_template('list.html')


@app.context_processor
def context_process():
    return {'username': 'Corley'}


if __name__ == '__main__':
    app.run(debug=True)

效果与之前一样;
此时在每个视图函数中不需要再传递参数,而是在context_processor装饰器定义的钩子函数中返回参数。

6.errorhandler

errorhandler接收状态码作为参数,可以自定义处理返回这个状态码的响应的方法。

测试如下:

from flask import Flask, render_template

app = Flask(__name__)


@app.route('/')
def index():
    1/0
    return render_template('index.html')


@app.route('/list/')
def list():
    return render_template('list.html')


@app.context_processor
def context_process():
    return {'username': 'Corley'}


@app.errorhandler(404)
def page_not_found(error):
    return render_template('404.html'), 404


@app.errorhandler(500)
def server_error(error):
    return '<p>服务器内部错误...</p>', 500


if __name__ == '__main__':
    app.run()

12345678910111213141516171819202122232425262728293031323334

新建404.html如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>页面404</title>
</head>
<body>
    <p>页面跑到火星去了...</p>
</body>
</html>
12345678910

显示:
flask hook errorhandler
显然,捕获到了404和500错误。

还可以在视图函数中用abort()方法主动抛出异常,如下:

from flask import Flask, render_template, abort

app = Flask(__name__)


@app.route('/')
def index():    
    return render_template('index.html')


@app.route('/list/')
def list():
    abort(404)
    return render_template('list.html')


@app.context_processor
def context_process():
    return {'username': 'Corley'}


@app.errorhandler(404)
def page_not_found(error):
    return render_template('404.html'), 404


@app.errorhandler(500)
def server_error(error):
    return '<p>服务器内部错误...</p>', 500


if __name__ == '__main__':
    app.run()

显示:
flask hook errorhandler abort
显然,此时再访问http://127.0.0.1:5000/list/,就会报错;
因为在list()视图函数中使用sort()方法抛出404错误,被钩子函数page_not_found()捕获到,因此访问会出现404错误。

19.WTForms表单验证

  • 我们在视图函数中,往往要对请求中的参数进行校验,判断其是否合法。如果使用常规的if等判断语句会使得我们的代码十分冗余,尤其是当请求中的参数较多时。
  • 我们可以使用WTForms表单来校验请求参数的合法性。
  • flask-wtf是flask-wtf是一个简化了WTFForms操作的第三方库。
  • 安装方式为:pip install flask-wtf

19.1 基本使用

新建forms.py如下:

from wtforms import Form, StringField
from wtforms.validators import Length, EqualTo

class RegisterForm(Form):
    username = StringField(validators=[Length(min=3, max=15, message='用户名长度不正确')])
    password = StringField(validators=[Length(min=6, max=20, message='密码长度不正确')])
    pwd_confirm = StringField(validators=[Length(min=6, max=20), EqualTo('password', message='两次密码不一致')])

新建wtf_demo.py如下:

from flask import Flask, request, render_template
from forms import RegisterForm

app = Flask(__name__)


@app.route('/')
def index():
    return '首页'


@app.route('/register/', methods=['GET', 'POST'])
def register():
    if request.method == 'GET':
        return render_template('register.html')
    else:
        form = RegisterForm(request.form) # 生成一个form对象
        if form.validate():
            return '验证成功'
        else:
            return '验证失败:\n' + str(form.errors)


if __name__ == '__main__':
    app.run(debug=True)

总结:

  • RegisterForm初始化时传入request.form(即需要校验的请求参数),并且根据form.validate()的值来判断用户提交的数据是否满足表单的验证。
  • RegisterForm初始化时传入request.form(即需要校验的请求参数),并且根据form.validate()的值来判断用户提交的数据是否满足表单的验证。
  • form.errors会返回错误提示,即表单校验时指定的message中的内容。
  • StringField表示校验的参数值的数据类型

19.2 Flask-WTF常用的验证器

(1)Email

可以直接使用Email类进行右键地址的验证。
在forms.py中新增LoginForm如下:

from wtforms import Form, StringField
from wtforms.validators import Length, EqualTo, Email

class RegisterForm(Form):
    username = StringField(validators=[Length(min=3, max=15, message='用户名长度不正确')])
    password = StringField(validators=[Length(min=6, max=20, message='密码长度不正确')])
    pwd_confirm = StringField(validators=[Length(min=6, max=20), EqualTo('password', message='两次密码不一致')])


class LoginForm(Form):
    email = StringField(validators=[Email(message='邮箱格式不正确')])
(2)Number

可以通过NumberRange类对数的范围进行验证。

class LoginForm(Form):
    age = IntegerField(validators=[NumberRange(1, 120, message='年龄范围有误!!!')])
  • IntegerField指定待校验的参数值类型为int
(3)是否必填

可以通过InputRequired类限制某些字段必填。

from wtforms import Form, StringField
from wtforms.validators import Length, EqualTo, InputRequired


class RegisterForm(Form):
    username = StringField(validators=[Length(min=3, max=15, message='用户名长度不正确')])
    password = StringField(validators=[Length(min=6, max=20, message='密码长度不正确')])
    pwd_confirm = StringField(validators=[Length(min=6, max=20), EqualTo('password', message='两次密码不一致')])


class LoginForm(Form):
    username = StringField(validators=[InputRequired(message='用户名必填')])
(5)正则表达式

可以通过Regexp类自定义正则表达式进行验证。

from wtforms import Form, StringField
from wtforms.validators import Length, EqualTo, Regexp


class RegisterForm(Form):
    username = StringField(validators=[Length(min=3, max=15, message='用户名长度不正确')])
    password = StringField(validators=[Length(min=6, max=20, message='密码长度不正确')])
    pwd_confirm = StringField(validators=[Length(min=6, max=20), EqualTo('password', message='两次密码不一致')])


class LoginForm(Form):
    phone_number = StringField(validators=[Regexp(r'1[35789]\d{9}', message='手机号码不正确')])
(5)URL

可以使用URL类验证某个字符串是否是标准的URL格式。

from wtforms import Form, StringField
from wtforms.validators import Length, EqualTo, URL


class RegisterForm(Form):
    username = StringField(validators=[Length(min=3, max=15, message='用户名长度不正确')])
    password = StringField(validators=[Length(min=6, max=20, message='密码长度不正确')])
    pwd_confirm = StringField(validators=[Length(min=6, max=20), EqualTo('password', message='两次密码不一致')])


class LoginForm(Form):
    info_page = StringField(validators=[URL(message='地址格式不正确')])
(6)扩展-验证码的验证

要验证验证码长度和有效性,长度用Length验证,有效性可以在表单类中自定义方法即可。

forms.py修改如下:

from wtforms import Form, StringField
from wtforms.validators import Length, EqualTo, ValidationError


class RegisterForm(Form):
    username = StringField(validators=[Length(min=3, max=15, message='用户名长度不正确')])
    password = StringField(validators=[Length(min=6, max=20, message='密码长度不正确')])
    pwd_confirm = StringField(validators=[Length(min=6, max=20), EqualTo('password', message='两次密码不一致')])


class LoginForm(Form):
    captcha = StringField(validators=[Length(min=4, max=4, message='验证码不正确')])

    def validate_captcha(self, field):
        '''自定义验证,方法名为固定格式,即validate_加要验证的变量名'''
        if field.data != '1234':
            raise ValidationError("验证码错误")
  • 可以得到,自定义验证需要在表单类中定义方法,方法名为固定格式,即validate_加要验证的变量名。验证失败的提示信息使用ValidationError(‘信息’)
(7)其他验证器
AnyOf 确保输入值在可选值列表中
NoneOf 确保输入值不在可选值列表中
FileRequired 确保文件是上传了的
FileAllowed(['jpg', 'png', 'gif']) 确保文件上传的类型

19.3 Flask-WTF中的常见数据类型

StringField 文本字段
TextAreaField 多行文本字段
PasswordField 密码文本字段
HiddenField 隐藏文本字段
DateField 文本字段,值为 datetime.date 格式
DateTimeField 文本字段,值为 datetime.datetime 格式
IntegerField 文本字段,值为整数
DecimalField 文本字段,值为 decimal.Decimal
FloatField 文本字段,值为浮点数
BooleanField 复选框,值为 True 和 False
RadioField 一组单选框
SelectField 下拉列表
SelectMultipleField 下拉列表,可选择多个值
FileField 文件上传字段
SubmitField 表单提交按钮
FormField 把表单作为字段嵌入另一个表单
FieldList 一组指定类型的字段
  • 0
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值