Мега-Учебник Flask, Часть 2: Шаблоны (издание 2018)
- четверг, 11 января 2018 г. в 03:14:43
Эта статья является переводом второй части нового издания учебника Мигеля Гринберга, выпуск которого автор планирует завершить в мае 2018.Прежний перевод давно утратил свою актуальность.
Я, со своей стороны, постараюсь не отставать с переводом.
Перевел и опубликовал: Александр Драгункин
В этом втором выпуске серии Мега-Учебник Flask я расскажу о том, как работать с шаблонами.
Для справки ниже приведен список статей этой серии.
Примечание 1: Если вы ищете старые версии данного курса, это здесь.
Примечание 2: Если вдруг Вы хотели бы выступить в поддержку моей(Мигеля) работы в этом блоге, или просто не имеете терпения дожидаться неделю статьи, я (Мигель Гринберг)предлагаю полную версию данного руководства упакованную электронную книгу или видео. Для получения более подробной информации посетите learn.miguelgrinberg.com.
После завершения главы 1 вы должны иметь полностью работающее, но простое веб-приложение, имеющее следующую структуру файлов:
microblog\
venv\
app\
__init__.py
routes.py
microblog.py
Для запуска приложения вы установили FLASK_APP=microblog.py
в сеансе терминала, а затем выполняете запуск flask. Он запустит веб-сервер с приложением, которое вы можете открыть, введя http://localhost:5000
/ URL в адресной строке вашего веб-браузера.
В этой главе вы продолжите работу над тем же приложением, и, в частности, вы узнаете, как создавать более сложные веб-страницы, которые имеют более навороченную структуру и множество динамических компонентов. Если что-либо о приложении или технологическом процессе разработки пока неясно, перед продолжением просмотрите главу 1.
Ссылки GitHub для этой главы: Browse, Zip, Diff.
Я хочу, чтобы на домашней странице моего приложения для микроблогов был заголовок, приветствующий пользователя. На данный момент я проигнорирую тот факт, что приложение еще не имеет концепции пользователей, поскольку это произойдет позже. Вместо этого я собираюсь использовать вымышленного (mock) пользователя, которого я собираюсь реализовать как словарь Python, следующим образом:
user = {'username': 'Miguel'}
Создание mock-объектов — полезный метод, который позволяет вам сосредоточиться на одной части приложения, не беспокоясь о других частях системы, которые еще не существуют. Я хочу создать домашнюю страницу своего приложения, и я не хочу, чтобы отсутствие системы пользователя отвлекало меня. Поэтому я просто создаю объект пользователя.
Функция просмотра в приложении возвращает простую строку. Теперь я хочу расширить эту возвращаемую строку в полную HTML-страницу, возможно, что-то вроде этого:
from app import app
@app.route('/')
@app.route('/index')
def index():
user = {'username': 'miguel'}
return '''
<html>
<head>
<title>Home Page - Microblog</title>
</head>
<body>
<h1>Hello, ''' + user['username'] + '''!</h1>
</body>
</html>'''
Если Вы не знакомы с HTML, я рекомендую, чтобы Вы прочитали Markup HTML в Википедии для краткого введения.
Обновите функцию представления как показано выше и смотрите в Вашем браузере.
Я надеюсь, Вы согласитесь со мной, что решение, используемое выше, не достаточно хорошо. Подумайте, насколько сложным будет код в этой функции просмотра, когда у меня появятся сообщения в блоге от пользователей, которые будут постоянно меняться. Приложение также будет иметь больше функций просмотра, которые будут связаны с другими URL-адресами, поэтому представьте, если в один прекрасный день я решу изменить макет этого приложения и придется обновлять HTML в каждой функции просмотра. Это явно не вариант, который будет масштабироваться по мере роста приложения.
Если бы вы могли сохранить логику своего приложения отдельно от макета или презентации ваших веб-страниц, тогда все было бы намного лучше организовано, не так ли? Вы даже можете нанять веб-дизайнера для создания убойного сайта, когда вы закодируете логику приложения в Python.
Шаблоны помогают достичь этого разделения между презентацией и бизнес-логикой. В Flask шаблоны записываются как отдельные файлы, хранящиеся в папке templates
, которая находится внутри пакета приложения. Поэтому, убедившись, что вы находитесь в каталоге microblog
, создайте каталог, в котором будут храниться шаблоны:
(venv) $ mkdir app/templates
или так:
(venv) C:\microblog>mkdir app\templates
Ниже вы можете увидеть свой первый шаблон, который похож по функциональности на страницу HTML, возвращаемую функцией просмотра index()
выше. Запишите этот файл в app/templates/index.html
:
<html>
<head>
<title>{{ title }} - Microblog</title>
</head>
<body>
<h1>Hello, {{ user.username }}!</h1>
</body>
</html>
Это стандартная, очень простая HTML-страница. Единственная интересная вещь на этой странице состоит в том, что для динамического контента есть несколько заполнителей, заключенных в {{...}}
разделы. Эти заполнители представляют части страницы, которые являются переменными и будут определены только во время выполнения.
Теперь, когда презентация страницы была выгружена в шаблон HTML, функция просмотра может быть упрощена (файл \app\routes.py):
# -*- coding: utf-8 -*-
from flask import render_template
from app import app
@app.route('/')
@app.route('/index')
def index():
user = {'username': 'Miguel'}
return render_template('index.html', title='Home', user=user)
Это выглядит намного лучше, не так ли? Попробуйте эту новую версию приложения, чтобы посмотреть, как работает шаблон. Как только вы загрузите страницу в свой браузер, вы можете просмотреть исходный HTML-код и сравнить его с исходным шаблоном.
Операция, которая преобразует шаблон в полную HTML-страницу, называется рендерингом. Чтобы отобразить шаблон, мне пришлось импортировать функцию, которая поставляется с флаговой инфраструктурой под названием render_template()
. Эта функция принимает имя файла шаблона и переменную список аргументов шаблона и возвращает один и тот же шаблон, но при этом все заполнители в нем заменяются фактическими значениями.
Функция render_template()
вызывает механизм шаблонов Jinja2
, который поставляется в комплекте с Flask. Jinja2
заменяет блоки {{...}}
соответствующими значениями, заданными аргументами, указанными в вызове render_template()
.
Вы видели, как Jinja2
заменяет заполнители фактическими значениями во время рендеринга, но это всего лишь одна из многих мощных операций, поддерживаемых Jinja2
в файлах шаблонов. Например, шаблоны также поддерживают управляющие операторы, заданные внутри блоков {% ...%}
. Следующая версия шаблона index.html
добавляет условное выражение:
<html>
<head>
{% if title %}
<title>{{ title }} - Microblog</title>
{% else %}
<title>Welcome to Microblog!</title>
{% endif %}
</head>
<body>
<h1>Hello, {{ user.username }}!</h1>
</body>
</html>
Теперь шаблон немного умнее. Если функция просмотра забывает передать значение переменной заполнитель заголовка, вместо того, чтобы показывать пустой заголовок, шаблон предоставит значение по умолчанию. Вы можете попробовать, как это условие работает, удалив аргумент title в вызове render_template()
функции view (файл \app\routes.py).
Пользователь, вошедший в систему, вероятно, захочет увидеть последние сообщения от подключенных пользователей на домашней странице, поэтому теперь я собираюсь расширить приложение, для поддержки этого.
Еще раз, использую трюк с поддельным объектом, чтобы создать некоторых пользователей и некоторые сообщения, для демонстрации:
# -*- coding: utf-8 -*-
from flask import render_template
from app import app
@app.route('/')
@app.route('/index')
def index():
user = {'username': 'Эльдар Рязанов'}
posts = [
{
'author': {'username': 'John'},
'body': 'Beautiful day in Portland!'
},
{
'author': {'username': 'Susan'},
'body': 'The Avengers movie was so cool!'
},
{
'author': {'username': 'Иполит'},
'body': 'Какая гадость эта ваша заливная рыба!!'
}
]
return render_template('index.html', title='Home', user=user, posts=posts)
Для представления пользовательских сообщений я использую список, где каждый элемент является словарем, имеющим поля Author
и Body
. Когда я доберусь до реализации пользователей и сообщений в блоге по-настоящему, я постараюсь сохранить эти имена полей, чтобы вся работа, которую сделал для проектирования и тестирования шаблона домашней страницы, используя эти временные объекты, продолжала действовать и на момент когда я представляю реальных пользователей и сообщения из базы данных.
На стороне шаблона я должен решить новую проблему. Список сообщений может иметь любое количество элементов. Сколько сообщений будут представлены на странице? Шаблон не может делать какие-либо предположения о том, сколько записей существует, поэтому он должен быть готов к отображению стольких сообщений, сколько отправляет представление общим способом.
Для этого типа проблемы, Jinja2 предлагает for
структуры управления (файл app/templates/index.html):
<html>
<head>
{% if title %}
<title>{{ title }} - Microblog</title>
{% else %}
<title>Welcome to Microblog</title>
{% endif %}
</head>
<body>
<h1>Hi, {{ user.username }}!</h1>
{% for post in posts %}
<div><p>{{ post.author.username }} says: <b>{{ post.body }}</b></p></div>
{% endfor %}
</body>
</html>
Просто, не так ли? Попробуйте эту новую версию приложения и не забудьте поиграть с добавлением большего количества контента в список сообщений, чтобы увидеть, как шаблон адаптируется и всегда отображает все сообщения, отправляемые функцией представления.
Прим.переводчика: Все не так просто с кодировками. Я попробовал заменить приветствие на Превед
и получил ошибку. Следите за кодировкой файла index.html. Он должен быть сохранен в utf-8.
В большинстве веб-приложений вверху страницы имеется панель навигации с несколькими часто используемыми ссылками, такими как ссылка для редактирования вашего профиля, логина, выхода из системы и т.д. Я могу легко добавить панель навигации в index.html
шаблон с некоторым количеством HTML, но по мере того, как приложение будет расти, мне понадобится эта же панель навигации на других страницах. Не хотелось бы поддерживать несколько копий навигационной панели во многих HTML-шаблонах, это хорошая практика, обходиться без дублирования кода, если это возможно.
Jinja2 имеет функцию наследования шаблона, которая специально решает эту проблему. По существу, вы можете перемещать части макета страницы, которые являются общими для всех шаблонов, к базовому шаблону, из которого выводятся все остальные шаблоны.
Итак, теперь я определяю базовый шаблон base.html
, который включает в себя простую навигационную панель, а также логику заголовка, которую я реализовал ранее. Вам необходимо написать следующий шаблон в файле app/templates/base.html
:
<html>
<head>
{% if title %}
<title>{{ title }} - Microblog</title>
{% else %}
<title>Welcome to Microblog</title>
{% endif %}
</head>
<body>
<div>Microblog: <a href="/index">Home</a></div>
<hr>
{% block content %}{% endblock %}
</body>
</html>
В этом шаблоне я использовал оператор управления блоком, чтобы определить место, где производные шаблоны могут вставляться. Блокам присваивается уникальное имя, которое производные шаблоны могут ссылаться, когда они предоставляют свой контент.
С базовым шаблоном я теперь могу упростить index.html
, наследуя его от base.html
:
{% extends "base.html" %}
{% block content %}
<h1>Hi, {{ user.username }}!</h1>
{% for post in posts %}
<div><p>{{ post.author.username }} says: <b>{{ post.body }}</b></p></div>
{% endfor %}
{% endblock %}
Поскольку шаблон base.html теперь будет заботиться об общей структуре страницы, я удалил все эти элементы из index.html и оставил только часть содержимого. Оператор extends
устанавливает ссылку наследования между двумя шаблонами, так что Jinja2 знает, что когда ему предлагается визуализировать index.html
, он должен встроить его в base.html
. Два шаблона имеют согласованные операторы block
с именами content
, и именно так Jinja2 знает, как объединить два шаблона в один. Теперь, если мне нужно создать дополнительные страницы для приложения, я могу создать их как производные шаблоны из одного и того же шаблона base.html
, и именно так я могу иметь все страницы приложения, которые будут одинаково выглядеть и выглядеть без дублирования.