Подтвердить что ты не робот

Флажок, перенаправляющий несколько маршрутов

Я пытаюсь реализовать шаблон перенаправления, аналогичный тому, что делает StackOverflow:

@route('/<int:id>/<username>/')
@route('/<int:id>/')
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) 

Вот простая таблица того, что должно произойти:

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

Сейчас я могу получить доступ к профилю с помощью /user/1/foo, но /user/1 создает BuildError. Я пробовал аргумент ключевого слова alias=True и что-то с defaults, но я не совсем уверен, что не работает.

Как мне перенаправить один маршрут на другой так?

4b9b3361

Ответ 1

маршруты отладки:

Обновление: для решения основного вопроса "что не так с моими маршрутами", самый простой способ отладки - использовать app.url_map; например:

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

В этом случае это подтверждает правильность установки конечной точки. Вот пример, демонстрирующий как простые flask, так и 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)

У них разные конечные точки, которые необходимо учитывать для 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>])

Без flask-classy имя конечной точки является именем функции, но, как вы выяснили, это отличается при использовании classy, и вы можете либо посмотреть имя конечной точки с помощью url_map(), либо назначить это на вашем маршруте с помощью @route(..., endpoint='name').


меньше переадресаций:

Чтобы ответить на отправленные вами URL-адреса, минимизируя количество переадресаций, вам нужно использовать strict_slashes=False, это позволит обрабатывать запросы, которые не завершаются с помощью /, а не перенаправлять их с помощью 301 перенаправлять на свой / -термированный экземпляр:

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

вот результат:

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

Поведение 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/"

Я считаю, что он точно соответствует вашей тестовой матрице:)

Ответ 2

Ты почти понял это. defaults - это то, что вы хотите. Вот как это работает:

@route('/<int:id>/<username>/')
@route('/<int:id>/', 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 - это dict со значениями по умолчанию для всех параметров маршрута, которые не находятся в правиле. Здесь во втором декодере маршрута в правиле нет параметра username, поэтому вы должны установить его в defaults.

Ответ 3

Хорошо, похоже, что мой оригинальный код действительно работал. Здесь была проблема с Flask-Classy (и поскольку у этого вопроса есть щедрость, я не могу удалить его).

Я забыл, что Flask-Classy переименовывает маршруты, поэтому вместо url_for('ClassName:profile') мне нужно будет выбрать самый внешний маршрут декоратора:

url_for('ClassName:profile_1')

Альтернативой было бы явно указать конечную точку маршрута:

@route('/<int:id>/<username>/', endpoint='ClassName:profile')

Ответ 4

Я не понимаю, почему вы перенаправляете. Вы ничего не получаете с помощью перенаправления, и, как вы уже упоминали, вы в конечном итоге просто запрашиваете базу данных несколько раз. Вы не используете данное имя пользователя каким-либо значимым образом, поэтому просто игнорируйте его.

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

Это удовлетворит все ваши примеры.