Маршрутизация во Flask, декоратор route

Прежде чем приступим к решению второй из проблем, обозначенных в конце первого урока, отметим для себя следующее. Когда в браузере вводится адрес несуществующей страницы, например, http://127.0.0.1:5000/animals , то в качестве ответа сервер возвращает технический код 404, который говорит о том, что страница не найдена. Также передает свой стандартный html-документ, чтобы отобразить сообщение об этом в браузере.

Далее, когда в нашей программе аргумент декоратора route() будет определяться динамически (то есть в процессе выполнения приложения), мы уже не получим ответ 404 при обращении к несуществующей странице. Вместо этого ошибка будет другого рода, и для ее решения программу придется немного усложнить.

Итак, обычно сайт состоит из множества страниц. Вернемся к нашему представлению page (мы убрали второй аргумент из вызова render_template, чтобы не отвлекал внимание):

@app.route('/plants')
def page():
    return render_template('plants.html')

Заметим, что адрес в декораторе и имя файла по большому счету совпадают. Не обязательно, чтобы это было так: адрес может быть одним, а файловая структура проекта и непосредственно имена шаблонов ‒ другими. Однако в нашем случае если URL содержит /plants, отображается шаблон-документ plants.html. В случае /animal рендерим animal.html. Для /evolution отдаем evolution.html.

Чтобы не множить в программе однотипные представления, фреймворк Flask позволяет передавать в тело функции путь, содержащийся в route(), или его часть. Делается это так:

  1. В аргументе роута используется переменная, которой фреймворк присваивает часть URL. Чтобы отделить другие части адреса от переменной, последнюю заключают в угловые скобки.
  2. Эта же переменная передается в качестве аргумента в связанную функцию-представление.
  3. Как использовать полученное значение, дело самой функции. В нашем случае ‒ значение является частью имени файла-шаблона.
@app.route('/<file_name>')
def page(file_name):
    return render_template(f'{file_name}.html')

Теперь, если у вас есть файлы plants.html, animal.html и evolution.html, все они будут отображаться с помощью одного и того же представления. К сожалению, эта функция будет вызываться при обращении к любому URL типа "http://домен_сайта/слово".

Когда Jinja не находит шаблон, то генерирует исключение TemplateNotFound. В браузере вы увидите сообщение об этом и трассировку. Одним из способов решения данной проблемы может быть перехват исключения с помощью инструкции try-except языка Python. Обработать исключение можно по-разному, обычно генерируют ответ 404 с помощью функции abort модуля werkzeug.exceptions.

...
from jinja2.exceptions import TemplateNotFound
from werkzeug.exceptions import abort
 
...
 
@app.route('/<file_name>')
def page(file_name):
    try:
        return render_template(f'{file_name}.html')
    except TemplateNotFound:
        abort(404)

Werkzeug, как и Jinja, устанавливается вместе с фреймворком Flask и представляет собой библиотеку инструментов для WSGI-приложений. В свою очередь WSGI ‒ это стандарт или протокол, который обеспечивает взаимодействие python-программы и веб-сервера.

Основная причина, по которой не следует оставлять ошибку TemplateNotFound заключается в том, что если страница была запрошена поисковым роботом, то он может ее проиндексировать (будет считать обычной страницей сайта). А поскольку на ней нет ценной информации, это ухудшит позиции сайта в поисковой выдаче. Поэтому, отдавая роботу 404, сервер сообщает, что индексировать тут нечего.

В ряде случае вместо "Not Found" может потребоваться редирект ‒ перенаправление на другую страницу. В этом случае используется функция redirect, которую предварительно надо импортировать из модуля flask. Например,

return redirect('/plants', code=301)

или

return redirect(url_for('index'))

Во втором примере используется функция url_for (из модуля flask). Она перенаправляет на адрес, который привязан к функции index(). В нашем случае ‒ на главную страницу.

301 ‒ это код перманентного (постоянного) редиректа. Например, вы изменили адрес страницы в пределах сайта, а изменить ссылки на нее из внешних источников не можете. Код 302 обозначает временный редирект (допустим, на другой сайт). Этот код используется функцией redirect по-умолчанию.

Вернемся к декоратору route. Если в адресе используется переменная, то у нее кроме имени есть еще так называемый конвертер (преобразователь). Его можно не указывать, как в нашем примере.

@app.route('/<file_name>')

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

@app.route('/<string:file_name>')

В обоих случаях переменная может принимать любое строковое значение (числа будут преобразованы в строку), но оно не должно содержать слэшей. Чтобы увидеть разницу, временно избавимся от try-except и вернемся к простому варианту представления page:

@app.route('/<file_name>')
def page(file_name):
    return render_template(f'{file_name}.html')

Если сейчас в браузере ввести адрес http://127.0.0.1:5000/dsfafa, не имея в каталоге templates файла dsfafa.html, будет выброшено исключение TemplateNotFound. Потому что page() была вызвана, а render_template() не нашла соответствующий шаблон.

Однако если адрес будет, например, таким: http://127.0.0.1:5000/ru/dsfafa, получим ответ 404, так как никакое представление в программе не обрабатывает подобные URL. В таких случаях фреймворк по-умолчанию сообщает серверу, что он должен вернуть "Страница не найдена".

В принципе нас все устраивает. Если несуществующая страница указана через один слэш, генерируем 404 с помощью abort(). Если адрес сложнее, сам фреймворк сделает всю работу.

Теперь представим, что сайт состоит из разделов, и мы хотим, чтобы адрес содержал в себе информацию, к какому разделу принадлежит открываемая страница. Также, чтобы нам было проще ориентироваться, файловая структура в каталоге templates тоже не будет одноуровневой. Пусть контент о растениях хранится в каталоге botany, о животных ‒ в zoology и т. п.

Таким образом, на сайте будут адреса такого плана: /botany/plants, /botany/algae, /zoology/worms, /zoology/birds и т. п.

Конечно, мы можем создать для каждого раздела свое представление:

@app.route('/botany/<file_name>')
def botany_page(file_name):
    try:
        return render_template(f'botany/{file_name}.html') 
    except TemplateNotFound:
        return abort(404)
 
 
@app.route('/zoology/<file_name>')
def zoology_page(file_name):
    try:
        return render_template(f'zoology/{file_name}.html')
    except TemplateNotFound:
        return abort(404)

Заметим, что в функцию render_template на самом деле передается не имя файла, а его местонахождение в каталоге templates (по умолчанию). В данном случае папки botany и zoology находятся в нем, а уже в них ‒ файлы-шаблоны.

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

@app.route('/<section>/<file_name>')
def page(section, file_name):
    try:
        return render_template(f'{section}/{file_name}.html') 
    except TemplateNotFound:
        return abort(404)

Однако в нашем случае и этот вариант избыточен, так как мы никак отдельно не обрабатываем значение переменной section. Хорошо бы вернуться к варианту @app.route('/<file_name>'), но таким образом, чтобы file_name захватывал адрес целиком, со слэшами внутри.

Выше мы упоминали о конвертерах в route(). Если тип переменной указывается как path, то в отличие от варианта по-умолчанию она может содержать слэши.

@app.route('/<path:file_name>')
def page(file_name):
    try:
        return render_template(f'{file_name}.html') 
    except TemplateNotFound:
        return abort(404)

Здесь file_name может принимать значения botany/plants, zoology/dfdf/animals и т. п.

Другими типами конвертеров являются int, float и uuid. Так представление, связанное с маршрутом @app.route('/blog/<int:article>'), будет обрабатывать запросы по адресам /blog/1, /blog/135, но не /blog/13w.

Flask для начинающих




Все разделы сайта