web-dev-qa-db-ja.com

Flask=複数のルートのリダイレクト

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/1BuildErrorを生成します。 alias=Trueキーワード引数とdefaultsを使用して何かを試しましたが、何が機能していないのかよくわかりません。

このように一方のルートを他方にリダイレクトするにはどうすればよいですか?

30
Blender

デバッグルート:

更新:「私のルートの何が問題なのか」という主要な質問に対処するには、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>])

この場合、これにより、エンドポイントが正しく設定されていることが確認されます。以下に、プレーンflaskflask-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を使用する必要があります。これにより、/で終了するリクエストを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/"

私はそれがあなたのテストマトリックスが正確に何であるかを信じています:)

32
dnozay

あなたはほとんどそれを持っています。 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であり、ルール内にないすべてのルートパラメーターのデフォルト値を持ちます。ここで、2番目のルートデコレーターでは、ルールにusernameパラメーターがないため、defaultsに設定する必要があります。

22
dAnjou

まあ、それは私の元のコードが実際に働いたように見えます。ここではFlask-Classyが問題でした(そして、この質問には恩恵があるため、削除することはできません)。

Flask-Classyがルートの名前を変更することを忘れていたので、url_for('ClassName:profile')の代わりに、最も外側のデコレータのルートを選択する必要があります。

url_for('ClassName:profile_1')

別の方法は、ルートへのエンドポイントを明示的に指定することです。

@route('/<int:id>/<username>/', endpoint='ClassName:profile')
3
Blender

リダイレクトする理由がわかりません。リダイレクトでは何も得られず、自分で述べたように、データベースを複数回クエリするだけになります。指定されたユーザー名を意味のある方法で使用しないため、無視してください。

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

これにより、指定されたすべての例が満たされます。

1
Michael Davis