Функция url_for и переменные URL-адреса

Следующая важная и очень распространенная функция, с которой мы познакомимся – это url_for(). Она позволяет генерировать URL-адрес по имени функции-обработчика. Например, рассмотрим вот такую программу:

from flask import Flask, render_template, url_for
 
app = Flask(__name__)
 
menu = ["Установка", "Первое приложение", "Обратная связь"]
 
@app.route("/")
def index():
    print( url_for('index') )
    return render_template('index.html', menu = menu)
 
@app.route("/about")
def about():
    print( url_for('about') )
    return render_template('about.html', title = "О сайте", menu = menu)
 
if __name__ == "__main__":
    app.run(debug=True)

Мы здесь дополнительно импортировали функцию url_for, а затем, вызываем ее в обработчиках index и about. Если теперь посетить данные страницы, то в консоли увидим соответствующие URL:

/ и /about

Но спрашивается: зачем она нам нужна, мы же и так знаем эти URL, поэтому, что нам мешает их явно указать? Если мы так будем делать, то столкнемся с множеством проблем. Во-первых, URL могут измениться, и тогда нам придется везде их менять, а если где-то забудем, то это негативно скажется на отображении страниц сайта. Или же, URL могут генерироваться динамически и тогда мы в принципе не сможем их явно прописать. Наконец, один и тот же шаблон для разных приложений, возможно, должен подставлять разный URL, например, для подключения внешних CSS или JS файлов. И это лишь малая толика проблем, с которыми мы можем столкнуться. Поэтому использовать явную запись URL – это пагубная практика и от нее лучше сразу отказаться, используя взамен функцию url_for. Ее полный синтаксис, следующий:

url_for(endpoint, **values)

где values – словарь именованных аргументов.

Вызов данной функции непосредственно связан с контекстом запроса и вне его она просто не будет работать. То есть, если выполнить ее за пределами наших обработчиков, например, так:

app = Flask(__name__)
 
menu = ["Установка", "Первое приложение", "Обратная связь"]
 
@app.route("/")
def index():
    return render_template('index.html', menu = menu)
 
@app.route("/about")
def about():
    return render_template('about.html', title = "О сайте", menu = menu)
 
print( url_for('index') )

То возникнет ошибка отсутствия контекста вызова. Как же можно в целях отладки тестировать ее работу? Для этого фреймворк Flask позволяет искусственно создавать контекст запроса без активации веб-сервера. Это делается так:

with app.test_request_context():
    print( url_for('index') )

Теперь все сработает и в консоли мы увидим URL для index.

Также эта функция корректно работает и в случае использования нескольких WSGI-приложений. Она обращается к переменной current_app и затем, берет URL из активного приложения.

Способы описания URL

В предыдущих примерах мы видели как с помощью декоратора route происходит привязка функции к URL-адресу. Но эти адреса можно делать и переменными, используя следующий синтаксис:

@app.route("/url/<variable>")

Здесь variable – это некоторая переменная, значение которой определяется  URL-адресом. Например, можно сделать так:

@app.route("/profile/<username>")
def profile(username):
    return f"Пользователь: {username}"

И если в браузере набрать запрос:

http://127.0.0.1:5000/profile/selfedu

то username примет значение selfedu. Или можно создать такой запрос:

http://127.0.0.1:5000/profile/12345678

Соответственно, username = «12345678», то есть все, что указывается в URL, записывается в виде строки.

При необходимости мы можем добавлять конверторы при определении переменных, например, указать, что следует использовать только целые числа:

@app.route("/profile/<int:username>")

Тогда при запросе вида:

http://127.0.0.1:5000/profile/12345678fff

Сервер вернет код страницы 404 – не существующий URL. И все потому, что предполагается запись в поле username из цифр. Если убрать буквы и обновить страницу, то все сработает.

В качестве конверторов можно использовать следующие обозначения:

  • int – должны присутствовать только цифры;
  • float – можно записывать число с плавающей точкой;
  • path – можно использовать любые допустимые символы URL плюс символ слеша ‘/’.

Например, при конверторе path:

@app.route("/profile/<path:username>")

Можно сформировать следующий запрос:

http://127.0.0.1:5000/profile/12345678ddd/fdfgh

и переменная username = «12345678ddd/fdfgh». Но, если убрать это определение:

@app.route("/profile/<username>")

то этот же запрос приведет к ошибке 404, т.к. обратный слеш воспринимается как продолжение URL, не относящееся к полю username.

В заключение этого занятия давайте посмотрим на работу функции url_for для переменных URL. Для создадим тестовый контекст запроса и в нем вызовем url_for для разных представлений:

with app.test_request_context():
    print(url_for('index'))
    print(url_for('about'))
    print(url_for('profile', username="selfedu"))

В консоли увидим следующие строчки:

/
/about
/profile/selfedu

Обратите внимание на последний вызов. Так как для profile реализован переменный URL с полем username, то этот параметр нужно явно прописать, чтобы получить конечный вид URL-адреса. Без этого параметра функция url_for приведет к ошибке.

Итак, на этом занятии мы с вами рассмотрели понятия контекста приложения и контекста запроса, познакомились с функцией url_for и увидели как можно описывать переменные URL-адреса.

Видео по теме