Flask(2)- 装饰器的坑及解决办法、flask中的路由/实例化配置/对象配置/蓝图/特殊装饰器(中间件、重定义错误页面)
一、装饰器的坑以及解决方法
1、使用装饰器装饰两个视图函数,代码如下
from flask import Flask, redirect, render_template, request, session
app = Flask(__name__)
app.secret_key = "wanglili" # 装饰器函数 def outer(func): def inner(*args, **kwargs): if session.get("user"): # 验证session ret = func(*args, **kwargs) return ret else: return redirect("/login") return inner @app.route("/") @outer # inner=outer(index) def index(): return render_template("index.html") @app.route("/course") @outer def course(): return render_template("course.html") @app.route("/login", methods=["POST", "GET"]) def login(): if request.method == "GET": return render_template("login.html") if request.form.get("username") == "wll"and request.form.get("password") == "123": session["user"] = request.form.get("username") # 写入session return redirect("/") return render_template("login.html") app.run(debug=True)
启动程序有如下错误:
我们还发现当装饰一个视图函数时可以正常运行,而装饰两个或两个以上视图函数则会报以上错误。
2、解决方式
1)方式一:使用functools模块
import functools def outer(func): @functools.wraps(func) # 保留原函数func的信息 def inner(*args, **kwargs): if session.get("user"): ret = func(*args, **kwargs) return ret else: return redirect("/login") return inner
2)方式二:使用flask提供的endpoint参数
@app.route("/", endpoint='index') @outer def index(): return render_template("index.html") @app.route("/course", endpoint='course') @outer def course(): return render_template("course.html")
二、flask中的路由
Flask中的路由系统我们已经见过了,并且一直在用,接下来学习几个有关路由的使用方法。
1、@app.route()装饰器中的参数
1)methods : 当前 url 地址,允许访问的请求方式,示例代码如下
@app.route("/info", methods=["GET", "POST"]) def stu_info(): return "student info"
注意:不写methods参数时默认是只允许GET请求,写methods参数时要指定所有希望的允许的请求方式,默认方式会被methods覆盖。
2)endpoint:反向url地址,默认为视图函数名,url_for利用它进行反向解析,示例代码如下
from flask import Flask, url_for
@app.route("/info", methods=["GET", "POST"], endpoint='s_info') def stu_info(): print(url_for('s_info')) # /info return "student info"
注意:endpoint默认为视图函数名一致,不写endpoint参数则使用视图函数名反向解析,即url_for('stu_info'),有则使用endpoint定义的名称进行反向解析。
3)defaults:视图函数的参数默认值,代码如下
@app.route("/info", methods=["GET", "POST"], defaults={"nid": 100}) def stu_info(nid): print(nid) # 100 return "student info"
注意:视图函数一定要接收这个参数,且形参名称一定要与defaults参数中的key一致。
4)strict_slashes:url地址结尾符"/"的控制(False : 无论结尾"/"是否存在均可以访问 , True : 结尾必须不能是"/",即与定义的路由完全一致),示例代码如下
# 只能访问 /info @app.route("/info", methods=["GET", "POST"], strict_slashes=True) def stu_info(): return "student info" # 可以访问 /info 或者 /info/ @app.route("/info", methods=["GET", "POST"], strict_slashes=False) def stu_info(): return "student info"
5)redirect_to:url地址永久重定向(对应状态码301)
# 访问地址 : /info 浏览器跳转至 /infos @app.route("/info", redirect_to="/infos") def student_info(): return "Hello info" @app.route("/infos") def student_infos(): return "Hello infos"
注意:在跳转之前进行重定向,原url对应的视图函数没有执行,立即跳转到新的url。且使用一次后对浏览器会有缓存。
6)subdomain:子域名前缀 subdomian="car" 这样写可以得到 car.oldboy.com 前提是app.config["SERVER_NAME"] = "oldboy.com"
app.config["SERVER_NAME"] = "oldboy.com" @app.route("/info",subdomain="car") def student_info(): return "Hello info"
# 访问地址为: car.oldboy.com/info
2、动态路由参数
from flask import Flask, url_for
# 访问地址 : http://127.0.0.1:5000/info/1 可以 # 访问地址:http://127.0.0.1:5000/info/e 报错 @app.route("/info/<int:nid>", methods=["GET", "POST"], endpoint="s_info") def student_info(nid): print(nid, type(nid)) # 1 <class 'int'> print(url_for("s_info", nid=nid)) # /info/1 print(url_for("s_info", nid=2)) # /info/2 return "Hello info" # 访问地址 : http://127.0.0.1:5000/info/1 可以 # 访问地址 : http://127.0.0.1:5000/info/e 可以 @app.route("/info/<string:nid>", methods=["GET", "POST"], endpoint="s_info") def student_info(nid): print(nid, type(nid)) # 1 <class 'str'> return "Hello info" # 访问地址 : http://127.0.0.1:5000/info/1 可以 # 访问地址 : http://127.0.0.1:5000/info/e 可以 @app.route("/info/<nid>", methods=["GET", "POST"], endpoint="s_info") def student_info(nid): print(nid, type(nid)) # 1 <class 'str'> return "Hello info"
通过以上示例发现,<int:nid>就是在url后定义一个参数接收url中的参数,通过演示,还发现,当定义int类型时只能传数字,当定义string类型时可以接收数字和字符串,当不定义类型时,默认是string类型。
注意:这种动态参数路由,在url_for的时候,一定要将动态参数名+参数值添加进去,否则会抛出参数错误的异常。
三、flask实例化配置
app = Flask(__name__) # Flask的实例化对象app
实例化对象时的参数如下:
template_folder="temp" 默认模板存放目录 templates static_folder="static" 默认静态文件存放目录 static static_url_path="/static" 访问静态文件路由地址,默认是"/"+static_folder static_host=None 指定静态文件服务器地址 host_matching = False 若不是特别需要时,慎用,否则所有的route 都需要host=""的参数 subdomain_matching = False 理论上来说是用来限制SERVER_NAME子域名的,但是目前还没有感觉出来区别在哪里 instance_path = None 指向另一个Flask实例的路径 instance_relative_config = False 是否加载另一个实例的配置 root_path = None 主模块所在的目录的绝对路径,默认项目目录
四、flask对象配置
Flask对象即Flask的实例化对象app,Flask的对象配置就是在 app.config 中添加键值对,例如,app.config["SECRET_KEY"]="/wrwfgs",但是你存进去的键必须是config中应该存在的,如果不存在的话,它会默认无用,所以我们来看一下config中有哪些key以及对应的作用?
'DEBUG': False, # 是否开启Debug模式 'TESTING': False, # 是否开启测试模式 'SECRET_KEY': None # 在启用Flask内置Session的时候/开启flash,一定要有它 'PERMANENT_SESSION_LIFETIME': 31, # days , Session的生命周期(天)默认31天 'SESSION_COOKIE_NAME': 'session', # 在cookies中存放session加密字符串的名字 'PROPAGATE_EXCEPTIONS': None, # 异常传播(是否在控制台打印LOG) 当Debug或者testing开启后,自动为True 'PRESERVE_CONTEXT_ON_EXCEPTION': None, # 一两句话说不清楚,一般不用它 'SECRET_KEY': None, # 之前遇到过,在启用Session的时候,一定要有它 'PERMANENT_SESSION_LIFETIME': 31, # days , Session的生命周期(天)默认31天 'USE_X_SENDFILE': False, # 是否弃用 x_sendfile 'LOGGER_NAME': None, # 日志记录器的名称 'LOGGER_HANDLER_POLICY': 'always', 'SERVER_NAME': None, # 服务访问域名 'APPLICATION_ROOT': None, # 项目的完整路径 'SESSION_COOKIE_NAME': 'session', # 在cookies中存放session加密字符串的名字 'SESSION_COOKIE_DOMAIN': None, # 在哪个域名下会产生session记录在cookies中 'SESSION_COOKIE_PATH': None, # cookies的路径 'SESSION_COOKIE_HTTPONLY': True, # 控制 cookie 是否应被设置 httponly 的标志, 'SESSION_COOKIE_SECURE': False, # 控制 cookie 是否应被设置安全标志 'SESSION_REFRESH_EACH_REQUEST': True, # 这个标志控制永久会话如何刷新 'MAX_CONTENT_LENGTH': None, # 如果设置为字节数, Flask 会拒绝内容长度大于此值的请求进入,并返回一个 413 状态码 'SEND_FILE_MAX_AGE_DEFAULT': 12, # hours 默认缓存控制的最大期限 'TRAP_BAD_REQUEST_ERRORS': False, # 如果这个值被设置为 True ,Flask不会执行 HTTP 异常的错误处理,而是像对待其它异常一样, # 通过异常栈让它冒泡地抛出。这对于需要找出 HTTP 异常源头的可怕调试情形是有用的。 'TRAP_HTTP_EXCEPTIONS': False, # Werkzeug 处理请求中的特定数据的内部数据结构会抛出同样也是“错误的请求”异常的特殊的 key errors 。 # 同样地,为了保持一致,许多操作可以显式地抛出 BadRequest 异常。 # 因为在调试中,你希望准确地找出异常的原因,这个设置用于在这些情形下调试。 'EXPLAIN_TEMPLATE_LOADING': False, # 如果这个值被设置为True,你只会得到常规的回溯。 'PREFERRED_URL_SCHEME': 'http', # 生成URL的时候如果没有可用的 URL 模式话将使用这个值 'JSON_AS_ASCII': True, # 默认情况下 Flask 使用 ascii 编码来序列化对象。如果这个值被设置为 False , # Flask不会将其编码为 ASCII,并且按原样输出,返回它的 unicode 字符串。 # 比如 jsonfiy 会自动地采用 utf-8 来编码它然后才进行传输。 'JSON_SORT_KEYS': True, # 默认情况下 Flask 按照 JSON 对象的键的顺序来序来序列化它。 # 这样做是为了确保键的顺序不会受到字典的哈希种子的影响,从而返回的值每次都是一致的,不会造成无用的额外 HTTP 缓存。 # 你可以通过修改这个配置的值来覆盖默认的操作。但这是不被推荐的做法因为这个默认的行为可能会给你在性能的代价上带来改善。 'JSONIFY_PRETTYPRINT_REGULAR': True, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None,
以上这些key都可以被改写,当然它们也有默认值,如果没有特殊情况,不要改写它的默认值。
除了单独修改某一项的配置外,还可以通过类的方式导入配置:
首先定义一个flask_settings.py,内容如下:
class FlaskDebug(object): # debug模式下的类 DEBUG=True SECRET_KEY="DEBUGmoshidesecret_key" PERMANENT_SESSION_LIFETIME = 7 SESSION_COOKIE_NAME = "debug_session"
然后再flask的启动文件中使用,如下:
from flask import Flask
app = Flask(__name__)
app.config.from_object(flask_settings.FlaskDebug) # 配置debug模式
五、初识flask蓝图(Blueprint)
作为初学flask的我们,可以将蓝图理解为没有run方法的Flask对象,接下来学习如何创建以及配置蓝图。
首先创建一个目录flask_project/app01,在app01目录中创建一个views.py文件,内容如下:
from flask import Blueprint # 从flask中导入蓝图 bp1 = Blueprint("bp1", __name__) # 实例化一个蓝图对象 @bp1.route('/stuList') # 添加路由并定义视图函数 def stulist(): return "student list"
在flask_project目录中创建manage.py文件,内容如下:
from flask import Flask from app01 import views # 导入刚写好的蓝图所在模块 app = Flask(__name__) app.register_blueprint(views.bp1) # 在Flask对象中注册蓝图对象,重要 if __name__ == '__main__': app.run(debug=True)
此时,运行manage.py文件启动服务,然后访问http://127.0.0.1:5000/stuList,即可看到返回结果。
上述蓝图的视图函数返回了一个HttpResponse对象,那么是否可以返回模板页面呢?当然可以,实例化蓝图(Blueprint)对象时,也可以配置蓝图的模板目录(template_folder参数)和静态文件目录(static_folder参数),也就说蓝图的对象配置与Flask的对象配置方法一样,在此不过多介绍。
需要特别说明的是蓝图可以指定url前缀(url_prefix参数),方法如下:
方法一:实例化蓝图对象时指定url前缀
bp1 = Blueprint("bp1", __name__, url_prefix='/app01')
方法二:在注册蓝图时指定url前缀
app.register_blueprint(views.bp1, url_prefix='/app01')
此时,浏览器要访问地址http://127.0.0.1:5000/app01/stuList
六、flask特殊装饰器
1、flask的中间件
1)@app.before_request # 请求进入视图函数之前,类似于django中间件的request_process
2)@app.after_request # 响应返回客户端之前,类似于django中间件的response_process
manage.py代码如下:
from flask import Flask app = Flask(__name__) @app.before_request def be1(): print('我是be1') @app.before_request def be2(): print('我是be2') @app.before_request def be3(): print('我是be3') @app.after_request def af1(response): print('我是af1') return response @app.after_request def af2(response): print('我是af2') return response @app.after_request def af3(response): print('我是af3') return response @app.route('/index') def index(): print('我是视图函数') return 'ok' if __name__ == '__main__': app.run(debug=True)
启动项目,访问http://127.0.0.1:5000/index,打印结果如下:
我是be1
我是be2
我是be3
我是视图函数
我是af3
我是af2
我是af1
分析:@app.befor_request自上而下依次执行,@app.after_request自下而上依次执行。与django的中间件类似,@app.before_request装饰的函数不写return或者returnNone表示放行,顺利通过,否则拦截请求,返回响应。@app.after_request装饰的函数一定要有返回值,且必须有参数接收response对象。
修改be1函数为如下代码:
@app.before_request def be2(): print('我是be2') return 'error'
执行结果如下:
我是be1
我是af3
我是af2
我是af1
总结:
正常情况下流程:be1 - be2 - be3 - af3 - af2 - af1
异常(be1函数发生异常)情况下流程:be1 - af3 - af2 - af1
2、重定义错误页面返回信息
@app.errorhandler(404)# 参数是错误代码 def error404(error_info):# 注意,一定要加参数接收错误信息 print(error_info) # 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again. return '页面不存在'# 可以返回 三剑客 + 小儿子
当访问时发生404错误时,会看到该函数返回的内容。