今天了解Flask框架的: 获取的请求数据,异常捕获,请求钩子,状态保持,上下文,Flast-script,以及模板的使用
获取请求数据
- 通过使用request.<属性>的方式,获取request对象传过来的数据
常用的属性如下:
- method ,记录请求使用的HTTP方法,类型为:GET/POST
- heafers,记录请求中的报文头,类型为:EnvironHeaders
- url,记录请求的URL地址,类型为:string
- cookies,记录请求中的cookies信息,类型为:Dict
- args,记录请求中的查询参数,类型为:MultiDict
- form,记录请求中的表单数据,类型为:MultiDict
- data,记录请求的数据,并且转换为字符串
- files,记录请求上传的文件
- 调用方法:直接使用request.<属性>即可
print 'method: %s' % request.method print 'headers: %s' % request.headers print 'url: %s' % request.url print 'cookies: %s' % request.cookies # args: name=XXX form:name=XXX data=XXX file=图片 print 'args: %s' % request.args.get('name') print 'form: %s' % request.form.get('name') print 'data: %s' % request.data print 'files: %s' % request.files # 保存图片到当前目录 # 需要设置当前项目的工作目录, 这样传入文件路径时, '.'就可以表示当前项目根目录 image_file = request.files.get('image') image_file.save('./image.jpg')
异常获取(理解)
- 使用 errorhandler装饰器捕获希望捕获的异常信息。
- 捕获到异常时,可对异常进行进一步的处理,自定义渲染过的404异常或者500异常页面并且返还给浏览器
abort方法
- 可以抛出一个给定状态代码的异常
- 如:
abort(404) # 抛出一个404异常
errorhandler装饰器
- 注册一个指定异常,当该异常发生时,执行errorhandler装饰器装饰的函数
- 如:
# 如果有404异常抛出,捕获到,并且执行关联的函数
@app.errorhandler(404)
def internal_server_error(error) # 参数是捕获到的异常
return '网页找不到了', 404
请求钩子(理解)
- 类似django中的中间键,在请求的前后执行额外的任务
- Flask支持如下4种请求钩子
- befor_first_request:在处理第一个请求之前运行,再次发起相同的的请求就不在执行
- before_request:在每次请求之前运行
- after_request:在每次请求后运行,前提是没有未处理的异常就抛出
- teardown_request:在每次请求后运行
@app.route('/')
def hello_world():
print '这里是视图主逻辑代码'
return 'hello world'
# before_first_request:在处理第一个请求之前运行,如:连接数据库操作
@app.before_first_request
def before_first_request():
print 'before_first_request'
# before_request:在每次请求前运行,如:对数据进行校验的操作,如果有问题,直接返回,不用再执行匹配的视图函数
@app.before_request
def before_request():
print 'before_request'
# after_request:在每次请求结束后执行(没有抛出异常),如:去拼接响应头信息,让所有的json数据,统一增加Content-Type:application/json属性
@app.after_request
def after_request(response):
print 'after_request'
response.headers['Content-Type'] = 'application/json'
return response
# teardown_request:在每次请求结束后执行(即使抛出异常),可以捕获到相应的异常
@app.teardown_request
def teardown_request(error): # 参数为捕获到的异常信息
print 'tearddown_request %s'% error
执行结果:
状态保持
- 让浏览器的请求状态可以保持,不用每次都重新输入数据
- 为什么需要状态保持,因为http是一种无状态协议,不会保持某一次请求所产生的信息,即没有记忆能力,每一次请求都是独立的。但是需要保持请求的状态信息。
- 状态保持的方式:
- cookie:数据存储在客户端,节省服务器空间,缺点是不安全
- session:会话,数据存储在服务器上
设置cookie
- 需要导入make_response方法,将return返回的数据当做参数传给这个方法并且使用set_cookie方法进行cookie的设置
from flask imoprt Flask,make_response
@app.route('/cookie')
def set_cookie():
resp = make_response('this is to set cookie')
resp.set_cookie('username', 'itcast')
return resp
获取cookie
- 导入request对象,使用request.cookies.get()方法进行获取
from flask import Flask,request
#获取cookie
@app.route('/request')
def resp_cookie():
resp = request.cookies.get('username')
return resp
设置session
- flask的session与其他框架不同的的一点是,它的session是完全保留在客户端浏览器的
- 往flask的session中写入数据,最终会以json字符串的形式,经过base64编码写入到用户浏览器的cookieli里
为什么要这样做?这样不是不安全吗?
- flask的最大特点就是轻巧灵活,这样做更符合作者和flask的气质
- session存储前是需要加盐混淆代码,并且进行加密处理的,保证了session在客户端的安全
- 考虑到负载均衡的问题,如使用Nginx进行负载均衡,将session存储在服务器端的架构,每次请求,nginx路由的服务器会不同,每次当需要使用session信息时,服务器还需要从统一的redis等数据库获取session信息。而flask处理的方式是直接将整个session的完整信息直接和cookies一起传送过来,不论路由到那一台服务器,都可以直接调用session数据,更为方便
- 其实,实际项目中,还是需要将session数据转移到redis中的,Flask-sesssion拓展中可以实现
设置步骤
- 设置secret-key
- 导入session对象
- 使用 session[‘key’] = ‘value’的形式进行设置
读取
- 使用 session[‘key’]进行读取操作
# 设置session前需要设置secret_key,session数据会加密保存在浏览器cookie中
app.secret_key = 'michael'# "michael"为加的盐值,是做内容混淆用的,不能告诉别人
# 设置session
@app.route('/set_session')
def set_session():
session['user_name']= "michael"
return redirect(url_for("read_session"))
# 读取session
@app.route('/read_session')
def read_session():
user_name = session['user_name']
return "user_name:%s"%user_name
上下文(理解)
- 上下文相当于一个容器,保存了Flask程序运行过程中的一些信息
- 有两种上下文:请求上下文和应用上下文
请求上下文
- 能满足请求过程中的信息调用和设置的对象
- 有request对象和session对象
request对象
- 封装了Http请求的内容,针对的时http请求,如,user = request.args.get(‘user’),获取的是get请求时的参数
- 当 app = Flask(name)之后,创建了应用对象app,在每次http请求发生时,WSGI-server调用Flask.call(),在Flask的内部创建了request对象
- app对象的生命周期大于request对象,一个app存活期间,可能发生多次request请求
- 最终通过return,redirect,或者render_template生成response对象。返回给客户端
session对象
- 记录了请求会话中的信息,针对的时用户信息。如,session[‘name’]=user.id,可以记录用户信息。还可以通过session.get(‘name’)来获取用户信息
应用上下文
- 是请求上下文中的一个对app的代理(local proxy)
- 主要作用是帮助request获取当前的应用
- 它是与request同生共灭的
- 包括 current_app 和 g 变量
current_app
- 一种应用程序上下文,用来存储应用程序中的变量
- 如
current_app.name
可以当存储在current_app中的name值 - 可以存储的信息有:
- 应用的启动脚本是哪个文件,启动时指定了那些参数
- 加载了哪些配置信息,导入了哪些配置
- 连接了哪个数据库
- 有哪些public的工具类,常量
- 应用跑在哪个机器上,IP多少,内存大
g变量
- 是应用上下文中一个临时的全局变量
- 可以在一下请求中,在不同的函数(如钩子函数和url匹配的视图函数)之间传递值
@app.route('/')
def hello_world():
print "这是主逻辑代码 %s"%g
return '{"code":0,"msg":"执行ok"}'
@app.before_first_request
def before_first_request():
# g变量可以在一次请求里的多个函数之间传递值
g.name = "xiaoming"
print "before_first_request"
@app.before_request
def before_request():
# request.args.get('xxx'),可以获取request里的数据
print "before_request %s" %g.name
执行结果
请求上下文和应用上下文的区别
- 请求上下文:保存了客户端和服务器交互的数据
- 应用上下文:在应用程序运行的过程中,保存的一些配置信息
Flask-script
- 能够让Flask服务器通过命令行的方式启动的拓展模块
- 实现步骤
- 从flask_script中导入Manager类
- 将app作为参数传给Manage方法,完成Manager类与app的关联
- 运行的时候使用:manager.run()命令
from flask import Flask
from flask_script import Manager
app = Flask(__name__)
# 把 Manager 类和应用程序实例进行关联
manager = Manager(app)
@app.route('/')
def index():
return '床前明月光'
if __name__ == "__main__":
manager.run()
命令行传参
- 可以在命令行中给Flask应用传参,通过
python hello.py runserver --help
来查看可供设置的参数
模板使用
- 模板是一个包含响应文本的文件。
- Flask是使用jinja2引擎来渲染模板,使用模板语言,动态的设置模板文件中的内容
- jinja2:是python下被广泛应用的模板引擎,是python实现的模板语言,是Flask内置的模板语言。
- 模板语言:是一种被设计来自动生成文档的简单文本格式。在模板语言中,一般都会把一些变量传给模板,替换模板的特定位置上预先定义好的占位比变量名。如变量代码块:{{‘这里是占位的变量名’}} ;控制代码块:{%‘这里是一些控制语句’%}
渲染模板
- 使用 render_template 函数进行渲染
- render_template函数的第一个参数是模板的文件名,后面的参数都是键值对,表示模板中变量对应的真实值
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def hello_world():
my_list = [1,2,3,4,5,6]
my_dict = {
'name':"michael",
'age':18
}
my_dict_list = [
{'name':'michael',"age":18},
{'name':'jackson',"age":10}
return render_template("demo_template.html", my_dict=my_dict,
my_list=my_list, my_dict_list=my_dict_list)
过滤器
- 过滤器本质就是函数
- 需求:有时候不仅仅是想输出变量的值,还想改变量的显示,甚至是格式化,运算等等。此时需要用到过滤器。
格式:变量名|过滤器
{{variable | filter_name(*args)}}
- 没有参数可以写成:
{{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:格式化输出
<p>{{ '%s is %d' | format('name',17) }}</p>
- striptags:渲染之前把值中所有的HTML标签都删掉
<p>{{ '<em>hello</em>' | 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 %}
自定义过滤器
- 有时候,系统内置的过滤器不能满足需求,就需要自定义过滤器
方法:
- 通过add_template_filter方法
- 通过add_template_filter装饰器(推荐使用)
方式一
通过调用应用程序实例的add_template_filter方法实现自定义过滤器。该方法第一个参数是函数名,第二个参数是自定义的过滤器名称:
def do_list_reverse(list):
list.reverse()
return list
app.add_template_filter(do_list_reverse, 'lsreverse')
- 方式二
用装饰器来实现自定义过滤器。装饰器传入的参数是自定义的过滤器名称。
@app.template_filter('lsreverse')
def do_list_reverse(list):
list.reverse()
return list
控制代码块
- 模板语言的一种用法,用来执行一些控制功能。如遍历,判断等
- 常用的有{% for x in xx %}{%endfor%}和{%if x ==2 %}{% endif %},也可以配合使用:
{% for num in my_list if num > 3 %}
{{ num }} <br>
{% endfor %}
- 在for 循环中可以访问一些特殊变量,如:loop.index(当前迭代的的次数);loop.cycle(循环某几个参数值)
比如:要是我们想知道当前被迭代的元素序号,则可以使用loop变量的index属性,例如
{% for num in my_list %}
{{ loop.index }} -
{% if num > 3 %}
{{ num }}
{% endif %} <br>
{% endfor %}
假设my_list=[1, 3, 5, 7, 9]会输出这样的结果
1-
2-
3-5
4-7
5-9
cycle函数会在每次循环的时候,返回其参数中的下一个元素,可以拿上面的例子来说明:
{% for num in my_list %}
{{ loop.cycle('a', 'b') }} -
{{ num }} <br>
{% endfor %}
会输出这样的结果:
a-
b-
a-5
b-7
a-9
- for循环中其他的特殊变量
- loop.index0 当前循环迭代的次数(从 0 开始)
- loop.revindex 到循环结束需要迭代的次数(从 1 开始)
- loop.revindex0 到循环结束需要迭代的次数(从 0 开始)
- loop.first 如果是第一次迭代,为 True 。
- loop.last 如果是最后一次迭代,为 True 。
- loop.length 序列中的项目数。
模板代码的复用
- 有时候模板中有许多重复代码,为了精简代码,提高代码的重用性,就需要对多次重复使用的模板代码进行复用设置。
- 复用模板的方式有三种:1.继承 2.包含 3.宏
继承
在父模板里挖好坑,子模板继承,填坑
父模板:base.html
{% block top %}
<h1>这是头部内容</h1>
{% endblock %}
{% block center %}
这是父类的中间的内容
{% endblock %}
{% block bottom %}
<h1>这是底部内容</h1>
{% endblock %}
- 子模板:extends 指令声明继承自拿个父模板
{% extends 'base.html' %}
{% block content %}
{{ super() }} <br>
需要填充的内容 <br>
{% endblock content %}
- 注意点:
- 不支持多继承
- 为了便于阅读,在子模板中使用extends时,尽量写在模板的第一行。
- 不能在一个模板文件中定义多个相同名字的block标签。
- 当在页面中使用多个block标签时,建议给结束标签起个名字,当多个block嵌套时,阅读性更好。
包含
- 将另一个模板整个加载到当前模板中
- 使用include指令
{% include 'hello.html' %}
- 注意点:
- 如果包含的模板文件不存在时,程序会抛出TemplateNotFound异常,可以加上 ignore missing 关键字。如果包含的模板文件不存在,会忽略这条include语句。
{% include 'hello.html' ignore missing %}
宏
- 可以理解成给模板代码抽出函数,需要时调用传参
- 使用 macro 指令
- 一个表单的书写,比较麻烦
<form>
<label>用户名:</label><input type="text" name="username"><br>
<label>昵称:</label><input type="text" name="nickname"><br>
<label>身份证:</label><input type="text" name="idcard"><br>
<label>密码:</label><input type="password" name="password"><br>
<label>确认密码:</label><input type="password" name="password2"><br>
<input type="submit" value="提交">
</form>
- 简单点,定义宏
{% macro input(label="", type="text", name="", value="") %}
<label>{{ label }}</label><input type="{{ type }}" name="{{ name }}" value="{{ value }}"><br>
{% endmacro %}
- 将定义的多个宏单独放在一个文件里,如
macro.html
方便复用 - 调用宏:
{% import 'macro.html' as func%}
<form>
{{ func.input("用户名:", name="username") }}
{{ func.input("昵称::", name="nickname") }}
{{ func.input("身份证:", name="idcard") }}
{{ func.input("密码:", type="password", name="password") }}
{{ func.input("确认密码:", type="password", name="password2") }}
{{ func.input(type="submit", value="提交") }}
</form>
总结
继承(Block)、包含(include)、宏(Macro)均能实现代码的复用。
- 继承(Block)的本质是代码替换,一般用来实现多个页面中重复不变的区域。
- 包含(include)是直接将目标模板文件整个渲染出来。
- 宏(Macro)的功能类似函数,可以传入参数,需要定义、调用。