#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.
Мега-Учебник Flask, Часть 2: Шаблоны
Это вторая статья в серии, где я описываю свой опыт написания веб-приложения на Python с использованием микрофреймворка Flask.
Цель данного руководства — разработать довольно функциональное приложение-микроблог, которое я за полным отсутствием оригинальности решил назвать microblog .
Оглавление
Краткое повторение
Если вы следовали инструкциям в первой части, то у вас должно быть полностью работающее, но еще очень простое приложение с такой файловой структурой:
microblog\ flask\ app\ static\ templates\ __init__.py views.py tmp\ run.py
Для запуска приложения вы запускаете скрипт run.py, затем открываете url http://localhost:5000 в вашем браузере.
Мы продолжим с того момента, где мы остановились, так что вам следует убедиться, что вышеупомянутое приложение правильно установлено и работает.
Зачем нам нужны шаблоны
Рассмотрим то, как мы можем расширить наше маленькое приложение.
Мы хотим, чтобы на главной странице нашего приложения микроблогов был заголовок, который приветствует вошедшего в систему пользователя, что весьма стандартно для приложений такого рода. Пока что игнорируем тот факт, что у нас нет пользователей в приложении, я предоставлю решение этой проблемы в нужный момент.
Простым средством вывода большого и красивого заголовка было бы сменить нашу функцию представлений на выдачу html, примерно так:
from app import app @app.route('/') @app.route('/index') def index(): user = < 'nickname': 'Miguel' ># выдуманный пользователь return ''' Home Page Hello, ''' + user['nickname'] + '''
'''
Попробуйте посмотреть как выглядит приложение в вашем браузере.
Пока у нас нет поддержки пользователей, тем не менее, я обратился к использованию прототипов модели пользователя, которые иногда называют фальшивыми или мнимыми прототипами. Это позволяет нам сконцентрироваться на некоторых аспектах нашего приложения, зависящих от частей системы, которые еще не были написаны.
Надеюсь вы согласитесь со мной, что решение выше очень уродливое. Представьте насколько сложным станет код, если вы должны возвращать громоздкую HTML страницу с большим количеством динамического содержимого. И что если вам нужно сменить макет вашего веб-сайта в большом приложении с множеством представлений, которые возвращают HTML напрямую? Это очевидно не масштабируемое решение.
Шаблоны спешат на помощь
Не задумывались ли вы о том, что если бы вы могли держать раздельно логику вашего приложения и макет, или представление ваших страниц было бы организовано куда лучше? Вы даже можете нанять веб-дизайнера, чтобы создать сногсшибательный сайт в то время, пока вы программируете его [сайта] поведение при помощи Python. Шаблоны помогут осуществить это разделение.
Напишем наш первый шаблон (файл app/templates/index.html ):
> - microblog Hello, >!
Как видно выше, мы просто написали стандартную HTML страничку, но с одним лишь отличием: плейсхолдеры для динамического содержимого заключены в секции >
Теперь рассмотрим использование шаблона в нашей функции представления (файл app/views.py ):
from flask import render_template from app import app @app.route('/') @app.route('/index') def index(): user = < 'nickname': 'Miguel' ># выдуманный пользователь return render_template("index.html", title = 'Home', user = user)
Запустите приложение на данном этапе, чтобы посмотреть как работают шаблоны. Если в вашем браузере отрисована страница, то вы можете сравнить ее исходный код с оригинальным шаблоном.
Чтобы отдать страницу, нам нужно импортировать из Flask новую функцию под названием render_template. Эта функция принимает имя шаблона и список переменных аргументов шаблона, а возвращает готовый шаблон с замененными аргументами.
Под капотом: функция render_template вызывает шаблонизатор Jinja2, который является частью фреймворка Flask. Jinja2 заменяет блоки > на соответствующие им значения, переданные как аргументы шаблона.
Управляющие операторы в шаблонах
Шаблоны Jinja2 помимо прочего поддерживают управляющие операторы, переданные внутри блоков . Давайте добавим оператор if в наш шаблон (файл app/templates/index.html ):
> - microblog Welcome to microblog Hello, >!
Теперь наш шаблон слегка поумнел. Если мы забудем определить название страницы в функции представления, то взамен исключения шаблон предоставит нам собственное название. Вы спокойно можете удалить аргумент заголовка из вызова render_template в нашей функции представления, чтобы увидеть как работает оператор if.
Циклы в шаблонах
Вошедший в наше приложение пользователь наверняка захочет увидеть недавние записи от пользователей из его контакт-листа на главной странице, давайте посмотрим как же это сделать.
В начале, мы провернем трюк, чтобы создать несколько ненастоящих пользователей и несколько записей для отображения (файл app/views.py ):
def index(): user = < 'nickname': 'Miguel' ># выдуманный пользователь posts = [ # список выдуманных постов < 'author': < 'nickname': 'John' >, 'body': 'Beautiful day in Portland!' >, < 'author': < 'nickname': 'Susan' >, 'body': 'The Avengers movie was so cool!' > ] return render_template("index.html", title = 'Home', user = user, posts = posts)
Чтобы отобразить пользовательские записи мы используем список, где у каждого элемента будут поля автор и основная часть. Когда мы доберемся до осуществления реальной базы данных мы сохраним эти имена полей, так что мы можем разрабатывать и тестировать наш шаблон, используя ненастоящие объекты, не беспокоясь об их обновлении, когда мы перейдем на базу данных.
Со стороны шаблона мы должны решить новую проблему. Имеющийся список может содержать любое количество элементов и нужно решить сколько сообщений будет представлено. Шаблон не может сделать никаких предположений о количестве сообщений, поэтому он должен быть готов отобразить столько сообщений, сколько пошлет представление.
Посмотрим как сделать это, используя управляющую структуру (файл app/templates/index.html ):
> - microblog microblog Hi, >!
> says: >
Не так уж и трудно, правда? Проверим приложение и обязательно проверим добавление нового содержимого в список записей.
Наследование шаблонов
Мы охватим еще одну тему, прежде чем закончить на сегодня.
Нашему приложению микроблогов необходима навигационная панель с несколькими ссылками сверху страницы. Там будут ссылки на редактирование вашего профиля, выход и т.д.
Мы можем добавить навигационную панель в наш шаблон index.html , но, как только наше приложение разрастется, нам понадобится больше шаблонов, и навигационную панель нужно будет скопировать в каждый из них. Тогда вы должны держать все эти копии одинаковыми. Это может стать трудоемкой задачей, если у вас много шаблонов.
Вместо этого мы можем использовать наследование в шаблонах Jinja2, которое позволяет нам переместить части макета страницы в общий для всех базовый шаблон, из которого все остальные шаблоны будут наследоваться.
Теперь определим базовый шаблон, который включает в себя навигационную панель, а также логику заголовка, которую мы реализовали ранее (файл app/templates/base.html ):
> - microblog microblog Microblog: Home
В этом шаблоне мы использовали управляющий оператор block для определения места, куда могут быть вставлены дочерние шаблоны. Блокам даются уникальные имена.
Теперь осталось изменить наш index.html так, чтобы он наследовался от base.html (файл app/templates/index.html ):
Hi, >!
> says: >
Теперь только шаблон base.html отвечает за общую структуру страницы. Мы убрали те элементы отсюда и оставили только часть с содержимым. Блок extends устанавливает наследственную связь между двумя шаблонами, таким образом Jinja2 знает: если нам нужно отдать index.html , то нужно включить его в base.html . Два шаблона имеют совпадающие операторы block с именем content , именно поэтому Jinja2 знает как cкомбинировать два шаблона в один. Когда мы будем писать новые шаблоны, то также станем создавать их как расширения base.html
Заключительные слова
Если вы хотите сэкономить время, то приложение микроблога в текущей стадии доступно здесь:
Скачать microblog-0.2.zip
Учтите, что zip файл не содержит в себе виртуального окружения flask. Создайте его сами, следуя инструкциям в первой части, после этого вы сможете запустить приложение.
В следующей части серии мы взглянем на формы. Надеюсь увидимся.
Alexis Hardware World
Публикация Python сайта на базе Flask на веб сервере nginx
Существуют готовые Python хостинги, такие как Heroku и PythonAnywhere, которые позволяют запустить Python код за считанные минуты, но это не наша цель сегодня.

Примечание: виртуальный хостинг подойдет только в том случае, если там глобально установлен Python и для запуска скриптов пользователям доступна обертка FastCGI и SSH доступ. Такой сценарий описан в некоторых статьях хостеров: ActiveCloud и TimeWeb.
Почему мы рассматриваем Flask, а не Django? Он проще и идеально подходит для небольших проектов и тестирования. Django — это профессиональный фреймворк для промышленных проектов.
В мире Python существует довольно много различных супервизоров, предназначенных для запуска веб-приложений Python: uWSGI, gunicorn, gevent, twisted web и т.д. Они выступают подсредником между веб сервером Apache или nginx и обработчиком Python, которые проксируют запросы на супервизор. Однако, пожалуй, наиболее производительным и гибким супервизором является uWSGI. Опять же, для небольших проектов я рекомендую gunicorn, который проще в настройке. Хотя есть масса примеров успешного использования gunicorn в высоконагруженных проектах.
Таким образом в данной статье мы будем использовать связку CentOS + Python + gunicorn + Flask.
План развертывания Flask приложения на VPS сервере
В этой статье мы считаем, что nginx уже развернут и работает. Итак, вот типичные шаги, которые нужно сделать на любом хостинге вне зависимости от операционной системы и версии:
- Установка python 3 и pip 3 на ОС
- Развертывание виртуальной среды virtualenv
- Установка Python, Flask и gunicron и обновление pip в virtualenv
- Копируем проект Python с локальной машины на хостинг в папку виртуального проекта
- Разрешаем порт 5000 в файрволе наружу
- Запускаем python с Flask проектом и проверяем его доступность на 5000 порту
- Создаем точку входа WSGI
- Запускаем gunicorn на 5000 порту и проверяем доступность приложения через супервизор
- Деактивируем виртуальную среду
- Создаем системный сервис для gunicorn с автозапуском и стартуем его
- Настраиваем nginx для проксирования запросов в gunicorn
- Перезапускаем nginx
Развертывание Flask по шагам
Шаг 1. Установка python 3 и pip 3 на ОС
В CentOS 7 по умолчанию установлен Python 2.7.5 (можно проверить командой python -V). Нам необходимо установить 3 версию совместно со 2й. Вы можете заменить стандартную версию, изменив символьную ссылку python с /usr/bin/python2 на /usr/bin/python36. Но если кому-то нужна будет старая версия, начнутся проблемы. Например, перестанет работать yum 🙂 Поэтому я рекомендую системный python не трогать.
Существует несколько способов установить python: собрать из исходников (выкачать последнюю версию с python.org), из репозитария epel и из репозитария ius. Сейчас в epel версии 3.6 python и pip доступны такие же, как в ius (раньше были старее), поэтому не вижу смысла заморачиваться в ius. Из исходников стоит ставить только, если вам нужна самая свежая версия 3.8 на момент написания статьи.
yum install -y python36 python3 -V yum install -y python3-pip yum install -y python3-devel
Возможно, вам дополнительно потребуются пакеты python3-dev, build-essential, libssl-dev, libffi-dev, python3-setuptools. Их установка на ваше усмотрение.
Шаг 2. Настройка виртуальной среды
Установим пакет virtualenv:
yum install -y python36-virtualenv
Создадим и перейдем в папку, где хотим создать наш проект. Укажите такое название папки, которое у вас используется в самом проекте в PyCharm. У меня это не корневая папка корневой директории аккаунта (так сложнее, но необходимо, когда у вас несколько сайтов на одной аккаунте).
cd /home/user/public_html/py/testsite/
Создаем тестовый проект в виртуальной среде:
python3 -m venv testprj
Локальные копии Python и pip будут установлены в каталог testprj в каталоге вашего проекта.
Прежде чем устанавливать приложения в виртуальной среде, ее нужно активировать. Для этого нужно ввести следующую команду:
source testprj/bin/activate
Командная строка изменится, показывая, что теперь вы работаете в виртуальной среде. Она будет выглядеть примерно так: (testprj) user@host:~/testsite$ .
Шаг 3. Установка пакетов в виртуальной среде
Находясь в виртуальной среде, вы можете установить Flask и Gunicorn и начать работу над созданием вашего приложения.
Вначале мы установим wheel с локальным экземпляром pip , чтобы убедиться, что наши пакеты будут устанавливаться даже при отсутствии архивов wheel, апгрейдим pip и устанавливаем Flask и gunicorn:
pip install wheel pip install --upgrade pip pip install gunicorn flask flask-sqlalchemy

Примечание: В виртуальной у нас стоит только 3 версия python и pip, поэтому мы используем команды python и pip, а не python 3 и pip3.
Шаги 4-6. Заливаем и тестируем приложение в виртуальной среде
Копируем по SFTP/FTP файлы в папку вашей виртуальной среды, в моем случае это /home/ipnets/public_html/py/testsite/ вместе с папками static и templates, которые обязательны для Flask проекта.
Также необходимо убедиться и исправить ваш код Python, чтобы он запускался не только на localhost (по умолчанию это так), но и на любом IP. Для этого в коде главной станицы проекта, с помощью которой вы запускаете приложение, укажите host:
if __name__ == "__main__": app.run(host='0.0.0.0')
Проверяем запуск приложения и открываем его через браузер. Мы помним, что Flask по умолчанию слушает на 5000 порту. Для того, чтобы открыть сайт из интернета на этом порту, необходимо разрешить это порт в файрволе. В iptables это делается так:
iptables -A INPUT -p tcp -m tcp --sport 5000 -j ACCEPT iptables -A OUTPUT -p tcp -m tcp --dport 5000 -j ACCEPT

Приложение у меня вызывается файлом app.py:
python app.py
После запуска вы должны увидеть следующее:
* Serving Flask app "app" (lazy loading) * Environment: production WARNING: Do not use the development server in a production environment. Use a production WSGI server instead. * Debug mode: off * Running on (Press CTRL+C to quit)
Если все открылось по http://your_server_ip:5000 — python и Flask проект настроены верно в виртуальном окружении. Когда вы закончите, нажмите CTRL+C в окне терминала, чтобы остановить сервер разработки.
Шаг 7. Создание точки входа WSGI
Теперь в виртуальной среде создадим файл, который будет служить точкой входа в наше приложение. Он будет лежать там же, где и приложение. Это покажет серверу Gunicorn, как взаимодействовать с приложением.
Мы назовем этот файл wsgi.py :
vim wsgi.py
from app import app if __name__ == "__main__": app.run()
Здесь важно, что первый app — это название вашего приложения, а второй app — это экземпляр объекта, который мы импортируем из нашего приложения (по сути, объявленная в приложении переменная app). У меня они совпадают, но это случайность.
Шаг 8-9. Настраиваем gunicorn в виртуальной среде
Нужно убедиться, что Gunicorn может правильно обслуживать приложение. Для этого нужно просто передать имя нашей точки входа. Оно составляется из имени файла модуля (без расширения .py ) и имени вызываемого элемента приложения. В нашем случае это wsgi:app .
Также мы укажем интерфейс и порт 5000 для привязки, чтобы приложение запускалось через общедоступный интерфейс. Во время тестирования я обнаружил, что если мы делаем более одного обращения к Gunicorn, то он зависает и выдает 502 ошибку Gateway Timeout. Для решения этой проблемы я просто добавил воркеров, иначе gunicorn уходит в бесконечный цикл.
gunicorn --bind 0.0.0.0:5000 wsgi:app --workers=3
Опять же, если приложение открывается через браузер на 5000 порту, то все хорошо. Можно закрыть gunicorn, нажав CTRL+C в окне терминала и деактивировать среду:
deactivate
Теперь любые команды Python снова будут использовать системную среду Python.
Шаг 10. Создаем системный сервис для gunicorn
Мы создадим файл служб systemd. Создание файла элементов systemd позволит системе инициализации Ubuntu автоматически запускать Gunicorn и обслуживать приложение Flask при загрузке сервера.
Для начала создаем файл с расширением .service в каталоге /etc/systemd/system :
Содержимое файла будет следующим:
[Unit] Description=Gunicorn instance to serve testrj After=network.target [Service] User=ipnets Group=nginx WorkingDirectory=/home/ipnets/public_html/py/testsite Environment="PATH=/home/ipnets/public_html/py/testsite/testprj/bin" ExecStart=/home/ipnets/public_html/py/testsite/testprj/bin/gunicorn --workers 3 --bind 127.0.0.1:5000 wsgi:app [Install] WantedBy=multi-user.target
Мы начнем с раздела [Unit] этого файла, где указываются метаданные и зависимости. Здесь мы разместим описание службы и предпишем системе инициализации запускать ее только после достижения сетевой цели.
В разделе [Service] указывается пользователь и группа, от имени которых мы хотим запустить данный процесс. Сделаем владельцем процесса учетную запись обычного пользователя, поскольку этот пользователь является владельцем всех соответствующих файлов. Также назначим владельцем группу nginx , чтобы упростить коммуникацию Nginx с процессом Gunicorn (в некоторых конфигурациях это будет группа www-data или nobody ).
Теперь укажем рабочий каталог приложения и зададим переменную среды PATH , чтобы система инициализации знала, что исполняемые файлы этого процесса находятся в нашей виртуальной среде. Systemd требует, чтобы мы указывали полный путь исполняемого файла Gunicorn, установленного в нашей виртуальной среде. Также укажем команду для запуска службы gunicorn. Эта команда будет выполнять следующее:
- Запуск 3 рабочих процессов (хотя вы можете изменить это при необходимости). Количество процессор вычисляется по формуле: (2*CPU_core)+1, где CPU_core — количество ядер процессора. Так, для двухядерной системы количество workers должно быть 5.
- Привязка gunicorn к localhost и порту 5000
- Указывается имя файла точки входа WSGI для gunicorn, а также вызываемый элемент Python в этом файле ( wsgi:app )
Когда Gunicorn запускается, он создает TCP сокет или Unix сокет файл и использует его, чтобы слушать запросы от nginx. Соответственно, существует два способа биндинга (привязки) gunicorn к nginx: через TCP порт или через sock файл. Стандартным по умолчанию (и менее ресурсоемким) является способ через sock файл, однако с ним бывают проблемы. В частности, nginx не всегда может прочитать этот файл. В таком случае вы получите ошибку 502.
В частности, у меня были проблемы с unix сокет файлом, менно из-за этого я выбрал способ с TCP сокетами выше. Однако. если вы хотите использовать чаще рекомендуемый способ с Unix сокетами, то вам надо будет изменить строку запуска gunicorn на следующую:
ExecStart=/home/ipnets/public_html/py/testsite/testprj/bin/gunicorn --workers 3 --bind unix:testprj.sock -m 007 wsgi:app
В ней происходит создание и привязвка к файлу сокетов Unix testprj .sock в каталоге нашего проекта. Мы зададим значение umask 007 , чтобы при создании файла сокета предоставлялся доступ для владельца и для группы, а любой другой доступ ограничивался.

Важно! sock файл будет создан при запуске службы от имени пользователя и группы, указанной в файле конфигурации службы. Вы должны убедиться, что эти права позволят группе, запустившей nginx, читать и писать в этот файл, в противном случае будет ошибка 502.
Проверить, под каким пользователем запущен nginx можно в файле конфигурации: /etc/nginx/nginx.conf
Наконец, добавим раздел [Install] . Это покажет systemd, куда привязывать эту службу, если мы активируем ее запуск при загрузке. Нам нужно, чтобы эта служба запускалась во время работы обычной многопользовательской системы.
Теперь мы запустим созданную службу Gunicorn, и активируем ее запуск при загрузке системы:
systemctl start gunicorn systemctl enable gunicorn systemctl status gunicorn
Если служба не может запуститься, то скорее всего ей не хватает прав на создание sock файла или порт биндинга занят.
Шаг 12. Настраиваем nginx для проксирования запросов в gunicorn
Сервер приложений Gunicorn должен быть запущен и ожидать запросы файла сокета в каталоге проекта или на порту TCP сокета. Теперь мы настроим Nginx для передачи веб-запросов на этот сокет. Для этого мы сделаем небольшие добавления в его файл конфигурации.
В зависимости от вашей конфигурации nginx, файлы виртуальных хостов будут лежать по разному пути. Это может быть или /etc/nginx/sites-available/ или, как в моем случае (я использую связку nginx+apache), в /etc/nginx/conf.d/vhosts/ . Для каждого сайта, хоста, поддомена в моем случае я использую отдельный conf файл, который подтягивается через агрегирующий файл /etc/nginx/conf.d/vhosts.conf .
В частности, для данного проекта я создал отдельный поддомен и отдельный conf файл с таким содержимым:
server < listen 10.20.30.40:80; server_name my.domain.ru www.my.domain.ru; location / < proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass ; >location ~ /\.ht location ~ /\.svn/ location ~ /\.git/ location ~ /\.hg/ location ~ /\.bzr/ disable_symlinks if_not_owner from=/home/ipnets/public_html/py; >
В данном файле вам надо заменить IP в строке listen на ваш публичный, server_name на ваш домен и отредактировать строку proxy_pass. Она должна полностью соответствовать тому, что вы указали в биндинге gunicorn.
Если вы использовали Unix сокет, то ваша строка будет выглядеть так:
proxy_pass http://unix:/home/user/public_html/py/testprj.sock;
Если вы используете sock файл, то пользователя, которым им владеет, нужно добавить в группу, под которой запущен nginx:
usermod -a -G ipnets nginx
Проверим конфигурацию nginx на ошибки:
nginx -t
Если все хорошо, то перезапускаем nginx и проверяем, что сайт открывается по адресу поддомена или домена.
systemctl restart nginx
[Посещений: 8 132, из них сегодня: 1]
Объекты 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 (Открывается в новом окне)