视图装饰器

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

注意

在对登录页面进行 GET 请求后,next 值将存在于 request.args 中。在从登录表单发送 POST 请求时,您必须传递它。您可以使用隐藏的输入标签来实现此目的,然后在用户登录时从 request.form 中检索它。

<input type="hidden" value="{{ request.args.get('next', '') }}"/>

缓存装饰器

假设您有一个视图函数执行昂贵的计算,因此您希望将生成的结果缓存一段时间。装饰器对此很有用。我们假设您已经设置了缓存,如 缓存 中所述。

这是一个缓存函数示例。它从特定前缀(实际上是一个格式字符串)和请求的当前路径生成缓存键。请注意,我们正在使用一个函数,该函数首先创建装饰器,然后装饰函数。听起来很糟糕?不幸的是,它稍微复杂一些,但代码仍然应该很容易阅读。

然后,装饰后的函数将按如下方式工作

  1. 根据当前路径获取当前请求的唯一缓存键。

  2. 从缓存中获取该键的值。如果缓存返回了某些内容,我们将返回该值。

  3. 否则,将调用原始函数,并将返回值存储在缓存中以获取提供的超时(默认情况下为 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"