请求钩子
在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理,比如:
- 在请求开始时,建立数据库连接;
- 在请求开始时,根据需求进行权限校验;
- 在请求结束时,指定数据的交互格式;
为了让每个视图函数避免编写重复功能的代码,Flask提供了通用设置的功能,即请求钩子。
请求钩子是通过装饰器的形式实现,Flask支持如下四种请求钩子:
- before_first_request
- 在处理第一个请求前执行[项目初始化时的钩子]
- before_request
- 在每次请求前执行
- 如果在某修饰的函数中返回了一个响应,视图函数将不再被调用
- after_request
- 如果没有抛出错误,在每次请求后执行
- 接受一个参数:视图函数作出的响应
- 在此函数中可以对响应值在返回之前做最后一步修改处理
- 需要将参数中的响应在此参数中进行返回
- teardown_request:
- 在每次请求后执行
- 接受一个参数:错误信息,如果有相关错误抛出
- 需要设置flask的配置DEBUG=False,teardown_request才会接受到异常对象。
代码
from flask import Flask,request
# 初始化
app = Flask(import_name=__name__)
# 声明和加载配置
class Config():
DEBUG = True
app.config.from_object(Config)
@app.before_first_request
def before_first_request():
"""
这个钩子会在项目启动后第一次被用户访问时执行
可以编写一些初始化项目的代码,例如,数据库初始化,加载一些可以延后引入的全局配置
"""
print("----before_first_request----")
print("系统初始化的时候,执行这个钩子方法")
print("会在接收到第一个客户端请求时,执行这里的代码")
@app.before_request
def before_request():
"""
这个钩子会在每次客户端访问视图的时候执行
# 可以在请求之前进行用户的身份识别,以及对于本次访问的用户权限等进行判断。..
"""
print("----before_request----")
print("每一次接收到客户端请求时,执行这个钩子方法")
print("一般可以用来判断权限,或者转换路由参数或者预处理客户端请求的数据")
@app.after_request
def after_request(response):
print("----after_request----")
print("在处理请求以后,执行这个钩子方法")
print("一般可以用于记录会员/管理员的操作历史,浏览历史,清理收尾的工作")
response.headers["Content-Type"] = "application/json"
response.headers["Company"] = "python oldboy..."
# 必须返回response参数
return response
@app.teardown_request
def teardown_request(exc):
print("----teardown_request----")
print("在每一次请求以后,执行这个钩子方法")
print("如果有异常错误,则会传递错误异常对象到当前方法的参数中")
# 在项目关闭了DEBUG模式以后,则异常信息就会被传递到exc中,我们可以记录异常信息到日志文件中
print(exc)
# 编写路由视图
@app.route(rule='/')
def index():
print("-----------视图函数执行了---------------")
return "<h1>hello world!</h1>"
if __name__ == '__main__':
# 运行flask
app.run(host="0.0.0.0")
- 在第1次请求时的打印:
----before_first_request----
系统初始化的时候,执行这个钩子方法
会在接收到第一个客户端请求时,执行这里的代码
----before_request----
127.0.0.1 - - [04/Aug/2020 14:40:22] "GET / HTTP/1.1" 200 -
每一次接收到客户端请求时,执行这个钩子方法
一般可以用来判断权限,或者转换路由参数或者预处理客户端请求的数据
-----------视图函数执行了---------------
----after_request----
在处理请求以后,执行这个钩子方法
一般可以用于记录会员/管理员的操作历史,浏览历史,清理收尾的工作
----teardown_request----
在每一次请求以后,执行这个钩子方法
如果有异常错误,则会传递错误异常对象到当前方法的参数中
None
- 在第2次请求时的打印:
----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)
抛出状态码的话,只能抛出 HTTP 协议的错误状态码
abort在工作中基本不会被使用,工作中的异常抛出往往在业务错误的时候使用raise进行抛出错误类型,而不是抛出http异常。
捕获错误
- errorhandler 装饰器
- 注册一个错误处理程序,当程序抛出指定错误状态码的时候,就会调用该装饰器所装饰的方法
- 参数:
- code_or_exception – HTTP的错误状态码或指定异常
- 例如统一处理状态码为500的错误给用户友好的提示:
@app.errorhandler(500)
def internal_server_error(e):
return '服务器搬家了'
- 捕获指定异常类型(0不可以作为除数)
@app.errorhandler(ZeroDivisionError)
def zero_division_error(e):
return '除数不能为0'
代码:
from flask import Flask
# 创建flask应用
app = Flask(__name__)
"""加载配置"""
class Config():
DEBUG = True
app.config.from_object(Config)
"""
flask中内置了app.errorhander提供给我们捕获异常,实现一些在业务发生错误时的自定义处理。
1. 通过http状态码捕获异常信息
2. 通过异常类进行异常捕获
"""
"""1. 捕获http异常[在装饰器中写上要捕获的异常状态码也可以是异常类]"""
@app.errorhandler(404)
def error_404(e):
return "<h1>您访问的页面失联了!</h1>"
# return redirect("/") # 如果失效网页,那么就返回首页
# 异常捕获
@app.route(rule="/11")
def index11():
print('------------视图函数执行-------------')
abort(404) # The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
# abort(403) # Not Found
return "<h1>hello world!</h1>"
@app.errorhandler(ZeroDivisionError)
def error_zero(exc):
print("0不能是除数")
return "0不能是除数"
@app.route(rule="/zero")
def index1():
1/0
return '111'
"""2. 捕获系统异常或者自定义异常"""
class APIError(Exception):
pass
@app.route("/")
def index():
raise APIError("api接口调用参数有误!")
return "个人中心,视图执行了!!"
@app.errorhandler(APIError)
def error_apierror(e):
return "错误: %s" % e
if __name__ == '__main__':
app.run(host="localhost",port=8080)
context
执行上下文:即语境,语意,在程序中可以理解为在代码执行到某一行时,根据之前代码所做的操作以及下文即将要执行的逻辑,可以决定在当前时刻下可以使用到的变量,或者可以完成的事情。
Flask中上下文对象:相当于一个容器,保存了 Flask 程序运行过程中的一些信息[变量、函数、类与对象等信息]。
Flask中有两种上下文,请求上下文(request context)和应用上下文(application context)。
- application 指的就是当你调用
app = Flask(__name__)
创建的这个对象app
; - request 指的是每次
http
请求发生时,WSGI server
(比如gunicorn)调用Flask.__call__()
之后,在Flask
对象内部创建的Request
对象; - application 表示用于响应WSGI请求的应用本身,request 表示每次http请求;
- application的生命周期大于request,一个application存活期间,可能发生多次http请求,所以,也就会有多个request
请求上下文(request context)
思考:在视图函数中,如何取到当前请求的相关数据?比如:请求地址,请求方式,cookie等等
在 flask 中,可以直接在视图函数中使用 request 这个对象进行获取相关数据,而 request 就是请求上下文的对象,保存了当前本次请求的相关数据,请求上下文对象有:request、session
- request
- 封装了HTTP请求的内容,针对的是http请求。举例:user = request.args.get('user'),获取的是get请求的参数。
- session
- 用来记录请求会话中的信息,针对的是用户信息。举例:session['name'] = user.id,可以记录用户信息。还可以通过session.get('name')获取用户信息。
请求上下文提供的变量/属性/方法/函数/类与对象,只能在视图中或者被视图调用的地方使用
应用上下文(application context)
它的字面意思是 应用上下文,但它不是一直存在的,它只是request context 中的一个对 app 的代理(人),所谓local proxy。它的作用主要是帮助 request 获取当前的应用,它是伴 request 而生,随 request 而灭的。
应用上下文对象有:current_app,g
current_app
应用程序上下文,用于存储应用程序中的变量,可以通过current_app.name打印当前app的名称,也可以在current_app中存储一些变量,例如:
- 应用的启动脚本是哪个文件,启动时指定了哪些参数
- 加载了哪些配置文件,导入了哪些配置
- 连接了哪个数据库
- 有哪些可以调用的工具类、常量
- 当前flask应用在哪个机器上,哪个IP上运行,内存多大
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变量(global)
g 作为 flask 程序全局的一个临时变量,充当者中间媒介的作用,我们可以通过它传递一些数据,g 保存的是当前请求的全局变量,不同的请求会有不同的全局变量,通过不同的thread id区别
g.name='abc' # name是举例,实际要保存什么数据到g变量中,可以根据业务而定,你可以任意的数据进去
注意:不同的请求,会有不同的全局变量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
都行。
run.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__":
manage.run()
启动终端脚本的命令:
# 端口和域名不写,默认为127.0.0.1:5000
python run.py runserver
# 通过-h设置启动域名,-p设置启动端口
python run.py runserver -h127.0.0.1 -p8888
格式:python + 文件名称 + 命令名称
Flask-Script 还可以为当前应用程序添加脚本命令
1. 引入Command命令基类
2. 创建命令类必须直接或间接继承Command,并在内部实现run方法,同时如果有自定义的其他参数,则必须实现__init__
3. 使用flask_script应用对象manage.add_command对命令类进行注册,并设置调用终端别名。
run.py
"""自定义flask_script终端命令"""
from flask_script import Command
# 1.引入命令父类,并创建命令子类
# 2.编写命令类的主方法run代码
class HelloCommand(Command):
'''命令的相关描述'''
def run(self):
'''工作中一般会在命令的run方法中对接其他程序的代码,获取自动生成文件/目录的代码'''
print("执行了hello命令")
# 3.把命令注册到manager对象中
manage.add_command("hello", HelloCommand) # 命令名称, 命令类
# 输入命令python run.py hello
# 输出数据:执行了hello命令
# 注册终端命令
manage.add_command('hello', HelloCommand())
测试命令:python run.py hello
Jinja2模板引擎
Flask内置的模板语言,它的设计思想来源于 Django 的模板引擎,并扩展了其语法和一系列强大的功能。
渲染模版函数
- Flask提供的 render_template 函数封装了该模板引擎
- render_template 函数的第一个参数是模板的文件名,后面的参数都是键值对,表示模板中变量对应的真实值。
模板基本使用
- 在flask应用对象创建的时候,设置或者保留template_folder参数,创建模板目录
app = Flask(__name__,template_folder='templates')
在项目下创建 templates
文件夹,用于存放所有的模板文件,并在目录下创建一个模板html文件 index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>{{title}}</h1>
</body>
</html>
- 在视图函数设置渲染模板并设置模板数据
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项目"
# 相当于传了 title = "我的flask项目"
return render_template("index.html",**data)
if __name__ == '__main__':
# 运行flask
manager.run()
输出变量
{{ }} 来表示变量名,一般和数据有关,这种 {{}} 语法叫做 变量代码块
{% %} 一般和逻辑有关,eg:if、for
视图代码:
@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>{{data_list}}</div>
<div>{{data_list[0]}}</div>
<div>{{data_list.0}}</div>
<div>{{data_list[-1]}}</div>
<div>{{data_dict}}</div>
<div>{{data_dict['name']}}</div>
<div>{{data_dict.name}}</div>
</body>
</html>
使用 {# #} 进行注释,注释的内容不会在html中被渲染出来
{# {{ name }} #}
模板中特有的变量和函数
你可以在自己的模板中访问一些 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>
pycharm中设置当前项目的模板语言:
files/settings/languages & frameworks/python template languages。
设置下拉框为jinja2,保存
![5953eb484ce7d77c30490eca58a8c6e7.png](https://i-blog.csdnimg.cn/blog_migrate/a12b60d412b4760b26e898e0c00f3062.png)
流程控制
主要包含两个:
- 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 循环块中你可以访问这些特殊的变量:
![cdf3fcd3bf2a58cb7369d48188974cf1.png](https://i-blog.csdnimg.cn/blog_migrate/6927f8a9abae453f32817342d07beb7d.jpeg)
- 在循环内部,你可以使用一个叫做loop的特殊变量来获得关于for循环的一些信息
- 比如:要是我们想知道当前被迭代的元素序号,并模拟Python中的enumerate函数做的事情,则可以使用loop变量的index属性,例如:
{% for post in posts%}
{{loop.index}}, {{post.title}}
{% endfor %}
- 会输出这样的结果
1, Post title
2, Second Post
- cycle函数会在每次循环的时候,返回其参数中的下一个元素,可以拿上面的例子来说明:
{% for post in posts%}
{{loop.cycle('odd','even')}} {{post.title}}
{% endfor %}
- 会输出这样的结果:
odd Post Title
even Second Post
示范代码:
视图代码:
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["book_list"] = [
{"id":10,"price":78.50,"title":"javascript入门"},
{"id":21,"price":78.50,"title":"python入门"},
{"id":33,"price":78.50,"title":"django项目实战"},
{"id":34,"price":78.50,"title":"django项目实战"},
{"id":33,"price":78.50,"title":"django项目实战"},
]
return render_template("index.html",**data)
if __name__ == '__main__':
# 运行flask
manager.run()
continue.html,模板代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{title}}</title>
</head>
<body>
<p>判断</p>
<!--{% if request.args.name %}-->
<!-- <p>欢迎回来,{{request.args.name}}</p>-->
<!--{% endif %}-->
<!--{% if request.args.name %}-->
<!-- <p>欢迎回来,{{request.args.name}}</p>-->
<!--{% else %}-->
<!-- <p>对不起,您尚未登录</p>-->
<!--{% endif %}-->
<!--{% if request.args.name=="root" %}-->
<!-- <p>欢迎回来,您是当前网站的超级管理员~</p>-->
<!--{% elif request.args.name %}-->
<!-- <p>尊敬的用户{{request.args.name}},欢迎回来</p>-->
<!--{% else %}-->
<!-- <p>对不起,您尚未登录</p>-->
<!--{% endif %}-->
<p>循环</p>
<table border="1" align="center" width="600">
<tr>
<th>序号</th>
<th>ID</th>
<th>price</th>
<th>title</th>
</tr>
{% for book in book_list %}
{% if loop.index %2 == 0 %}
<tr bgcolor="#add8e6">
{% else %}
<tr>
{% endif %}
<td>{{ loop.index }}</td>
<td>{{ book.id }}</td>
<td>{{ book.price }}</td>
<td>{{ book.title }}</td>
</tr>
{% endfor %}
</table>
</body>
</html>
过滤器
过滤器的本质就是函数。有时候我们不仅仅只是需要输出变量的值,我们还需要修改变量的显示,甚至格式化、运算等等,而在模板中是不能直接调用 Python 中的某些方法,那么这就用到了过滤器。
使用方式:
- 过滤器的使用方式为:变量名 | 过滤器。
{{variable | filter_name(args1,args2,....)}}
- 如果没有任何参数传给过滤器,则可以把括号省略掉
{{variable | filter_name }}
- 如:``,这个过滤器的作用:把变量variable 的值的首字母转换为大写,其他字母转换为小写
在 jinja2 中,过滤器是可以支持链式调用的,示例如下:
{{ "hello world" | reverse | upper }}
常见的内建过滤器
字符串操作
- safe:禁用转义
<p>{{ '<em>hello</em>' | safe }}</p>
- capitalize:把变量值的首字母转成大写,其余字母转小写
<p>{{ 'hello' | capitalize }}</p>
- lower:把值转成小写
<p>{{ 'HELLO' | lower }}</p>
- upper:把值转成大写
<p>{{ 'hello' | upper }}</p>
- title:把值中的每个单词的首字母都转成大写
<p>{{ 'hello' | title }}</p>
- reverse:字符串反转
<p>{{ 'olleh' | reverse }}</p>
- format:格式化输出
<p>{{ '%s is %d' | format('name',17) }}</p>
- striptags:渲染之前把值中所有的HTML标签都删掉
如果内容中,存在大小于号的情况,则不要使用这个过滤器,容易误删内容。
<p>{{ '<em>hello</em>' | striptags }}</p>
<p>{{ "如果x<y,z>x,那么x和z之间是否相等?" | striptags }}</p>
- truncate: 字符串截断
这个故事讲述了...
<p>{{ 'hello every one' | truncate(9)}}</p>
列表操作
- first:取第一个元素
<p>{{ [1,2,3,4,5,6] | first }}</p>
- last:取最后一个元素
<p>{{ [1,2,3,4,5,6] | last }}</p>
- length:获取列表长度
<p>{{ [1,2,3,4,5,6] | length }}</p>
- sum:列表求和
<p>{{ [1,2,3,4,5,6] | sum }}</p>
- sort:列表排序
<p>{{ [6,2,3,1,5,4] | sort }}</p>
语句块过滤
{% filter upper %}
#一大堆文字#
{% endfilter %}
自定义过滤器
过滤器的本质是函数。当模板内置的过滤器不能满足需求,可以自定义过滤器。自定义过滤器有两种实现方式:
- 一种是通过Flask应用对象的 add_template_filter 方法
- 通过装饰器来实现自定义过滤器
重要:自定义的过滤器名称如果和内置的过滤器重名,会覆盖内置的过滤器。
需求:添加列表反转的过滤器
方式一
通过调用应用程序实例的 add_template_filter 方法实现自定义过滤器。该方法第一个参数是函数名,第二个参数是自定义的过滤器名称:
以do_开头是约定俗成的
# 自定义过滤器
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.template_filter('lrev')
def do_list_reverse(old_list):
# 因为字典/列表是属于复合类型的数据,所以改动数据的结构,也会应该能影响到原来的变量
# 通过list新建一个列表进行操作,就不会影响到原来的数据
new_list = list(old_list)
new_list.reverse()
return new_list
- 主程序中创建和注册过滤器
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>
- 运行结果
['xiaoming', '小黑白', '小红']
['小红', '小黑白', 'xiaoming']
['xiaoming', '小黑白', '小红']
案例:给手机进行部分屏蔽
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.template_filter("mobile")
def do_mobile(data,string):
return data[:3]+string+data[7:]
@app.route("/")
def index():
data = {}
data["user_list"] = [
{"id":1,"name":"张三","mobile":"13112345678"},
{"id":2,"name":"张三","mobile":"13112345678"},
{"id":3,"name":"张三","mobile":"13112345678"},
{"id":4,"name":"张三","mobile":"13112345678"},
]
return render_template("index2.html",**data)
if __name__ == '__main__':
manage.run()
index2.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 user in user_list %}
<tr>
<td>{{ user.id }}</td>
<td>{{ user.name }}</td>
<td>{{ user.mobile | mobile(string="****") }}</td>
</tr>
{% endfor %}
</table>
</body>
</html>
效果:
![caa6d8772b1a04944718bb820dfd7ee3.png](https://i-blog.csdnimg.cn/blog_migrate/e9d4086f81afb08054eb6dd810f2ec50.png)
姓名隐藏案例:
张三变张* ,张三丰变张**,张三李四变张三**
run.py
@app.template_filter('name')
def do_name_filer(name):
if len(name) == 4:
return name[:2] + "**"
return name[:1] + '*' * (len(name) - 1)
@app.route(rule='/name')
def name():
data = {}
data["user_list"] = [
{"id": 1, "name": "张三风", "mobile": "13112345678"},
{"id": 2, "name": "张三啊", "mobile": "13112345678"},
{"id": 3, "name": "张三李四", "mobile": "13112345678"},
{"id": 4, "name": "张三", "mobile": "13112345678"},
]
return render_template("index4.html", **data)
templates/index4.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<table>
<tr>
<th>序号</th>
<th>id</th>
<th>名字</th>
<th>手机号</th>
</tr>
{% for user in user_list %}
{% if loop.index %2 ==0 %}
<tr bgcolor="aqua">
{% else %}
<tr>
{% endif %}
<td>{{ loop.index }}</td>
<td>{{ user.id }}</td>
<td>{{ user.name | name}}</td>
<td>{{ user.mobile}}</td>
</tr>
{% endfor %}
</table>
</body>
</html>
效果:
![ac1cea15a14649af9b08d3b46e808ad2.png](https://i-blog.csdnimg.cn/blog_migrate/68eb79cd3af89b395ced9098fb04ec77.png)
模板继承
![01c61b7c1b566c517c30181182c61158.png](https://i-blog.csdnimg.cn/blog_migrate/c18064b20219b9fd86b053f67d34bcc3.jpeg)
在项目中,可能会遇到以下情况:
- 多个模板具有完全相同的顶部和底部内容
- 多个模板中具有相同的模板代码内容,但是内容中部分值不一样
- 多个模板中具有完全相同的 html 代码块内容
像遇到这种情况,可以使用 JinJa2 模板中的 继承 来进行实现
模板继承是为了重用模板中的公共内容。一般Web开发中,继承主要使用在网站的顶部菜单、底部菜单版权信息,或弹出窗口。这些内容可以定义在父模板中,子模板直接继承,而不需要重复书写。
- 标签定义的内容
{% block top %} {% endblock top %}
- 相当于在父模板中挖个坑,当子模板继承父模板时,可以进行填充。
- 子模板使用 extends 指令声明这个模板继承自哪个模板
- 父模板中定义的块在子模板中被重新定义,在子模板中调用父模板的内容可以使用super()
父模板代码:
base.html
{% block top %}
顶部菜单
{% endblock top %}
{% block content %}
{% endblock content %}
{% block bottom %}
底部
{% endblock bottom %}
子模板代码:
- extends指令声明这个模板继承自哪
{% extends 'base.html' %}
{% block content %}
需要填充的内容
{% endblock content %}
模板继承使用时注意点:
- 不支持多继承,一个子模板只能使用一次extends继承父模板
- 为了便于阅读,在子模板中使用extends时,尽量写在模板的第一行。
- 不能在一个模板文件中定义多个相同名字的block标签。重复则报错。
- 当在页面中使用多个block标签时,建议给结束标签起个名字,当多个block嵌套时,阅读性更好。
测试代码:
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
app.config.from_object(Config)
@app.route(rule='/')
def index():
data={}
return render_template("index2.html",**data)
if __name__ == '__main__':
# 运行flask
manager.run()
templates/base.html,代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
{% block style %}{% endblock %}
{% block script %}{% endblock %}
</head>
<body>
{% block head %}
<div class="header">
<h1>页面头部</h1>
</div>
{% endblock %}
{% block main %}
<div>页面主体内容</div>
{% block left %}
<div>页面主体左边内容</div>
{% endblock left %}
{% block right %}
<div>页面主体右边内容</div>
{% endblock right %}
{% endblock main %}
{% block footer %}
<div class="footer">
<h1>页面脚部</h1>
</div>
{% endblock footer %}
{% block script2 %}{% endblock script2 %}
</body>
</html>
templates/index2.html,代码;
{% extends "base.html" %}
{% block style %}
<style>
body{
background: #aabbcc;
}
</style>
{% endblock %}
{% block head %}
<div>当前index2.html编写的头部内容</div>
{% endblock %}
{% block right %}
<div>当前index2.html编写的主体右边内容</div>
{% endblock %}
模板继承进阶用法
模板继承还提供了同一模板相互调用的操作和在继承父级模板时继承父模板内容的操作
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
app.config.from_object(Config)
@app.route(rule='/')
def index():
data={}
return render_template("index3.html",**data)
if __name__ == '__main__':
# 运行flask
manager.run()
templates/base2.html,代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% block header %}
<div>头部内容</div>
{% block nav %}
<div>导航菜单内容</div>
{% endblock %}
{% endblock %}
{% block main %}
<div>base里面编写的公共主体内容</div>
{% endblock %}
{% block footer %}
<div>脚部内容</div>
{# 调用当前模板中的其他block板块 #}
{{ self.nav() }}
{% endblock %}
</body>
</html>
templates/index3.html,代码:
{% extends "base2.html" %}
{% block main %}
<div>index3.html编写的主体内容</div>
{# 重写父模板内容时,如果希望继承原来父模板中的block模块,可以使用supper() #}
{{ super() }}
{% endblock %}
除了模板继承以外,在flask中还提供了include标签给我们进行模板分离使用的。
include加载子模板
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={}
return render_template("index4.html",**data)
if __name__ == '__main__':
# 运行flask
manager.run()
templates/index4.html,代码:
{% include "header.html" %}
<div>index4.html的主体内容</div>
{% include "footer.html" %}
templates/header.html,代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>导航菜单</div>
templates/footer.html,代码:
<div>脚部内容</div>
</body>
</html>
模板宏
所谓的宏,就是模板中预先写好的一段类似函数的代码。这段代码名称叫[macro]。也就是宏。
在开发中经常出现一些html结构一样,但是样式属性可能不一样的代码部分,这时候可以预设一些模板宏,来进行复用模板代码。
模板宏需要通过{% macro 函数名(参数1,参数2….) %}
和 {% endmacro %}
进行声明。
{% macro func(text, name,type="text") %}
<div>
{{ text }}: <input type="{{ type }}" name="{{ name }}" />
</div>
{% endmacro %}
模板宏的调用需要使用import “模板宏所在的文件名.html as 别名变量”
进行导入宏,通过{{ 别名变量.函数名() }}
进行调用。
{% import "macro.html" as macro %}
{{ macro.func("用户名","username") }}
{{ macro.func("密码","password","password") }}
测试代码:
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
app.config.from_object(Config)
@app.route(rule='/')
def index():
data={}
return render_template("index5.html",**data)
if __name__ == '__main__':
# 运行flask
manager.run()
templates/macro.html,代码:
{% macro func(text, name,type="text") %}
<div>
{{ text }}: <input type="{{ type }}" name="{{ name }}" />
</div>
{% endmacro %}
templates/index5.html,代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% import "macro.html" as macro %}
{{ macro.func("用户名","username") }}
{{ macro.func("密码","password","password") }}
</body>
</html>
在 Flask 项目中防范 CSRF 攻击
![fbe17e755696eec53a899375286ff196.png](https://i-blog.csdnimg.cn/blog_migrate/2d1eeb5b66eaa5d1fce63f14ebd018ad.png)
pip install flask_wtf
在 Flask 中, Flask-wtf 扩展有一套完善的 csrf 防护体系,对于我们开发者来说,使用起来非常简单
- 设置应用程序的 secret_key,用于加密生成的 csrf_token 的值
# 1. session加密的时候已经配置过了.如果没有在配置项中设置,则如下:
app.secret_key = "#此处可以写随机字符串#"
# 2. 也可以写在配置类中。
class Config(object):
DEBUG = True
SECRET_KEY = "dsad32DASSLD*13%^32"
"""加载配置"""
app.config.from_object(Config)
- 导入 flask_wtf.csrf 中的 CSRFProtect 类,进行初始化,并在初始化的时候关联 app
from flask.ext.wtf import CSRFProtect
CSRFProtect(app)
- 在表单中使用 CSRF 令牌:
<form method="post" action="/">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
</form>
视图代码;
from flask import Flask, render_template,request
# 初始化
app = Flask(import_name=__name__,template_folder='templates')
# 配置终端脚本运行项目
from flask_script import Manager
manager = Manager(app)
# 声明和加载配置
class Config():
DEBUG = True
# 使用csrf防范机制,必须设置秘钥
SECRET_KEY = "3fdsaf43ssa232"
app.config.from_object(Config)
# 注册csrf攻击防范模块到app应用中
from flask.ext.wtf import CSRFProtect
CSRFProtect(app)
@app.route(rule='/')
def index():
data={}
return render_template("index6.html",**data)
@app.route("/login",methods=["post"])
def login():
print( request.form )
return "ok"
if __name__ == '__main__':
# 运行flask
manager.run()
templates/index6.html,代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/login" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
用户名:<input type="text" name="username"><br><br>
密码:<input type="password"><br><br>
<input type="submit" value="注册">
</form>
</body>
</html>