Flask多个URL处理程序部分处理URLs
在Flask中,是否可以定义一些网址路由,让它们处理网址的一部分,然后把剩下的部分传给下一个处理程序继续处理呢?
举个例子,假设网址中有一部分是静态的,重复出现,而且每次都需要做相同的处理。
比如说:
/user/1/something
还有:
/user/1/something-else
再比如:
/user/2/...
理想情况下,一个处理程序可以处理 /user/<id>
这一部分(比如从数据库中加载记录等),并把结果存储到本地上下文中。然后,另一个处理程序再处理剩下的部分。这种方式让我可以更换 user
这一部分(例如换成 /user/<name>
),而不需要修改其他的路由。
在Flask中,这样做是否可行?如果可以,应该怎么做呢?
1 个回答
我不太确定你想要的是否能实现。不过,从你上面描述的用法来看,我觉得你只需要一个转换器。
什么是转换器?
它就像魔法一样,让你可以这样定义路由:
@app.route('/double/<int:number>')
def double(number):
return '%d' % (number * 2)
在上面的例子中,这个路由只接受 /double/ints
。更棒的是,传入的 URL 部分会被自动转换成整数。所以叫它转换器,哈哈。
转换器的格式是 <converter:variable_name>
。你可以在这里了解 Flask 内置的转换器 这里。
转换器最酷的地方是你可以为自定义数据类型 编写自己的转换器!只需从 werkzeug.routing.BaseConverter 继承,并实现 to_python 和 to_url 方法。下面,我为常见的用户对象创建了一个简单的转换器。
class UserConverter(BaseConverter):
"""Converts Users for flask URLs."""
def to_python(self, value):
"""Called to convert a `value` to its python equivalent.
Here, we convert `value` to a User.
"""
# Anytime an invalid value is provided, raise ValidationError. Flask
# will catch this, and continue searching the other routes. First,
# check that value is an integer, if not there is no way it could be
# a user.
if not value.isdigit():
raise ValidationError()
user_id = int(value)
# Look up the user in the database.
if user_id not in _database:
raise ValidationError()
# Otherwise, return the user.
return _database[user_id]
def to_url(self, value):
"""Called to convert a `value` to its `url` equivalent.
Here we convert `value`, the User object to an integer - the user id.
"""
return str(value.id)
转换器什么时候被调用?
当 Flask 匹配到你的路由,并需要填充由转换器处理的部分时,就会调用转换器。所以,给定以下路由 - /find/<user:user>
,下面所有的 URL 都会被我们的转换器处理。
- /find/1
- /find/2
- /find/jaime
- /find/zasdf123
对于上面的 URL,UserConverter.to_python 方法会被依次调用,参数是 '1'、'2'、'jaime' 和 'zasdf123'。这个方法需要判断这些值是否能转换成有效的用户。如果可以,合法的用户就会直接传递给路由的 user
参数。
不过,Flask 仍然需要 知道 你的新转换器。这很简单:
app.url_map.converters['user'] = UserConverter
自定义转换器的最后一个酷炫功能是,它们让构建 URL 变得自然且简单。要创建一个 URL,只需这样做:
url_for('find_user', user=user)
最后,这里有一个简单的程序,把所有这些内容结合起来。
from flask import Flask, url_for
from werkzeug.routing import BaseConverter, ValidationError
class User:
def __init__(self, id, name):
self.id = id
self.name = name
class UserConverter(BaseConverter):
"""Converts Users for flask URLs."""
def to_python(self, value):
"""Called to convert a `value` to its python equivalent.
Here, we convert `value` to a User.
"""
# Anytime an invalid value is provided, raise ValidationError. Flask
# will catch this, and continue searching the other routes. First,
# check that value is an integer, if not there is no way it could be
# a user.
if not value.isdigit():
raise ValidationError()
user_id = int(value)
# Look up the user in the database.
if user_id not in _database:
raise ValidationError()
# Otherwise, return the user.
return _database[user_id]
def to_url(self, value):
"""Called to convert a `value` to its `url` equivalent.
Here we convert `value`, the User object to an integer - the user id.
"""
return str(value.id)
# Create a `database` of users.
_database = {
1: User(1, 'Bob'),
2: User(2, 'Jim'),
3: User(3, 'Ben')
}
app = Flask(__name__)
app.url_map.converters['user'] = UserConverter
@app.route('/find/<user:user>')
def find_user(user):
return "User: %s" % user.name
@app.route('/find/<user:user>/extrapath')
def find_userextra(user):
return 'User extra: %s' % user.name
@app.route('/users')
def list_users():
# Return some broken HTML showing url's to our users.
s = ''
for user in _database.values():
s += url_for('find_user', user=user) + '<br/>'
return s
if __name__ == '__main__':
app.run(debug=True)
如果有什么不清楚的地方,或者 哎呀 有 bug,请告诉我 :D。这与你的问题有关,因为你提到了一系列开头相同的 URL --
/user/1/something
/user/1/something-else
/user/2/
对于这些路由,你可以这样应用自定义转换器:
@app.route('/user/<user:user>/something')
@app.route('/user/<user:user>/something-else')
@app.route('/user/<user:user>/)