J'essaie d'implémenter un modèle de redirection, similaire à ce que fait 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)
Voici un tableau simple de ce qui devrait se produire:
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
Pour le moment, je peux accéder au profil avec /user/1/foo
, mais /user/1
produit un BuildError
. J'ai essayé le alias=True
argument de mot clé et quelque chose avec defaults
, mais je ne sais pas trop ce qui ne fonctionne pas.
Comment pourrais-je avoir un itinéraire redirigé vers l'autre comme celui-ci?
Mise à jour: pour répondre à la question principale "qu'est-ce qui ne va pas avec mes itinéraires", la manière la plus simple de déboguer est d'utiliser app.url_map
; par exemple:
>>> 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>])
Dans ce cas, cela confirme que le point de terminaison est correctement défini. Voici un exemple présentant à la fois les simples flask
et 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)
Ils ont différents points de terminaison, dont vous devez tenir compte pour 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>])
Sans flask-classy
, Le nom du point de terminaison est le nom de la fonction, mais comme vous l'avez découvert, cela est différent lorsque vous utilisez classy
, et vous pouvez soit regarder le nom du point de terminaison avec url_map()
ou affectez-le à votre itinéraire avec @route(..., endpoint='name')
.
Pour répondre aux URL que vous avez publiées tout en minimisant le nombre de redirections, vous devez utiliser strict_slashes=False
, Cela veillera à gérer les demandes qui ne se terminent pas par un /
Au lieu de les rediriger avec un 301
Redirigez vers leur /
- homologue terminé:
@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)
voici le résultat:
>>> 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)
Comportement de 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/"
Je crois qu'il fait exactement de quoi parle votre matrice de test :)
Vous l'avez presque compris. defaults
est ce que vous voulez. Voici comment cela fonctionne:
@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
est un dict
avec des valeurs par défaut pour tous les paramètres de route qui ne sont pas dans la règle. Ici, dans le deuxième décorateur de route, il n'y a pas de paramètre username
dans la règle, vous devez donc le définir dans defaults
.
Eh bien, il semble que mon code d'origine ait réellement fonctionné. Flask-Classy était le problème ici (et puisque cette question a une prime, je ne peux pas la supprimer).
J'ai oublié que Flask-Classy renomme les itinéraires, donc au lieu de url_for('ClassName:profile')
, je devrais sélectionner l'itinéraire du décorateur le plus à l'extérieur:
url_for('ClassName:profile_1')
Une alternative serait de spécifier explicitement un point de terminaison de la route:
@route('/<int:id>/<username>/', endpoint='ClassName:profile')
Je ne comprends pas pourquoi vous redirigez. Vous ne gagnez rien avec la redirection et comme vous l'avez mentionné vous-même, vous finissez par interroger la base de données plusieurs fois. Vous n'utilisez pas le nom d'utilisateur donné de manière significative, alors ignorez-le.
@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)
Cela satisfera tous vos exemples donnés.