我正在尝试实现重定向模式,类似于StackOverflow的功能:
@route('/<int:id>/<username>/')
@route('/<int:id>/')
def profile(id, username=None):
user = User.query.get_or_404(id)
if user.clean_username != username:
return redirect(url_for('profile', id=id, username=user.clean_username))
return render_template('user/profile.html', user=user)
这是一个应该发生什么的简单表格:
URL Redirects/points to
====================================================
/user/123 /user/123/clean_username
/user/123/ /user/123/clean_username
/user/123/foo /user/123/clean_username
/user/123/clean_username /user/123/clean_username
/user/123/clean_username/ /user/123/clean_username/
/user/125698 404
目前,我可以使用/user/1/foo
访问个人资料,但/user/1
会生成BuildError
。我已经尝试了alias=True
关键字参数以及defaults
的内容,但我不太确定哪些不起作用。
我如何将一条路线重定向到另一条路线?
答案 0 :(得分:32)
更新:解决主要问题“我的路线有什么问题”,最简单的调试方法是使用app.url_map
; e.g:
>>> app.url_map
Map([<Rule '/user/<id>/<username>/' (HEAD, OPTIONS, GET) -> profile>,
<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
<Rule '/user/<id>/' (HEAD, OPTIONS, GET) -> profile>])
在这种情况下,这确认端点已正确设置。
以下示例展示了普通flask
和flask-classy
:
from app import app, models
from flask import g, redirect, url_for, render_template, request
from flask.ext.classy import FlaskView, route
@app.route('/user/<int:id>', strict_slashes=False)
@app.route('/user/<int:id>/<username>', strict_slashes=False)
def profile(id, username=None):
user = models.User.query.get_or_404(id)
if user.clean_username != username:
return redirect(url_for('profile', id=id, username=user.clean_username))
return render_template('profile.html', user=user)
class ClassyUsersView(FlaskView):
@route('/<int:id>', strict_slashes=False)
@route('/<int:id>/<username>', strict_slashes=False, endpoint='classy_profile')
def profile(self, id, username=None):
user = models.User.query.get_or_404(id)
if user.clean_username != username:
return redirect(url_for('classy_profile', id=id, username=user.clean_username))
return render_template('profile.html', user=user)
ClassyUsersView.register(app)
他们有不同的端点,您需要考虑url_for
:
>>> app.url_map
Map([<Rule '/classyusers/<id>/<username>' (HEAD, OPTIONS, GET) -> classy_profile>,
<Rule '/user/<id>/<username>' (HEAD, OPTIONS, GET) -> profile>,
<Rule '/classyusers/<id>' (HEAD, OPTIONS, GET) -> ClassyUsersView:profile_1>,
<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
<Rule '/user/<id>' (HEAD, OPTIONS, GET) -> profile>])
如果没有flask-classy
,则端点名称是函数名称,但正如您所知,这与使用classy
时不同,您可以使用{查看端点名称{1}}或使用url_map()
在您的路线中指定。
要在最小化重定向数量的同时回复您发布的网址,您需要使用@route(..., endpoint='name')
,这将确保处理未使用strict_slashes=False
终止的请求,而不是使用/
重定向301
重定向到/
- 已终止的对应方:
@app.route('/user/<int:id>', strict_slashes=False)
@app.route('/user/<int:id>/<username>', strict_slashes=False)
def profile(id, username=None):
user = models.User.query.get_or_404(id)
if user.clean_username != username:
return redirect(url_for('profile', id=id, username=user.clean_username))
return render_template('profile.html', user=user)
结果如下:
>>> client = app.test_client()
>>> def check(url):
... r = client.get(url)
... return r.status, r.headers.get('location')
...
>>> check('/user/123')
('302 FOUND', 'http://localhost/user/123/johndoe')
>>> check('/user/123/')
('302 FOUND', 'http://localhost/user/123/johndoe')
>>> check('/user/123/foo')
('302 FOUND', 'http://localhost/user/123/johndoe')
>>> check('/user/123/johndoe')
('200 OK', None)
>>> check('/user/123/johndoe/')
('200 OK', None)
>>> check('/user/125698')
('404 NOT FOUND', None)
strict_slashes
的行为:
with strict_slashes=False
URL Redirects/points to # of redirects
===========================================================================
/user/123 302 /user/123/clean_username 1
/user/123/ 302 /user/123/clean_username 1
/user/123/foo 302 /user/123/clean_username 1
/user/123/foo/ 302 /user/123/clean_username 1
/user/123/clean_username 302 /user/123/clean_username 1
/user/123/clean_username/ 200 /user/123/clean_username/ 0
/user/125698 404
with strict_slashes=True (the default)
any non '/'-terminated urls redirect to their '/'-terminated counterpart
URL Redirects/points to # of redirects
===========================================================================
/user/123 301 /user/123/ 2
/user/123/foo 301 /user/123/foo/ 2
/user/123/clean_username 301 /user/123/clean_username/ 1
/user/123/ 302 /user/123/clean_username/ 1
/user/123/foo/ 302 /user/123/clean_username/ 1
/user/123/clean_username/ 200 /user/123/clean_username/ 0
/user/125698 404
example:
"/user/123/foo" not terminated with '/' -> redirects to "/user/123/foo/"
"/user/123/foo/" -> redirects to "/user/123/clean_username/"
我相信它完全符合你的测试矩阵:)
答案 1 :(得分:21)
你几乎得到了它。 defaults
就是你想要的。以下是它的工作原理:
@route('/<int:id>/<username>/')
@route('/<int:id>/', defaults={'username': None})
def profile(id, username):
user = User.query.get_or_404(id)
if username is None or user.clean_username != username:
return redirect(url_for('profile', id=id, username=user.clean_username))
return render_template('user/profile.html', user=user)
defaults
是dict
,其中包含规则中不包含的所有路径参数的默认值。在这里,在第二个路径装饰器中,规则中没有username
参数,因此您必须在defaults
中进行设置。
答案 2 :(得分:3)
好吧,看起来我的原始代码确实有效。 Flask-Classy是这里的问题(因为这个问题有赏金,我不能删除它)。
我忘了Flask-Classy重命名路线,所以我不得不选择最外面装饰者的路线而不是url_for('ClassName:profile')
:
url_for('ClassName:profile_1')
另一种方法是明确指定路线的终点:
@route('/<int:id>/<username>/', endpoint='ClassName:profile')
答案 3 :(得分:1)
我不明白你为什么要重定向。你没有通过重定向获得任何东西,正如你自己提到的那样,你最终只是多次查询数据库。您不以任何有意义的方式使用给定的用户名,因此请忽略它。
@route('/<int:id>/<username>/')
@route('/<int:id>/')
def profile(id, username=None):
user = User.query.get_or_404(id)
return render_template('user/profile.html', user=user)
这将满足您提供的所有示例。