Matraz de redireccionamiento de rutas múltiples.

Estoy tratando de implementar un patrón de redireccionamiento, similar a lo que hace StackOverflow:

@route('///') @route('//') 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) 

Aquí hay una tabla simple de lo que debería suceder:

 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 

En este momento, puedo acceder al perfil con /user/1/foo , pero /user/1 produce un BuildError . He intentado el argumento alias=True palabra clave alias=True y algo con defaults , pero no estoy muy seguro de qué no funciona.

¿Cómo tendría una ruta redirigir a la otra como esta?

    Rutas de depuración:

    Actualización: para abordar la pregunta principal “¿Qué hay de malo con mis rutas”, la forma más sencilla de depurar es usar app.url_map ? p.ej:

     >>> app.url_map Map([//' (HEAD, OPTIONS, GET) -> profile>, ' (HEAD, OPTIONS, GET) -> static>, /' (HEAD, OPTIONS, GET) -> profile>]) 

    En este caso, esto confirma que el punto final está correctamente configurado. Aquí hay un ejemplo que muestra el flask liso y el 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) 

    Tienen diferentes puntos finales, que debe tener en cuenta para 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>]) 

    Sin flask-classy el nombre del punto final es el nombre de la función, pero como ya descubrió, este es diferente para usar classy , y puede mirar el nombre del punto final con url_map() o asignarlo en su ruta con @route(..., endpoint='name') .


    menos redirecciones:

    Para responder a las direcciones URL que publicó al tiempo que minimiza la cantidad de redireccionamientos, debe utilizar strict_slashes=False , esto asegurará el manejo de solicitudes que no terminen con una / lugar de redirigirlas con una redirección 301 a su contraparte terminada en / :

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

    Aquí está el resultado:

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

    Comportamiento 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/" 

    Creo que hace exactamente de lo que trata tu matriz de prueba 🙂

    Casi lo tienes. defaults es lo que quieres. Así es como funciona:

     @route('///') @route('//', 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 son un dict con valores predeterminados para todos los parámetros de ruta que no están en la regla. Aquí, en el decorador de la segunda ruta no hay ningún parámetro de username en la regla, por lo que debe configurarlo en los defaults .

    Bueno, parece que mi código original realmente funcionó. Flask-Classy fue el problema aquí (y dado que esta pregunta tiene una recompensa, no puedo eliminarla).

    Olvidé que Flask-Classy cambia el nombre de las rutas, así que en lugar de url_for('ClassName:profile') , tendría que seleccionar la ruta del decorador más externo:

     url_for('ClassName:profile_1') 

    Una alternativa sería especificar explícitamente un punto final a la ruta:

     @route('///', endpoint='ClassName:profile') 

    No entiendo por qué estás redirigiendo. No ganas nada con la redirección y, como te has mencionado, terminas simplemente consultando la base de datos varias veces. No usa el nombre de usuario dado de ninguna manera significativa, así que simplemente ignórelo.

     @route('///') @route('//') def profile(id, username=None): user = User.query.get_or_404(id) return render_template('user/profile.html', user=user) 

    Esto satisfará todos sus ejemplos dados.