本次主要讲解flask框架
flask框架是一款非常受欢迎的python web框架,它非常的灵活小巧。
from flask import Flask
# 传入__name__初始化一个Flask实例
app = Flask(__name__)
# 装饰器,将当前路由映射到指定函数
@app.route('/')
def hello_world():
return 'hello world'
if __name__ == '__main__':
app.run()
运行结果如下:
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
flask项目的配置都是以app.config对象来进行配置的。
在Flask项目中,有四种方式进行项目的配置。
app = Flask(__name__)
app.config['DEBUG'] = True # 设置项目以debug模式运行
这种方式不灵活,复用性太低
因为app.config本质上是一个dict,使用可以使用update()来进行配置
app = Flask(__name__)
app.config.update(
DEBUG=True,
SECRET_KEY='....'
)
如果项目的配置项特别多,那么我们可以把所有的配置项都放在一个模块中,然后通过加载模块的方式进行配置,假设有一个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模块。
app.config.from_pyfile()
方法传入一个文件名,通常是以.py结尾的文件,但也不限于只使用.py后缀的文件。
通过导入Python文件的形式导入配置文件:
if __name__ == '__main__':
app.config.from_pyfile('config.py')
app.run()
from_pyfile()
方法有一个silent参数,设置为True时,如果配置文件不存在也不会报错;
不仅支持Python格式的配置文件,也支持.ini等格式。
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)
视图函数中可以获取到url中的参数。
from flask import Flask
# 传入__name__初始化一个Flask实例
app = Flask(__name__)
app.config.update(
DEBUG=True,
)
# 装饰器,将当前路由映射到指定函数,id为url中的可变参数
@app.route('/app/' )
def hello_world(id):
return f'hello app{id}'
if __name__ == '__main__':
app.run(host="0.0.0.0",port=5055)
效果如下:
说明:
<变量名称>
的方式在app.route中设置路由<类型:变量名>
,常见的限制类型有: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/' )
def hello_world(id):
return f'hello app{id}'
if __name__ == '__main__':
app.run(host="0.0.0.0",port=5055)
运行效果如下:
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来访问效果如下:
可以使用url_for(视图函数名称,函数参数1,函数参数2,…)来获取对应视图函数的url地址
from flask import Flask, url_for
# 传入__name__初始化一个Flask实例
app = Flask(__name__)
app.config.update(
DEBUG=True,
)
@app.route('/app/' )
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)
运行后效果如下:
页面重定向就是当我们访问一个页面时,自动跳转到另一个页面。一般分为两种重定向:
第一种:永久重定向。比如某个网站地址变为了另一个地址,则当我们访问这个网站时,会自动跳转到新的地址。例如输入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
视图函数中可以返回以下类型的值:
(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,会报错:
提示返回的类型只能是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)
显示:
也可以返回元组,是元组时,只返回元组的第一个元素;
在一般情况下,返回元组的用法是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)
显示:
可以看到,给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)
效果与前者是一样的。
视图函数中可以使用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)
Restful API是用于在前端与后台进行通信的一套规范,使用这个规范可以让前后端开发变得更加简单。
需要采用http或者是采用https协议。
数据之间传输的格式应该使用json形式
方法 | 含义 | 举例 |
---|---|---|
GET | 从服务器上获取资源 | /users/ 获取所有用户 |
… | … | /user/id/ 根据id获取一个用户 |
POST | 在服务器上新创建一个资源 | /user/ 新建一个用户 |
PUT | 在服务器上更新资源(客户端提供所有改变后的数据) | /user/id/ 更新某个id的用户的信息(需要提供用户的所有信息) |
PATCH | 在服务器上更新资源(客户端只提供需要改变的属性) | /user/id/ 更新某个id的用户信息(只需要提供需要改变的信息) |
DELETE | 从服务器上删除资源 | /user/id/ 删除一个用户 |
状态码 | 描述 | 含义 |
---|---|---|
200 | OK | 服务器成功响应客户端的请求 |
400 | INVALID REQUEST | 用户发出的请求有错误,服务器没有进行新建或修改数据的操作 |
401 | Unauthorized | 用户没有权限访问这个请求 |
403 | Forbidden | 因为某些原因禁止访问这个请求 |
404 | NOT FOUND | 用户发送的请求的url不存在 |
406 | NOT Acceptable | 用户请求不被服务器接收(比如服务器期望客户端发送某个字段,但是没有发送) |
500 | Internal server error | 服务器内部错误,比如出现了bug |
Flask-Restful是Flask中专门用来实现Restful API的插件,使用它可以快速集成Restful API功能。
在普通的网站中,这个插件显得有些鸡肋,因为在普通的网页开发中,是需要去渲染HTML代码的,而Flask-Restful在每个请求中都返回json格式的数据。但是在前后端分离的项目中,Flask-Restful是flask非常好用的一个插件。
使用pip install flask-restful
命令安装。
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)
说明:
在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)
运行后结果如下:
Flask-Restful插件提供了类似WTForms来验证提交的数据是否合法的包,即reqparse
。
RequestParser对象的add_argument()
方法可以指定字段的名字、数据类型等,常见的参数有:
练习如下:
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)
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)
运行结果如下:
在Flask中操作cookie,是通过Response对象来设置cookie,具体实现为在response返回之前,通过response.set_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)
运行效果如下:
设置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值
Flask项目中上下文包括两类:
请求上下文request和应用上下文current_app都是全局变量,所有请求都是共享的。
Flask中使用特殊的机制来保证每次请求的数据都是隔离的,即用户A请求所产生的数据不会影响到用户B的请求,所以直接导入request对象也不会被一些脏数据影响,并且不需要像Django一样在每个函数中使用request的时候传入request参数。
4类上下文对象如下:
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
显示:
显然,得到了当前所处的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
钩子函数是指在执行函数和目标函数之间挂载的函数,框架开发者给调用方提供一个point即挂载点,至于挂载什么函数由调用方决定, 从而大大提高了灵活性。
第一次请求之前执行。
练习如下:
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
在每次请求之前执行,通常可以用这个装饰器来给视图函数增加一些变量。
练习如下:
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
在每次请求之后执行。
练习如下:
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 -
不管是否有异常,注册的函数都会在每次请求之后执行。
练习如下:
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/,显示:
控制台打印:
在第一次请求之前执行
在每一次请求之前执行
在每一次请求之后执行
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()
函数的。
上下文处理器,返回的字典中的键可以在模板上下文中使用。
假如在多个视图函数中渲染模板时都需要传入同样的参数,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
显示:
显然,达到了效果,但是在每个视图函数中都要传入参数,很麻烦冗余,可以用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装饰器定义的钩子函数中返回参数。
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 '服务器内部错误...
', 500
if __name__ == '__main__':
app.run()
12345678910111213141516171819202122232425262728293031323334
新建404.html如下:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>页面404title>
head>
<body>
<p>页面跑到火星去了...p>
body>
html>
12345678910
还可以在视图函数中用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 '服务器内部错误...
', 500
if __name__ == '__main__':
app.run()
显示:
显然,此时再访问http://127.0.0.1:5000/list/,就会报错;
因为在list()
视图函数中使用sort()
方法抛出404错误,被钩子函数page_not_found()
捕获到,因此访问会出现404错误。
pip install flask-wtf
新建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)
总结:
form.validate()
的值来判断用户提交的数据是否满足表单的验证。form.validate()
的值来判断用户提交的数据是否满足表单的验证。可以直接使用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='邮箱格式不正确')])
可以通过NumberRange类对数的范围进行验证。
class LoginForm(Form):
age = IntegerField(validators=[NumberRange(1, 120, message='年龄范围有误!!!')])
可以通过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='用户名必填')])
可以通过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='手机号码不正确')])
可以使用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='地址格式不正确')])
要验证验证码长度和有效性,长度用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("验证码错误")
AnyOf 确保输入值在可选值列表中
NoneOf 确保输入值不在可选值列表中
FileRequired 确保文件是上传了的
FileAllowed(['jpg', 'png', 'gif']) 确保文件上传的类型
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 一组指定类型的字段