Шаг 3. Обслуживание статических файлов, добавление страниц и использование наследования шаблонов с приложением Flask
Область применения:Visual Studio Visual Studio для Mac
Visual Studio Code ![]()
В предыдущих шагах этого руководства рассматривалось создание минимального приложения Flask с одной страницей автономного HTML. Современные веб-приложения обычно состоят из многих страниц и используют общие ресурсы, такие как файлы CSS и JavaScript, для обеспечения согласованного стиля и реакции на события.
На этом шаге вы научитесь делать следующее:
- с помощью шаблонов элементов Visual Studio быстро добавлять новые файлы различных типов с удобным стереотипным кодом (шаг 3.1);
- обрабатывать статические файлы из кода (шаг 3.2, дополнительный);
- добавлять дополнительные страницы в приложение (шаг 3–3);
- использовать наследование шаблона для создания заголовка и панели навигации, которая используется на разных страницах (шаг 3–4).
Шаг 3–1. Знакомство с шаблонами элементов
По мере разработки приложения Flask обычно добавляется гораздо больше дополнительных файлов Python, HTML, CSS и JavaScript. Для каждого типа файла (и других файлов, таких как web.config, которые могут понадобиться при развертывании) Visual Studio предоставляет удобные шаблоны элементов для начала работы.
Чтобы просмотреть доступные шаблоны, перейдите к обозревателю решений, щелкните правой кнопкой мыши папку, в которой необходимо создать элемент, выберите Добавить>Новый элемент:

Чтобы использовать шаблон, выберите нужный шаблон, укажите имя файла и нажмите кнопку ОК. При добавлении элемента таким образом автоматически добавляется файл в проект Visual Studio и отмечает изменения для системы управления версиями.
Вопрос. Как Visual Studio определяет, какой шаблон элемента предложить?
Ответ. Файл проекта Visual Studio (PYPROJ) содержит идентификатор типа проекта, который помечает его как проект Python. Visual Studio использует этот тип идентификатора для отображения шаблонов элементов, подходящих для типа проекта. Таким образом Visual Studio может предоставлять богатый набор шаблонов элементов для многих типов проектов, который не требуется отсортировывать каждый раз.
Шаг 3–2. Обработка статических файлов из приложения
В веб-приложении, созданном с помощью Python (с помощью любой платформы), файлы Python всегда выполняются на сервере веб-узла и никогда не передаются на компьютер пользователя. Другие файлы, такие как каскадные таблицы стилей и JavaScript, используются исключительно браузером, поэтому сервер узла просто доставляет их без изменений всякий раз, когда они запрашиваются. Такие файлы называются статическими файлами, Flask может доставлять их автоматически без необходимости написания кода. В HTML-файлах, например, можно ссылаться на статические файлы, используя относительный путь в проекте. Первый раздел в этом шаге служит для добавления файла CSS в существующий шаблон страницы.
Если статический файл требуется предоставлять из кода, например через реализацию конечной точки API, Flask предоставляет удобный метод, который позволяет ссылаться на файлы, используя относительные пути в папке с именем static (в корневом каталоге проекта). Во втором разделе этого шага демонстрируется работа этого метода с помощью простого статического файла данных.
В любом случае можно упорядочить файлы в static так, как вам удобно.
Использование статического файла в шаблоне

