How do I use url_for if my method has multiple route annotations?


Question

So I have a method that is accessible by multiple routes:

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

Now, how can I call url_for("foo") and know that I will get the first route?

1
37
10/16/2011 2:49:54 AM

Accepted Answer

Ok. It took some delving into the werkzeug.routing and flask.helpers.url_for code, but I've figured out. You just change the endpoint for the route (in other words, you name your 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"))

will produce

canonical path is /canonical/path/, alternative is /alternate/path/

There is a drawback of this approach. Flask always binds the last defined route to the endpoint defined implicitly (foo in your code). Guess what happens if you redefine the endpoint? All your url_for('old_endpoint') will throw werkzeug.routing.BuildError. So, I guess the right solution for the whole issue is defining canonical path the last and 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"))
65
6/29/2012 4:54:17 PM

Rules in Flask are unique. If you define the absolute same URL to the same function it will by default clash because you're doing something which we stop you from doing since from our perspective that is wrong.

There is one reason why you would want to have more than one URL to the absolute same endpoint and that is backwards compatibility with a rule that existed in the past. Since WZ0.8 and Flask 0.8 you can explicitly specify an alias for a route:

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

In this case if the user requests /index.html Flask will automatically issue a permanently redirect to just /.

That does not mean a function could not be bound to more than one url though, but in this case you would need to change the endpoint:

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

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

Or alternatively:

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

In this case you can define a view a second time under a different name. However this is something you generally want to avoid because then the view function would have to check request.endpoint to see what is called. Instead better do something like this:

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

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

def _index(alt):
    ...

In both of these cases URL generation is url_for('index') or url_for('alt_index').

You can also do this on the routing system level:

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

In this case url generation is url_for('index', alt=True) or url_for('index', alt=False).


Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon