web-dev-qa-db-fra.com

Comment utiliser url_for si ma méthode comporte plusieurs annotations de route?

J'ai donc une méthode accessible par plusieurs routes:

@app.route("/canonical/path/")
@app.route("/alternate/path/")
def foo():
    return "hi!"

Maintenant, comment puis-je appeler url_for("foo") et savoir que je vais obtenir le premier itinéraire?

37
jiggy

D'accord. Il a fallu un peu de fouille dans les codes werkzeug.routing et flask.helpers.url_for, mais j'ai compris. Vous venez de changer la endpoint pour la route (en d’autres termes, vous nom votre route)

@app.route("/canonical/path/", endpoint="foo-canonical")
@app.route("/alternate/path/")
def foo():
    return "hi!"

@app.route("/wheee")
def bar():
    return "canonical path is %s, alternative is %s" % (url_for("foo-canonical"), url_for("foo"))

produira

chemin canonique est/canonique/chemin /, alternative est/alternative/chemin /

Il y a un inconvénient de cette approche. Flask lie toujours la dernière route définie au point de terminaison défini de manière implicite (foo dans votre code). Devinez ce qui se passe si vous redéfinissez le point final? Tous vos url_for('old_endpoint') lanceront werkzeug.routing.BuildError. Donc, je suppose que la bonne solution pour l’ensemble du problème est de définir le dernier chemin canonique et name alternative:

""" 
   since url_for('foo') will be used for canonical path
   we don't have other options rather then defining an endpoint for
   alternative path, so we can use it with url_for
"""
@app.route('/alternative/path', endpoint='foo-alternative')
""" 
   we dont wanna mess with the endpoint here - 
   we want url_for('foo') to be pointing to the canonical path
"""
@app.route('/canonical/path') 
def foo():
    pass

@app.route('/wheee')
def bar():
    return "canonical path is %s, alternative is %s" % (url_for("foo"), url_for("foo-alternative"))
64
Nemoden

Les règles de Flask sont uniques. Si vous définissez la même URL absolue vers la même fonction, elle se heurtera par défaut car vous faites quelque chose que nous vous empêchons de faire car, de notre point de vue, c'est faux.

Il y a une raison pour laquelle vous voudriez avoir plus d'une URL vers le même point de terminaison absolu: il s'agit d'une compatibilité ascendante avec une règle qui existait auparavant. Depuis WZ0.8 et Flask 0.8, vous pouvez spécifier explicitement un alias pour un itinéraire:

@app.route('/')
@app.route('/index.html', alias=True)
def index():
    return ...

Dans ce cas, si l'utilisateur demande /index.html, Flask émettra automatiquement une redirection permanente vers uniquement /.

Cela ne signifie cependant pas qu'une fonction ne puisse pas être liée à plus d'une URL, mais dans ce cas, vous devrez changer le point final:

@app.route('/')
def index():
    ...

app.add_url_rule('/index.html', view_func=index, endpoint='alt_index')

Ou bien:

@app.route('/')
@app.route('/index.html', endpoint='alt_index')
def index():
    ...

Dans ce cas, vous pouvez définir une vue une seconde fois sous un nom différent. Cependant, il s’agit généralement d’une chose à éviter, car la fonction de vue devrait alors vérifier request.endpoint pour savoir comment elle est appelée. Mieux vaut plutôt faire quelque chose comme ça:

@app.route('/')
def index():
    return _index(alt=False)

@app.route('/index.html')
def alt_index():
    return _index(alt=True)

def _index(alt):
    ...

Dans les deux cas, la génération d'URL est url_for('index') ou url_for('alt_index').

Vous pouvez également le faire au niveau du système de routage:

@app.route('/', defaults={'alt': False})
@app.route('/index.html', defaults={'alt': True})
def index(alt):
    ...

Dans ce cas, la génération d'URL est url_for('index', alt=True) ou url_for('index', alt=False).

51
Armin Ronacher

En outre, pour ceux qui utilisent un itinéraire catch all construit avec des variables: Flask créera correctement le chemin de l'URL si un dictionnaire contenant les variables est transmis à url_for.

Par exemple...

app.py:

app.route('/<path:pattern1>')
app.route('/<path:pattern1>/<path:pattern2>')
def catch_all(pattern1, pattern2=None):
    return render_template('template.html', p1=pattern1, p2=pattern2)

app.route('/test')
def test_routing:
    args = {'pattern1': 'Posts', 'pattern2': 'create'}
    return render_template('test.html', args=args)

test.html:

<a href="{{url_for('catch_all', **args)}}">click here</a>

Lorsque vous cliquez sur le lien "cliquez ici", vous serez dirigé vers l'itinéraire "Messages/créer".

0
vincent