- В обозревателе решений в проекте Visual Studio щелкните папку HelloFlask правой кнопкой мыши, выберите Добавить>Новая папка и назовите папку static .
- Щелкните папку static правой кнопкой мыши и выберите Добавить>Новый элемент. В открывшемся диалоговом окне выберите шаблон Таблица стилей, назовите файл site.css и нажмите кнопку ОК. Файл site.css появится в проекте и откроется в редакторе. Структура папки должна выглядеть похоже на структуру на следующем рисунке:
- Замените все содержимое site.css следующим кодом и сохраните файл:
.message
Обслуживание статического файла из кода
Flask предоставляет функцию с именем serve_static_file , которую можно вызвать из кода для ссылки на любой файл в папке static проекта. В рамках следующей процедуры создается простая конечная точка API, которая возвращает статический файл данных.
- Создайте папку static, если это еще не сделано: в обозревателе решений в проекте Visual Studio щелкните папку HelloFlask правой кнопкой мыши, выберите Добавить>Новая папка и назовите папку static .
- В папке static создайте статический файл данных JSON с именем data.json со следующим содержимым (бессмысленный пример данных):
@app.route('/api/data') def get_data(): return app.send_static_file('data.json')
Вопрос. Существуют ли соглашения для организации статических файлов?
Ответ. Вы можете добавить другие файлы CSS, JavaScript и HTML в свою папку static любым способом. Типичный способ организации статических файлов — это создание вложенных папок fonts, scripts и content (для таблиц стилей и других файлов).
Вопрос. Как обрабатывать переменные URL-адреса и параметры запроса в API?
Шаг 3–3. Добавление страницы в приложение
Добавление еще одной страницы в приложение означает:
- добавление функции Python, которая определяет представление;
- добавление шаблона для исправления страницы;
- добавление необходимой маршрутизации в файл urls.py проекта Flask.
Следующие действия добавляются на странице «Дополнительные сведения» проекта HelloFlask и связывают эту страницу с домашней страницей:
-
В обозревателе решений щелкните правой кнопкой мыши папку templates, выберите Добавить>Новый элемент, щелкните шаблон элемента HTML-страница, назовите файл about.html и нажмите кнопку ОК.
Совет Если команда Новый элемент не появляется в меню Добавить, убедитесь, что вы остановили приложение, чтобы среда Visual Studio вышла из режима отладки.
> Home >
@app.route('/about') def about(): return render_template( "about.html", title = "About HelloFlask", content = "Example app page for Flask.")
Вопрос. Имеет ли имя функции страницы значение в Flask?
Ответ. Нет, так как URL-адреса, для которых Flask вызывает функцию для получения ответа, определяет декоратор @app.route . Разработчики обычно подбирают имя функции в соответствии с маршрутом, но такое сопоставление необязательно.
Шаг 3–4. Использование наследования шаблона для создания заголовка и навигационной панели
Вместо создания явных навигационных ссылок на каждой странице современные веб-приложения обычно используют заголовок фирменной символики и панель навигации, которая предоставляет самые важные ссылки на страницы, всплывающие меню и т. д. Чтобы обеспечить согласованность приложения, на всех страницах необходимо использовать одинаковые заголовок и панель навигации, однако при этом в каждом шаблоне страницы не должен повторяться один и тот же код. Вместо этого необходимо определить общие компоненты всех страниц в одном месте.
Система шаблонов Flask (по умолчанию Jinja) предоставляет два способа повторного использования определенных элементов в нескольких шаблонах: включение и наследование.
- Включение. Это другие шаблоны страниц, которые вставляются в определенное место в ссылочном шаблоне, используя синтаксис %> . Кроме того, можно использовать переменную, если необходимо динамически изменять путь в коде. Включение обычно используется в тексте страницы для извлечения общего шаблона в определенном расположении на странице.
- Наследование. В начале шаблона страницы используется %> для определения общего базового шаблона, на основе которого затем создается ссылочный шаблон. Наследование обычно используется для определения общих макета, панели навигации и других структур для страниц приложения, так что ссылочным шаблонам необходимо только добавлять или изменять определенные области базового шаблона, называемые блоками.
В обоих случаях относится к папке templates приложения ( ../ или ./ также разрешены).
Базовый шаблон отделяет блоки с помощью тегов %> и . Если ссылающийся на него шаблон использует теги с тем же именем блока, содержимое его блока переопределяет содержимое базового шаблона.
Далее демонстрируется наследование:
-
В папке templates приложения создайте HTML-файл (с помощью пункта контекстного меню Добавить>Новый элемент или Добавить>HTML-страница) с именем layout.html и замените его содержимое приведенным ниже кодом. Вы увидите, что этот шаблон содержит блок под названием content, который должен быть заменен для всех ссылающихся страниц:
> "
.navbar < background-color: lightslategray; font-size: 1em; font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif; color: white; padding: 8px 5px 8px 5px; >.navbar a < text-decoration: none; color: inherit; >.navbar-brand < font-size: 1.2em; font-weight: 600; >.navbar-item < font-variant: small-caps; margin-left: 30px; >.body-content

Следующие шаги
Изучить тему подробнее можно с помощью следующих ресурсов:
- Сведения о дополнительных возможностях шаблонов Jinja, таких как поток управления, см. в документации по конструктору шаблонов Jinja (jinja.pocoo.org).
- Дополнительные сведения об использовании url_for см. в разделе url_for в документации по объекту приложения Flask (flask.pocoo.org).
- Исходный код, используемый в руководстве, на сайте GitHub: Microsoft/python-sample-vs-learning-flask
#6 Шаблоны во Flask
До этого момента HTML-строки записывались прямо в функцию представления. Это нормально в демонстрационных целях, но неприемлемо при создании реальных приложений. Большинство современных веб-страниц достаточно длинные и состоят из множества динамических элементов. Вместо того чтобы использовать огромные блоки HTML-кода прямо в функциях (с чем еще и неудобно будет работать), применяются шаблоны.
Шаблоны
Шаблон — это всего лишь текстовый файл с HTML-кодом и дополнительными элементами разметки, которые обозначают динамический контент. Последний станет известен в момент запроса. Процесс, во время которого динамическая разметка заменяется, и генерируется статическая HTML-страница, называется отрисовкой (или рендерингом) шаблона. Во Flask есть встроенный движок шаблонов Jinja, который и занимается тем, что конвертирует шаблон в статический HTML-файл.
Jinja — один из самых мощных и популярных движков для обработки шаблонов для языка Python. Он должен быть известен пользователям Django. Но стоит понимать, что Flask и Jinja – два разных пакета, и они могут использоваться отдельно.
Отрисовка шаблонов с помощью render_template()
По умолчанию, Flask ищет шаблоны в подкаталоге templates внутри папки приложения. Это поведение можно изменить, передав аргумент template_folder конструктору Flask во время создания экземпляра приложения.
Этот код меняет расположение шаблонов по умолчанию на папку jinja_templates внутри папки приложения.
app = Flask(__name__, template_folder="jinja_templates")
Сейчас в этом нет смысла, поэтому пока стоит продолжать использовать папку templates для хранения шаблонов.
Создаем новую папку templates внутри папки приложения flask_app . В templates — файл index.html со следующим кодом:
html lang="en"> head> meta charset="UTF-8"> title>Titletitle> head> body> p>Name: >p> body> html>
Стоит обратить внимание, что в «базовом» HTML-шаблоне есть динамический компонент > . Переменная name внутри фигурных скобок представляет собой переменную, значение которой будет определено во время отрисовки шаблона. В качестве примера можно написать, что значением name будет Jerry . Тогда после рендеринга шаблона выйдет следующий код.
html lang="en"> head> meta charset="UTF-8"> title>Titletitle> head> body> p>Name: Jerryp> body> html>
Flask предоставляет функцию rended_template для отрисовки шаблонов. Она интегрирует Jinja во Flask. Чтобы отрисовать шаблон, нужно вызвать rended_template() с именем шаблона и данными, которые должны быть в шаблоне в виде аргументов-ключевых слов. Аргументы-ключевые слова, которые передаются шаблонам, известны как контекст шаблона. Следующий код показывает, как отрисовать шаблон index.html с помощью render_template() .
from flask import Flask, request, render_template app = Flask(__name__) @app.route('/') def index(): return render_template('index.html', name='Jerry') #.
Важно обратить внимание, что name в name=’Jerry’ ссылается на переменную, упомянутую в шаблоне index.html .
Если сейчас зайти на https://localhost:5000/ , выйдет следующий ответ:

