Flask redirecting multiple routes

debugging routes:

Update: to address the primary question “what’s wrong with my routes”, the simplest way to debug that is to use app.url_map; e.g:

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

In this case, this confirms that the endpoint is correctly set.
Here is an example showcasing both plain flask and 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)

They have different endpoints, which you need to take into account for 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>])

Without flask-classy the name of the endpoint is the function name, but as you’ve found out, this is different for when using classy, and you can either look at the endpoint name with url_map() or assign it in your route with @route(..., endpoint="name").


less redirects:

To respond to the urls you posted while minimizing the amount of redirects, you need to use strict_slashes=False, this will make sure to handle requests that are not terminated with a / instead of redirecting them with a 301 redirect to their /-terminated counterpart:

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

here is the result:

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

Behavior of 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 "https://stackoverflow.com/"-terminated urls redirect to their "https://stackoverflow.com/"-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 "https://stackoverflow.com/" -> redirects to "/user/123/foo/"
"/user/123/foo/" -> redirects to "/user/123/clean_username/"

I believe it does exactly what your test matrix is about 🙂

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)