蘑菇APP开发前置二
1.请求钩子
在客户端和服务器交互的过程中,我们有些工作总是需要在一开始或者结束的时候进行,例如:
1.请求开始的时候建立数据库连接
2.请求开始时根据需求进行权限校验
3.请求结束的时指定数据的交互格式
为了避免写重复功能,flask提供了四种请求钩子供我们使用
before_first_request处理第一个请求前执行(初始化钩子)
before_request每次请求前执行,如果返回了一个响应,将不再有效
after_request如果没抛出错误,每次请求后执行,接受视图函数做出的相应作为参数,再次函数中可以对响应值返回之前做最后一步处理,必须返回响应值response
teardown_request每次请求后执行,接受错误信息,需要设置flask的配置DEBUG=False,teardown_request才会接受到异常对象
... @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")
2.异常捕获
通过abort来主动抛出异常 abort(404)但是使用较少(其实压根没见过)主要还是介绍捕获错误
捕获错误
errorhandler装饰器
@app.errorhandler(500) def internal_server_error(e): return '想找也找不到了' #捕获指定异常类型 @app.errorhandler(ZeroDivisionError) def zero_division_error(e): return '除数不能为0' """ flask中内置了app.errorhander提供给我们捕获异常,实现一些在业务发生错误时的自定义处理。 1. 通过http状态码捕获异常信息 2. 通过异常类进行异常捕获 """ """1. 捕获http异常[在装饰器中写上要捕获的异常状态码也可以是异常类]""" @app.errorhandler(404) def error_404(e): return "<h1>您访问的页面失联了!</h1>" # return redirect("/") """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)
执行上下文:即语境,语意,在程序中可以理解为在代码执行到某一行时,根据之前代码所做的操作以及下文即将要执行的逻辑,可以决定在当前时刻下可以使用到的变量,或者可以完成的事情。
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)
我们在使用request.方法的时候是如何火的请求的相关数据的呢?例如请求方式,请求地址等等
request
-
封装了HTTP请求的内容,针对的是http请求。举例:user = request.args.get('user'),获取的是get请求的参数。
session
-
用来记录请求会话中的信息,针对的是用户信息。举例:session['name'] = user.id,可以记录用户信息。还可以通过session.get('name')获取用户信息。
请求上下文提供的变量/属性/方法/函数/类与对象,只能在视图中或者被视图调用的地方使用
应用上下文(application context)
application并不是一直存在的,虽然广义上来讲application要相较于request存在的时间更久,但是实际上application context 是伴随着 request context存在而存在 消亡而消亡的。
应用上下文的对象有:current_app,g
current_app:用于存储应用中的变量
g变量作为flask程序全局的一个临时变量,充当媒介作用,来传递数据,不同的请求会有不同的全局变量g
所以总而言之请求上下文:保存了客户端和服务器交互的数据,一般来自于客户端。应用上下文:flask 应用程序运行过程中,保存的一些配置信息,比如路由列表,程序名、数据库连接、应用信息等
4.flask_script扩展
安装
pip install flask_script
集成flask_script到flask应用中可以是我们在运行文件之外像是调用django中manage那样通过命令来运行一些脚本,在之后的数据库迁移中我们将接触到其使用,现在我们来介绍一下简单的注册
from flask_script import Manager manage = Manager(app) #通过这种方式来进行注册 让我们可以运行脚本一样去运行flask
5.jinja2模板引擎
flask内置的模板语言,设计思想来自于django的模板引擎,Flask提供的 render_template 函数封装了该模板引擎,render_template 函数的第一个参数是模板的文件名,后面的参数都是键值对,表示模板中变量对应的真实值。
使用
app = Flask(__name__,template_folder='templates')
在视图函数中渲染数据(你已经设置好了一个模板,这个还要我来教???)
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()
接下来我们来介绍一下模板语法
输出变量
{{}} 来表示变量名,这种 {{}} 语法叫做 变量代码块
Jinja2 模版中的变量代码块可以是任意 Python 类型或者对象,只要它能够被 Python 的 __str__
方法或者str()转换为一个字符串就可以
模板中特有的变量和函数
config :可以从末班中直接访问flask当前的config对象
{{config.SQLALCHEMY_DATABASE_URI}} #获取flask中相关配置
request:代表着当前请求的request对象
{{request.url}} #request 对象通过点方法调用属性
session:flask的session对象,显示session数据
{{session.new}} #session对象 通过点属性名调用相关属性
g:flask中的g变量
{{g.name}} #g变量
url_for():重定向会根据传入的路由器函数名。返回该路由对应的url,在模板中始终使用url_for()就可以安全的修改路由绑定的url
{{url_for('home')}} #无参数 {{url_for('home',user_id=1)}} #有参数
流程控制
Jinja2 语法中的if语句跟 Python 中的 if 语句相似,后面的布尔值或返回布尔值的表达式将决定代码中的哪个流程会被执行.
用 {%%} 定义的控制代码块,可以实现一些语言层次的功能,比如循环或者if语句
if: {# 判断一个参数是否是奇数 #} {% if name % 2 == 0 %} 偶数<br> {% else %} 奇数<br> {% endif %}
for: {# for循环 #}
{% for book in book_list %}
<tr>
<td>{{ book.id }}</td>
<td>{{ book.title }}</td>
<td>{{ book.price }}</td>
</tr>
{% endfor %}
过滤器:
过滤器本质上就是函数,有时候我们需要在一堆数据中挑选出一些特殊的数据,这是我们需要过滤器发挥它的作用
过滤器的使用:变量名|过滤器 {{variable | filter_name(args1,args2,....)}} 如果没有参数传递()可以省略 {{variable | filter_name }} 例如 {{ "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:格式化输出 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 %}
自定义过滤器
当内置过滤器不能满足我们的要求的时候我们可以自己设置,因为过滤器本质上就是函数
我们有两种方式来自定义过滤器
1.通过flask中的add_template_filter方法
2.装饰器
注意:如果我们自定义的过滤器和内置过滤器重名的话会覆盖
方式一 def do_list_reverse(old_list): new_list = list(old_list) new_list.reverse() return new_list app.add_template_filter(do_list_reverse,"rev") #注册过滤器 方式二 @app.template_filter('lrev') def do_list_reverse(old_list): # 因为字典/列表是属于复合类型的数据,所以改动数据的结构,也会应该能影响到原来的变量 # 通过list新建一个列表进行操作,就不会影响到原来的数据 new_list = list(old_list) new_list.reverse() return new_list
模板继承
在项目中,可能会遇到以下情况:
-
多个模板具有完全相同的顶部和底部内容
-
多个模板中具有相同的模板代码内容,但是内容中部分值不一样
-
多个模板中具有完全相同的 html 代码块内容
像遇到这种情况,可以使用 JinJa2 模板中的 继承 来进行实现
模板继承是为了重用模板中的公共内容。一般Web开发中,继承主要使用在网站的顶部菜单、底部菜单版权信息,或弹出窗口。这些内容可以定义在父模板中,子模板直接继承,而不需要重复书写。
标签定义的内容{% block top %} {% endblock %}
{% block top %} {% endblock %}
-
相当于在父模板中挖个坑,当子模板继承父模板时,可以进行填充。
-
子模板使用 extends 指令声明这个模板继承自哪个模板
-
父模板中定义的块在子模板中被重新定义,在子模板中调用父模板的内容可以使用super()
模板继承使用时注意点:
-
不支持多继承,一个子模板只能使用一次extends继承父模板
-
为了便于阅读,在子模板中使用extends时,尽量写在模板的第一行。
-
不能在一个模板文件中定义多个相同名字的block标签。重复则报错。
-
当在页面中使用多个block标签时,建议给结束标签起个名字,当多个block嵌套时,阅读性更好。
{% 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 %}
继承进阶用法
模板继承还提供了同一模板相互调用的操作和在继承父级模板时继承父模板内容的操作
父模板 <!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> 子模板 {% extends "base2.html" %} {% block main %} <div>index3.html编写的主体内容</div> {# 重写父模板内容时,如果希望继承原来父模板中的block模块,可以使用supper() #} {{ super() }} {% endblock %}
{% include "header.html" %} <div>index4.html的主体内容</div> {% include "footer.html" %}
模板宏
所谓的宏就是在模板中预先写好的一段类似函数的代码,用于一些内容上可能相似度不高但是结构上是大体相似的
模板 {% macro func(text, name,type="text") %} <div> {{ text }}: <input type="{{ type }}" name="{{ name }}" /> </div> {% endmacro %}
{% import "macro.html" as macro %} {{ macro.func("用户名","username") }} {{ macro.func("密码","password","password") }}
在 Flask 项目中防范 CSRF 攻击
在 Flask 中, Flask-wtf 扩展有一套完善的 csrf 防护体系,对于我们开发者来说,使用起来非常简单
pip install flask_wtf #安装 # 1. session加密的时候已经配置过了.如果没有在配置项中设置,则如下: app.secret_key = "#此处可以写随机字符串#" # 2. 也可以写在配置类中。 class Config(object): DEBUG = True SECRET_KEY = "dsad32DASSLD*13%^32" from flask.ext.wtf import CSRFProtect #导包 CSRFProtect(app) #注册 使用令牌 <form method="post" action="/"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" /> </form>