请求钩子
什么是请求钩子
在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理,比如:
- 在请求开始时,建立数据库连接;
- 在请求开始时,根据需求进行权限校验;
- 在请求结束时,指定数据的交互格式;
为了让每个视图函数避免编写重复功能的代码,Flask提供了通用设置的功能,即请求钩子。
请求钩子是通过装饰器的形式实现,Flask支持如下四种请求钩子:
- before_first_request
- 在处理第一个请求前执行[项目初始化时的钩子]
- before_request
- 在每次请求前执行
- 如果在某修饰的函数中返回了一个响应,视图函数将不再被调用
- after_request
- 如果没有抛出错误,在每次请求后执行
- 接受一个参数:视图函数作出的响应
- 在此函数中可以对响应值在返回之前做最后一步修改处理
- 需要将参数中的响应在此参数中进行返回
- teardown_request:
- 在每次请求后执行
- 接受一个参数:错误信息,如果有相关错误抛出
- 需要设置flask的配置DEBUG=False,teardown_request才会接受到异常对象。
----before_request----
每一次接收到客户端请求时,执行这个钩子方法
一般可以用来判断权限,或者转换路由参数或者预处理客户端请求的数据
127.0.0.1 - - [04/Aug/2020 14:40:49] "GET / HTTP/1.1" 200 -
-----------视图函数执行了---------------
----after_request----
在处理请求以后,执行这个钩子方法
一般可以用于记录会员/管理员的操作历史,浏览历史,清理收尾的工作
----teardown_request----
在每一次请求以后,执行这个钩子方法
如果有异常错误,则会传递错误异常对象到当前方法的参数中
None
异常捕获
主动抛出HTTP异常
- abort 方法
抛出一个给定状态代码的 HTTPException 或者 指定响应,例如想要用一个页面未找到异常来终止请求,你可以调用 abort(404)
- 参数:
code – HTTP的错误状态码
# abort(404)
abort(500)
PS: 抛出状态码的话,只能抛出 HTTP 协议的错误状态码
abort在工作中基本不会被使用,工作中的异常抛出往往在业务错误的时候使用raise进行抛出错误类型,而不是抛出http异常。
捕获错误
- errorhandler 装饰器
注册一个错误处理程序,当程序抛出指定错误状态码的时候,就会调用该装饰器所装饰的方法
- 参数:
code_or_exception – HTTP的错误状态码或指定异常
flask中内置了app.errorhander提供给我们捕获异常,实现一些在业务发生错误时的自定义处理。
1. 通过http状态码捕获异常信息
2. 通过异常类进行异常捕获
例如统一处理状态码为500的错误给用户友好的提示:
@app.errorhandler(500)
def internal_server_error(e):
return '服务器搬家了'
捕获指定异常类型
@app.errorhandler(ZeroDivisionError)
def zero_division_error(e):
return '除数不能为0'
context
Flask中上下文对象:相当于一个容器,保存了 Flask 程序运行过程中的一些信息[变量、函数、类与对象等信息]。
Flask中有两种上下文,请求上下文(request context)和应用上下文(application context)。
1. *application* 指的就是当你调用`app = Flask(__name__)`创建的这个对象`app`;
2. *request* 指的是每次`http`请求发生时,`WSGI server`(比如gunicorn)调用`Flask.__call__()`之后,在`Flask`对象内部创建的`Request`对象;
3. *application* 表示用于响应WSGI请求的应用本身,*request* 表示每次http请求;
4. *application*的生命周期大于*request*,一个*application*存活期间,可能发生多次http请求,所以,也就会有多个*request*
请求上下文(request context)
在 flask 中,可以直接在视图函数中使用 request 这个对象进行获取相关数据,而 request
- request
封装了HTTP请求的内容,针对的是http请求。举例:user = request.args.get('user'),获取的是get请求的参数。
- session
用来记录请求会话中的信息,针对的是用户信息。举例:session['name'] = user.id,可以记录用户信息。还可以通过session.get('name')获取用户信息。
PS: 请求上下文提供的变量/属性/方法/函数/类与对象,只能在视图中或者被视图调用的地方使用
应用上下文(application context)
它的字面意思是 应用上下文,但它不是一直存在的,它只是request context 中的一个对 app 的代理(人),所谓local proxy。它的作用主要是帮助 request 获取当前的应用,它是伴 request 而生,随 request 而灭的。
应用上下文对象有:current_app,g
current_app
应用程序上下文,用于存储应用程序中的变量,可以通过current_app.name打印当前app的名称,也可以在current_app中存储一些变量
from flask import Flask,request,session,current_app,g
# 初始化
app = Flask(import_name=__name__)
# 声明和加载配置
class Config():
DEBUG = True
app.config.from_object(Config)
# 编写路由视图
@app.route(rule='/')
def index():
# 应用上下文提供给我们使用的变量,也是只能在视图或者被视图调用的地方进行使用,
# 但是应用上下文的所有数据来源于于app,每个视图中的应用上下文基本一样
print(current_app.config) # 获取当前项目的所有配置信息
print(current_app.url_map) # 获取当前项目的所有路由信息
return "<h1>hello world!</h1>"
if __name__ == '__main__':
# 运行flask
app.run(host="0.0.0.0")
g变量
g 作为 flask 程序全局的一个临时变量,充当者中间媒介的作用,我们可以通过它传递一些数据,g 保存的是当前请求的全局变量,不同的请求会有不同的全局变量,通过不同的thread id区别
g.name='abc' # name是举例,实际要保存什么数据到g变量中,可以根据业务而定,你可以任意的数据进去
from flask import Flask,request,session,current_app,g
# 初始化
app = Flask(import_name=__name__)
# 声明和加载配置
class Config():
DEBUG = True
app.config.from_object(Config)
@app.before_request
def before_request():
g.name = "root"
def get_two_func():
name = g.name
print("g.name=%s" % name)
def get_one_func():
get_two_func()
# 编写路由视图
@app.route(rule='/')
def index():
# 请求上下文提供的变量/属性/方法/函数/类与对象,只能在视图中或者被视图调用的地方使用
# 请求上下文里面信息来源于每次客户端的请求,所以每个视图中请求上下文的信息都不一样
# print(session)
# 应用上下文提供给我们使用的变量,也是只能在视图或者被视图调用的地方进行使用,
# 但是应用上下文的所有数据来源于于app,每个视图中的应用上下文基本一样
print(current_app.config) # 获取当前项目的所有配置信息
print(current_app.url_map) # 获取当前项目的所有路由信息
get_one_func()
return "<h1>hello world!</h1>"
if __name__ == '__main__':
# 运行flask
app.run(host="0.0.0.0")
两者区别:
- 请求上下文:保存了客户端和服务器交互的数据,一般来自于客户端。
- 应用上下文:flask 应用程序运行过程中,保存的一些配置信息,比如路由列表,程序名、数据库连接、应用信息等
Flask-Script 扩展
这个模块的作用可以让我们通过终端来控制flask项目的运行,类似于django的manage.py
安装命令: pip install flask-script
集成 Flask-Script到flask应用中,创建一个主应用程序,一般我们叫manage.py/run.py/main.py
都行。
from flask import Flas
app = Flask(__name__)
"""使用flask_script启动项目"""
from flask_script import Manager
manage = Manager(app)
@app.route('/')
def index():
return 'hello world'
if __name__ == "__main__":
manager.run()
启动终端脚本的命令:
# 端口和域名不写,默认为127.0.0.1:5000
python run.py runserver
# 通过-h设置启动域名,-p设置启动端口
python run.py runserver -h127.0.0.1 -p8888
Flask-Script 还可以为当前应用程序添加脚本命令
1. 引入Command命令基类
2. 创建命令类必须直接或间接继承Command,并在内部实现run方法,同时如果有自定义的其他参数,则必须实现__init__
3. 使用flask_script应用对象manage.add_command对命令类进行注册,并设置调用终端别名。
"""自定义flask_script终端命令"""
from flask_script import Command
class HelloCommand(Command):
"""命令的相关描述"""
def run(self):
with open("text.txt","w") as f:
f.write("hellornhello")
pass
print("这是执行了hello命令")
manage.add_command('hello', HelloCommand() )
Jinja2模板引擎
Flask内置的模板语言,它的设计思想来源于 Django 的模板引擎,并扩展了其语法和一系列强大的功能。
渲染模版函数
- Flask提供的 render_template
- render_template
模板基本使用
- 在flask应用对象创建的时候,设置或者保留template_folder参数,创建模板目录
app = Flask(__name__,template_folder='templates')
2. 在项目下创建 templates
文件夹,用于存放所有的模板文件,并在目录下创建一个模板html文件 index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>{{title}}</h1>
</body>
</html>
3. 在视图函数设置渲染模板并设置模板数据
from flask import Flask, render_template
# 初始化
app = Flask(import_name=__name__,template_folder='templates')
# 配置终端脚本运行项目
from flask_script import Manager
manager = Manager(app)
# 声明和加载配置
class Config():
DEBUG = True
app.config.from_object(Config)
# 编写路由视图
@app.route(rule='/')
def index():
data={}
data["title"] = "我的flask项目"
return render_template("index.html",**data)
if __name__ == '__main__':
# 运行flask
manager.run()
输出变量
{{}} 来表示变量名,这种 {{}} 语法叫做 变量代码块
视图代码:
@app.route("/")
def index():
data={}
data["title"] = "我的flask项目"
return render_template("index.html",**data)
模板代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{title}}</title>
</head>
<body>
<h1>{{title}}</h1>
</body>
</html>
Jinja2 模版中的变量代码块可以是任意 Python 类型或者对象,只要它能够被 Python 的 __str__
方法或者str()转换为一个字符串就可以,比如,可以通过下面的方式显示一个字典或者列表中的某个元素:
视图代码:
from flask import Flask,render_template
from settings.dev import Config
from flask_script import Manager
"""创建flask应用"""
app = Flask(__name__,template_folder='templates')
"""使用脚手架[终端脚本]启动项目"""
manage = Manager(app)
"""加载配置"""
app.config.from_object(Config)
@app.route("/")
def index():
data = {}
data["title"] = "我的项目"
data["data_list"] = ["a","b","c"]
data["data_dict"] = {
"name":"xiaoming",
"id":100,
}
# return render_template("index.html",
# title="我的flask项目",
# data_list=data_list,
# data_dict=data_dict
# )
return render_template("index.html",**data)
if __name__ == '__main__':
manage.run()
模板代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{title}}</title>
</head>
<body>
<div>{{title}}</div>
<div>{{list}}</div>
<div>{{list[0]}}</div>
<div>{{list.0}}</div>
<div>{{list[-1]}}</div>
<div>{{dict}}</div>
<div>{{dict['name']}}</div>
<div>{{dict.name}}</div>
</body>
</html>
使用 {# #} 进行注释,注释的内容不会在html中被渲染出来
模板中特有的变量和函数
可以在自己的模板中访问一些 Flask 默认内置的函数和对象
config
可以从模板中直接访问Flask当前的config对象:
{{config.SQLALCHEMY_DATABASE_URI}}
sqlite:///database.db
request
就是flask中代表当前请求的request对象
{{request.url}}
http://127.0.0.1
session
为Flask的session对象,显示session数据
{{session.new}}
True
g变量
在视图函数中设置g变量的 name 属性的值,然后在模板中直接可以取出
{{ g.name }}
url_for()
url_for会根据传入的路由器函数名,返回该路由对应的URL,在模板中始终使用url_for()就可以安全的修改路由绑定的URL,则不比担心模板中渲染出错的链接:
{{url_for('home')}}
如果我们定义的路由URL是带有参数的,则可以把它们作为关键字参数传入url_for(),Flask会把他们填充进最终生成的URL中:
{{ url_for('index', post_id=1)}}
/1
例
主程序 run.py:
from flask import Flask, render_template
# 初始化
app = Flask(import_name=__name__,template_folder='templates')
# 配置终端脚本运行项目
from flask_script import Manager
manager = Manager(app)
# 声明和加载配置
class Config():
DEBUG = True
SECRET_KEY = "abc"
app.config.from_object(Config)
# 编写路由视图
@app.route(rule='/')
def index():
data={}
data["title"] = "我的项目"
data["list"] = ["a","b","c"]
data["dict"] = {
"name":"xiaoming",
"id":100,
}
return render_template("index.html",**data)
from flask import session
@app.route("/session/set")
def set_session():
session["name"] = "root"
return "ok"
if __name__ == '__main__':
# 运行flask
manager.run()
模板 templates/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{title}}</title>
</head>
<body>
<div>{{title}}</div>
<div>{{list}}</div>
<div>{{list[0]}}</div>
<div>{{list.0}}</div>
<div>{{list[-1]}}</div>
<div>{{dict}}</div>
<div>{{dict['name']}}</div>
{# flask模板引擎的注释 #}
{# <div>{{dict.name}}</div> #}
<div>{{config}}</div>
<div>{{config.DEBUG}}</div>
<div>name={{request.args.name}}</div>
<div>session.name={{session.name}}</div>
<div>{{config.PREFERRED_URL_SCHEME}}://{{request.headers.Host}}{{url_for("set_session")}}</div>
</body>
</html>
PS: pycharm中设置当前项目的模板语言:
files/settings/languages & frameworks/python template languages。
设置下拉框为jinja2,保存
流程控制
主要包含两个
- if/else if /else / endif
- for / endfor
if语句
Jinja2 语法中的if语句跟 Python 中的 if 语句相似,后面的布尔值或返回布尔值的表达式将决定代码中的哪个流程会被执行.
用 {%%} 定义的控制代码块,可以实现一些语言层次的功能,比如循环或者if语句
视图代码:
from flask import Flask,render_template,request
from settings.dev import Config
from flask_script import Manager
"""创建flask应用"""
app = Flask(__name__,template_folder='templates')
"""使用脚手架[终端脚本]启动项目"""
manage = Manager(app)
"""加载配置"""
app.config.from_object(Config)
@app.route("/list")
def list_page():
data = {}
data["book_list"] = [
{"id":1,"price":78.50,"title":"javascript入门"},
{"id":2,"price":78.50,"title":"python入门"},
{"id":3,"price":78.50,"title":"django项目实战"}
]
data["name"] = int( request.args.get("name") )
return render_template("list.html",**data)
if __name__ == '__main__':
manage.run()
list.html,模板代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<table border="1" align="center" width="680">
<tr>
<th>id</th>
<th>标题</th>
<th>价格</th>
</tr>
{# for循环 #}
{% for book in book_list %}
<tr>
<td>{{ book.id }}</td>
<td>{{ book.title }}</td>
<td>{{ book.price }}</td>
</tr>
{% endfor %}
</table>
{# 判断一个参数是否是奇数 #}
{% if name % 2 == 0 %}
偶数<br>
{% else %}
奇数<br>
{% endif %}
</body>
</html>
flask中也有过滤器,并且也可以被用在 if 语句或者for语句中:
视图代码:
from flask import Flask,render_template,request
from settings.dev import Config
from flask_script import Manager
"""创建flask应用"""
app = Flask(__name__,template_folder='templates')
"""使用脚手架[终端脚本]启动项目"""
manage = Manager(app)
"""加载配置"""
app.config.from_object(Config)
@app.route("/")
def index():
data = {}
data["title"] = "我的项目"
data["data_list"] = ["a","b","c"]
data["data_dict"] = {
"name":"xiaoming",
"id":100,
}
# return render_template("index.html",
# title="我的flask项目",
# data_list=data_list,
# data_dict=data_dict
# )
return render_template("index.html",**data)
@app.route("/list")
def list_page():
data = {}
data["book_list"] = [
{"id":1,"price":78.50,"title":"javascript入门"},
{"id":2,"price":78.50,"title":"python入门"},
{"id":3,"price":78.50,"title":"django项目实战"}
]
data["name"] = int( request.args.get("name") )
return render_template("list.html",**data)
@app.route("/filter")
def filter():
data = {}
data["text"] = "hello flask"
data["img_url"] = '<img width="300px" src="https://github.githubassets.com/images/modules/site/heroes/octocat-paper.svg">'
return render_template("fitler.html",**data)
if __name__ == '__main__':
manage.run()
filter.html,模板代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>{{ text }}</p>
<p>{{ text|upper }}</p>
<p>{{ text|length }}</p>
<p>{{ img_url }}</p>
<p>{{ img_url|safe }}</p>
{% if request.args.get("name")| int % 2 == 0 %}
<p>偶数</p>
{% else %}
<p>奇数</p>
{% endif %}
</body>
</html>
循环语句
我们可以在 Jinja2 中使用循环来迭代任何列表或者生成器函数
{% for post in posts %}
<div>
<h1>{{ post.title }}</h1>
<p>{{ post.text | safe }}</p>
</div>
{% endfor %}
循环和if语句可以组合使用,以模拟 Python 循环中的 continue 功能,下面这个循环将只会渲染post.text不为None的那些post:
{% for post in posts if post.text %}
<div>
<h1>{{ post.title }}</h1>
<p>{{ post.text | safe }}</p>
</div>
{% endfor %}
在一个 for 循环块中你可以访问这些特殊的变量:
loop.index 当前循环迭代的次数(从 1 开始)
loop.index0 当前循环迭代的次数(从 0 开始)
loop.revindex 到循环结束需要迭代的次数(从 1 开始)
loop.revindex 0到循环结束需要迭代的次数(从 0 开始)
loop.first 如果是第一次迭代,为 True 。
loop.last 如果是最后一次迭代,为 True 。
loop.length 序列中的项目数。
loop.cycle 在一串序列间期取值的辅助函数。见下面示例程序。
在循环内部,你可以使用一个叫做loop的特殊变量来获得关于for循环的一些信息
比如:要是我们想知道当前被迭代的元素序号,并模拟Python中的enumerate函数做的事情,则可以使用loop变量的index属性,例如:
{% for post in posts%}
{{loop.index}}, {{post.title}}
{% endfor %}
过滤器
过滤器的本质就是函数。有时候我们不仅仅只是需要输出变量的值,我们还需要修改变量的显示,甚至格式化、运算等等,而在模板中是不能直接调用 Python 中的某些方法,那么这就用到了过滤器。
使用方式:
- 过滤器的使用方式为:变量名 | 过滤器。
{{variable | filter_name(args1,args2,....)}}
- 如果没有任何参数传给过滤器,则可以把括号省略掉
{{variable | filter_name }}
在 jinja2 中,过滤器是可以支持链式调用的,示例如下:
在 jinja2 中,过滤器是可以支持链式调用的,示例如下:
常见的内建过滤器
字符串操作
safe:禁用转义
capitalize:把变量值的首字母转成大写,其余字母转小写
lower:把值转成小写
upper:把值转成大写
title:把值中的每个单词的首字母都转成大写
reverse:字符串反转
format:格式化输出
striptags:渲染之前把值中所有的HTML标签都删掉
ps:如果内容中,存在大小于号的情况,则不要使用这个过滤器,容易误删内容。
<p>{{ '<em>hello</em>' | striptags }}</p>
<p>{{ "如果x<y,z>x,那么x和z之间是否相等?" | striptags }}</p>
truncate: 字符串截断
列表操作
first:取第一个元素
last:取最后一个元素
length:获取列表长度
sum:列表求和
sort:列表排序
自定义过滤器
过滤器的本质是函数。当模板内置的过滤器不能满足需求,可以自定义过滤器。自定义过滤器有两种实现方式:
- 一种是通过Flask应用对象的 add_template_filter
- 通过装饰器来实现自定义过滤器
PS:自定义的过滤器名称如果和内置的过滤器重名,会覆盖内置的过滤器
需求:添加列表反转的过滤器
方式一
通过调用应用程序实例的 add_template_filter 方法实现自定义过滤器。该方法第一个参数是函数名,第二个参数是自定义的过滤器名称:
# 自定义过滤器
def do_list_reverse(old_list):
# 因为字典/列表是属于复合类型的数据,所以改动数据的结构,也会应该能影响到原来的变量
# 通过list新建一个列表进行操作,就不会影响到原来的数据
new_list = list(old_list)
new_list.reverse()
return new_list
# 注册过滤器
app.add_template_filter(do_list_reverse, "lrev")
方式二
用装饰器来实现自定义过滤器。装饰器传入的参数是自定义的过滤器名称。
from flask import Flask, render_template
# 初始化
app = Flask(import_name=__name__,template_folder='templates')
# 配置终端脚本运行项目
from flask_script import Manager
manager = Manager(app)
# 声明和加载配置
class Config():
DEBUG = True
app.config.from_object(Config)
# 自定义过滤器
def do_list_reverse(old_list):
# 因为字典/列表是属于复合类型的数据,所以改动数据的结构,也会应该能影响到原来的变量
# 通过list新建一个列表进行操作,就不会影响到原来的数据
new_list = list(old_list)
new_list.reverse()
return new_list
# 注册过滤器
app.add_template_filter(do_list_reverse, "lrev")
@app.route(rule='/')
def index():
data={}
data["user_list"] = ["xiaoming","小黑白","小红"]
return render_template("index.html",**data)
if __name__ == '__main__':
# 运行flask
manager.run()
html调用过滤器
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>title</title>
</head>
<body>
<p>{{ user_list }}</p>
<p>{{ user_list | lrev }}</p>
<p>{{ user_list }}</p>
</body>
</html>