视图装饰器¶
Python 有一个非常有趣的功能叫做函数装饰器。这为 Web 应用程序带来了一些非常巧妙的东西。由于 Flask 中的每个视图都是一个函数,因此可以使用装饰器将额外的功能注入到一个或多个函数中。route()
装饰器是你可能已经使用过的。但是,在某些情况下,你需要实现自己的装饰器。例如,假设你有一个视图,该视图应该只供已登录的用户使用。如果用户访问该站点但未登录,则应将其重定向到登录页面。这是一个很好的用例示例,装饰器是极好的解决方案。
登录必需装饰器¶
那么让我们实现这样一个装饰器。装饰器是一个包装和替换另一个函数的函数。由于原始函数被替换,你需要记住将原始函数的信息复制到新函数。使用 functools.wraps()
来为你处理此事。
此示例假定登录页面名为 'login'
,当前用户存储在 g.user
中,如果没有人登录,则为 None
。
from functools import wraps
from flask import g, request, redirect, url_for
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if g.user is None:
return redirect(url_for('login', next=request.url))
return f(*args, **kwargs)
return decorated_function
要使用装饰器,请将其作为最内层的装饰器应用于视图函数。当应用进一步的装饰器时,始终记住 route()
装饰器是最外层的。
@app.route('/secret_page')
@login_required
def secret_page():
pass
注意
next
值将在对登录页面进行 GET
请求后存在于 request.args
中。在从登录表单发送 POST
请求时,你必须将其传递下去。你可以使用隐藏的输入标签来做到这一点,然后在用户登录时从 request.form
中检索它。
<input type="hidden" value="{{ request.args.get('next', '') }}"/>
缓存装饰器¶
假设你有一个视图函数,它执行昂贵的计算,因此你希望将生成的结果缓存一段时间。装饰器对于此目的会很好。我们假设你已设置像 缓存 中提到的缓存。
这是一个示例缓存函数。它从特定的前缀(实际上是一个格式字符串)和请求的当前路径生成缓存键。请注意,我们正在使用一个首先创建装饰器的函数,然后装饰该函数。听起来很糟糕?不幸的是,它有点复杂,但是代码仍然应该很容易阅读。
然后,装饰后的函数将按如下方式工作
基于当前路径获取当前请求的唯一缓存键。
从缓存中获取该键的值。如果缓存返回了某些内容,我们将返回该值。
否则,将调用原始函数,并将返回值存储在缓存中,超时时间为提供的时间(默认为 5 分钟)。
这是代码
from functools import wraps
from flask import request
def cached(timeout=5 * 60, key='view/{}'):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
cache_key = key.format(request.path)
rv = cache.get(cache_key)
if rv is not None:
return rv
rv = f(*args, **kwargs)
cache.set(cache_key, rv, timeout=timeout)
return rv
return decorated_function
return decorator
请注意,这假定已实例化 cache
对象可用,请参阅 缓存。
模板装饰器¶
TurboGears 的家伙们在很久以前发明了一种常见的模式,即模板装饰器。该装饰器的想法是,你从视图函数返回一个包含传递给模板的值的字典,并且模板会自动呈现。这样,以下三个示例的功能完全相同
@app.route('/')
def index():
return render_template('index.html', value=42)
@app.route('/')
@templated('index.html')
def index():
return dict(value=42)
@app.route('/')
@templated()
def index():
return dict(value=42)
如你所见,如果没有提供模板名称,它将使用 URL 映射的端点,并将点转换为斜杠 + '.html'
。否则,将使用提供的模板名称。当装饰后的函数返回时,返回的字典将传递给模板渲染函数。如果返回 None
,则假定为空字典;如果返回字典以外的其他内容,我们将从函数中不变地返回它。这样,你仍然可以使用重定向函数或返回简单的字符串。
这是该装饰器的代码
from functools import wraps
from flask import request, render_template
def templated(template=None):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
template_name = template
if template_name is None:
template_name = f"{request.endpoint.replace('.', '/')}.html"
ctx = f(*args, **kwargs)
if ctx is None:
ctx = {}
elif not isinstance(ctx, dict):
return ctx
return render_template(template_name, **ctx)
return decorated_function
return decorator
端点装饰器¶
当你想使用 werkzeug 路由系统以获得更大的灵活性时,你需要将 Rule
中定义的端点映射到视图函数。这可以通过此装饰器实现。例如
from flask import Flask
from werkzeug.routing import Rule
app = Flask(__name__)
app.url_map.add(Rule('/', endpoint='index'))
@app.endpoint('index')
def my_index():
return "Hello world"