Если render_template() нужно передать много аргументов, можно не разделять их запятыми ( , ), а создать словарь и использовать оператор ** , чтобы передать аргументы-ключевые слова функции. Например:
@app.route('/') def index(): name, age, profession = "Jerry", 24, 'Programmer' template_context = dict(name=name, age=age, profession=profession) return render_template('index.html', **template_context)
Шаблон index.html теперь имеет доступ к трем переменным шаблона: name , age и profession .
Что случится, если не определить контекст шаблона?
Ничего не случится, не будет ни предупреждений, ни исключений. Jinja отрисует шаблон как обычно, а на местах пропусков использует пустые строки. Чтобы увидеть это поведение, необходимо изменить функцию представления index() следующим образом:
#. @app.route('/') def index(): return render_template('index.html') #.
Теперь при открытии https://localhost:5000/ выйдет следующий ответ:
html lang="en"> head> meta charset="UTF-8"> title>Titletitle> head> body> p>Name: p> body> html>
Сейчас должна сложиться картина того, как используются шаблоны во Flask, а в следующем разделе речь пойдет о том, как рендерить их в консоли.
Отрисовка шаблонов в консоли
Для тестирования рендерить шаблоны можно и в консоли. Это просто и не требует создания нескольких файлов. Для начала нужно запустить Python и импортировать класс Template из пакета jinja2 следующим образом.
>>> from jinja2 import Template
Для создания объекта Templates нужно передать содержимое шаблона в виде строки.
>>> t = Template("Name: >")
Чтобы отрендерить шаблон, нужно вызвать метод render() объекта Template вместе с данными аргументами-ключевыми словами
>>> t.render(name='Jerry') 'Name: Jerry'
В следующем уроке речь пойдет о шаблонизаторе Jinja.
Как добавить готовый веб элемент на флваск

Фронтенд сделан на Bootstrap с несколькими дополнительными JS скриптами – например, записи в блоге фильтруются (без перезагрузки страницы) по тегам с помощью скрипта isotope.js , при этом теги для фильтра скрипт получает из расширения Flask – FlatPages. Записи в блоге и карточки в портфолио можно перелистывать свайпом, без перезагрузки страницы. Bootstrap обеспечивает адаптивность: сайт одинаково хорошо смотрится на широкоформатном мониторе и на смартфоне.
Первый этап
На этом этапе мы установим Flask вместе со всеми нужными расширениями и зависимостями, напишем первый вариант кода для блога и сделаем два простейших шаблона.
Установка Flask
Сначала нужно создать папку для проекта и активировать виртуальное окружение:

Весь код и тестовый контент для этого этапа есть здесь .
Второй этап
На этом этапе мы сделаем первые шаблоны и подключим файл с настройками.
Шаблонизатор Jinja2
Flask использует шаблонизатор Jinja2. Синтаксис Jinja2 идентичен шаблонизатору Django и напоминает Python. Если вам еще не приходилось работать с Django, на этом этапе достаточно знать, что логика в Jinja2 заключается в такие фигурные скобки , а переменные – в такие > .
Шаблон Jinja2 представляет собой обычный html-файл, в котором блоки с логикой и переменными размещаются в уже упомянутых скобках. К шаблону можно подключать любые JS-скрипты, иконки, шрифты. Большое количество переменных можно передать в шаблон в виде словаря:

Перейдем к созданию первого шаблона, расширяющего index.html – header.html. Добавьте переменные в файл settings.txt:

Весь код и контент для этого этапа – здесь . Во второй части туториала мы завершим работу над приложением и загрузим статическую копию сайта на GitHub Pages.
Материалы по теме
- Django с нуля. Часть 1: пишем многопользовательский блог для клуба любителей задач Python
- Django с нуля. Часть 2: регистрация, авторизация, ограничение доступа
- Django с нуля. Часть 3: создание профилей, сжатие изображений, CRUD и пагинация
Объекты Response фреймворка Flask

