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
を使用して何かを試しましたが、何が機能していないのかよくわかりません。
このように一方のルートを他方にリダイレクトするにはどうすればよいですか?
更新:「私のルートの何が問題なのか」という主要な質問に対処するには、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>])
この場合、これにより、エンドポイントが正しく設定されていることが確認されます。以下に、プレーン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
を使用する場合と異なり、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/"
私はそれがあなたのテストマトリックスが正確に何であるかを信じています:)
あなたはほとんどそれを持っています。 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
であり、ルール内にないすべてのルートパラメーターのデフォルト値を持ちます。ここで、2番目のルートデコレーターでは、ルールにusername
パラメーターがないため、defaults
に設定する必要があります。
まあ、それは私の元のコードが実際に働いたように見えます。ここではFlask-Classyが問題でした(そして、この質問には恩恵があるため、削除することはできません)。
Flask-Classyがルートの名前を変更することを忘れていたので、url_for('ClassName:profile')
の代わりに、最も外側のデコレータのルートを選択する必要があります。
url_for('ClassName:profile_1')
別の方法は、ルートへのエンドポイントを明示的に指定することです。
@route('/<int:id>/<username>/', endpoint='ClassName:profile')
リダイレクトする理由がわかりません。リダイレクトでは何も得られず、自分で述べたように、データベースを複数回クエリするだけになります。指定されたユーザー名を意味のある方法で使用しないため、無視してください。
@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)
これにより、指定されたすべての例が満たされます。