I\'m trying to implement a redirecting pattern, similar to what StackOverflow does:
@route(\'///\')
@route(\'//\')
de
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([//' (HEAD, OPTIONS, GET) -> profile>,
' (HEAD, OPTIONS, GET) -> static>,
/' (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/', strict_slashes=False)
@app.route('/user//', 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('/', strict_slashes=False)
@route('//', 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([/' (HEAD, OPTIONS, GET) -> classy_profile>,
/' (HEAD, OPTIONS, GET) -> profile>,
' (HEAD, OPTIONS, GET) -> ClassyUsersView:profile_1>,
' (HEAD, OPTIONS, GET) -> static>,
' (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')
.
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/', strict_slashes=False)
@app.route('/user//', 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 '/'-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/"
I believe it does exactly what your test matrix is about :)