使用 URL 处理器

Changelog

Added in version 0.7.

Flask 0.7 引入了 URL 处理器(URL Processors)的概念。 这个功能的设计理念是:你可能有一批具有相同 URL 部分的资源, 而你并不希望在每一个视图函数中都显式地处理这些相同的部分。 例如,你的多个 URL 中可能都包含语言代码,但你不想在每个函数中都手动处理这个参数。

当 URL 处理器与蓝图(Blueprint)结合使用时,尤其有用。本节将介绍应用级别和蓝图级别的 URL 处理器。

国际化应用的 URL

设想你有如下的应用结构:

from flask import Flask, g

app = Flask(__name__)

@app.route('/<lang_code>/')
def index(lang_code):
    g.lang_code = lang_code
    ...

@app.route('/<lang_code>/about')
def about(lang_code):
    g.lang_code = lang_code
    ...

这种做法中会有大量重复的代码,因为你必须在每个函数中手动将语言代码设置到 g 对象中。当然,也可以使用装饰器来简化这一过程,但如果你想从一个函数生成另一个函数的 URL,你仍然必须显式地提供语言代码,这可能会很烦人。

为了解决后者,可以使用 url_defaults() 函数。它们可以在调用 url_for() 时自动注入值。下面的代码会检查 URL 参数字典中是否缺少语言代码,以及端点是否需要一个名为 'lang_code' 的参数:

@app.url_defaults
def add_language_code(endpoint, values):
    if 'lang_code' in values or not g.lang_code:
        return
    if app.url_map.is_endpoint_expecting(endpoint, 'lang_code'):
        values['lang_code'] = g.lang_code

URL map 的 is_endpoint_expecting() 方法可以用于判断给定的端点是否需要提供语言代码。

与该函数相对的是 url_value_preprocessor()s。它们会在请求匹配之后立即执行,并可以基于 URL 参数运行代码。其目标是从值字典中提取信息并放到其他位置:

@app.url_value_preprocessor
def pull_lang_code(endpoint, values):
    g.lang_code = values.pop('lang_code', None)

这样你就不再需要在每个函数中将 lang_code 赋值给 g。你还可以进一步通过编写装饰器将语言代码前缀添加到 URL 中,但更优雅的解决方案是使用蓝图。一旦 'lang_code' 从参数字典中被弹出,它将不再被传递给视图函数,使代码简化如下:

from flask import Flask, g

app = Flask(__name__)

@app.url_defaults
def add_language_code(endpoint, values):
    if 'lang_code' in values or not g.lang_code:
        return
    if app.url_map.is_endpoint_expecting(endpoint, 'lang_code'):
        values['lang_code'] = g.lang_code

@app.url_value_preprocessor
def pull_lang_code(endpoint, values):
    g.lang_code = values.pop('lang_code', None)

@app.route('/<lang_code>/')
def index():
    ...

@app.route('/<lang_code>/about')
def about():
    ...

国际化蓝图的 URL

由于蓝图可以自动为所有 URL 添加一个公共字符串前缀,因此可以轻松地对每个函数实现自动处理。此外,蓝图可以有各自的 URL 处理器,这样就不需要在 url_defaults() 函数中处理所有逻辑,因为它无需再判断某个 URL 是否确实需要 'lang_code' 参数:

from flask import Blueprint, g

bp = Blueprint('frontend', __name__, url_prefix='/<lang_code>')

@bp.url_defaults
def add_language_code(endpoint, values):
    values.setdefault('lang_code', g.lang_code)

@bp.url_value_preprocessor
def pull_lang_code(endpoint, values):
    g.lang_code = values.pop('lang_code')

@bp.route('/')
def index():
    ...

@bp.route('/about')
def about():
    ...