37

我正在尝试实现重定向模式,类似于 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,但我不太确定什么不起作用。

我如何将一条路线重定向到另一条路线?

4

4 回答 4

40

调试路线:

更新:为了解决主要问题“我的路线有什么问题”,最简单的调试方法是使用app.url_map; 例如:

>>> 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>])

在这种情况下,这确认端点设置正确。这是一个展示plainflask和的示例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,您可以使用 来查看端点名称,也可以url_map()使用@route(..., endpoint='name').


更少的重定向:

要在最小化重定向数量的同时响应您发布的 url,您需要使用strict_slashes=False,这将确保处理未以 a 终止的请求,/而不是通过重定向将它们301重定向到它们的/-terminated 对应项:

@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/"

我相信它完全符合您的测试矩阵的用途:)

于 2013-07-13T08:31:12.647 回答
30

你几乎明白了。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)

defaultsdict所有不在规则中的路由参数的默认值。在这里,在第二个路由装饰器中,规则中没有username参数,因此您必须将其设置为defaults.

于 2013-07-14T14:50:41.960 回答
3

好吧,看起来我的原始代码确实有效。Flask-Classy 是这里的问题(因为这个问题有赏金,我不能删除它)。

我忘记了 Flask-Classy 重命名路由,所以url_for('ClassName:profile')我必须选择最外层装饰器的路由,而不是 :

url_for('ClassName:profile_1')

另一种方法是显式指定路由的端点:

@route('/<int:id>/<username>/', endpoint='ClassName:profile')
于 2013-07-14T20:22:59.603 回答
2

我不明白你为什么要重定向。您不会通过重定向获得任何收益,正如您自己提到的那样,您最终只是多次查询数据库。您不会以任何有意义的方式使用给定的用户名,因此请忽略它。

@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)

这将满足您给定的所有示例。

于 2013-06-25T00:06:20.033 回答