0 added
0 removed
Original
2026-01-01
Modified
2026-02-26
1
<p><strong>Рассказываем про один из самых популярных и лаконичных микрофреймворков для Python - Flask. Как написать простое приложение, подключить к нему Bootstrap и базу данных, и есть ли вообще у Flask минусы.</strong></p>
1
<p><strong>Рассказываем про один из самых популярных и лаконичных микрофреймворков для Python - Flask. Как написать простое приложение, подключить к нему Bootstrap и базу данных, и есть ли вообще у Flask минусы.</strong></p>
2
<p>Flask - это микрофреймворк для создания веб-приложений на Python. В нем из коробки доступен только минимальный инструментарий, но при этом он поддерживает расширения так, как будто они реализованы в самом Flask. Расширения для микрофреймворка позволяют коммуницировать с базами данных, проверять формы, контролировать загрузку на сервер, работать с аутентификацией и многое другое.</p>
2
<p>Flask - это микрофреймворк для создания веб-приложений на Python. В нем из коробки доступен только минимальный инструментарий, но при этом он поддерживает расширения так, как будто они реализованы в самом Flask. Расширения для микрофреймворка позволяют коммуницировать с базами данных, проверять формы, контролировать загрузку на сервер, работать с аутентификацией и многое другое.</p>
3
<p>Первая публичная версия Flask вышла 16 апреля 2010 года. Автор проекта -<a>Армин Ронахер</a>, который возглавлял команду энтузиастов в Python-разработке Poocco. Flask основан на быстром и расширяемом механизме шаблонов Jinja и наборе инструментов Werkzeug. Кроме того, Flask использует одну из самых передовых служебных библиотек WSGI (Web Server Gateway Interface - стандарт взаимодействия между Python-программой, выполняющейся на стороне сервера, и самим веб-сервером).</p>
3
<p>Первая публичная версия Flask вышла 16 апреля 2010 года. Автор проекта -<a>Армин Ронахер</a>, который возглавлял команду энтузиастов в Python-разработке Poocco. Flask основан на быстром и расширяемом механизме шаблонов Jinja и наборе инструментов Werkzeug. Кроме того, Flask использует одну из самых передовых служебных библиотек WSGI (Web Server Gateway Interface - стандарт взаимодействия между Python-программой, выполняющейся на стороне сервера, и самим веб-сервером).</p>
4
<p>При этом WSGI тоже разработал Армин Ронахер. По его словам, идея Flask изначально была первоапрельской шуткой, которая стала популярной и превратилась в серьезное приложение.</p>
4
<p>При этом WSGI тоже разработал Армин Ронахер. По его словам, идея Flask изначально была первоапрельской шуткой, которая стала популярной и превратилась в серьезное приложение.</p>
5
<blockquote><h3>Изучите Python на Хекслете</h3>
5
<blockquote><h3>Изучите Python на Хекслете</h3>
6
<p>Пройдите нашу профессию "<a>Python-разработчик</a>", чтобы поменять свою жизнь и стать бэкенд-программистом.</p>
6
<p>Пройдите нашу профессию "<a>Python-разработчик</a>", чтобы поменять свою жизнь и стать бэкенд-программистом.</p>
7
</blockquote><h2>Содержание</h2>
7
</blockquote><h2>Содержание</h2>
8
<ul><li><a>Плюсы и минусы Flask</a></li>
8
<ul><li><a>Плюсы и минусы Flask</a></li>
9
<li><a>Как создать проект на Flask</a></li>
9
<li><a>Как создать проект на Flask</a></li>
10
<li><a>Структура приложения на Flask</a></li>
10
<li><a>Структура приложения на Flask</a></li>
11
<li><a>Подключаем Bootstrap</a></li>
11
<li><a>Подключаем Bootstrap</a></li>
12
<li><a>Подключаем базу данных</a></li>
12
<li><a>Подключаем базу данных</a></li>
13
<li><a>Что еще почитать про Flask</a></li>
13
<li><a>Что еще почитать про Flask</a></li>
14
</ul><h2>Плюсы и минусы Flask</h2>
14
</ul><h2>Плюсы и минусы Flask</h2>
15
<p>Практически все плюсы и минусы Flask появились именно из-за того, что он является микрофреймворком.</p>
15
<p>Практически все плюсы и минусы Flask появились именно из-за того, что он является микрофреймворком.</p>
16
<p>Среди достоинств:</p>
16
<p>Среди достоинств:</p>
17
<ul><li><strong>Простота.</strong>Flask легко установить и настроить.</li>
17
<ul><li><strong>Простота.</strong>Flask легко установить и настроить.</li>
18
<li><strong>Гибкость</strong>. Микрофреймворк позволяет разработчикам самостоятельно выбирать технологии и инструменты, которые они хотят применять в своих проектах</li>
18
<li><strong>Гибкость</strong>. Микрофреймворк позволяет разработчикам самостоятельно выбирать технологии и инструменты, которые они хотят применять в своих проектах</li>
19
<li><strong>Расширяемость.</strong>Flask позволяет расширять функциональность с помощью плагинов и модулей, которые можно легко интегрировать в проект.</li>
19
<li><strong>Расширяемость.</strong>Flask позволяет расширять функциональность с помощью плагинов и модулей, которые можно легко интегрировать в проект.</li>
20
<li><strong>Активное сообщество</strong>. Flask является одним из самых используемых фреймворков для Python, поэтому имеет большое комьюнити разработчиков.</li>
20
<li><strong>Активное сообщество</strong>. Flask является одним из самых используемых фреймворков для Python, поэтому имеет большое комьюнити разработчиков.</li>
21
</ul><p>При этом у Flask есть и свои недостатки:</p>
21
</ul><p>При этом у Flask есть и свои недостатки:</p>
22
<ul><li><strong>Отсутствие готовых решений.</strong>Разработчики изначально могут использовать во Flask только минимальный набор функциональности. Если же программисту нужны более широкие возможности, такие как аутентификация пользователя, ему придется добавить дополнительные библиотеки или реализовать это самостоятельно.</li>
22
<ul><li><strong>Отсутствие готовых решений.</strong>Разработчики изначально могут использовать во Flask только минимальный набор функциональности. Если же программисту нужны более широкие возможности, такие как аутентификация пользователя, ему придется добавить дополнительные библиотеки или реализовать это самостоятельно.</li>
23
<li><strong>Нет встроенной многопоточности</strong>. Flask был разработан как однопоточный фреймворк. И чтобы управлять многопоточными веб-приложениями, придется установить дополнительные библиотеки.</li>
23
<li><strong>Нет встроенной многопоточности</strong>. Flask был разработан как однопоточный фреймворк. И чтобы управлять многопоточными веб-приложениями, придется установить дополнительные библиотеки.</li>
24
<li><strong>Ограниченные возможности для масштабирования</strong>. Если проект начинает расти и усложняться, то могут появиться сложности в поддержке нужной функциональности.</li>
24
<li><strong>Ограниченные возможности для масштабирования</strong>. Если проект начинает расти и усложняться, то могут появиться сложности в поддержке нужной функциональности.</li>
25
</ul><p>То есть Flask можно удобно использовать в небольших проектах - он идеален для макетирования идей и быстрого прототипирования. При этом его редко используют в крупных проектах, и он плохо подходит для асинхронного программирования.</p>
25
</ul><p>То есть Flask можно удобно использовать в небольших проектах - он идеален для макетирования идей и быстрого прототипирования. При этом его редко используют в крупных проектах, и он плохо подходит для асинхронного программирования.</p>
26
<h2>Как создать проект на Flask</h2>
26
<h2>Как создать проект на Flask</h2>
27
<p>Для начала работы с микрофреймворком нужно<a>скачать</a>последнюю версию Flask:</p>
27
<p>Для начала работы с микрофреймворком нужно<a>скачать</a>последнюю версию Flask:</p>
28
<p>pip install Flask</p>
28
<p>pip install Flask</p>
29
<p>Для примера мы напишем на Flask тестовое веб-приложение с минимальным функционалом. Как работают приложения такого типа:</p>
29
<p>Для примера мы напишем на Flask тестовое веб-приложение с минимальным функционалом. Как работают приложения такого типа:</p>
30
<ul><li>Пользователь вводит в браузере url, например - hexlet.io. В нашем тестовом приложении пользователь не будет вводить url, потому что мы будем работать с локальным сервером по адресу http://127.0.0.1:5000.</li>
30
<ul><li>Пользователь вводит в браузере url, например - hexlet.io. В нашем тестовом приложении пользователь не будет вводить url, потому что мы будем работать с локальным сервером по адресу http://127.0.0.1:5000.</li>
31
<li>Браузер получает у DNS IP-адрес нужного нам сервера. DNS - это Domain Name System, распределенная системе серверов. Она работает как общая "контактная книга" в интернете.</li>
31
<li>Браузер получает у DNS IP-адрес нужного нам сервера. DNS - это Domain Name System, распределенная системе серверов. Она работает как общая "контактная книга" в интернете.</li>
32
<li>Браузер отправляет запрос по этому адресу и получает ответ. Как правило - в виде HTML-страницы.</li>
32
<li>Браузер отправляет запрос по этому адресу и получает ответ. Как правило - в виде HTML-страницы.</li>
33
<li>Браузер отображает содержимое страницы.</li>
33
<li>Браузер отображает содержимое страницы.</li>
34
</ul><p>Итак, создадим файл hello.py и запишем в него следующий код:</p>
34
</ul><p>Итак, создадим файл hello.py и запишем в него следующий код:</p>
35
<p>Давайте подробно разберем, что делает код, который мы написали.</p>
35
<p>Давайте подробно разберем, что делает код, который мы написали.</p>
36
<p>Первой строкой мы импортируем класс Flask. После этого мы создаем объект этого класса, передав первым аргументом имя модуля, - это и будет наше приложение для общения с веб-cервером. __name__ - это удобный способ передать именно то приложение, из которого запущен Flask.</p>
36
<p>Первой строкой мы импортируем класс Flask. После этого мы создаем объект этого класса, передав первым аргументом имя модуля, - это и будет наше приложение для общения с веб-cервером. __name__ - это удобный способ передать именно то приложение, из которого запущен Flask.</p>
37
<p>Декоратор route() сообщает Flask, при обращении к какому URL-адресу запустится декорируемая разработчиком функция - в нашем примере это index. Последней строчкой мы открываем локальный веб-сервер с параметром debug=True - это позволит следить за всеми ошибками в логе программы.</p>
37
<p>Декоратор route() сообщает Flask, при обращении к какому URL-адресу запустится декорируемая разработчиком функция - в нашем примере это index. Последней строчкой мы открываем локальный веб-сервер с параметром debug=True - это позволит следить за всеми ошибками в логе программы.</p>
38
<blockquote><h3>Читайте также:</h3>
38
<blockquote><h3>Читайте также:</h3>
39
<p>Программирование на Python:<a>особенности обучения</a>, перспективы, ситуация на рынке труда</p>
39
<p>Программирование на Python:<a>особенности обучения</a>, перспективы, ситуация на рынке труда</p>
40
</blockquote><p>Запускаем веб-приложение через терминал:</p>
40
</blockquote><p>Запускаем веб-приложение через терминал:</p>
41
<p>python hello.py</p>
41
<p>python hello.py</p>
42
<p>Если мы все сделали правильно, то в терминале появятся эти сообщения:</p>
42
<p>Если мы все сделали правильно, то в терминале появятся эти сообщения:</p>
43
<p>В основном тут отображается служебная информация. Единственное, что нас интересует - сообщение, что наш локальный сервер запущен по адресу http://127.0.0.1:5000/. В нем красными буквами указывается, что локальный сервер не подходит для продакшена. Но, так как мы реализовали тестовое приложение, то не будем деплоить его на реальный сервер.</p>
43
<p>В основном тут отображается служебная информация. Единственное, что нас интересует - сообщение, что наш локальный сервер запущен по адресу http://127.0.0.1:5000/. В нем красными буквами указывается, что локальный сервер не подходит для продакшена. Но, так как мы реализовали тестовое приложение, то не будем деплоить его на реальный сервер.</p>
44
<p>Вернемся к коду. С помощью переменной части маршрута Flask может передавать в функцию аргументы.</p>
44
<p>Вернемся к коду. С помощью переменной части маршрута Flask может передавать в функцию аргументы.</p>
45
<p>В нашем примере значения просто появятся в браузере как часть строки. На стартовой странице нашего сайта будет запускаться функция index(). В ней пользователю, помимо приветствия, будет предлагаться нажать на ссылку, при клике на которую он перейдет на user/Аникин/Георгий. Этот URL-маршрут будет обрабатываться уже функцией get_user.</p>
45
<p>В нашем примере значения просто появятся в браузере как часть строки. На стартовой странице нашего сайта будет запускаться функция index(). В ней пользователю, помимо приветствия, будет предлагаться нажать на ссылку, при клике на которую он перейдет на user/Аникин/Георгий. Этот URL-маршрут будет обрабатываться уже функцией get_user.</p>
46
<p>Функция get_user декорируется @app.route('/<surname>/<name>’), а в адресной строке у нас /user/Аникин/Георгий. То есть наша функция получает аргументы из URL-адреса, эти значения лежат между косых скобок. По умолчанию тип таких значений string принимает любой текст без косой черты. Но переменные маршрутов могут быть и иных типов: int, float, path и других. Типы указываются в формате <тип:имя переменной>.</p>
46
<p>Функция get_user декорируется @app.route('/<surname>/<name>’), а в адресной строке у нас /user/Аникин/Георгий. То есть наша функция получает аргументы из URL-адреса, эти значения лежат между косых скобок. По умолчанию тип таких значений string принимает любой текст без косой черты. Но переменные маршрутов могут быть и иных типов: int, float, path и других. Типы указываются в формате <тип:имя переменной>.</p>
47
<h2>Структура приложения на Flask</h2>
47
<h2>Структура приложения на Flask</h2>
48
<p>Создадим подкаталог flask_app с такой структурой файлов и папок:</p>
48
<p>Создадим подкаталог flask_app с такой структурой файлов и папок:</p>
49
<p>Чтобы написать приложение сложнее одной строки, в директории проекта должны находиться папки static и templates. Директория static содержит ресурсы, которые используются шаблонами. В том числе включая файлы CSS, JavaScript и картинки. Папка templates содержит только шаблоны с расширением *.html.</p>
49
<p>Чтобы написать приложение сложнее одной строки, в директории проекта должны находиться папки static и templates. Директория static содержит ресурсы, которые используются шаблонами. В том числе включая файлы CSS, JavaScript и картинки. Папка templates содержит только шаблоны с расширением *.html.</p>
50
<p>Заполним наши файлы кодом. Сначала - наш основной файл проекта app.py:</p>
50
<p>Заполним наши файлы кодом. Сначала - наш основной файл проекта app.py:</p>
51
<p>После этого - index.html:</p>
51
<p>После этого - index.html:</p>
52
<p>И файл about.html:</p>
52
<p>И файл about.html:</p>
53
<p>Для отображения HTML-шаблонов мы используем функцию render_template(). В нашем коде она принимает только имя шаблона и возвращает строку с результатом рендеринга шаблона.</p>
53
<p>Для отображения HTML-шаблонов мы используем функцию render_template(). В нашем коде она принимает только имя шаблона и возвращает строку с результатом рендеринга шаблона.</p>
54
<p>Однако render_template() может принимать неограниченное количество именованных аргументов, которые можно использовать в этом шаблоне. Это позволит решить проблему нашего тестового проекта - сейчас у нас две функции, две страницы, и очень много дублированного кода.</p>
54
<p>Однако render_template() может принимать неограниченное количество именованных аргументов, которые можно использовать в этом шаблоне. Это позволит решить проблему нашего тестового проекта - сейчас у нас две функции, две страницы, и очень много дублированного кода.</p>
55
<p>Напишем базовый шаблон base.html и пару его наследников. При этом блоки {% block smth %} … {% endblock %} - это части базового шаблона, которые можно заменить в наследнике. Переменные передаются по именам в конструкции {{ variable }}.</p>
55
<p>Напишем базовый шаблон base.html и пару его наследников. При этом блоки {% block smth %} … {% endblock %} - это части базового шаблона, которые можно заменить в наследнике. Переменные передаются по именам в конструкции {{ variable }}.</p>
56
<p>После появления файла с базовым HTML-шаблоном можем поправить наши остальные HTML-файлы:</p>
56
<p>После появления файла с базовым HTML-шаблоном можем поправить наши остальные HTML-файлы:</p>
57
<p>Кроме того, нужно поправить и основной файл Flask-проекта app.py:</p>
57
<p>Кроме того, нужно поправить и основной файл Flask-проекта app.py:</p>
58
<h2>Подключаем Bootstrap</h2>
58
<h2>Подключаем Bootstrap</h2>
59
<p><a>Bootstrap</a>- это открытый и бесплатный набор инструментов для создания сайтов и веб-приложений.</p>
59
<p><a>Bootstrap</a>- это открытый и бесплатный набор инструментов для создания сайтов и веб-приложений.</p>
60
<p>В нашем проекте в папке templates у нас есть подкаталог bootstrap, а в нем файл base.html - это немного модифицированная заготовка сайта-документации Bootstrap-Flask:</p>
60
<p>В нашем проекте в папке templates у нас есть подкаталог bootstrap, а в нем файл base.html - это немного модифицированная заготовка сайта-документации Bootstrap-Flask:</p>
61
<p>В файлах index.html и about.html заменим строку наследования на:</p>
61
<p>В файлах index.html и about.html заменим строку наследования на:</p>
62
<p>Второй путь подключения Bootstrap к проекту на Flask - через CDN. Подробнее об этом можно почитать в документации<a>фреймворка</a>.</p>
62
<p>Второй путь подключения Bootstrap к проекту на Flask - через CDN. Подробнее об этом можно почитать в документации<a>фреймворка</a>.</p>
63
<blockquote><h3>Читайте также:</h3>
63
<blockquote><h3>Читайте также:</h3>
64
<p>Как создатель Python Гвидо ван Россум устроился в Microsoft и теперь работает над<a>развитием CPython</a></p>
64
<p>Как создатель Python Гвидо ван Россум устроился в Microsoft и теперь работает над<a>развитием CPython</a></p>
65
</blockquote><p>После подключения Bootstrap нужно будет немного поправить основной файл нашего проекта app.py:</p>
65
</blockquote><p>После подключения Bootstrap нужно будет немного поправить основной файл нашего проекта app.py:</p>
66
<p>Последним элементом нашего веб-приложения будет форма отправки. Для этого нужно немного модифицировать index.html:</p>
66
<p>Последним элементом нашего веб-приложения будет форма отправки. Для этого нужно немного модифицировать index.html:</p>
67
<p>Вообще, Bootstrap может добавить огромное количество элементов в приложение буквально в несколько кликов. Мы ограничились четырьмя - три поля и одна кнопка. Ключевой элемент здесь - это {{ super() }}.</p>
67
<p>Вообще, Bootstrap может добавить огромное количество элементов в приложение буквально в несколько кликов. Мы ограничились четырьмя - три поля и одна кнопка. Ключевой элемент здесь - это {{ super() }}.</p>
68
<h2>Подключаем базу данных</h2>
68
<h2>Подключаем базу данных</h2>
69
<p>Итак, у нас есть форма отправки, но она пока ничего не делает с данными. Для нас было бы неплохо хранить, обрабатывать и в будущем легко извлекать данные этих форм. Обычно такие задачи решают с помощью реляционных баз данных (далее БД).</p>
69
<p>Итак, у нас есть форма отправки, но она пока ничего не делает с данными. Для нас было бы неплохо хранить, обрабатывать и в будущем легко извлекать данные этих форм. Обычно такие задачи решают с помощью реляционных баз данных (далее БД).</p>
70
<p>Есть большое количество способов работы с SQL-запросами во Flask. Мы можем использовать, например, sqlite3 и чистый SQL, а можем - библиотеку sqlite3 для Python. Кроме того, можно обернуть чистые SQL-запросы в код, либо использовать Psycopg2 для работы с PostgresSQL в Python (мы рекомендуем делать именно так и<a>вот почему</a>). Для примера в этом тексте мы<a>используем</a>библиотеку Flask<a>SQLAlchemy</a>(расширение для Flask), которая предлагает технологию ORM для взаимодействия с БД.</p>
70
<p>Есть большое количество способов работы с SQL-запросами во Flask. Мы можем использовать, например, sqlite3 и чистый SQL, а можем - библиотеку sqlite3 для Python. Кроме того, можно обернуть чистые SQL-запросы в код, либо использовать Psycopg2 для работы с PostgresSQL в Python (мы рекомендуем делать именно так и<a>вот почему</a>). Для примера в этом тексте мы<a>используем</a>библиотеку Flask<a>SQLAlchemy</a>(расширение для Flask), которая предлагает технологию ORM для взаимодействия с БД.</p>
71
<p>Подключаем базу данных к нашему проекту через файл app.py:</p>
71
<p>Подключаем базу данных к нашему проекту через файл app.py:</p>
72
<p>В нашей БД появился класс Event c атрибутами, который наследуется от db.Model. Это позволяет с помощью SQLAlchemy создать таблицу event, а поля нашего класса сделать колонками этой таблицы. Кроме того, мы определили магический метод __str__ для строкового отображения экземпляров класса - это пригодится для отображения в HTML.</p>
72
<p>В нашей БД появился класс Event c атрибутами, который наследуется от db.Model. Это позволяет с помощью SQLAlchemy создать таблицу event, а поля нашего класса сделать колонками этой таблицы. Кроме того, мы определили магический метод __str__ для строкового отображения экземпляров класса - это пригодится для отображения в HTML.</p>
73
<p>Для создания таблицы в блок if __name__ == ‘__main__’ мы добавили команду db.create_all(), а для обработки отправленной формы - метод add_event. Он работает с методом POST, который указывает Flask, что данные будут отправлены на сервер.</p>
73
<p>Для создания таблицы в блок if __name__ == ‘__main__’ мы добавили команду db.create_all(), а для обработки отправленной формы - метод add_event. Он работает с методом POST, который указывает Flask, что данные будут отправлены на сервер.</p>
74
<p>В методе POST мы считываем данные отправленной формы и создаем для каждой строки временную переменную. После этого мы создаем объект event класса Event, передаем наши временные переменные как именованные аргументы, добавляем event в БД и фиксируем изменения.</p>
74
<p>В методе POST мы считываем данные отправленной формы и создаем для каждой строки временную переменную. После этого мы создаем объект event класса Event, передаем наши временные переменные как именованные аргументы, добавляем event в БД и фиксируем изменения.</p>
75
<p>Нам осталось лишь немного поправить форму: в файле index.html в открывающем теге <form> добавим атрибуты action="{{ url_for('add_event') }}" method="POST". Теперь форма отправки по нажатию на кнопку "Записать" будет отправлять данные в базу данных.</p>
75
<p>Нам осталось лишь немного поправить форму: в файле index.html в открывающем теге <form> добавим атрибуты action="{{ url_for('add_event') }}" method="POST". Теперь форма отправки по нажатию на кнопку "Записать" будет отправлять данные в базу данных.</p>
76
<p>Добавим страницу отображения наших записей в новый файл Events.html:</p>
76
<p>Добавим страницу отображения наших записей в новый файл Events.html:</p>
77
<p>В файл app.py добавим view:</p>
77
<p>В файл app.py добавим view:</p>
78
<p>А в основном контейнере index.html добавим ссылку на эту страницу:</p>
78
<p>А в основном контейнере index.html добавим ссылку на эту страницу:</p>
79
<p>Наш тестовый проект на Flask готов! Его можно запустить на локальном сервере через команду python app.py (в некоторых случаях надо будет написать название директории перед названием файла app.py).</p>
79
<p>Наш тестовый проект на Flask готов! Его можно запустить на локальном сервере через команду python app.py (в некоторых случаях надо будет написать название директории перед названием файла app.py).</p>
80
<h2>Что еще почитать про Flask</h2>
80
<h2>Что еще почитать про Flask</h2>
81
<ul><li><a>Большой курс по Flask на Хекслете</a></li>
81
<ul><li><a>Большой курс по Flask на Хекслете</a></li>
82
<li><a>Документация Flask</a></li>
82
<li><a>Документация Flask</a></li>
83
<li><a>Цикл статей на Real Python</a></li>
83
<li><a>Цикл статей на Real Python</a></li>
84
<li><a>Проектирование RESTful API с помощью Python и Flask</a></li>
84
<li><a>Проектирование RESTful API с помощью Python и Flask</a></li>
85
</ul><blockquote><h3>Изучите Python на Хекслете</h3>
85
</ul><blockquote><h3>Изучите Python на Хекслете</h3>
86
<p>Пройдите нашу профессию "<a>Python-разработчик</a>", чтобы поменять свою жизнь и стать бэкенд-программистом.</p>
86
<p>Пройдите нашу профессию "<a>Python-разработчик</a>", чтобы поменять свою жизнь и стать бэкенд-программистом.</p>
87
</blockquote>
87
</blockquote>