Шаблоны
Ключом к разработке приложений, простых в сопровождении, является чистый и хорошо структурированный код. Примеры, которые вы видели до сих пор, слишком просты, чтобы продемонстрировать это, но проблема в том, что функции представлений преследуют две полностью разные цели, одна из которых скрыта от глаз.
Очевидной целью функций представления является создание ответов на запросы, как было показано ранее. Для простых запросов этого вполне достаточно, но в общем случае запросы вызывают изменение состояния приложения, и функции представления как раз являются тем местом, где производятся эти изменения.
Смешивание прикладной логики с логикой представления делает код сложным для понимания и сопровождения. Вообразите себе код, конструирующий большую HTML-таблицу путем объединения информации, извлеченной из базы данных, с литеральными строками, содержащими разметку HTML. Перемещение логики представления в шаблоны помогает упростить сопровождение приложения в будущем
Шаблон – это файл, содержащий текст ответа с переменными-заполнителями для подстановки динамических данных, которые могут быть получены только в контексте запроса. Процесс подстановки фактических значений на место переменных-заполнителей и возврата получившегося ответа называется отображением (rendering). Для решения задачи отображения шаблонов фреймворк Flask использует мощный механизм шаблонов Jinja2.
Механизм шаблонов Jinja2
В простейшем виде шаблон Jinja2 представляет собой текстовый файл с ответом.
<h1>Hello World!</h1>
В следующем примере приводится шаблон, реализующий ответ, который возвращает функция при использовании переменного пути:
<h1>Hello, {{ name }}!</h1>
Отображение шаблонов
По умолчанию фреймворк Flask ищет шаблоны в подкаталоге templates, находящемся внутри папки приложения. Необходимо сохранить шаблоны из примеров выше в новой папке templates, в файлах с именами index.html и user.html. Функции представления в приложении требуется изменить так, чтобы они отображали эти шаблоны:
from flask import Flask, render_template
# ...
@app.route('/index')
def index():
return render_template('index.html')
@app.route('/user/<name>')
def user(name):
return render_template('user.html', name=name)
Функция render_template
, предоставляемая фреймворком Flask, дает возможность интегрировать механизм шаблонов Jinja2 в приложение. Эта функция принимает имя файла шаблона в первом аргументе. Любые дополнительные аргументы, представленные парами ключ/значение, определяют фактические значения соответствующих переменных в шаблоне. В этом примере второй шаблон принимает переменную name
.
Переменные
Конструкция {{ name }}
в шаблоне ссылается на специальную переменную-заполнитель, сообщающую механизму шаблонов, что ее значение следует получить из данных, переданных во время выполнения операции отображения шаблона.
Механизм Jinja2 распознает переменные любых типов, даже составных, таких как списки, словари и объекты. Ниже приводится еще несколько примеров использования переменных в шаблонах:
<p>A value from a dictionary: {{ mydict['key'] }}.</p>
<p>A value from a list: {{ mylist[3] }}.</p>
<p>A value from a list, with a variable index: {{ mylist[myintvar] }}.</p>
<p>A value from an object’s method: {{ myobj.somemethod() }}.</p>
Переменные можно изменять с помощью фильтров, которые добавляются после имени переменной через символ вертикальной черты. Например, ниже показано, как перевести в верхний регистр все символы в тексте, содержащемся в переменной name
:
Hello, {{ name|capitalize }}
Перечислим некоторые наиболее часто используемые фильтры, поддерживаемые механизмом Jinja2:
- safe: Отображает содержимое без экранирования специальных символов;
- capitalize: Преобразует первый символ в верхний регистр, а остальные – в нижний;
- lower: Преобразует все символы в нижний регистр;
- upper: Преобразует все символы в верхний регистр;
- title: Преобразует первые символы в каждом слове в верхний регистр;
- trim: Удаляет начальные и завершающие пробельные символы;
- striptags: Удаляет теги HTML из содержимого перед отображением.
Управляющие структуры
Jinja2 поддерживает несколько управляющих структур, которые можно использовать для изменения потока интерпретации шаблона. Ниже показано, как можно использовать условные инструкции в шаблонах:
{% if user %}
Hello, {{ user }}!
{% else %}
Hello, Stranger!
{% endif %}
Часто в шаблонах бывает необходимо отобразить список элементов. Следующий пример показывает, как это можно реализовать с помощью цикла for
:
<ul>
{% for comment in comments %}
<li>{{ comment }}</li>
{% endfor %}
</ul>
Jinja2 также поддерживает макросы, напоминающие функции в языке Python. Например
{% macro render_comment(comment) %}
<li>{{ comment }}</li>
{% endmacro %}
<ul>
{% for comment in comments %}
{{ render_comment(comment) }}
{% endfor %}
</ul>
Для поддержки многократного использования макросов их можно сохранить в отдельном файле и затем импортировать этот файл во всех шаблонах :
{% import 'macros.html' as macros %}
<ul>
{% for comment in comments %}
{{ macros.render_comment(comment) }}
{% endfor %}
</ul>
Фрагменты шаблонов, которые повторяются в разных местах, также можно сохранять в отдельных файлах и затем подключать их там, где они необходимы:
{% include 'common.html' %}
Еще одна мощная возможность обеспечить повторное использование – наследование. Наследование шаблонов напоминает наследование классов в Python. Например, создадим сначала базовый шаблон с именем base.html:
<html>
<head>
{% block head %}
<title>{% block title %}{% endblock %} - My Application</title>
{% endblock %}
</head>
<body>
{% block body %}
{% endblock %}
</body>
</html>
Здесь теги block
определяют элементы, которые могут изменяться в производных шаблонах. В данном примере присутствуют блоки с именами head
, title
и body
. Обратите внимание, что блок title
находится внутри блока head
. Ниже приводится пример шаблона, наследующего базовый шаблон:
{% extends «base.html» %}
{% block title %}Index{% endblock %}
{% block head %}
{{ super() }}
<style>
</style>
{% endblock %}
{% block body %}
<h1>Hello, World!</h1>
{% endblock %}
Директива extends
объявляет, что этот шаблон наследует base.html. За ней следуют новые определения трех блоков, объявленных в базовом шаблоне, которые будут вставлены в соответствующие места. Обратите внимание, что новое определение блока head
, непустого в базовом шаблоне, использует функцию super()
, чтобы получить оригинальное содержимое.
Ссылки
Любое приложение, имеющее более одного маршрута, неизменно должно включать ссылки, связывающие разные страницы, такие как, например, ссылки в строке навигации.
Простое включение адресов URL в виде ссылок непосредственно в шаблон еще возможно для тривиально простых маршрутов, но для динамических маршрутов с переменными компонентами в адресах URL такой прием никуда не годится. Кроме того, явные адреса URL в шаблонах создают нежелательные зависимости между маршрутами. Если впоследствии будет принято решение о реорганизации системы маршрутов, ссылки в шаблонах могут оказаться недействительными.
Чтобы избежать этих проблем, Flask предоставляет вспомогательную функцию url_for()
, которая генерирует адреса URL из информации, хранящейся в карте адресов URL приложения.
В простейшем случае эта функция принимает имя функции представления (или имя конечной точки маршрута, определенного с помощью app.add_url_route()
) и возвращает готовый адрес URL. Например, в текущей версии приложения вызов url_for('index')
вернет /
. Вызов url_for('index', _external=True)
вернет абсолютный адрес URL, то есть в данном случае: http://localhost:5000/
.
Динамические адреса URL можно генерировать с помощью функции url_for()
, передавая ей динамические части в виде именованных аргументов. Например, вызов url_for('user', name='john', _external=True)
вернет http://localhost:5000/user/john
. В именованных аргументах функции url_for()
можно передавать не только динамические части маршрутов. Любые дополнительные аргументы она добавит в строку запроса. Например, вызов url_for('index', page=2)
вернет /?page=2
.
Статические файлы
Веб-приложения состоят не только из программного кода на языке Python и шаблонов. Большинство приложений также использует статические файлы, такие как изображения, сценарии на языке JavaScript и таблицы стилей CSS, ссылки на которые присутствуют в разметке HTML.
С настройками по умолчанию фреймворк Flask ищет статические файлы в подкаталоге static, находящемся в корневой папке приложения. При желании файлы можно организовать в подкаталоги внутри этой папки. В следующем примере показано, как можно подключить ярлык favicon.ico в базовом шаблоне для отображения в адресной строке браузера.
{% block head %}
{{ super() }}
<link rel="shortcut icon" href="{{ url_for('static', fi lename = 'favicon.ico') }}"
type="image/x-icon">
<link rel="icon" href="{{ url_for('static', fi lename = 'favicon.ico') }}"
type="image/x-icon">
{% endblock %}
Определение ярлыка добавлено в конец блока head
. Обратите внимание на вызов super()
, который выполняется для сохранения содержимого, определяемого базовыми шаблонами.
Локализация дат и времени с помощью Flask-Moment
Обработка дат и времени перестает быть тривиальной, когда приложение оказывается доступным для пользователей из разных уголков мира.
Чтобы не зависеть от географического местоположения каждого пользователя, сервер должен работать с некоторым универсальным представлением времени, поэтому на серверах обычно используется универсальное координированное время (Coordinated Universal Time, UTC). Однако, увидев время UTC, многие пользователи могут быть обескуражены, так как в подавляющем большинстве они предпочитают видеть даты и время в своем часовом поясе, отформатированные в соответствии с национальными правилами.
Наилучшим решением было бы оперировать на сервере временем исключительно в часовом поясе UTC и отправлять его браузеру, где преобразовывалось бы в местный часовой пояс и отображалось в соответствии с национальными настройками. И такое решение есть – отличная клиентская библиотека на JavaScript, отображающая даты и время в браузере, – moment.js. Для фреймворка Flask имеется расширение Flask-Moment , интегрирующее библиотеку moment.js в шаблоны Jinja2. Установить Flask-Moment можно с помощью утилиты pip
:
(venv) $ pip install flask-moment
В следующем примере показано, как выполняется инициализация расширения.
from flask.ext.moment import Moment
moment = Moment(app)
Данный пример показывает, как подключить библиотеку moment.js. в блоке scripts
базового шаблона.
{% block scripts %}
{{ super() }}
{{ moment.include_moment() }}
{% endblock %}
Для работы с отметками времени (timestamps) в расширении Flask-Moment имеется класс moment
, доступный для использования в шаб-
лонах. Здесь выполняется передача переменной current_time
в шаблон для отображения.
from datetime import datetime
@app.route('/')
def index():
return render_template('index.html', current_time=datetime.utcnow())
Далее показано, как осуществляется отображение current_time
в шаблоне.
<p>The local date and time is {{ moment(current_time).format('LLL') }}.</p>
<p>That was {{ moment(current_time).fromNow(refresh=True) }}</p>
Вызов format('LLL')
отобразит дату и время в соответствии с локальным часовым поясом и национальными настройками на клиентском компьютере.
Вызов fromNow()
во второй строке отобразит время в секундах относительно указанного в переменной и автоматически будет обновлять его с течением времени. Первоначально это время будет отображено как строка «a few seconds ago» (несколько секунд тому назад). Но так как параметр refresh
вынуждает постоянно обновлять прошедшее время, то если оставить страницу открытой на несколько минут, можно увидеть последовательно сменяющие друг друга надписи «a minute ago» (минуту тому назад), затем «2 minutes ago» (2 минуты тому назад) и так далее.
Вопросы для самопроверки
- Как обозначаются переменные в шаблоннах?
- Назовите функцию, которя используется для генерации шаблонов в Flask.
- Какие управляющие конструкции могут использоваться в шаблонах?
- Для чего используются директивы
import
,include
иextends
? - Какое расширение Flask можно использовать для локализации дат и времени?
Задания
- Создайте шаблон с переменной и сгенерируйте его с помощью соответствующей функции Flask.
- Добавьте в шаблон условный оператор для изменения вывода в зависимости от значения переменной.
- С помощью соответствующего расширения Flask выведите в шаблоне текущую дату в соответствии с клиентскими настройками.