Для возвращаемых ответов Flask использует класс под названием Response . Но в самом приложении он редко встречается. Flask оборачивает в него данные ответа как контейнер, при каждом обращении к URL, добавляя необходимую информацию HTTP ответа.
Мало кто знает, что Flask даёт возможность заменить стандартный класс на свой (пользовательский), что позволяет создать более гибкий ответ с переопределённым функционалом. В этой статье я собираюсь показать вам, как воспользоваться преимуществами этой технологии, чтобы упростить код приложения.
Как Flask отдаёт ответы?
Большинство Flask приложений не используют непосредственно класс Response . Но при этом Flask создаёт объект для каждого ответа. Как это работает?
Ответ начинается с того места, когда Flask вызывает функцию обработки запроса. Веб приложения использует маршрут и оканчивает работу вызовом функции render_template , которая вызывает рендеринг файла шаблона и возвращает строку:
@app.route('/index') def index(): # . return render_template('index.html')
Но обработчик маршрута Flask не обязан возвращать два значения (код состояния и HTTP заголовки):
@app.route('/data') def index(): # . return render_template('data.json'), 201,
Пример выше показывает, как Flask заменяет код ответа 200 по умолчанию на принудительный 201. В примере также установлен параметр Content-Type заголовка ответа, явно указывающий на содержащиеся в ответе данные в формате JSON (по умолчанию Flask возвращает тип HTML).
Приведенные примеры демонстрируют три базовых компонента ответа: данные или тело ответа, код состояния и заголовки. Экземпляр приложения Flask содержит функцию make_response() , которая принимает возвращаемое маршрутом значение (которое, в свою очередь, может быть одним значением или кортежем с одним, двумя или тремя значениями), и создаёт из возвращаемого маршрутом значения объект Response.
Подробности можно увидеть в консоли Python. Создайте виртуальную среду и установите в неё Flask. Затем запустите сессию Python и выполните следующее:
>>> from flask import Flask >>> app = Flask(__name__) >>> app.make_response(‘Hello, World’) >>> app.make_response((‘Hello, World’, 201))
Здесь я создал экземпляр приложения Flask и вызвал метод make_response() , создавший объект Response . В первом вызове я отправил строку как единственный аргумент, код возврата и заголовки заполняются автоматически значениями по умолчанию. Во втором вызове я отправил кортеж из двух значений, что привело к принудительной установке кода возврата. Обратите внимание на двойные круглые скобки. Это необходимость вызвана тем, что make_response() принимает только один аргумент, и мы отправили кортеж из двух значений (строка и код возврата).
После создания функцией маршрута объекта Response, Flask вызывает обработчик after_request (в числе прочих действий). Обработчик позволяет вставить или изменить заголовки, тело или код возврата. Возможно даже полностью выбросить ответ и/или создать новый. В конце Flask формирует итоговый объект ответа, обрабатывает его как HTTP и отправляет клиенту.
Класс Response
Давайте посмотрим на наиболее интересные аспекты класса ответа. Следующее определение класса показывает характерные атрибуты и методы:
class Response: charset = 'utf-8' default_status = 200 default_mimetype = 'text/html' def __init__(self, response=None, status=None, headers=None, mimetype=None, content_type=None, direct_passthrough=False): pass @classmethod def force_type(cls, response, environ=None): pass
Обращаем Ваше внимание, что в исходном коде Flask Вы не найдёте определений. Класс Response во Flask является небольшой обёрткой вокруг класса Response Werkzeug, который в свою очередь является обёрткой класса BaseResponse (внутри которого и определены элементы).
Три класса атрибутов charset , default_status и default_mimetype определены по умолчанию. При необходимости Вы можете создать собственный класс Response или наследовать имеющийся, и установить необходимые значения для каждого ответа. Для примера рассмотрим реализацию приложения API, которое возвращает XML на все маршруты. Вы можете установить значение default_mimetype в application/xml и Flask будет возвращать XML ответы по умолчанию.
Я не буду вдаваться в подробности описания конструктора __init__ (Вы прочтёте об этом самостоятельно в документации Werkzeug), но обратите внимание на три важных элемента ответов Flask: тело, код возврата и заголовок; которые передаются в качестве аргументов. В подклассе конструктор может переопределить правила формирования ответов.
Метод класса force_type() необычный, но очень важный. Иногда Werkzeug или Flask необходимо создать собственный объект ответа. Например сообщить клиенту о возникновении ошибки в приложении. В этом случае ответ не приходит из приложения. Программная платформа должна создать его (ответ) самостоятельно. В приложении, использующем пользовательский класс ответа, Flask и Werkzeug ничего не знают о деталях класса, поэтому они создают ответ используя стандартный класс. Метод force_type() допускает создание ответа на основе преобразования пользовательского экземпляра класса ответа в собственный формат.
Я уверен, что Вы запутались в описании force_type() . Суть заключается в том, что указанным методом Flask приводит пользовательский объект ответа к стандартному виду. Третий вариант, который я покажу на практике ниже, состоит в возвращении Flask маршрутами таких объектов, как словари, списки и другие пользовательские объекты.
Переходя от теории к практике, я покажу как работает Response. Готовы запачкать руки?
Использование собственного класса Response
Я уверен, что есть интересные случаи использования класса ответа. И перед тем, как их показать, рассмотрим настройку приложения Flask c использованием пользовательского класса ответа. Взгляните на следующий пример:
from flask import Flask, Response class MyResponse(Response): pass app = Flask(__name__) app.response_class = MyResponse # .
Здесь определен пользовательский класс с именем MyResponse . Обычно пользовательский класс добавляет или изменяет поведение по умолчанию, поэтому для создания пользовательских классов используем наследование от класса Response импортированного из модуля Flask. И далее сообщаем приложению Flask использовать наш пользовательский класс, подключив его к app.response_class .
Атрибут response_class класса Flask получает в наш класс ответа в качестве подкласса Flask и позволяет полностью описать наш вариант обработки ответов:
from flask import Flask, Response class MyResponse(Response): pass class MyFlask(Flask) response_class = MyResponse app = MyFlask(__name__) # .
Пример #1: Изменение ответа по умолчанию
Первый пример чрезвычайно прост. Мы указываем приложению возвращать XML для всех ответов. Для этого устанавливаем MIME-тип по умолчанию в application/xml . Буквально две строчки кода:
class MyResponse(Response): default_mimetype = 'application/xml'
Легко, не так ли? Пример:
@app.route('/data') def get_data(): return ''' John Smith '''
Использование пользовательского класса избавляет Вас от необходимости указывать тип возвращаемого ответа каждый раз для каждого маршрута. По умолчанию возвращается тип text/html . В случае необходимости возвращения разных типов для разных маршрутов, потребуется явное указание типа:
@app.route('/') def index(): return 'Hello, World!
',
Пример #2: Автоматическое определение Content-Type
Следующий пример сложнее. Допустим приложение имеет маршруты HTML и XML в равных количествах. Первый пример будет бесполезен, т.к. половина маршрутов будет с неверным типом содержимого.
Лучшим решением является создание класса ответа, который установит корректный тип на основе данных (текста). Рабочий вариант:
class MyResponse(Response): def __init__(self, response, **kwargs): if 'mimetype' not in kwargs and 'contenttype' not in kwargs: if response.startswith('
В этом примере сначала проверяем, не указан ли MIME-тип явно. А далее примем факт, что XML документ начинается со строки XML и отсылаю в конструктор родительского класса.
С помощью этого класса ответа, любой правильный формат XML-документа автоматически получит тип XML, в то время как другие ответы получат тип по умолчанию. И, при необходимости, тип содержимого будет указан явно.
Пример #3: Automatic JSON Responses
Последний пример иллюстрирует то, как обойти проблему при проектировании API интерфейса: вызов в каждом маршруте функции jsonify() для конвертации словаря Python в представление JSON и указание в ответе типа содержимого JSON. Выглядит это так:
@app.route('/data') def get_data(): return jsonify()
Повтор вызова функции jsonify() некорректен как с точки зрения философии Python, так и понижает само качество кода. Как Вам такой вариант?
@app.route('/data') def get_data(): return
Пользовательский класс ниже поддерживает описанный выше код. При этом не нарушает логику других маршрутов, которые не работают с JSON:
class MyResponse(Response): @classmethod def force_type(cls, rv, environ=None): if isinstance(rv, dict): rv = jsonify(rv) return super(MyResponse, cls).force_type(rv, environ)
Пояснение: Flask принимает только определённые типы значений, возвращаемых функцией маршрута ( str , unikbd , bytes , bytearray ), или Вы можете вернуть готовый объект ответа. Flask принимает и понимает такие типы.
Однако в нашем примере возвращается неподдерживаемый тип (словарь). Flask проверяет ответ и для неизвестного типа объекта вызывает метод force_type() , который преобразует неизвестный тип. В нашем примере перегруженный метод применяет необходимые преобразования к неизвестному типу (словарю) и вызывает функцию jsonify() .
Такой код не влияет на нормальную отдачу других ответов. Для любых маршрутов, которые возвращают нормальные типы ответа, подкласс ничего не делает, все вызовы передаются прозрачно для родительского класса.
Итог
Надеюсь, что эта статья проливает немного света на работу ответов Flask. Если вы знаете другие варианты формирования ответов Flask — я хотел бы их услышать!
Поделиться ссылкой:
- Нажмите, чтобы поделиться на Twitter (Открывается в новом окне)
- Нажмите, чтобы открыть на Facebook (Открывается в новом окне)
