Flask python руководство

Время на прочтение
12 мин

Количество просмотров 338K

blog.miguelgrinberg.com

Miguel Grinberg


>>> следующая глава >>>

Эта статья является переводом нового издания учебника Мигеля Гринберга. Прежний перевод давно утратил свою актуальность.

Автор планирует завершить его выпуск в мае 2018. Я, со своей стороны, постараюсь не отставать с переводом.

Для справки ниже приведен список статей этой серии.

Новый учебник написан в 2017 году, и, наконец, он выглядит так, как если бы он был настроен на версию Python 3. Решены проблемы с совместимостью, изменен фокус в сторону Python 3, а не Python 2 как было в 2012 году в прежней версии учебника.

К сожалению, Python 2 или 3 — это не единственное, что изменилось. Есть также несколько технологий, которые имели смысл в 2012 году, но теперь устарели. К ним относятся:

  • Механизм аутентификации OpenID, который больше не поддерживается многими провайдерами.
  • Пакет sqlalchemy-migrate для миграции баз данных, который, похоже, так же потерял поддержку сообщества. В эти дни Alembic — намного более лучший выбор для миграции чем SQLAlchemy.
  • Интерфейс командной строки Flask (Flask Shell), который в то время не существовал, а теперь является незаменимым инструментом разработчика.
  • В то время, когда Мигель начал работать над учебником, Flask blueprints были слишком новыми, поэтому он решил не использовать эту функцию. В 2017 году blueprints являются обязательными для применения в Flask.
  • Bootstrap 2 для стилизации и форматирования веб-страниц, теперь имеет две основные версии. Новые версии Bootstrap не имеют обратной совместимости с версией 2.
  • Изменения в сервисе Heroku, инструкции по развертыванию которые представлены учебнике устарели.

Многие вещи, которые изменились за эти пять лет, в основном означают, что нужно пересмотреть все главы, причем большинство из них претерпели довольно важные изменения.

Сам Мигель считает, что вырос профессионально за эти пять лет, и думает, что сможет принести гораздо большую ценность этому учебнику с этим новым опытом. После выхода первого учебника он провел более дюжины конференций и выпустил кучу учебных пособий, написал очень успешную книгу разработки Flask для крупного издательства, создал несколько популярных проектов с открытым исходным кодом .

Кроме того, у Мигеля есть еще пять лет опыта работы в качестве технического блоггера, так как продолжал создавать контент для своего блога на протяжении всех этих лет. Весь этот опыт будет отражен в обновлениях, которые он приводит в учебнике.

Более подробно читайте в блоге Мигеля

VIDEO

Добро пожаловать!

Вы собираетесь отправиться в путешествие, чтобы узнать, как создавать веб-приложения с помощью Python и микрофреймворка Flask. Видео выше даст вам обзор содержимого этого руководства. В этой первой главе вы узнаете, как настроить проект Flask. В конце этой главы у вас будет простое веб-приложение Flask, работающее на вашем компьютере!

Примечание 1: Если вы ищете старые версии данного курса, это здесь.

Примечание 2: Если вдруг Вы хотели бы выступить в поддержку моей(Мигеля) работы в этом блоге, я (Мигель Гринберг) предлагаю полную версию данного руководства упакованную электронную книгу или видео. Для получения более подробной информации посетите learn.miguelgrinberg.com.

Все примеры кода представленные в этом учебном курсе, размещены на GitHub. Загрузка кода из GitHub поможет вам сэкономить время, но я настоятельно рекомендую набирать код самостоятельно, по крайней мере, первые несколько глав. После того, как вы станете ближе знакомы с Flask можно получить доступ к коду прямо из GitHub, только в том случае, если ввод становится слишком утомительным.

В начале каждой главы, я дам вам три GitHub ссылки, которые могут быть полезны при изучении главы. Browse откроет GitHub репозиторий для микроблога в том месте, где собраны изменения к главе, которую Вы читаете, за исключением любых изменений, внесенных в будущих главах. Zip — это ссылка для загрузки zip-архива, в том числе приложения и изменений в главе. Diff — откроет графическое представление всех изменений, внесенных в главу, которую Вы собираетесь читать.

На GitHub ссылки в этой главе: Browse, Zip, Diff.

Установка Python

Если на вашем компьютере не установлен Python, установите его. Если ваша операционная система не имеет предустановленный пакет Python, вы можете скачать программу установки с официального сайта Python. Если вы используете Microsoft Windows вместе с WSL или Cygwin, обратите внимание, что вы не будете использовать родную версию Python для Windows, а версию, совместимую с Unix, которую вам нужно получить от Ubuntu (если вы используете WSL) или от Cygwin.

Чтобы убедиться, что ваша установка Python является функциональной, вы можете открыть окно терминала и ввести python3, или если это не работает, просто python. Вот что вам следует ожидать:

$ python3
Python 3.5.2 (default, Nov 17 2016, 17:05:23)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> _

или так в cmd (окно командной строки Microsoft Windows) :

c:cp>c:python33python
Python 3.3.5 (v3.3.5:62cf4e77f785, Mar  9 2014, 10:37:12) [MSC v.1600 32 bit (In
tel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>

Интерпретатор Python теперь находится в ожидании пользовательского ввода. В будущих главах вы узнаете, для чего это интерактивное приглашение полезно. Но пока Вы подтвердили, что Python установлен в вашей системе. Чтобы выйти из интерактивного приглашения, вы можете ввести exit() и нажать Enter.

В версиях Python для Linux и Mac OS X вы также можете выйти из интерпретатора, нажав Ctrl-D.

В Windows, комбинация клавиш для выхода — Ctrl-Z, затем Enter.

Установка Flask

Следующий шаг — установить Flask, но прежде чем я расскажу об этом, я хочу рассказать вам о лучших методах, связанных с установкой пакетов Python.

В Python пакеты, такие как Flask, доступны в общем репозитории, откуда их можно загрузить и установить. Официальный репозиторий пакетов Python называется PyPI, что означает Python Package Index (также известный, как «cheese shop»). Установка пакета из PyPI очень проста, потому что у Python есть инструмент под названием pip, который выполняет эту работу (в Python 2.7 pip не входит в комплект с Python и его нужно устанавливать отдельно).

Чтобы установить пакет на свой компьютер, вы используете pip следующим образом:

$ pip install <package-name>

Интересно, что этот метод установки пакетов не будет работать в большинстве случаев. Если ваш интерпретатор Python был установлен глобально для всех пользователей вашего компьютера, велика вероятность того, что ваша обычная учетная запись пользователя не получит разрешения на внесение в нее изменений, поэтому единственный способ выполнить вышеприведенную команду — запустить ее от имени администратора. Но даже без этого осложнения поймите, что происходит, когда вы устанавливаете пакет, как указанным выше способом. Инструмент pip загрузит пакет из PyPI, а затем добавит его в вашу папку Python. С этого момента каждый скрипт Python, который у вас есть в вашей системе, будет иметь доступ к этому пакету. Представьте ситуацию, когда вы закончили веб-приложение с использованием версии 0.11 Flask, которая была самой последней версией Flask при запуске, но теперь она была заменена версией 0.12. Теперь вы хотите запустить второе приложение, для которого вы хотели бы использовать версию 0.12, но если вы замените установленную версию 0.11, вы рискуете сломать свое старое приложение. Вы видите проблему? Было бы идеально, если бы можно было установить Flask 0.11, который будет использоваться вашим старым приложением, а также установить Flask 0.12 для Вашего нового.

Чтобы решить проблему поддержки различных версий пакетов для разных приложений, Python использует концепцию виртуальных сред. Виртуальная среда — это полная копия интерпретатора Python. Когда вы устанавливаете пакеты в виртуальной среде, общесистемный интерпретатор Python не затрагивается, только копия. Таким образом, решение иметь полную свободу для установки любых версий ваших пакетов для каждого приложения — использовать другую виртуальную среду для каждого приложения. Виртуальные среды имеют дополнительное преимущество, они принадлежат пользователю, который их создает, поэтому им не требуется учетная запись администратора.

Начнем с создания каталога, в котором будет жить проект. Я собираюсь назвать этот каталог microblog, так как это имя приложения:

$ mkdir microblog
$ cd microblog

Если вы используете версию Python 3, в нее включена поддержка виртуальной среды, поэтому все, что вам нужно сделать для ее создания, это:

$ python3 -m venv venv

С помощью этой команды Python запустит пакет venv, который создает виртуальную среду с именем venv. Первым venv в команде является имя пакета виртуальной среды Python, а второе — имя виртуальной среды, которое я буду использовать для этой конкретной среды. Если вы считаете, что это сбивает с толку, вы можете заменить второй venv другим именем, которое вы хотите назначить своей виртуальной среде. В общем, я создаю свои виртуальные среды с именем venv в каталоге проекта, поэтому всякий раз, когда я подключаюсь к проекту, я нахожу его соответствующую виртуальную среду.

Обратите внимание, что в некоторых операционных системах вам может понадобиться использовать python вместо python3 в приведенной выше команде. Некоторые установки используют python для релизов Python 2.x и python3 для релизов 3.x, в то время как другие отображают python в выпусках 3.x.

По завершении команды вы будете иметь каталог с именем venv, где хранятся файлы виртуальной среды.

Если вы используете любую версию Python старше 3.4 (включая выпуск 2.7), виртуальные среды не поддерживаются изначально. Для этих версий Python вам необходимо загрузить и установить сторонний инструмент virtualenv, прежде чем создавать виртуальные среды. После того, как virtualenv установлен, вы можете создать виртуальную среду со следующей командой:

$ virtualenv venv

или так

$ python virtualenv.py venv

Прим.переводчика: У меня установлено несколько версий Python. Я использую Python3.3. В моем случае пришлось вводить строку так:

C:microblog>c:Python33python.exe c:Python33Libsite-packagesvirtualenv.py venv

В полученном сообщении видно, что установлен pip и ряд пакетов:

Using base prefix 'c:\Python33'
New python executable in C:microblogvenvScriptspython.exe
Installing setuptools, pip, wheel...done.

Независимо от метода, который вы использовали для его создания, вы создали свою виртуальную среду. Теперь вам нужно сообщить системе, что вы хотите ее использовать, активируя ее. Чтобы активировать новую виртуальную среду, используете следующую команду:

$ source venv/bin/activate
(venv) $ _

Если вы используете cmd (окно командной строки Microsoft Windows), команда активации немного отличается:

$ venvScriptsactivate
(venv) $ _

Когда вы активируете виртуальную среду, конфигурация сеанса терминала изменяется так, что интерпретатор Python, хранящийся внутри нее, станет тем, который вызывается при вводе python. Кроме того, в запросе терминала включено имя активированной виртуальной среды. Изменения, внесенные в сеанс терминала, являются временными и частными для этого сеанса, поэтому они не будут сохраняться при закрытии окна терминала. Если вы одновременно работаете с несколькими терминальными окнами, отлично видно, чтобы на каждом из них были задействованы разные виртуальные среды.

Теперь, когда вы создали и активировали виртуальную среду, вы можете, наконец, установить в нее Flask:

(venv) C:microblog>pip install flask
Collecting flask
  Using cached Flask-0.12.2-py2.py3-none-any.whl
Requirement already satisfied: click>=2.0 in c:python33libsite-packages (from flask)
Requirement already satisfied: Werkzeug>=0.7 in c:python33libsite-packages (from flask)
Requirement already satisfied: Jinja2>=2.4 in c:python33libsite-packages (from flask)
Requirement already satisfied: itsdangerous>=0.21 in c:python33libsite-packages (from flask)
Requirement already satisfied: markupsafe in c:python33libsite-packages (from Jinja2>=2.4->flask)
Installing collected packages: flask
Successfully installed flask-0.12.2

(venv) C:microblog>

Если вы хотите проверить, что в вашей виртуальной среде установлен Flask, вы можете запустить интерпретатор Python и импортировать Flask в него:

(venv) C:microblog>python
Python 3.3.5 (v3.3.5:62cf4e77f785, Mar  9 2014, 10:37:12) [MSC v.1600 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import flask
>>>

Если этот импорт не дает вам никаких ошибок, вы можете поздравить себя, так как Flask установлен и готов к использованию.

Flask приложение «Привет, мир»

Если вы зайдете на сайт Flask, вас приветствует очень простое примерное приложение с пятью строками кода. Вместо того, чтобы повторять этот тривиальный пример, я покажу вам немного более сложный, который даст вам хорошую базовую структуру для написания больших приложений.

Приложение будет существовать в виде пакета.

Прим.переводчика: Пакет — это коллекция модулей.

В Python подкаталог, содержащий файл __init__.py, считается пакетом и может быть импортирован. Когда вы импортируете пакет, __init__.py выполняет и определяет, какие символы предоставляют пакет для внешнего мира.

Давайте создадим пакет под названием app, в котором будет размещено приложение. Убедитесь, что вы находитесь в каталоге microblog, а затем выполните следующую команду:

(venv) $ mkdir app

__init__.py для пакета приложения будет содержать следующий код:

from flask import Flask

app = Flask(__name__)

from app import routes

Сценарий выше просто создает объект приложения как экземпляр класса Flask, импортированного из пакета flask. Переменная __name__, переданная в класс Flask, является предопределенной переменной Python, которая задается именем модуля, в котором она используется. Flask использует расположение модуля, переданного здесь как отправную точку, когда ему необходимо загрузить связанные ресурсы, такие как файлы шаблонов, которые я расскажу в главе 2. Для всех практических целей передача __name__ почти всегда будет настраивать flask в правильном направлении. Затем приложение импортирует модуль routes, который еще не существует.

Один из аспектов, который может показаться запутанным вначале, состоит в том, что существуют два объекта с именем app. Пакет приложения определяется каталогом приложения и сценарием __init__.py и указан в инструкции routes импорта приложения. Переменная app определяется как экземпляр класса Flask в сценарии __init__.py, что делает его частью пакета приложения.

Другая особенность заключается в том, что модуль routes импортируется внизу, а не наверху скрипта, как это всегда делается. Нижний импорт является обходным путем для циклического импорта, что является общей проблемой при использовании приложений Flask. Вы увидите, что модуль routes должен импортировать переменную приложения, определенную в этом скрипте, поэтому, поместив один из взаимных импортов внизу, вы избежите ошибки, которая возникает из взаимных ссылок между этими двумя файлами.

Так что же входит в модуль routes? routes — это разные URL-адреса, которые приложение реализует. В Flask обработчики маршрутов приложений записываются как функции Python, называемые функциями просмотра. Функции просмотра сопоставляются с одним или несколькими URL-адресами маршрутов, поэтому Flask знает, какую логику следует выполнять, когда клиент запрашивает данный URL-адрес.

Вот ваша первая функция просмотра, которую вам нужно написать в новом модуле с именем app/routes.py:

from app import app

@app.route('/')
@app.route('/index')
def index():
    return "Hello, World!"

Эта функция просмотра на самом деле довольно проста, она просто возвращает приветствие в виде строки. Две странные строки @app.route над функцией — декораторы, уникальная особенность языка Python. Декоратор изменяет функцию, которая следует за ней. Общий шаблон с декораторами — использовать их для регистрации функций как обратных вызовов для определенных событий. В этом случае декоратор @app.route создает связь между URL-адресом, заданным как аргумент, и функцией. В этом примере есть два декоратора, которые связывают URL-адреса / и /index с этой функцией. Это означает, что когда веб-браузер запрашивает один из этих двух URL-адресов, Flask будет вызывать эту функцию и передавать возвращаемое значение обратно в браузер в качестве ответа. Если вам кажется, что это еще не имеет смысла, это будет недолго, пока вы запустите это приложение.

Чтобы завершить приложение, вам нужно создать сценарий Python на верхнем уровне, определяющий экземпляр приложения Flask. Давайте назовем этот скрипт microblog.py и определим его как одну строку, которая импортирует экземпляр приложения:

from app import app

Помните два объекта app? Здесь вы можете видеть оба вместе в одном предложении. Экземпляр приложения Flask называется app и входит в пакет app. from app import app импортирует переменную app, входящую в пакет app. Если вы считаете это запутанным, вы можете переименовать либо пакет, либо переменную во что то другое.

Чтобы убедиться, что вы все делаете правильно, ниже приведена диаграмма структуры проекта:

microblog/
  venv/
  app/
    __init__.py
    routes.py
  microblog.py

Верьте или нет, но первая версия приложения завершена! Прежде чем запускать его, Flask нужно сообщить, как его импортировать, установив переменную среды FLASK_APP:

(venv) $ export FLASK_APP=microblog.py

Если вы используете Microsoft Windows, используйте команду ‘set’ вместо ‘export’ в команде выше.

Готовы ли вы быть потрясены? Вы можете запустить свое первое веб-приложение со следующей командой:

(venv) $ flask run
 * Serving Flask app "microblog"
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Прим.переводчика: Я был потрясен поскольку получил ошибку кодировки. Если у вас версия Python старше 3.6 вас скорее всего ждет сюрприз. Типа:

Syntax Error: (unicode error) 'utf-8' codec can't decode byte 0xcf in position 0: invalid continuation byte:

В моем случае виноваты кириллические символы ПК в имени компьютера. Заменил на PK и все заработало. Виноват модуль socket

(venv) C:microblog>python
Python 3.3.5 (v3.3.5:62cf4e77f785, Mar  9 2014, 10:37:12) [MSC v.1600 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from socket import gethostbyaddr
>>> gethostbyaddr('127.0.0.1')
('Acer-PK', [], ['127.0.0.1'])
>>>

Действительно все заработало:

(venv) C:microblog>set FLASK_APP=microblog.py

(venv) C:microblog>flask run
 * Serving Flask app "microblog"
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Что бы написать по русски «Привет, Мир!» потребуется скорректировать
модуль routes.py

Добавить строку # -*- coding: utf-8 -*-

# -*- coding: utf-8 -*-
from app import app

@app.route('/')
@app.route('/index')
def index():
    return "Привет, Мир!"

Когда вы закончите играть с сервером, вы можете просто нажать Ctrl-C, чтобы остановить его.

Поздравляем, вы совершили первый большой шаг, чтобы стать веб-разработчиком!

>>> следующая глава >>>

Язык Python — это лёгкая дорога в программирование. А Flask помогает проложить путь в веб-разработку и научиться писать сайты с помощью Python. Получается, он такой же лёгкий, как и Python? Да, но есть нюансы.

В этой статье мы расскажем, что собой представляет фреймворк Flask, чем он лучше других и когда стоит выбрать именно его. А параллельно напишем свой первый небольшой сайт, где даже сможем публиковать посты.

Всё, что нужно знать о Flask в Python:

  • Что это такое
  • Чем он лучше других фреймворков
  • Как установить Flask
  • Как написать простой сайт
  • Как создать блог
  • Что нужно запомнить

Flask — это легковесный веб-фреймворк для языка Python, который предоставляет минимальный набор инструментов для создания веб-приложений. На нём можно сделать и лендинг, и многостраничный сайт с кучей плагинов и сервисов. Не фреймворк, а мечта!

У Flask много преимуществ, которые выделяют его среди других фреймворков:

  • простой синтаксис — это всё-таки Python;
  • удобные шаблоны — можно быстро создавать прототипы веб-приложений;
  • куча инструментов для гибкой настройки сайтов под любые нужды.

Логотип Flask
Изображение: Wikimedia Commons

Ещё под Flask написаны сотни расширений и плагинов, которые добавляют дополнительные возможности — разные виды аутентификации, управление базами данных и работу с формами. И всё это — бесплатно и с открытым кодом.

Скачать фреймворк можно на официальном сайте. А если появятся вопросы по установке или настройке, то на помощь всегда придёт огромное сообщество Flask-разработчиков.

Flask входит в топ-15 самых популярных фреймворков для веб-разработки среди опытных программистов. Рядом с ним в рейтинге находятся Django, Express.js, Laravel, Ruby on Rails, Spring и ASP.NET. И, конечно, на Flask написано немало популярных сайтов. Вот лишь несколько примеров:

  • Pinterest — одна из крупнейших социальных сетей для обмена изображениями и идеями;
  • Netflix — один из крупнейших сервисов видеостриминга в мире;
  • Uber — сервис вызова такси и автомобильного транспорта;
  • Reddit — один из самых популярных новостных UGC-агрегаторов;
  • Twilio — платформа для разработки приложений для обмена сообщениями.

В общем, его используют везде: и в малом, и в крупном бизнесе, и на частных предприятиях, и в госучреждениях.

У Flask есть ряд особенностей, за которые его любят веб-разработчики. Давайте их перечислим:

  • Минимальный набор инструментов из коробки. Причём они не навязывают какую-то архитектуру или жёсткую структуру проектов. Разработчики сами решают, как и что они будут создавать.
  • Гибкость. Работая с Flask, программист может выбирать только необходимые встроенные инструменты и подключать дополнительные внешние, не перегружая проект лишними модулями.
  • Расширяемость. У Flask много расширений и плагинов, которые помогают быстро добавить новую функциональность. Например, авторизацию, управление базами данных и работу с формами.
  • Простота. У Flask простой синтаксис, что делает изучение этого фреймворка более простым, а также позволяет быстрее создавать прототипы веб-приложений.
  • Поддержка сообщества. Flask запустили в 2010 году, и почти по любому связанному с ним вопросу в интернете уже есть ответы.

В общем, Flask как будто бы создан для новичков. Он несложен, в нём есть все необходимые базовые функции и возможности для расширения. Но при этом Flask может показаться слабеньким фреймворком, непригодным для крупных проектов. Кстати, это тоже можно исправить сторонними плагинами и библиотеками.

Чтобы освоить азы Flask, в этой статье мы создадим небольшой сайт-блог и объясним главные концепции фреймворка в деле. Начнём с установки.

Чтобы установить Flask, сначала нужно установить Python. Приступим.

Flask требует наличия Python версии 3.5 или выше. Если у вас нет Python, его можно загрузить с официального сайта Python. Подробную инструкцию можно посмотреть в нашем гайде по установке Python для всех операционных систем.

Если вы скачиваете официальную версию Python или пакет Anaconda, у вас автоматически установится PIP. Это менеджер пакетов для Python, который позволяет управлять сторонними библиотеками. Нам он понадобится, чтобы установить Flask.

Чтобы проверить, есть ли у вас PIP, введите в консоли:

pip --version

или

python3 -m pip --version

В ответ на экран выведется версия PIP. Если ничего не происходит, значит, PIP у вас не установлен. Исправим это:

python -m ensurepip --default-pip

Снова проверим, появился ли в системе менеджер пакетов. Если всё равно что-то не получается, попробуйте найти решение проблемы на Stack Overflow — или обратитесь к астрологу :)

Теперь поставим сам Flask. Делается это очень просто:

pip install Flask

Начнётся процесс загрузки Flask, после которого он будет готов к использованию. Если вам нужна конкретная версия Flask, установить её можно, указав её номер с помощью дополнительного параметра ==<version>.

pip install Flask==<version>

Например, мы можем установить версию 2.0.1:

pip install Flask==2.0.1

Чтобы проверить, работает ли Flask, введём следующую команду:

pip show flask

или создадим Python-файл и впишем туда такую строку:

import flask

Теперь запустим интерпретатор и убедимся, что программа исполняется без ошибок.

Приступим к коду. Для начала нам понадобится основа для приложения. Создадим новый файл с именем app.py. Это и будет наше Flask-приложение.

На первом этапе импортируем класс Flask из библиотеки Flask:

from flask import Flask

Затем создадим экземпляр класса Flask:

app = Flask(__name__)

Здесь мы передаем аргумент __name__ конструктору класса, этот аргумент скажет Flask, где находится наше приложение. Так Flask сможет определить местоположение шаблонов и статических файлов, о которых речь пойдёт дальше. Если вы ещё не особо знакомы с классами в Python, советуем прочитать нашу статью об объектно-ориентированном программировании на Python.

Весь бэкенд строится на маршрутах — или URL-адресах. Они помогают задавать удобную структуру и понятное поведение веб-приложениям.

Для пользователя маршруты — это отдельные «вкладки» на сайте. Например, если зайти на сайт Skillbox, откроется его главная страница www.skillbox.ru. А если кликнуть на любой курс, мы перейдём на другую страницу сайта с другим URL-адресом, таким как www.skillbox.ru/course/profession-python. Видим, что к адресу нашего сайта добавился текст: /course/profession-python/. Эта «приписка» и перенесла нас на другую страницу с другим содержимым. Получается, маршруты позволяют создавать разные страницы с разным наполнением в рамках одного сайта.

Чтобы задать маршрут во Flask, нужно написать следующее:

@app.route('/')
def hello_world():
    return 'Hello, World!'

Так мы создали URL-адрес главной страницы сайта. Например, для Skillbox главной страницей будет www.skillbox.ru. Тут мы мысленно можем дописать слеш: www.skillbox.ru/.

Сам маршрут задаётся в строке @app.route(‘/’). Внутрь круглых скобочек мы по ходу статьи будем вписывать разные маршруты, а пока нам хватит стандартного.

Внутрь маршрута мы поместили функцию hello_world(), которая будет выполняться при обращении к корневому URL, или главной странице нашего сайта (ведь наш маршрут ведёт именно на неё). Функция возвращает строку Hello, World! в браузере.

Теперь нам нужно запустить приложение:

if __name__ == '__main__':
    app.run()

Этот код гарантирует, что сервер Flask будет запущен только в том случае, если файл app.py был запущен напрямую, а не импортирован как модуль.

Сохраняем файл app.py и запускаем его с помощью команды в консоли:

python app.py

После запуска вы должны увидеть сообщение о том, что сервер Flask был запущен:

* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Чтобы взглянуть на работу нашего приложения, нужно перейти по адресу, который был указан в консоли — http://127.0.0.1:5000/. Вот что мы там увидим:

Скриншот: Skillbox Media

Ликуем — у нас всё получилось, сайт работает. Дальше будем усложнять наше приложение и начнём создавать блог.

Чтобы создать блог, одним простым приложением уже не обойтись, придётся научиться использовать HTML-шаблоны и подключать базу данных.

В дальнейшем мы будем использовать наш базовый код из предыдущего раздела. Полностью он выглядит так:

app.py

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
   return 'Hello, World!'

if __name__ == '__main__':
   app.run()

HTML-шаблоны — это файлы, которые задают структуру и содержимое страниц сайта. Шаблоны упрощают жизнь программистам — им не приходится десятки раз писать один и тот же HTML-код, ведь его можно просто взять и… шаблонизировать.

Ещё немного об HTML-шаблонах

HTML-шаблоны позволяют создавать динамические веб-страницы с помощью заготовленных блоков, которые мы можем настраивать и использовать повторно. Шаблоны помогают избежать дублирования кода и облегчают поддержку приложения, так как мы можем легко изменять отображение страниц, не затрагивая внутреннюю логику. Например, в нашем медиа о программировании оформление всех статей очень похоже: структура, внешние по отношению к статье элементы и тому подобное — это возможно как раз благодаря шаблонам.

Например, можно использовать один и тот же базовый шаблон для всех страниц сайта и переопределять блоки контента для каждой страницы. В таком случае, если нам понадобится изменить дизайн всего сайта, мы можем изменить только базовый шаблон, а все страницы обновятся автоматически, без дополнительных усилий.

Если вы не знаете, как писать HTML-код, советуем прочитать нашу статью об HTML. А теперь создадим HTML-шаблоны: выделим под них папку templates и добавим в неё файл base.html со следующим содержимым:

<!DOCTYPE html>
<html>
  <head>
    <title>{% block title %}{% endblock %}</title>
  </head>
  <body>
    {% block content %}{% endblock %}
  </body>
</html>

Этот шаблон будет отправной точкой для всех остальных HTML-страниц. В нём мы прописали заголовок:

<head>
  <title>{% block title %}{% endblock %}</title>
</head>

и основной контент (тело) страницы:

<body>
  {% block content %}{% endblock %}
</body>

Вы, наверное, уже обратили внимание на странные элементы, а точнее теги {% block %} и {% endblock %}. Они как раз нужны, чтобы динамически добавлять туда новые элементы: другие HTML-блоки, JavaScript-код и тому подобное.

Теперь давайте создадим второй шаблон и назовём его index.html. Он будет наследовать элементы базового шаблона:

{% extends "base.html" %}

{% block title %}Home{% endblock %}

{% block content %}
  <h1>Welcome to my website!</h1>
  <p>This is the homepage.</p>
{% endblock %}

Этот шаблон переопределяет заголовок страницы и определяет контент, который будет отображаться на домашней странице: заголовок страницы Home и немного текста.

Пришло время воспользоваться шаблонами. Изменим файл app.py и импортируем функцию render_template из библиотеки Flask, которая позволяет работать с шаблонами:

from flask import Flask, render_template

Изменим маршрут главной страницы и используем в нём новую функцию, чтобы отобразить шаблон index.html:

@app.route('/')
def index():
    return render_template('index.html')

Сохраним изменения в файлах app.py, index.html и base.html, а затем снова запустим наше приложение:

python app.py

Открываем браузер и переходим по адресу http://127.0.0.1:5000/. У нас отобразится содержимое шаблона index.html:

Скриншот: Skillbox Media

Чтобы вести блог, нужно куда-то сохранять все посты, картинки, видео и тому подобное. Куда-то — это в базу данных. Баз данных существует немало, мы даже написали про них отдельную статью. Но если коротко — база данных нужна, чтобы удобно хранить, обрабатывать и сохранять данные.

Мы будем использовать базу данных SQLite, потому что она занимает совсем немного места, легка в освоении и вообще клёвая. А главный плюс — её не нужно скачивать отдельно, потому что она сразу есть в Python.

Займёмся привычным делом — импортируем модуль SQLite3:

import sqlite3

Теперь мы можем создать функцию, которая будет подключать нас к базе данных и возвращать объект подключения:

def get_db_connection():
    conn = sqlite3.connect('database.db')
    conn.row_factory = sqlite3.Row
    return conn

Эта функция создаст подключение к базе данных database.db и установит режим получения результатов запросов в виде словаря Row, что позволит обращаться к столбцам базы данных по их именам.

Другими словами — наши данные будут храниться в виде таблицы, где каждая строка — уникальный пост, а каждый столбец (или ячейка) — информация об этом посте, например, сам текст, количество лайков, автор и так далее.

Но ещё нам понадобится функция, которая будет закрывать подключение к базе данных:

def close_db_connection(conn):
    conn.close()

Теперь мы можем использовать эти функции для выполнения запросов к базе данных внутри нашего приложения. Обновим функцию index(), чтобы она получала все записи из таблицы posts:

@app.route('/')
def index():
    conn = get_db_connection()
    posts = conn.execute('SELECT * FROM posts').fetchall()
    conn.close()
    return render_template('index.html', posts=posts)

Здесь мы получаем подключение к базе данных с помощью функции get_db_connection(), выполняем запрос к таблице posts и получаем все записи, используя метод fetchall(). Затем мы закрываем подключение с помощью функции close_db_connection() и передаём полученные записи в шаблон index.html с помощью функции render_template().

Но перед тем как использовать базу данных, её нужно инициализировать и создать таблицу posts.

У нашего учебного поста будет три поля с данными: уникальный идентификатор (ID), заголовок и текст поста. Назовём их соответственно — id, title и content:

def init_db():
    conn = get_db_connection()
    conn.execute('CREATE TABLE IF NOT EXISTS posts (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, content TEXT NOT NULL)')
    conn.close()

Функция execute() создаст нам таблицу posts с полями id, title и content. У каждого из этих полей будет собственный тип данных: целое число, строка и строка соответственно.

Мы задали полю id специальный параметр AUTOINCREMENT, чтобы при добавлении нового поста его айдишник автоматически увеличивался на 1. А параметр PRIMARY KEY нужен для уточнения, что строки в таблице уникальные и не пустые.

Инициализировать базу данных нужно в самом начале. Во Flask мы можем сделать это перед первым запросов к базе данных с помощью следующего кода:

@app.before_first_request
def before_first_request():
    init_db()

Здесь мы используем декоратор @app.before_first_request, который указывает, что функция before_first_request() вызывается перед тем, как запустится рендер шаблонов.

Готово — наше приложение подключено к базе данных SQLite, а мы можем переходить дальше. Но сперва убедимся, что всё работает. Сохраняем изменения и запускаем приложение:

python app.py

Если перейти по адресу http://127.0.0.1:5000/, то может показаться, что ничего не изменилось. Но если посмотреть в папку с файлами, то можно увидеть там новый файл — database.db. Это и есть наша база данных.

Скриншот: Skillbox Media

Настало время сделать новый и прекрасный шаблон — для постов. Он будет простым: заголовок, текст и ссылка на главную страницу. Создадим в папке templates файл post.html и добавим следующий код:

<!DOCTYPE html>
<html>
<head>
    <title>{{ post.title }}</title>
</head>
<body>
    <h1>{{ post.title }}</h1>
    <p>{{ post.content }}</p>
    <a href="{{ url_for('index') }}">Back to index</a>
</body>
</html>

В момент рендеринга в эту HTML-страницу мы будем передавать пост, который достали из базы данных. У этого поста будут заголовок (title) и основной текст (content), а всё вместе будет лежать внутри объекта post.

Пока всё просто, но обратите внимание на новую функцию — url_for(). Она позволяет перейти на другой маршрут и отрисовать другую HTML-страницу. В нашем случае, если мы нажмём на ссылку Back to index, запустится функция index(), которая перенаправит нас на главную страницу (маршрут /) и отрисует шаблон index.html.

Теперь нам нужно сделать новый маршрут под один из постов в файле app.py:

@app.route('/<int:post_id>')
def get_post(post_id):
    conn = get_db_connection()
    post = conn.execute('SELECT * FROM posts WHERE id = ?', (post_id,)).fetchone()
    conn.close()
    return render_template('post.html', post=post)

Тут уже всё немного сложнее, но обо всём по порядку:

  • @app.route(‘/<int:post_id>’) — задаём новый маршрут. Он будет выглядеть так: www.oursite.com/1. Единица будет указывать на индекс поста из базы данных. <int:post_id> — это ещё одно указание на то, что индекс поста должен быть целым числом (int), а не, например, строкой.
  • get_post(post_id) — передаём айдишник поста, который как раз и «встанет» в URL-адрес и добавится к запросу к базе данных.
  • post = conn.execute (‘SELECT * FROM posts WHERE id =? ‘, (post_id,)).fetchone() — запрашиваем из базы данных пост по нашему айдишнику и берём одну строку функцией fetchone().
  • return render_template (‘post.html’, post=post) — рендерим HTML-шаблон и передаём туда полученный пост.

Давайте убедимся, что всё работает корректно и страница отрисовывается. Временно напишем «костыль» и вручную добавим пост (исключительно для проверки):

@app.route('/<int:post_id>')
def get_post(post_id):
    conn = get_db_connection()
    conn.execute('INSERT INTO posts (title, content) VALUES ("Random Title", "Lorem ipsum dolor sit amet consectetur adipiscing elit")')
    post = conn.execute('SELECT * FROM posts WHERE id = ?', (post_id,)).fetchone()
    conn.close()
    return render_template('post.html', post=post)

Наш «костыль» — это четвёртая строка. Здесь мы сами вставляем новую строку в базу данных с помощью запроса INSERT. Теперь перейдём по адресу http://127.0.0.1:5000/1:

Скриншот: Skillbox Media

Круто — всё отрисовалось, кнопка «Назад» работает! Идём дальше и не забываем удалить «костыль».

Дополним файл index.html и создадим список, в котором будут находиться все посты из базы данных:

<!DOCTYPE html>
<html>
<head>
    <title>Blog</title>
</head>
<body>
    <h1>Blog</h1>
    <ul>
        {% for post in posts %}
        <li><a href="{{ url_for('get_post', post_id=post['id']) }}">{{ post['title'] }}</a></li>
        {% endfor %}
    </ul>
</body>
</html>

Главный движ у нас происходит в теге <ul>: мы идём по всем элементам списка posts и для каждого создаём заголовок со ссылкой на пост. При нажатии на ссылку вызывается функция url_for(), а ей передаётся функция-рендер get_post() и ID поста.

Ещё мы немного декорировали заголовок самой страницы. Можете сравнить с прошлой версией.

Если перезапустить приложение, то мы не увидим новых постов, потому что их просто нет в базе данных. Но можно опять заполнить всё вручную. Изменим функцию index():

@app.route('/')
def index():
    conn = get_db_connection()
    conn.execute('INSERT INTO posts (title, content) VALUES ("Why I love Flask", "This is so cool!!!")')
    conn.execute('INSERT INTO posts (title, content) VALUES ("Cats >> Dogs", "It was a joke because they are all so adorable.")')
    posts = conn.execute('SELECT * FROM posts').fetchall()
    conn.close()
    return render_template('index.html', posts=posts)

Видим результат:

Скриншот: Skillbox Media

Правда, если перейти по ссылке, то ничего не отобразится, но и это мы со временем исправим.

Отображение постов работает, осталось добавить возможность создавать новые посты. Для этого создадим новый HTML-шаблон и метод для рендеринга.

В папке templates создаём новый файл add_post.html:

{% extends 'base.html' %}

{% block content %}
  <h1>Add New Post</h1>
  {% with messages = get_flashed_messages() %}
    {% if messages %}
      <ul class="flashes">
        {% for message in messages %}
          <li>{{ message }}</li>
        {% endfor %}
      </ul>
    {% endif %}
  {% endwith %}
  <form method="post">
    <label for="title">Title</label><br>
    <input type="text" id="title" name="title"><br>
    <label for="content">Content</label><br>
    <textarea id="content" name="content"></textarea><br>
    <input type="submit" value="Submit">
  </form>
{% endblock %}

Он будет наследовать файл base.html, а внутри содержать форму, где пользователь указывает заголовок и тело поста. Если он вдруг введёт некорректные данные, то мы покажем ему сообщение об ошибке с помощью флеш-сообщений (когда некорректно заполненное поле подсвечивается красным и у вас нет возможности отправить заполненную форму).

Теперь добавим новый маршрут в файл app.py:

@app.route('/new', methods=['GET', 'POST'])
def new_post():
    if request.method == 'POST':
        title = request.form['title']
        content = request.form['content']

        conn = get_db_connection()
        conn.execute('INSERT INTO posts (title, content) VALUES (?, ?)', (title, content))
        conn.commit()
        conn.close()

        return redirect(url_for('index'))

    return render_template('add_post.html')

Этот маршрут обрабатывает GET- и POST-запросы по адресу /new. Если запрос выполняется методом GET, то функция просто отображает форму для ввода данных с помощью шаблона add_post.html. А если запрос выполняется методом POST, мы достаём заголовок и тело поста из формы, а затем добавляем их в базу данных. В конце — редиректим (то есть перенаправляем) пользователя обратно на главную.

Подробнее об HTTP-запросах вы можете прочитать в нашей статье.

Чтобы все эти функции работали, мы также должны их импортировать в начале файла:

from flask import Flask, render_template, request, redirect, url_for

Теперь давайте сохраним свои наработки и запустим приложение:

python app.py

Переходим по адресу http://127.0.0.1:5000/ и видим, что опять ничего не изменилось:

Скриншот: Skillbox Media

Попробуем перейти по адресу http://127.0.0.1:5000/new:

Скриншот: Skillbox Media

Ура! Мы видим нашу форму! Давайте впишем туда что-нибудь и нажмём Submit:

Скриншот: Skillbox Media

Нас перекинуло на главную страницу — и на ней находится наш новый пост. Ура! Всё наконец-то работает!

Скриншот: Skillbox Media

К тому же теперь мы можем перейти по ссылке и посмотреть пост целиком:

Скриншот: Skillbox Media

Но всё ещё есть проблема — нужно добавить кнопку Add new post, чтобы каждый раз не добавлять пост, вбивая в адресную строку наш URL. Для этого нам нужно добавить всего одну строчку в файл index.html:

<!DOCTYPE html>
<html>
<head>
    <title>Blog</title>
</head>
<body>
    <h1>Blog</h1>
    <a href="{{ url_for('new_post') }}"><button>Add New Post</button></a>
    <ul>
        {% for post in posts %}
        <li><a href="{{ url_for('get_post', post_id=post['id']) }}">{{ post['title'] }}</a></li>
        {% endfor %}
    </ul>
</body>
</html>

Мы создали кнопку, нажав которую, мы перейдём на страницу со ссылкой для создания нового поста. Проверяем:

Скриншот: Skillbox Media

Кликаем — переходим куда нужно:

Скриншот: Skillbox Media

Добавили новый пост:

Скриншот: Skillbox Media

Наше приложение приобрело следующую структуру:

Blog Flask/
|-- templates/
|   |-- index.html
|   |-- base.html
|   |-- post.html
|   |-- add_post.html
|-- app.py
|-- database.db

В папке templates хранятся HTML-шаблоны: index.html, add_post.html, post.html и base.html. Файл app.py содержит основной код приложения Flask, в котором определены маршруты и функции для работы с базой данных. А файл database.db — это база данных SQLite.

Полный код каждого файла можно посмотреть ниже:

index.html:

<!DOCTYPE html>
<html>
<head>
    <title>Blog</title>
</head>
<body>
    <h1>Blog</h1>
    <a href="{{ url_for('new_post') }}"><button>Add New Post</button></a>
    <ul>
        {% for post in posts %}
        <li><a href="{{ url_for('get_post', post_id=post['id']) }}">{{ post['title'] }}</a></li>
        {% endfor %}
    </ul>
</body>
</html>

base.html:

<!DOCTYPE html>
<html>
  <head>
    <title>{% block title %}{% endblock %}</title>
  </head>
  <body>
    {% block content %}{% endblock %}
  </body>
</html>

post.html:

<!DOCTYPE html>
<html>
<head>
    <title>{{ post.title }}</title>
</head>
<body>
    <h1>{{ post.title }}</h1>
    <p>{{ post.content }}</p>
    <a href="{{ url_for('index') }}">Back to index</a>
</body>
</html>

add_post.html:

{% extends 'base.html' %}

{% block content %}
  <h1>Add New Post</h1>
  {% with messages = get_flashed_messages() %}
    {% if messages %}
      <ul class="flashes">
        {% for message in messages %}
          <li>{{ message }}</li>
        {% endfor %}
      </ul>
    {% endif %}
  {% endwith %}
  <form method="post">
    <label for="title">Title</label><br>
    <input type="text" id="title" name="title"><br>
    <label for="content">Content</label><br>
    <textarea id="content" name="content"></textarea><br>
    <input type="submit" value="Submit">
  </form>
{% endblock %}

app.py:

from flask import Flask, render_template, request, redirect, url_for
import sqlite3

app = Flask(__name__)

def get_db_connection():
    conn = sqlite3.connect('database.db')
    conn.row_factory = sqlite3.Row
    return conn

def close_db_connection(conn):
    conn.close()

def init_db():
    conn = get_db_connection()
    conn.execute('CREATE TABLE IF NOT EXISTS posts (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, content TEXT NOT NULL)')
    conn.close()

@app.route('/')
def index():
    conn = get_db_connection()
    posts = conn.execute('SELECT * FROM posts').fetchall()
    conn.close()
    return render_template('index.html', posts=posts)

@app.route('/<int:post_id>')
def get_post(post_id):
    conn = get_db_connection()
    post = conn.execute('SELECT * FROM posts WHERE id = ?', (post_id,)).fetchone()
    conn.close()
    return render_template('post.html', post=post)

@app.route('/new', methods=['GET', 'POST'])
def new_post():
    if request.method == 'POST':
        title = request.form['title']
        content = request.form['content']

        conn = get_db_connection()
        conn.execute('INSERT INTO posts (title, content) VALUES (?, ?)', (title, content))
        conn.commit()
        conn.close()

        return redirect(url_for('index'))

    return render_template('add_post.html')

@app.before_first_request
def before_first_request():
    init_db()

if __name__ == '__main__':
    app.run()

Наш блог полностью готов к работе. В него можно добавлять новые функции (например, редактирование постов или систему авторизации), а также применять CSS-стили, чтобы он выглядел красивее. Но главное — мы заложили основу для бэкенда и разобрались, как это сделать на Flask.

Вот некоторые важные вещи, которые стоит помнить при работе с фреймворком Flask:

  • Flask — это микрофреймворк для создания веб-приложений на языке Python.
  • Flask использует декораторы для связывания функций с URL-адресами и методами HTTP.
  • Чтобы удобно отображать HTML-страницы, можно использовать шаблоны, которые упрощают разработку.
  • Flask не имеет встроенной поддержки баз данных, но к нему всегда можно подключить сторонние — например, SQLite.
  • Flask использует объект request для доступа к данным, отправленным пользователем через формы и URL-адреса.
  • Flask использует флеш-сообщения для отображения сообщений об ошибках или на веб-странице.

Quickstart

Eager to get started? This page gives a good introduction to Flask.
Follow :doc:`installation` to set up a project and install Flask first.

A Minimal Application

A minimal Flask application looks something like this:

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<p>Hello, World!</p>"

So what did that code do?

  1. First we imported the :class:`~flask.Flask` class. An instance of
    this class will be our WSGI application.
  2. Next we create an instance of this class. The first argument is the
    name of the application’s module or package. __name__ is a
    convenient shortcut for this that is appropriate for most cases.
    This is needed so that Flask knows where to look for resources such
    as templates and static files.
  3. We then use the :meth:`~flask.Flask.route` decorator to tell Flask
    what URL should trigger our function.
  4. The function returns the message we want to display in the user’s
    browser. The default content type is HTML, so HTML in the string
    will be rendered by the browser.

Save it as :file:`hello.py` or something similar. Make sure to not call
your application :file:`flask.py` because this would conflict with Flask
itself.

To run the application, use the flask command or
python -m flask. You need to tell the Flask where your application
is with the --app option.

$ flask --app hello run
 * Serving Flask app 'hello'
 * Running on http://127.0.0.1:5000 (Press CTRL+C to quit)

Application Discovery Behavior

As a shortcut, if the file is named app.py or wsgi.py, you
don’t have to use --app. See :doc:`/cli` for more details.

This launches a very simple builtin server, which is good enough for
testing but probably not what you want to use in production. For
deployment options see :doc:`deploying/index`.

Now head over to http://127.0.0.1:5000/, and you should see your hello
world greeting.

If another program is already using port 5000, you’ll see
OSError: [Errno 98] or OSError: [WinError 10013] when the
server tries to start. See :ref:`address-already-in-use` for how to
handle that.

Externally Visible Server

If you run the server you will notice that the server is only accessible
from your own computer, not from any other in the network. This is the
default because in debugging mode a user of the application can execute
arbitrary Python code on your computer.

If you have the debugger disabled or trust the users on your network,
you can make the server publicly available simply by adding
--host=0.0.0.0 to the command line:

$ flask run --host=0.0.0.0

This tells your operating system to listen on all public IPs.

Debug Mode

The flask run command can do more than just start the development
server. By enabling debug mode, the server will automatically reload if
code changes, and will show an interactive debugger in the browser if an
error occurs during a request.

The interactive debugger in action.

Warning

The debugger allows executing arbitrary Python code from the
browser. It is protected by a pin, but still represents a major
security risk. Do not run the development server or debugger in a
production environment.

To enable debug mode, use the --debug option.

$ flask --app hello run --debug
 * Serving Flask app 'hello'
 * Debug mode: on
 * Running on http://127.0.0.1:5000 (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: nnn-nnn-nnn

See also:

  • :doc:`/server` and :doc:`/cli` for information about running in debug mode.
  • :doc:`/debugging` for information about using the built-in debugger
    and other debuggers.
  • :doc:`/logging` and :doc:`/errorhandling` to log errors and display
    nice error pages.

HTML Escaping

When returning HTML (the default response type in Flask), any
user-provided values rendered in the output must be escaped to protect
from injection attacks. HTML templates rendered with Jinja, introduced
later, will do this automatically.

:func:`~markupsafe.escape`, shown here, can be used manually. It is
omitted in most examples for brevity, but you should always be aware of
how you’re using untrusted data.

from markupsafe import escape

@app.route("/<name>")
def hello(name):
    return f"Hello, {escape(name)}!"

If a user managed to submit the name <script>alert("bad")</script>,
escaping causes it to be rendered as text, rather than running the
script in the user’s browser.

<name> in the route captures a value from the URL and passes it to
the view function. These variable rules are explained below.

Routing

Modern web applications use meaningful URLs to help users. Users are more
likely to like a page and come back if the page uses a meaningful URL they can
remember and use to directly visit a page.

Use the :meth:`~flask.Flask.route` decorator to bind a function to a URL.

@app.route('/')
def index():
    return 'Index Page'

@app.route('/hello')
def hello():
    return 'Hello, World'

You can do more! You can make parts of the URL dynamic and attach multiple
rules to a function.

Variable Rules

You can add variable sections to a URL by marking sections with
<variable_name>. Your function then receives the <variable_name>
as a keyword argument. Optionally, you can use a converter to specify the type
of the argument like <converter:variable_name>.

from markupsafe import escape

@app.route('/user/<username>')
def show_user_profile(username):
    # show the user profile for that user
    return f'User {escape(username)}'

@app.route('/post/<int:post_id>')
def show_post(post_id):
    # show the post with the given id, the id is an integer
    return f'Post {post_id}'

@app.route('/path/<path:subpath>')
def show_subpath(subpath):
    # show the subpath after /path/
    return f'Subpath {escape(subpath)}'

Converter types:

string (default) accepts any text without a slash
int accepts positive integers
float accepts positive floating point values
path like string but also accepts slashes
uuid accepts UUID strings

Unique URLs / Redirection Behavior

The following two rules differ in their use of a trailing slash.

@app.route('/projects/')
def projects():
    return 'The project page'

@app.route('/about')
def about():
    return 'The about page'

The canonical URL for the projects endpoint has a trailing slash.
It’s similar to a folder in a file system. If you access the URL without
a trailing slash (/projects), Flask redirects you to the canonical URL
with the trailing slash (/projects/).

The canonical URL for the about endpoint does not have a trailing
slash. It’s similar to the pathname of a file. Accessing the URL with a
trailing slash (/about/) produces a 404 «Not Found» error. This helps
keep URLs unique for these resources, which helps search engines avoid
indexing the same page twice.

URL Building

To build a URL to a specific function, use the :func:`~flask.url_for` function.
It accepts the name of the function as its first argument and any number of
keyword arguments, each corresponding to a variable part of the URL rule.
Unknown variable parts are appended to the URL as query parameters.

Why would you want to build URLs using the URL reversing function
:func:`~flask.url_for` instead of hard-coding them into your templates?

  1. Reversing is often more descriptive than hard-coding the URLs.
  2. You can change your URLs in one go instead of needing to remember to
    manually change hard-coded URLs.
  3. URL building handles escaping of special characters transparently.
  4. The generated paths are always absolute, avoiding unexpected behavior
    of relative paths in browsers.
  5. If your application is placed outside the URL root, for example, in
    /myapplication instead of /, :func:`~flask.url_for` properly
    handles that for you.

For example, here we use the :meth:`~flask.Flask.test_request_context` method
to try out :func:`~flask.url_for`. :meth:`~flask.Flask.test_request_context`
tells Flask to behave as though it’s handling a request even while we use a
Python shell. See :ref:`context-locals`.

from flask import url_for

@app.route('/')
def index():
    return 'index'

@app.route('/login')
def login():
    return 'login'

@app.route('/user/<username>')
def profile(username):
    return f'{username}'s profile'

with app.test_request_context():
    print(url_for('index'))
    print(url_for('login'))
    print(url_for('login', next='/'))
    print(url_for('profile', username='John Doe'))
/
/login
/login?next=/
/user/John%20Doe

HTTP Methods

Web applications use different HTTP methods when accessing URLs. You should
familiarize yourself with the HTTP methods as you work with Flask. By default,
a route only answers to GET requests. You can use the methods argument
of the :meth:`~flask.Flask.route` decorator to handle different HTTP methods.

from flask import request

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        return do_the_login()
    else:
        return show_the_login_form()

The example above keeps all methods for the route within one function,
which can be useful if each part uses some common data.

You can also separate views for different methods into different
functions. Flask provides a shortcut for decorating such routes with
:meth:`~flask.Flask.get`, :meth:`~flask.Flask.post`, etc. for each
common HTTP method.

@app.get('/login')
def login_get():
    return show_the_login_form()

@app.post('/login')
def login_post():
    return do_the_login()

If GET is present, Flask automatically adds support for the HEAD method
and handles HEAD requests according to the HTTP RFC. Likewise,
OPTIONS is automatically implemented for you.

Static Files

Dynamic web applications also need static files. That’s usually where
the CSS and JavaScript files are coming from. Ideally your web server is
configured to serve them for you, but during development Flask can do that
as well. Just create a folder called :file:`static` in your package or next to
your module and it will be available at /static on the application.

To generate URLs for static files, use the special 'static' endpoint name:

url_for('static', filename='style.css')

The file has to be stored on the filesystem as :file:`static/style.css`.

Rendering Templates

Generating HTML from within Python is not fun, and actually pretty
cumbersome because you have to do the HTML escaping on your own to keep
the application secure. Because of that Flask configures the Jinja2 template engine for you automatically.

Templates can be used to generate any type of text file. For web applications, you’ll
primarily be generating HTML pages, but you can also generate markdown, plain text for
emails, and anything else.

For a reference to HTML, CSS, and other web APIs, use the MDN Web Docs.

To render a template you can use the :func:`~flask.render_template`
method. All you have to do is provide the name of the template and the
variables you want to pass to the template engine as keyword arguments.
Here’s a simple example of how to render a template:

from flask import render_template

@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
    return render_template('hello.html', name=name)

Flask will look for templates in the :file:`templates` folder. So if your
application is a module, this folder is next to that module, if it’s a
package it’s actually inside your package:

Case 1: a module:

/application.py
/templates
    /hello.html

Case 2: a package:

/application
    /__init__.py
    /templates
        /hello.html

For templates you can use the full power of Jinja2 templates. Head over
to the official Jinja2 Template Documentation for more information.

Here is an example template:

<!doctype html>
<title>Hello from Flask</title>
{% if name %}
  <h1>Hello {{ name }}!</h1>
{% else %}
  <h1>Hello, World!</h1>
{% endif %}

Inside templates you also have access to the :data:`~flask.Flask.config`,
:class:`~flask.request`, :class:`~flask.session` and :class:`~flask.g` [1] objects
as well as the :func:`~flask.url_for` and :func:`~flask.get_flashed_messages` functions.

Templates are especially useful if inheritance is used. If you want to
know how that works, see :doc:`patterns/templateinheritance`. Basically
template inheritance makes it possible to keep certain elements on each
page (like header, navigation and footer).

Automatic escaping is enabled, so if name contains HTML it will be escaped
automatically. If you can trust a variable and you know that it will be
safe HTML (for example because it came from a module that converts wiki
markup to HTML) you can mark it as safe by using the
:class:`~markupsafe.Markup` class or by using the |safe filter in the
template. Head over to the Jinja 2 documentation for more examples.

Here is a basic introduction to how the :class:`~markupsafe.Markup` class works:

>>> from markupsafe import Markup
>>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
Markup('<strong>Hello &lt;blink&gt;hacker&lt;/blink&gt;!</strong>')
>>> Markup.escape('<blink>hacker</blink>')
Markup('&lt;blink&gt;hacker&lt;/blink&gt;')
>>> Markup('<em>Marked up</em> &raquo; HTML').striptags()
'Marked up » HTML'
.. versionchanged:: 0.5

   Autoescaping is no longer enabled for all templates.  The following
   extensions for templates trigger autoescaping: ``.html``, ``.htm``,
   ``.xml``, ``.xhtml``.  Templates loaded from a string will have
   autoescaping disabled.

[1] Unsure what that :class:`~flask.g` object is? It’s something in which
you can store information for your own needs. See the documentation
for :class:`flask.g` and :doc:`patterns/sqlite3`.

Accessing Request Data

For web applications it’s crucial to react to the data a client sends to
the server. In Flask this information is provided by the global
:class:`~flask.request` object. If you have some experience with Python
you might be wondering how that object can be global and how Flask
manages to still be threadsafe. The answer is context locals:

Context Locals

Insider Information

If you want to understand how that works and how you can implement
tests with context locals, read this section, otherwise just skip it.

Certain objects in Flask are global objects, but not of the usual kind.
These objects are actually proxies to objects that are local to a specific
context. What a mouthful. But that is actually quite easy to understand.

Imagine the context being the handling thread. A request comes in and the
web server decides to spawn a new thread (or something else, the
underlying object is capable of dealing with concurrency systems other
than threads). When Flask starts its internal request handling it
figures out that the current thread is the active context and binds the
current application and the WSGI environments to that context (thread).
It does that in an intelligent way so that one application can invoke another
application without breaking.

So what does this mean to you? Basically you can completely ignore that
this is the case unless you are doing something like unit testing. You
will notice that code which depends on a request object will suddenly break
because there is no request object. The solution is creating a request
object yourself and binding it to the context. The easiest solution for
unit testing is to use the :meth:`~flask.Flask.test_request_context`
context manager. In combination with the with statement it will bind a
test request so that you can interact with it. Here is an example:

from flask import request

with app.test_request_context('/hello', method='POST'):
    # now you can do something with the request until the
    # end of the with block, such as basic assertions:
    assert request.path == '/hello'
    assert request.method == 'POST'

The other possibility is passing a whole WSGI environment to the
:meth:`~flask.Flask.request_context` method:

with app.request_context(environ):
    assert request.method == 'POST'

The Request Object

The request object is documented in the API section and we will not cover
it here in detail (see :class:`~flask.Request`). Here is a broad overview of
some of the most common operations. First of all you have to import it from
the flask module:

from flask import request

The current request method is available by using the
:attr:`~flask.Request.method` attribute. To access form data (data
transmitted in a POST or PUT request) you can use the
:attr:`~flask.Request.form` attribute. Here is a full example of the two
attributes mentioned above:

@app.route('/login', methods=['POST', 'GET'])
def login():
    error = None
    if request.method == 'POST':
        if valid_login(request.form['username'],
                       request.form['password']):
            return log_the_user_in(request.form['username'])
        else:
            error = 'Invalid username/password'
    # the code below is executed if the request method
    # was GET or the credentials were invalid
    return render_template('login.html', error=error)

What happens if the key does not exist in the form attribute? In that
case a special :exc:`KeyError` is raised. You can catch it like a
standard :exc:`KeyError` but if you don’t do that, a HTTP 400 Bad Request
error page is shown instead. So for many situations you don’t have to
deal with that problem.

To access parameters submitted in the URL (?key=value) you can use the
:attr:`~flask.Request.args` attribute:

searchword = request.args.get('key', '')

We recommend accessing URL parameters with get or by catching the
:exc:`KeyError` because users might change the URL and presenting them a 400
bad request page in that case is not user friendly.

For a full list of methods and attributes of the request object, head over
to the :class:`~flask.Request` documentation.

File Uploads

You can handle uploaded files with Flask easily. Just make sure not to
forget to set the enctype="multipart/form-data" attribute on your HTML
form, otherwise the browser will not transmit your files at all.

Uploaded files are stored in memory or at a temporary location on the
filesystem. You can access those files by looking at the
:attr:`~flask.request.files` attribute on the request object. Each
uploaded file is stored in that dictionary. It behaves just like a
standard Python :class:`file` object, but it also has a
:meth:`~werkzeug.datastructures.FileStorage.save` method that
allows you to store that file on the filesystem of the server.
Here is a simple example showing how that works:

from flask import request

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        f = request.files['the_file']
        f.save('/var/www/uploads/uploaded_file.txt')
    ...

If you want to know how the file was named on the client before it was
uploaded to your application, you can access the
:attr:`~werkzeug.datastructures.FileStorage.filename` attribute.
However please keep in mind that this value can be forged
so never ever trust that value. If you want to use the filename
of the client to store the file on the server, pass it through the
:func:`~werkzeug.utils.secure_filename` function that
Werkzeug provides for you:

from werkzeug.utils import secure_filename

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        file = request.files['the_file']
        file.save(f"/var/www/uploads/{secure_filename(file.filename)}")
    ...

For some better examples, see :doc:`patterns/fileuploads`.

Cookies

To access cookies you can use the :attr:`~flask.Request.cookies`
attribute. To set cookies you can use the
:attr:`~flask.Response.set_cookie` method of response objects. The
:attr:`~flask.Request.cookies` attribute of request objects is a
dictionary with all the cookies the client transmits. If you want to use
sessions, do not use the cookies directly but instead use the
:ref:`sessions` in Flask that add some security on top of cookies for you.

Reading cookies:

from flask import request

@app.route('/')
def index():
    username = request.cookies.get('username')
    # use cookies.get(key) instead of cookies[key] to not get a
    # KeyError if the cookie is missing.

Storing cookies:

from flask import make_response

@app.route('/')
def index():
    resp = make_response(render_template(...))
    resp.set_cookie('username', 'the username')
    return resp

Note that cookies are set on response objects. Since you normally
just return strings from the view functions Flask will convert them into
response objects for you. If you explicitly want to do that you can use
the :meth:`~flask.make_response` function and then modify it.

Sometimes you might want to set a cookie at a point where the response
object does not exist yet. This is possible by utilizing the
:doc:`patterns/deferredcallbacks` pattern.

For this also see :ref:`about-responses`.

Redirects and Errors

To redirect a user to another endpoint, use the :func:`~flask.redirect`
function; to abort a request early with an error code, use the
:func:`~flask.abort` function:

from flask import abort, redirect, url_for

@app.route('/')
def index():
    return redirect(url_for('login'))

@app.route('/login')
def login():
    abort(401)
    this_is_never_executed()

This is a rather pointless example because a user will be redirected from
the index to a page they cannot access (401 means access denied) but it
shows how that works.

By default a black and white error page is shown for each error code. If
you want to customize the error page, you can use the
:meth:`~flask.Flask.errorhandler` decorator:

from flask import render_template

@app.errorhandler(404)
def page_not_found(error):
    return render_template('page_not_found.html'), 404

Note the 404 after the :func:`~flask.render_template` call. This
tells Flask that the status code of that page should be 404 which means
not found. By default 200 is assumed which translates to: all went well.

See :doc:`errorhandling` for more details.

About Responses

The return value from a view function is automatically converted into
a response object for you. If the return value is a string it’s
converted into a response object with the string as response body, a
200 OK status code and a :mimetype:`text/html` mimetype. If the
return value is a dict or list, :func:`jsonify` is called to produce a
response. The logic that Flask applies to converting return values into
response objects is as follows:

  1. If a response object of the correct type is returned it’s directly
    returned from the view.
  2. If it’s a string, a response object is created with that data and
    the default parameters.
  3. If it’s an iterator or generator returning strings or bytes, it is
    treated as a streaming response.
  4. If it’s a dict or list, a response object is created using
    :func:`~flask.json.jsonify`.
  5. If a tuple is returned the items in the tuple can provide extra
    information. Such tuples have to be in the form
    (response, status), (response, headers), or
    (response, status, headers). The status value will override
    the status code and headers can be a list or dictionary of
    additional header values.
  6. If none of that works, Flask will assume the return value is a
    valid WSGI application and convert that into a response object.

If you want to get hold of the resulting response object inside the view
you can use the :func:`~flask.make_response` function.

Imagine you have a view like this:

from flask import render_template

@app.errorhandler(404)
def not_found(error):
    return render_template('error.html'), 404

You just need to wrap the return expression with
:func:`~flask.make_response` and get the response object to modify it, then
return it:

from flask import make_response

@app.errorhandler(404)
def not_found(error):
    resp = make_response(render_template('error.html'), 404)
    resp.headers['X-Something'] = 'A value'
    return resp

APIs with JSON

A common response format when writing an API is JSON. It’s easy to get
started writing such an API with Flask. If you return a dict or
list from a view, it will be converted to a JSON response.

@app.route("/me")
def me_api():
    user = get_current_user()
    return {
        "username": user.username,
        "theme": user.theme,
        "image": url_for("user_image", filename=user.image),
    }

@app.route("/users")
def users_api():
    users = get_all_users()
    return [user.to_json() for user in users]

This is a shortcut to passing the data to the
:func:`~flask.json.jsonify` function, which will serialize any supported
JSON data type. That means that all the data in the dict or list must be
JSON serializable.

For complex types such as database models, you’ll want to use a
serialization library to convert the data to valid JSON types first.
There are many serialization libraries and Flask API extensions
maintained by the community that support more complex applications.

Sessions

In addition to the request object there is also a second object called
:class:`~flask.session` which allows you to store information specific to a
user from one request to the next. This is implemented on top of cookies
for you and signs the cookies cryptographically. What this means is that
the user could look at the contents of your cookie but not modify it,
unless they know the secret key used for signing.

In order to use sessions you have to set a secret key. Here is how
sessions work:

from flask import session

# Set the secret key to some random bytes. Keep this really secret!
app.secret_key = b'_5#y2L"F4Q8znxec]/'

@app.route('/')
def index():
    if 'username' in session:
        return f'Logged in as {session["username"]}'
    return 'You are not logged in'

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        session['username'] = request.form['username']
        return redirect(url_for('index'))
    return '''
        <form method="post">
            <p><input type=text name=username>
            <p><input type=submit value=Login>
        </form>
    '''

@app.route('/logout')
def logout():
    # remove the username from the session if it's there
    session.pop('username', None)
    return redirect(url_for('index'))

How to generate good secret keys

A secret key should be as random as possible. Your operating system has
ways to generate pretty random data based on a cryptographic random
generator. Use the following command to quickly generate a value for
:attr:`Flask.secret_key` (or :data:`SECRET_KEY`):

$ python -c 'import secrets; print(secrets.token_hex())'
'192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'

A note on cookie-based sessions: Flask will take the values you put into the
session object and serialize them into a cookie. If you are finding some
values do not persist across requests, cookies are indeed enabled, and you are
not getting a clear error message, check the size of the cookie in your page
responses compared to the size supported by web browsers.

Besides the default client-side based sessions, if you want to handle
sessions on the server-side instead, there are several
Flask extensions that support this.

Message Flashing

Good applications and user interfaces are all about feedback. If the user
does not get enough feedback they will probably end up hating the
application. Flask provides a really simple way to give feedback to a
user with the flashing system. The flashing system basically makes it
possible to record a message at the end of a request and access it on the next
(and only the next) request. This is usually combined with a layout
template to expose the message.

To flash a message use the :func:`~flask.flash` method, to get hold of the
messages you can use :func:`~flask.get_flashed_messages` which is also
available in the templates. See :doc:`patterns/flashing` for a full
example.

Logging

.. versionadded:: 0.3

Sometimes you might be in a situation where you deal with data that
should be correct, but actually is not. For example you may have
some client-side code that sends an HTTP request to the server
but it’s obviously malformed. This might be caused by a user tampering
with the data, or the client code failing. Most of the time it’s okay
to reply with 400 Bad Request in that situation, but sometimes
that won’t do and the code has to continue working.

You may still want to log that something fishy happened. This is where
loggers come in handy. As of Flask 0.3 a logger is preconfigured for you
to use.

Here are some example log calls:

app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')

The attached :attr:`~flask.Flask.logger` is a standard logging
:class:`~logging.Logger`, so head over to the official :mod:`logging`
docs for more information.

See :doc:`errorhandling`.

Hooking in WSGI Middleware

To add WSGI middleware to your Flask application, wrap the application’s
wsgi_app attribute. For example, to apply Werkzeug’s
:class:`~werkzeug.middleware.proxy_fix.ProxyFix` middleware for running
behind Nginx:

from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app)

Wrapping app.wsgi_app instead of app means that app still
points at your Flask application, not at the middleware, so you can
continue to use and configure app directly.

Using Flask Extensions

Extensions are packages that help you accomplish common tasks. For
example, Flask-SQLAlchemy provides SQLAlchemy support that makes it simple
and easy to use with Flask.

For more on Flask extensions, see :doc:`extensions`.

Deploying to a Web Server

Ready to deploy your new Flask app? See :doc:`deploying/index`.

  • Главная

  • Инструкции

  • Python

  • Создание веб-приложения с использованием Python Flask

Blog

В предыдущей статье в блоге Timeweb Cloud мы познакомились с веб-разработкой на python с использованием Flask и рассмотрели способы работы с входящими данными. В этом туториале мы шагнем чуть дальше и напишем простое веб-приложение на python с базой данных для авторизации пользователей.

Мы будем работать в PyCharm+pipenv, а сайт сделаем на HTML+CSS. Операционная система — Win 10. Установка Flask, PyCharm и pipenv подробно описана в этой статье.

Создание Веб Приложения С Использованием Python Flask (2)

В рамках статьи мы будем использовать тестовые, учебные примеры, которые не подойдут для реализации на продакшене. Например,  для проверки пароля в БД необходимо хранить хэш пароля и сравнивать хэши, а не пароли. Также для работы с СУБД нужно использовать ORM, а не писать «сырой» SQL (подробнее про ORM см. здесь).

Кстати, в официальном канале Timeweb Cloud собрали комьюнити из специалистов, которые говорят про IT-тренды, делятся полезными инструкциями и даже приглашают к себе работать.

БД для логинов и паролей

После того, как вы установите Flask и остальные инструменты, можно перейти к работе. Для хранения данных пользователей будем использовать СУБД SQlite. Она отлично подходит для небольших проектов. Её основное преимущество заключается в автономности: для работы с ней не потребуется сервер. К тому же в python встроен модуль sqlite3 для работы с ней. Но если вы решите работать с СУБД на сервере, то обратите внимание на облачные серверы Timeweb Cloud.

Итак, работа с модулем начинается с импорта:

import sqlite3

Теперь мы можем создать БД и таблицу с логинами и паролями:

db_lp = sqlite3.connect('login_password.db')
cursor_db = db_lp.cursor()
sql_create = '''CREATE TABLE passwords(
login TEXT PRIMARY KEY,
password TEXT NOT NULL);'''

cursor_db.execute(sql_create)
db_lp.commit()

cursor_db.close()
db_lp.close()

Распишем подробно, что этот код делает:

  • Подключаемся к БД с помощью метода connect(). Метод будет искать файл login_password.db в каталоге проекта. Если не найдет, то создаст самостоятельно.
  • Создаем объект cursor_db для взаимодействия с БД;
  • sql_create — это SQL-запрос для создания таблицы с логинами и паролями;
  • С помощью метода execute() выполняем sql_create;
  • Сохраняем изменения в БД методом commit();
  • Закрываем объекты cursor_db и db_lp во избежание проблем с БД;

Авторизация

Основная форма

Сначала создадим форму, с помощью которой будет осуществляться авторизация в абстрактный сервис.

HTML

<form method="POST">
  <div class="container">
    <label for="Login"><b>Логин</b></label>

    <input type="text" placeholder="" name="Login" required>
    <label for="Password"><b>Пароль</b></label>
    <input type="password" placeholder="" name="Password" required>
    <button type="submit">Войти</button>
   <div class="container">
    <span class="reg"> <a href="/registration">Регистрация</a></span>
  </div>
</form>  

<link rel="stylesheet" href="{{ url_for('static', filename= 'css/auth.css') }}">

  Здесь важно 3 момента:

  • <form method=»POST»> — указываем, что будем использовать POST-запросы;
  • label for=»Login» и label for=»Password» — отмечаем Login, который в дальнейшем будем обрабатывать с помощью метода get() из модуля request;
  • <link rel=»stylesheet» href=»{{ url_for(‘static’, filename= ‘css/auth.css’) }}»> — сообщаем HTML, где хранятся css файл. Чуть дальше подробнее разберем, где нужно его размещать.

CSS

form {
    font: 14px Stem-Regular,arial, sans-serif;/*Выбираем шрифт */
    border: 1px solid black;/*Цвет, размер и тип границы */
    -webkit-border-radius: 20px;/*Скругляем углы */
    color: #777991;/*Цвет label */
    width: 25%; /*Ширина формы */
    margin-right: auto; /*Положение формы */
    margin-left: auto; /*Положение формы */
    text-align: center; /*Центровка текста*/

}

input[type=text], input[type=password] {
    text-align: center; /*Центровка текста*/
    -webkit-border-radius: 4px;/*Скругляем углы */
    width: auto; /*Ширина */
    padding: 15px 20px; /*Размер внутренних отступов*/
    margin: 10px 0; /*Размер внешних отступов*/
    margin-right: auto; /*Размер внешних отступов справа*/
    margin-left: auto; /*Размер внешних отступов слева*/
    display: block; /*тип отображения*/
    border: 1px solid #050c26;/*Цвет, размер и тип границы */
    box-sizing: border-box; /*Размер объекта по отношению к родительскому */
    font: 14px Stem-Regular,arial, sans-serif;/*Выбираем шрифт */
    color: #777991;/*Цвет текста в input */
}

button {
    font: 16px Stem-medium, arial, sans-serif; /*Выбираем шрифт кнопки */
    background-color: #454cee; /*Выбираем цвет фона */
    -webkit-border-radius: 8px; /*Закругление */
    color: white; /*Выбираем цвет текста*/
    padding: 16px 20px; /*Размер внутренних отступов*/
    margin: 8px 0;/*Размер внешних отступов*/
    border: none; /*Без границы*/
    cursor: pointer; /*Изменение курсора при наведении на кнопку*/
    width: auto; /*Ширина*/
}

button:hover { 
    opacity: 0.9; /*Изменение яркости кнопки при наведении*/
}

.container {
    padding: 20px; /*Размер внутренних отступов контейнера*/
}

Вот как выглядит эта форма:

Image5

Сообщение для успешной авторизации

Если пользователь ввёл верную пару логин-пароль, то выведем соответствующее сообщение.

HTML

<form method="POST">
  <div class="container">
    <label><b>Вы успешно авторизовались</b></label>   
</form>

<link rel="stylesheet" href="{{ url_for('static', filename= 'css/successfulauth.css') }}">

CSS

form {
    font: 14px Stem-Regular,arial, sans-serif;/*Выбираем шрифт */
    border: 1px solid black;/*Цвет, размер и тип границы */
    -webkit-border-radius: 20px;/*Скругляем углы */
    color: #777991;/*Цвет label */
    width: 25%; /*Ширина формы */
    margin-right: auto; /*Положение формы */
    margin-left: auto; /*Положение формы */
    text-align: center; /*Центровка текста*/
}
.container {
    padding: 30px; /*Размер внутренних отступов контейнера*/
}

Image2

Сообщение при неудачной авторизации

HTML

<form method="POST">
<form method="POST">
  <div class="container">
    <label><b>Введен неправильный логин или пароль</b></label>   
</form>  

<link rel="stylesheet" href="{{ url_for('static', filename= 'css/auth_bad.css') }}">

  CSS

form {
    font: 14px Stem-Regular,arial, sans-serif;/*Выбираем шрифт */
    border: 1px solid black;/*Цвет, размер и тип границы */
    -webkit-border-radius: 20px;/*Скругляем углы */
    color: #777991;/*Цвет label */
    width: 25%; /*Ширина формы */
    margin-right: auto; /*Положение формы */
    margin-left: auto; /*Положение формы */
    text-align: center; /*Центровка текста*/

}

.container {
    padding: 30px;/*Размер внутренних отступов контейнера*/
}

Image3

Теперь создадим форму для регистрации.

Регистрация

С помощью формы регистрации пользователь сможет создать свой аккаунт. Вот HTML и CSS основной формы:

HTML

<form method="POST">
  <div class="container">
    <label for="Login"><b>Логин</b></label>
    <input type="text" placeholder="" name="Login" required>

    <label for="Password"><b>Пароль</b></label>
    <input type="password" placeholder="" name="Password" required>   

    <button type="submit">Зарегистрироваться</button>
  </div>
</form>  

<link rel="stylesheet" href="{{ url_for('static', filename= 'css/regis.css') }}">

CSS

form {
     font: 14px Stem-Regular,arial, sans-serif;/*Выбираем шрифт */
    border: 1px solid black;/*Цвет, размер и тип границы */
    -webkit-border-radius: 20px;/*Скругляем углы */
    color: #777991;/*Цвет label */
    width: 25%; /*Ширина формы */
    margin-right: auto; /*Положение формы */
    margin-left: auto; /*Положение формы */
    text-align: center; /*Центровка текста*/

}

input[type=text], input[type=password] {
    text-align: center; /*Центровка текста*/
    -webkit-border-radius: 4px;/*Скругляем углы */
    width: auto; /*Ширина */
    padding: 15px 20px; /*Размер внутренних отступов*/
    margin: 10px 0; /*Размер внешних отступов*/
    margin-right: auto; /*Размер внешних отступов справа*/
    margin-left: auto; /*Размер внешних отступов слева*/
    display: block; /*тип отображения*/
    border: 1px solid #050c26;/*Цвет, размер и тип границы */
    box-sizing: border-box; /*Размер объекта по отношению к родительскому */
    font: 14px Stem-Regular,arial, sans-serif;/*Выбираем шрифт */
    color: #777991;/*Цвет текста в input */  
}

button {
    font: 16px Stem-medium, arial, sans-serif; /*Выбираем шрифт кнопки */
    background-color: #454cee; /*Выбираем цвет фона */
    -webkit-border-radius: 8px; /*Закругление */
    color: white; /*Выбираем цвет текста*/
    padding: 16px 20px; /*Размер внутренних отступов*/
    margin: 8px 0;/*Размер внешних отступов*/
    border: none; /*Без границы*/
    cursor: pointer; /*Изменение курсора при наведении на кнопку*/
    width: auto; /*Ширина*/
}

button:hover { 
    opacity: 0.9; /*Изменение яркости кнопки при наведении*/
}

.container {
    padding: 20px; /*Размер внутренних отступов контейнера*/
}

Форма регистрации выглядит вот так:

Image1

При завершении регистрации пользователь увидит такое сообщение:

Image4

HTML

<form method="POST">
<div class="container">
    <label><b>Вы успешно зарегистрировались</b></label>    

   <div class="container">
    <span class="reg"> <a href="/authorization">Вернуться к авторизации</a></span>
  </div>
</form>  

<link rel="stylesheet" href="{{ url_for('static', filename= 'css/successfulregis.css') }}">

CSS

form {
    font: 14px Stem-Regular,arial, sans-serif;/*Выбираем шрифт */
    border: 1px solid black;/*Цвет, размер и тип границы */
    -webkit-border-radius: 20px;/*Скругляем углы */
    color: #777991;/*Цвет label */
    width: 25%; /*Ширина формы */
    margin-right: auto; /*Положение формы */
    margin-left: auto; /*Положение формы */
    text-align: center; /*Центровка текста*/

}

.container {
    padding: 20px;/*Размер внутренних отступов контейнера*/
}

Декоратор авторизации

После создания всех форм и БД мы можем начать разработку веб-приложения Flask. Для отправления html-документа в ответ на запрос клиента в Flask нужно использовать метод render_template()

Этот метод Flask использует папку templates для хранения html-файлов в каталоге проекта. Нам необходимо создать её. Также в html-документах нужно указать относительную ссылку на css. В нашем случае мы размещаем их в соседней папке static/css в каталоге проекта, чтобы не допускать беспорядка в файлах. Сейчас каталог проекта с html и css имеет такую структуру:

Timeweb
|— template
|     `— authorization.html
|     `— auth_bad.html
|     `— successauth.html
|     `— successregis.html
|     `— registration.html
|— static
|    — css
|         `— auth_bad.css
|         `— auth.css
|         `— successauth.css
|         `— regis.css
|         `— successfulregis.css

Импортируйте пакет Flask и другие модули:

from flask import Flask, request, render_template

Код для авторизации пользователей представлен ниже. Подробнее об аутентификации рекомендуем почитать здесь.

@app.route('/authorization', methods=['GET', 'POST'])
def form_authorization():
   if request.method == 'POST':
       Login = request.form.get('Login')
       Password = request.form.get('Password')

      db_lp = sqlite3.connect('login_password.db')
       cursor_db = db_lp.cursor()
       cursor_db.execute(('''SELECT password FROM passwords
                                               WHERE login = '{}';
                                               ''').format(Login))
       pas = cursor_db.fetchall()

       cursor_db.close()
       try:
           if pas[0][0] != Password:
               return render_template('auth_bad.html')
       except:
           return render_template('auth_bad.html')

       db_lp.close()
       return render_template('successfulauth.html')

   return render_template('authorization.html')

Вот логика его работы:

  • Пользователь переходит на /authorization — это GET-запрос и декоратор возвращает authorization.html;
  • Когда пользователь введет логин, пароль и нажмет кнопку “Войти”, сервер получит POST-запрос, который декоратор будет обрабатывать. Декоратор получит логин и пароль, которые ввел пользователь;
  • Затем подключаемся к БД и выполняем SQL-запрос к ней. С помощью cursor_db.execute() и cursor_db.fetchall() получаем строку password(возможно, пустую), соответствующую введенному логину;
  • Из строки “вытаскиваем” пароль и:
    • Если строка пустая, то это вызовет ошибку (выход за пределы массива), которую мы обрабатываем конструкцией try-except и сообщаем пользователю о неверных введенных данных. Декоратор завершает работу;
    • Если пароль в БД не совпадает с полученным паролем, то просто возвращает сообщение о некорректности данных и завершаем работу;
    • Если пароль верный, то выдаем сообщение о успешной авторизации и завершаем работу Flask-декоратора.

Декоратор регистрации

На страницу /registration пользователь попадает из формы авторизации. Вот код декоратора:

@app.route('/registration', methods=['GET', 'POST'])
def form_registration():

   if request.method == 'POST':
       Login = request.form.get('Login')
       Password = request.form.get('Password')

       db_lp = sqlite3.connect('login_password.db')
       cursor_db = db_lp.cursor()
       sql_insert = '''INSERT INTO passwords VALUES('{}','{}');'''.format(Login, Password)

       cursor_db.execute(sql_insert)
       db_lp.commit()

       cursor_db.close()
       db_lp.close()

       return render_template('successfulregis.html')

   return render_template('registration.html')

  • Сначала обрабатывается GET-запрос /registration. Возвращаем registration.html;
  • Когда пользователь введет данные и нажмет кнопку “Зарегистрироваться”, сервер получит POST-запрос. Из него получаем Login и Password;
  • Подключаемся к БД;
  • sql_insert — запрос на добавление новой строки с данными пользователя;
  • Выполняем sql_insert и сохраняем изменения;
  • Закрываем cursor_db, db_lp и возвращаем сообщение об успешной регистрации.

Полный код программы

from flask import Flask, request, render_template
import sqlite3

app = Flask(__name__)

@app.route('/authorization', methods=['GET', 'POST'])
def form_authorization():
   if request.method == 'POST':
       Login = request.form.get('Login')
       Password = request.form.get('Password')

       db_lp = sqlite3.connect(login_password.db')
       cursor_db = db_lp.cursor()
       cursor_db.execute(('''SELECT password FROM passwords
                                               WHERE login = '{}';
                                               ''').format(Login))
       pas = cursor_db.fetchall()

       cursor_db.close()
       try:
           if pas[0][0] != Password:
               return render_template('auth_bad.html')
       except:
           return render_template('auth_bad.html')

       db_lp.close()
       return render_template('successfulauth.html')

   return render_template('authorization.html')

@app.route('/registration', methods=['GET', 'POST'])
def form_registration():

   if request.method == 'POST':
       Login = request.form.get('Login')
       Password = request.form.get('Password')

       db_lp = sqlite3.connect('login_password.db')
       cursor_db = db_lp.cursor()
       sql_insert = '''INSERT INTO passwords VALUES('{}','{}');'''.format(Login, Password)

       cursor_db.execute(sql_insert)

       cursor_db.close()

       db_lp.commit()
       db_lp.close()

       return render_template('successfulregis.html')

   return render_template('registration.html')

if __name__ == "__main__":
 app.run()

Flask является микрофреймворком для создания вебсайтов на языке Python. В основу статьи положен перевод из официальной документации Flask. Поэтому в ней имеется обращение от первого лица, то есть от создателя фреймворка Армина Ронахера.

Flask в данном контексте переводится как пороховой рожок, на это указывает официальное лого.

Предисловие[править]

Что значит «микро»?[править]

На мой взгляд «микро» в фреймворке относится не только к простоте и небольшому размеру базы, но это также может означать тот факт, что он не предлагает вам много проектных решений. Несмотря на то, что Flask использует нечто подобное в виде шаблонизатора, мы не будем принимать подобные решения для вашего хранилища данных или других частей. Тем не менее, для нас термин «микро» не означает, что вся реализация должна вписываться в один файл.

Одним из проектных решений во Flask является то, что простые задачи должны быть простыми; они не должны занимать много кода, и это не должно ограничивать вас. Поэтому мы сделали несколько вариантов дизайна, некоторые люди могут посчитать это удивительным и даже странным. Например, Flask использует локальные треды внутри объектов, так что вы не должны передавать объекты в пределах одного запроса от функции к функции, оставаясь в безопасном треде. Хоть это и очень простой подход, который позволяет сэкономить время, такое решение может вызвать некоторые проблемы для слишком больших приложений, поскольку изменения в этих локальных тредах-объектах могут произойти где угодно в этом треде. Для того, чтобы решить эти проблемы, мы не стали скрывать от вас локальные треды-объекты, вместо этого мы охватываем их и предоставляем вам много инструментов, чтобы сделать работу с ними настолько приятной, насколько это возможно.

Во Flask многие вещи предварительно сконфигурированы, на основе общей базовой конфигурации. Например, шаблоны и статические файлы сохранены в подкаталогах в пределах исходного дерева. Вы также можете изменить это, но обычно этого не требуется.

Основная причина почему Flask называется «микрофреймворком» — это идея сохранить ядро простым, но расширяемым. В нем нет абстрактного уровня базы данных, нет валидации форм или всего того, что уже есть в других библиотеках. Однако, Flask поддерживает расширения, которые могут добавить необходимую функциональность и имплементирует их так, как будто они уже были встроены изначально. В настоящее время уже есть расширения: формы валидации, поддержка закачки файлов, различные технологии аутентификации и многие другие.

Безопасность[править]

Ваше безопасное веб-приложение можно взломать различными способами, так как веб-программирование — это небезопасное занятие. Вы позволяете пользователям оставлять информацию на сервере, следовательно можно найти способ взломать ваше веб-приложение. Flask защищает вас от наиболее распространенных и известных способов взлома, такие как XSS (cross-site scripting). До тех пор, пока вы сами сознательно не отмечаете опасный html как безопасный, Flask и шаблонизатор Jinja2 защищают вас, но все равно могут найтись способы взломать ваш сайт.

Статус насчет Python 3[править]

Werkzeug и Flask поддерживают Python 3.

Установка[править]

Имеются две зависимости:
Jinja2 — движок темплейтов
Werkzeug — набор инструментов WSGI, стандартного интерфейса Python для развертывания веб-приложений и взаимодействия между ними и различными серверами разработки.
Virtualenv — не является зависимостью. Это инструмент, который призван решить проблему обратной совместимости. Вы можете использовать различные версии Python или версии библиотек, это может вызвать конфликт зависимостей. Virtualenv решает эту проблему, создает изолированную среду для каждого проекта.

Под Windows[править]

Запустить командную строку cmd
Проще всего установить при помощи скрипта easy_install. В командной строке должно получиться что-то вроде этого:
C:Python27python.exe C:Python27Scriptseasy_install-2.7-script.py Flask
Установка зависимостей:
C:Python27python.exe C:Python27Scriptseasy_install-2.7-script.py Jinja2
C:Python27python.exe C:Python27Scriptseasy_install-2.7-script.py Werkzeug
C:Python27python.exe C:Python27Scriptseasy_install-2.7-script.py Virtualenv

Установка под Linux[править]

В терминале написать
pip install flask

Краткая документация[править]

Простейший Hello world[править]

Создадим файл hello.py следующего содержания:

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    app.run()

Проверим как установился Flask. Запустим скрипт:
C:Python27python.exe hello.py
Наберите в браузере в адресной строке http://localhost:5000/ и увидите надпись Hello World!
В версии Flask 1.0.2 процесс запуска изменился. Требуется следующая команда:
FLASK_APP=hello.py flask run
Не забудьте, для запуска сначала требуется перейти в консоли в папку, где хранится скрипт.

Разбор программы Hello world![править]

Рассмотрим, как работает программа, которую вы запустили выше:

  1. Сначала мы импортируем Flask класс. Экземпляр этого класса будет нашим WSGI приложением. Первым аргументом является имя модуля приложения. Если вы используете один модуль (как в данном примере), вы должны использовать __name__, потому что в зависимости от того, было ли это начато как приложение или как импорт модуля, название будет другим (‘__main__’ по сравнению с реальным именем импорта).
  2. Далее мы создаем экземпляр этого класса. Мы передаем ему имя модуля или пакета. Это необходимо, так как Flask не знает, где искать шаблоны, статические файлы, и так далее.
  3. Затем мы используем route(). Декоратор говорит Flask, что URL должен вызывать нашу функцию.
  4. Функция задает имя, которое также используется для создания URL-адресов для этой функции, и возвращает сообщение, что мы хотим отобразить в браузере пользователя.
  5. Наконец, мы используем run() функцию для запуска локального сервера с нашим приложением. Условие __name__ == «__main__» означает, что сервер работает только в том случае, если скрипт выполняется непосредственно из Python интерпретатора и не используется в качестве импортированного модуля.

Общедоступный сервер[править]

Если вы запустите сервер, вы заметите, что он доступен только с вашего компьютера, а не с любого другого компьютера в сети. Это значение выставлено по умолчанию, потому что в режиме отладки пользователь приложения может выполнить произвольный код на вашем компьютере.

Если вы доверяете пользователям в вашей сети, вы можете сделать сервер общедоступным, просто изменив вызов run(), который должен выглядеть следующим образом:

app.run(host='0.0.0.0')

Это говорит вашей операционной системе слушать на всех ip (интерфейсах).

Режим отладки[править]

Метод run() хорош для начала разработки на локальном сервере. Но это потребует ручного перезапуска сервера после каждого изменения в коде. Хорошо что Flask может справиться с этой проблемой. Если включить Debug Mode, сервер будет сам перегружаться после каждого изменения в коде. Еще вы получите полезный отладчик, на тот случай если что-то пойдет не так.
Есть два способа включить режим отладки:
app.debug = True
app.run()
Или
app.run(debug=True)
Оба метода дадут одинаковый эффект

Внимание
Несмотря на то, что интерактивный отладчик не работает в разветвленной среде (что делает его практически невозможным в использовании на производственных серверах), он все же позволяет выполнить произвольный код. Это делает его одним из основных рисков в безопасности, и поэтому он никогда не должен быть использован на производственных машинах.

Маршрутизация[править]

Современные веб-приложения имеют красивые URL. Это помогает людям помнить их, особенно это удобно для приложений, которые используются в мобильных устройствах с медленным сетевым подключением. Если пользователь может попасть на страницу через URL минуя главную страницу, то более вероятно, что он вернется в следующий раз.
Как вы видели выше, декоратор route() используется для связывания функций с URL. Вот несколько основных примеров:

@app.route('/')
def index():
    return 'Index Page'
@app.route('/status')
def status():
    return "Сделано на flask"
@app.route('/hello')
def hello():
    return 'Hello World'

Но это еще не все! Вы можете сделать некоторые части URL динамическими и применить несколько правил к функции.

Использование переменных[править]

Для добавления переменной части в URL вы можете пометить эти разделы, как <variable_name>. Такая часть затем передается как аргумент ключевого слова в вашу функцию. Дополнительно преобразователь может быть определен путем указания правила <converter:variable_name>. Вот некоторые хорошие примеры:

@app.route('/user/<username>')
def show_user_profile(username):
    # show the user profile for that user
    return 'User %s' % username
@app.route('/post/<int:post_id>')
def show_post(post_id):
    # show the post with the given id, the id is an integer
    return 'Post %d' % post_id

Имеются следующие конверторы

int Принимает целые числа
float то же самое, что int, только с плавающей точкой
path похоже на то, что установлено по умолчанию, но принимает слэши

Уникальные линки и редирект[править]

Линки во Flask основаны на базе модуля маршрутизации от Werkzeug. Идея этого модуля заключается в обеспечении красивых и уникальных URL-адресов на основе правил ранее сформировавшихся в Apache и в HTTP серверах.
Используйте два правила:

@app.route('/projects/')
def projects():
    return 'The project page'
@app.route('/about')
def about():
    return 'The about page'

Хоть эти примеры и выглядят очень похоже, они различаются по слэшу в конце URL. В первом случае, доступ без слэша вызывает редирект на канонические URL с обратным слэшем.
Во втором случае, без обратного слэша, URL определяется, как путь к файлу на UNIX-подобных системах. Доступ к URL со слэшем будет перенаправляться на ошибку 404 «не найдено».
Такое поведение позволяет пользователям получить доступ к странице, даже если они забыли ввести в конце обратный слэш. Кроме того, URL-адрес будет оставаться уникальным, и это поможет поисковым системам избегать повторного индексирования страницы.

Генерация URL[править]

Flask может генерировать URL. Для создания URL, используйте функцию url_for(). Она принимает имя функции в качестве первого аргумента, а также ряд ключевых аргументов, каждый из которых соответствует переменной части URL правила. Части неизвестной переменной добавляется к URL в качестве параметров запроса. Вот несколько примеров:

>>> from flask import Flask, url_for
>>> app = Flask(__name__)
>>> @app.route('/')
... def index(): pass
...
>>> @app.route('/login')
... def login(): pass
...
>>> @app.route('/user/<username>')
... def profile(username): pass
...
>>> with app.test_request_context():
...  print(url_for('index'))
...  print(url_for('login'))
...  print(url_for('login', next='/'))
...  print(url_for('profile', username='John Doe'))
...
/
/login
/login?next=/
/user/John%20Doe

Здесь также используется метод test_request_context(), который объяснен ниже. Он говорит Flask, как нужно обрабатывать запрос, даже если мы взаимодействуем через шел Python. Почему мы используем построение URL вместо их жесткого задания в шаблонах? На то есть три хорошие причины:

  • Реверсирование, зачастую является более описательным методом и позволяет изменять URL на одном дыхании.
  • Автоматическое экранирование специальных символов, уникода. Вам даже не придется задумываться над этим.
  • Если ваше приложение находится вне корневой URL (например, /myapplication вместо /), функция url_for () будет это правильно обрабатывать для вас.

HTTP методы[править]

HTTP (мы говорим о протоколе веб-приложения) знает различные способы доступа к URL-адресам. По умолчанию маршрут реагирует только на ответы GET-запросов, но это можно изменить путем предоставления методов, используя аргументы к декоратору route(). Вот несколько примеров:

from flask import request

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        do_the_login()
    else:
        show_the_login_form()

Если присутствует GET, тогда HEAD будет добавлен автоматически. Вам не нужно об этом заботиться. Также будьте уверены, что HEAD поддерживает HTTP RFC зависимости, так что вы можете полностью игнорировать HTTP спецификации.
Если вы ничего не знаете про HTTP методы, приводим небольшую справку:

  • GET

Браузер сообщает серверу, что желает получить информацию со страницы и просит отправить ее. Возможно, это самый распространенный метод.

  • HEAD

Браузер сообщает серверу, что желает получить информацию, но интересуется только заголовком, а не содержанием страницы. Приложение допускает запрос, в случае если GET был получен.

  • POST

Браузер сообщает серверу, что хочет запостить новую информацию по данному URL и сервер должен быть уверен, что сохранит ее и сделает один раз. Так обычно передается информация из HTML форм на сервер.

  • PUT

Очень схоже с POST, но сервер может инициировать сохранение информации несколько раз, переписывая старые значения более одного раза. Вы можете задаться вопросом: «Для чего это нужно?». Есть несколько хороших причин, чтобы делать это таким образом. Учтите, что соединение может быть потеряно во время передачи: в этой ситуации система между браузером и сервером может затребовать передачу во второй раз. С POST это невозможно, так как запрос возможен только один раз.

  • DELETE

Удаление информации в полученной локации.

  • OPTIONS

Быстрый способ для клиента выяснить какой из методов поддерживается по данному URL. Начиная с Flask 0.6 эта возможность имплементирована автоматически.

Самое интересное в том, что HTML4 и XHTML1 поддерживают методы GET и POST. Но с использованием JavaScript и будущих стандартов HTML можно использовать другие методы. Кроме того HTTP стал весьма популярным в последнее время и браузеры — уже не единственные клиенты, использующие протокол HTTP. Например, многие системы контроля версий используют его.

Статические файлы[править]

Динамическим веб-приложениям также требуются статические файлы. Обычно это css и javascript файлы. В идеале, ваш веб-сервер уже настроен, чтобы их обслуживать, но если вам потребуется, вы можете изменить настройки во Flask.
Просто создайте папку с названием static в вашем пакете или рядом с модулем и она будет доступна в /static по применению.
Для генерации адресов для статических файлов, используйте специальное имя ‘static’:

url_for('static', filename='style.css')

Файл будет доступен в файловой системе по пути static/style.css

Рендеринг шаблонов[править]

Создание HTML из Python — это не удовольствие, а громоздкий процесс. Так как HTML обладает своей свободой, вы должны озаботиться безопасностью своего приложения. В связи с этим Flask автоматически настраивает для нас Jinja2 (механизм шаблонов).

Чтобы создать шаблон, можно использовать метод render_template(). Все, что вам нужно сделать, — это указать имя шаблона и переменные, которые вы хотите передать в шаблоны как ключевые аргументы. Вот простой пример того, как сделать шаблон:

from flask import render_template

@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
    return render_template('hello.html', name=name)

Flask будет искать шаблоны в папке шаблонов. Таким образом, если ваше приложение представляет собой модуль, эта папка находится рядом с этим модулем, если же это пакет, то на самом деле внутри вашего пакета:
Случай с модулем:

/application.py
/templates
    /hello.html

Случай с пакетом:

/application
    /__init__.py
    /templates
        /hello.html

Пример шаблона:

<!doctype html>
<title>Hello from Flask</title>
{% if name %}
  <h1>Hello {{ name }}!</h1>
{% else %}
  <h1>Hello World!</h1>
{% endif %}

Внутри шаблонов, вы также имеете доступ к реквестам, сессиям и g-объектам, а также можете использовать get_flashed_messages() функцию. G-объекты, имеется ввиду, если вам необходимо сохранить информацию для ваших нужд, смотрите документацию «Использование SQLite 3 во Flask».
Шаблоны особенно полезны, если используется наследование. Прочтите документацию как используется наследование в шаблонах. Наследование в шаблонах позволяет сохранить некоторые элементы на каждой страницы (например: заголовок, навигация и футер).
Включено автоматическое экранирование, что позволит избежать проблем, если имя содержит HTML-теги. Если доверяете переменной, а вы знаете, что это будет безопасно в HTML (например, потому что оно пришло от модуля, который преобразует вики-разметки в HTML), вы можете пометить его как безопасный с помощью Markup класса или с помощью |safe фильтра в шаблоне. В документации Jinja 2 представлено несколько примеров.

Вот пример, как работает класс разметки:

>>> from flask import Markup
>>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
Markup(u'<strong>Hello &lt;blink&gt;hacker&lt;/blink&gt;!</strong>')
>>> Markup.escape('<blink>hacker</blink>')
Markup(u'&lt;blink&gt;hacker&lt;/blink&gt;')
>>> Markup('<em>Marked up</em> &raquo; HTML').striptags()
u'Marked up xbb HTML'

Доступ к данным запроса[править]

Для веб-приложений крайне важно реагировать на данные клиента отправляя их на сервер. Flask обеспечивает этой информацией — глобальный объект запроса. Если у вас есть некоторый опыт работы с Python, вы можете быть удивлены, как этот объект может быть глобальным, и как Flask удается оставаться потоко-безопасным. Ответ заключается в локальных контекстах:

Локальные контексты (Context Locals)[править]

Инсайдерская информация
Если вы хотите понять, как это работает и как вы можете осуществить испытания с контекстными локалами, 
прочитайте этот раздел, в противном случае, пропустите его.

Некоторые объекты во Flask — глобальные, но это не обычный вид глобальных объектов. Эти объекты являются на самом деле прокси к объектам, которые являются локальными для конкретного контекста. Это труднопроизносимо. Но на самом деле довольно легко понять.
Представьте, что контекст обрабатывает поток. Приходит запрос и веб-сервер решает породить новый поток (или что-то другое, основной объект может иметь дело с параллельными системами в отличие от потоков). Когда Flask начинает свою внутреннюю обработку запросов, он выясняет, какой текущий поток является активным контекстом и связывает текущее приложение и WSGI среду в этом контексте (потоке). Он делает это умно, таким образом, чтобы одно приложение может вызвать другое приложение без разрушения.
Итак, что же это значит для вас? В принципе, пока вы тестируете, вы можете полностью это игнорировать. Но вы можете заметить, что код, который зависит от объекта запроса может внезапно сломаться, потому что нет объекта запроса. Решением является создание объекта запроса для себя и привязка его к контексту. Самое простое решение для тестирования, это использование контекст менеджера test_request_context() В сочетании с постановкой, он свяжет тестовый запрос, так что вы можете взаимодействовать с ним. Вот пример:

from flask import request

with app.test_request_context('/hello', method='POST'):
    # now you can do something with the request until the
    # end of the with block, such as basic assertions:
    assert request.path == '/hello'
    assert request.method == 'POST'

Другая возможность состоит в передаче всей WSGI среды к методу request_context():

from flask import request

with app.request_context(environ):
    assert request.method == 'POST'

Объект запроса[править]

Объект запроса описан в разделе API, и мы не будем рассматривать его здесь подробно (см. запрос). Вот широкий обзор некоторых из наиболее распространенных операций. Прежде всего вы должны импортировать request из Flask:

from flask import request

В настоящее время метод запроса доступен с использованием атрибута method. Чтобы получить доступ к данным формы (данным, передаваемым в POST или PUT запросе), вы можете воспользоваться атрибутом form. Вот полный пример двух атрибутов, упомянутых выше:

@app.route('/login', methods=['POST', 'GET'])
def login():
    error = None
    if request.method == 'POST':
        if valid_login(request.form['username'],
                       request.form['password']):
            return log_the_user_in(request.form['username'])
        else:
            error = 'Invalid username/password'
    # this is executed if the request method was GET or the
    # credentials were invalid

Что произойдет, если ключ не существует в виде атрибута? В этом случае выбрасывается исключение KeyError. Вы можете поймать его как обычный KeyError, но если вы не сделаете этого, то вы увидите страницу с ошибкой HTTP 400 Bad Request. Таким образом, для многих ситуаций, вам не придется иметь дело с этой проблемой.

Чтобы получить доступ к параметрам, указанным в URL (?key=value), Вы можете использовать атрибут args:

searchword = request.args.get('q', '')

Мы рекомендуем получать доступ к URL параметрам с использованием метода get или отлавливать KeyError, потому что пользователи могут изменить URL. Страница 400 bad request page в этом случае выглядит не дружественно.

Загрузка файла[править]

Через Flask легко использовать загрузку файлов. Просто убедитесь, что не забыли поставить атрибут enctype="multipart/form-data" в вашей HTML форме, иначе браузер не передаст файлы.
Загруженные файлы сохраняются в память сервера или во временное хранилище в файловой системе. Вы можете получить доступ к этим файлам через атрибут files в объекте запроса. Каждый загруженный файл хранится в этом словаре. Он ведет себя так же, как стандартный объект file в Python, но имеет метод save(), который позволяет сохранить этот файл в файловую систему сервера. Вот простой пример, показывающий, как это работает:

from flask import request

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        f = request.files['the_file']
        f.save('/var/www/uploads/uploaded_file.txt')
    ...

Если вы хотите знать, как файл был назван клиентом, перед тем как он был загружен в ваше приложение, вы можете получить доступ к файлу через атрибут filename. Однако имейте в виду, что это значение может быть фальсифицировано. Если вы все же хотите использовать имя файла, который дал клиент, для хранения файлов на сервере, лучше передать его через secure_filename(), который предлагает Werkzeug:

from flask import request
from werkzeug import secure_filename

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        f = request.files['the_file']
        f.save('/var/www/uploads/' + secure_filename(f.filename))
    ...

Cookies[править]

Доступ к куки осуществляется через их атрибут. Для установки кукис, используйте метод set_cookie в объектах запроса. Данный атрибут представляет собой словарь со всеми переданными клиентскими кукис. Если вы хотите использовать сессии, то не используете куки напрямую, вместо них лучше использовать сессии Flask, что добавит вам некоторую безопасность поверх кукис.

Чтение кукис:

from flask import request

@app.route('/')
def index():
    username = request.cookies.get('username')
    # use cookies.get(key) instead of cookies[key] to not get a
    # KeyError if the cookie is missing.

Запись кукис:

from flask import make_response

@app.route('/')
def index():
    resp = make_response(render_template(...))
    resp.set_cookie('username', 'the username')
    return resp

Обратите внимание, что куки устанавливаются в объектах ответа. Которые обычно возвращаются как строки и Flask конвертируют в их объекты. Если вы хотите влиять на этот процесс, то используйте функцию make_response()

Редиректы и ошибки[править]

Для перенаправления пользователей в другое место используйте функцию redirect() А чтобы прервать запрос рано с кодом ошибки используйте функцию abort().

from flask import abort, redirect, url_for

@app.route('/')
def index():
    return redirect(url_for('login'))

@app.route('/login')
def login():
    abort(401)
    this_is_never_executed()

Это довольно бессмысленный пример, потому что пользователи будут перенаправлены со страницы индекса. Они не смогут получить доступ (получат ошибку 401 — отказано в доступе), но это показывает, как это работает.

По умолчанию черно-белые страницы ошибок показываются в каждом коде ошибок. Если вы хотите настроить страницу ошибки, вы можете использовать декоратор errorhandler()

from flask import render_template

@app.errorhandler(404)
def page_not_found(error):
    return render_template('page_not_found.html'), 404

Об ответах[править]

Возвращаемые значения от обозревающей функции автоматически преобразуется в объект-ответ. То есть если возвращаемое значение является строкой, оно конвертируется в объект-ответ со строкой в ответе тела, код ошибки «200 OK» и text/html mimetype. Логика преобразования для возвращаемого значения выглядит следующим образом:

  1. Если объект-ответ возвращает правильный тип, то это есть прямое возвращение непосредственно от обозревающей функции.
  2. Если это строка, объект-ответ создается с этими данными и параметрами по умолчанию.
  3. Если кортеж возвращает элементы в кортеже, то он может предоставить дополнительную информацию. Такие наборы должны быть в форме (response, status, headers), где хотя бы один элемент должен быть в кортеже. Статус значения заменит статус кода и заголовков, которые могут быть списком или словарем в дополнительных значениях заголовка.
  4. Если ничего из этого не работает, Flask примет возвращаемые значения, как пригодные для применения в WSGI приложении и преобразует их в объекты-ответа.

Если вы хотите заполучить результаты объект-ответ внутри обзора, используйте функцию make_response()

@app.errorhandler(404)
def not_found(error):
    return render_template('error.html'), 404

Вам просто нужно обернуть возвращение выражения от make_response() и получить результат объекта, чтобы изменить его, а затем вернуть его обратно:

@app.errorhandler(404)
def not_found(error):
    resp = make_response(render_template('error.html'), 404)
    resp.headers['X-Something'] = 'A value'
    return resp

Сеансы[править]

В дополнение к объекту-запроса есть и второй объект под названием сессия, которая сохраняет информацию относящуюся к пользователю от одного запроса к другому. Это осуществляется поверх кукис и записывает куки криптографически. Это означает, что пользователь может посмотреть содержимое кукис, но не изменить его, так как они не знают закрытый ключ, который используется для подписи.

Для того, чтобы использовать сессии вы должны установить секретный ключ. Вот как работают сессии:

from flask import Flask, session, redirect, url_for, escape, request

app = Flask(__name__)

@app.route('/')
def index():
    if 'username' in session:
        return 'Logged in as %s' % escape(session['username'])
    return 'You are not logged in'

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        session['username'] = request.form['username']
        return redirect(url_for('index'))
    return '''
        <form action="" method="post">
            <p><input type=text name=username>
            <p><input type=submit value=Login>
        </form>
    '''

@app.route('/logout')
def logout():
    # remove the username from the session if its there
    session.pop('username', None)
    return redirect(url_for('index'))

# set the secret key.  keep this really secret:
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'

escape() — указывается в этом примере, так как в данном случае не используется шаблонизатор.

Как сгенерировать хороший секретный ключ?
Самый простой и наиболее доступный способ, сделать это на основе генератора псевдослучайных чисел.

 >>> import os
 >>> os.urandom(24)
 'xfd{Hxe5<x95xf9xe3x96.5xd1x01O<!xd5xa2xa0x9fR"xa1xa8'

Небольшое замечание о кукисах основанных на базе сессий: Flask примет значение, положит в объект-сессию и сериализует их в кукисы. Если вы обнаружили, что некоторые значения не сохранились между запросами, то учтите, что кукисы на самом деле активны, и вы не получите четкое сообщение об ошибке, поэтому проверяйте размер куки в ваших ответах на странице по сравнению с размером, который поддерживается веб-браузерами.

Фидбэк, система флэшинга[править]

Хорошие приложения и пользовательские интерфейсы, имеют систему обратной связи. Если пользователь не получает достаточно обратной связи, то в конечном итоге, он начнет ненавидеть ваше приложение. Flask дает простой путь, чтобы получить обратную связь с пользователем через флэшинг систему. Флэшинг система в основном записывает сообщения в конце запроса и доступна к нему на следующий (и только на следующий) запрос. Сообщение предоставляется обычно через разметку шаблона.
Для флэш-сообщения используйте метод flash() А чтобы получить сообщение, можете использовать get_flashed_messages(), которые также доступны в шаблонах.
Примеры использования можете посмотреть здесь

Логирование[править]

Вы можете оказаться в ситуации, когда вы имеете дело с данными, которые по идее должны быть правильными, но на самом деле это не так. Например, некоторый клиентский код, который посылает HTTP-запрос к серверу. Это может быть вызвано манипуляциями с данными со стороны пользователя, или падение клиентского кода. Вполне нормально ответить на это ошибкой 400 Bad Request, но бывают ситуации, когда несмотря на ошибку, код должен продолжать работать.
Если вы хотите видеть, что произошло что-то подозрительное, используйте логирование.

app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')

Здесь можете ознакомится с документацией по логированию.

Подключение WSGI Middlewares[править]

Если вы хотите добавить WSGI middleware в ваше приложение, вы можете обернуть его внутри WSGI приложения. Например, если вы хотите добавить middleware от Werkzeug в работе, чтобы обойти ошибки в lighttpd, вы можете сделать это следующим образом:

from werkzeug.contrib.fixers import LighttpdCGIRootFix
app.wsgi_app = LighttpdCGIRootFix(app.wsgi_app)

Учебное пособие[править]

Мы назовем наш первый блог Flaskr, не стесняйтесь в выборе имени. В основном мы хотим сделать следующие вещи:

  1. Позволить пользователю входить и выходить с учетными данными, указанными в конфигурации. Только один пользователь поддерживается.
  2. Когда пользователь вошел в систему, он может добавить новую запись на страницу, состоящую из текстового названия и некоторого HTML текста. Этот HTML не будет проверен, потому что мы доверяем пользователю.
  3. Страница отображает все записи в обратном порядке (новые сверху), и пользователь может добавлять новые записи из формы вверху, если он залогинился.

Мы будем использовать напрямую SQLite3, потому что этого достаточно для приложения такого размера. Для приложений большого размера лучше использовать SQLAlchemy, которая обрабатывает подключения к базе данных более разумным способом и дает много возможностей в обработке запросов. Кроме того, есть еще одна из самых популярных баз данных — NoSQL, можно использовать ее, если она вам больше подходит.

Шаг 0: Создание каталогов[править]

Вначале создадим каталоги, необходимые Вашему приложению:

/flaskr
    /static
    /templates

Каталог flaskr не пакет Python, а лишь место размещения наших файлов. Непосредственно в эту папку поместим схему нашей базы данных и основной модуль. Файлы каталога static доступны для пользователей через HTTP, оттуда подгружаются css и javascript файлы. В каталоге templates Flask будет искать Jinja2-шаблоны, которые будут созданы в конце урока.

Шаг 1: Схема базы данных[править]

Создадим базу данных. Это не сложно, для этого приложения хватит одной таблицы и поддержки только SQLite. Просто поместим файл следующего содержания:

drop table if exists entries;
create table entries (
  id integer primary key autoincrement,
  title string not null,
  text string not null
);

с именем schema.sql в только что созданный каталог flaskr.

Эта схема состоит из одной таблицы с названием entries. У каждой записи в таблице есть свой идентификатор id, заголовок title и текст text. Id — автоматически увеличивающееся натуральное число и первичный ключ. Два строковых значения не должны быть неопределёнными и пустыми (not null).

Шаг 2: Установочный код приложения[править]

Имея схему, можно создать модуль приложения. Назовем его flaskr.py, поместив в корень каталога flaskr. В начале добавим нужный список импорта и раздел конфигурационных определений. В небольших приложениях можно размещать определения непосредственно в модуле. Однако, правильнее было бы создать отдельный .ini или .pу файл, впоследствии загружая его или импортируя оттуда значения.

В файле flaskr.py:

# all the imports
import sqlite3
from flask import Flask, request, session, g, redirect, url_for, 
     abort, render_template, flash

# configuration
DATABASE = '/tmp/flaskr.db'
DEBUG = True
SECRET_KEY = 'development key'
USERNAME = 'admin'
PASSWORD = 'default'

Теперь можно создать практическое приложение и инициализировать его с конфигурацией из того же файла flaskr.py:

# create our little application :)
app = Flask(__name__)
app.config.from_object(__name__)

from_object() проанализирует данный объект (если это строка, то импортирует её) и найдёт все переменные (в верхнем регистре), определённые там. В нашем случае, конфигурация была в нескольких строках кода, приведенных выше. Её можно поместить в отдельный файл.

Обычно, хорошей идеей будет загрузка определений из конфигурационного файла. Это может сделать from_envvar() вместо from_object().

app.config.from_envvar('FLASKR_SETTINGS', silent=True)

Для изменения значений по умолчанию таким способом можно установить в переменной окружения FLASKR_SETTINGS название конфигурационного файла для загрузки. Флаг silent («тихо, безмолвно») отключает вывод сообщений Flask при отсутствии такой переменной окружения.

Секретный ключ (SECRET_KEY) необходим для безопасности клиентских сессий. Вдумчиво выбирайте ключевые слова: они должны быть настолько сложными, насколько это возможно.

Флаг отладки (DEBUG) включает или выключает интерактивный отладчик. Никогда не оставляйте режим отладки активным на производственном сервере, это позволит пользователям выполнять код на нём!

Добавим метод для подключения к указанной базе данных. Он может быть использован для соединения по запросу, а также из интерактивной оболочки Python или скрипта. Этот метод пригодится позже.

def connect_db():
    return sqlite3.connect(app.config['DATABASE'])

Наконец, если нужно запустить этот файл в качестве отдельного приложения, просто добавим в конец строку, запускающую сервер:

if __name__ == '__main__':
    app.run()

Для запуска приложения используйте команду python flaskr.py, выводящую сообщение о том, что сервер запустился, и адрес, по которому следует обращаться. Однако, открыв этот адрес в браузере, получим ошибку «404 Страница не найдена», ведь у нас пока нет содержимого для просмотра. Но на этом сосредоточимся чуть позже. Сначала нам нужна рабочая база данных.

Шаг 3: Создание базы данных[править]

Flaskr — приложение на основе базы данных, а, точнее, реляционной базы данных. Такие системы нуждаются в схеме, сообщающей, как хранить информацию. Поэтому, важно создать такую схему перед первым запуском сервера.

Такая схема может быть создана передачей файла schema.sql в sqlite3:

sqlite3 /tmp/flaskr.db < schema.sql

Минусом этого решения является то, что для установки требуется приложение sqlite3, которое установлено не в каждой системе. Кроме того, нужно указать путь к базе данных, где можно сделать ошибку. Хорошая идея — добавить функцию, которая инициализирует базу данных в самом приложении.

Желая поступить так, сначала Вы должны импортировать функцию contextlib.closing() из пакета contextlib. При использовании Python 2.5 необходимо импортировать with_statement из пакета __future__ (импорт из __future__ всегда должен быть первым в списке):

from __future__ import with_statement
from contextlib import closing

Теперь мы можем создать функцию с названием init_db() для инициализации базы данных. Для этого используем функцию connect_db, определённую ранее.

def init_db():
    with closing(connect_db()) as db:
        with app.open_resource('schema.sql') as f:
            db.cursor().executescript(f.read())
        db.commit()

closing() — вспомогательная функция, позволяющая сохранить соединение открытым до конца блока with. Метод open_resource() поддерживает эту функциональность из коробки, так что его можно использовать в этом блоке. Функция открывает файл по пути (ваш flaskr каталог) и позволяет читать из него. Мы используем её здесь для выполнения скрипта с подключением к базе данных.

Соединяясь с базой данных, получаем объект связи (называемый db), предоставляющий нам курсор cursor с методом для выполнения всего сценария. В конце нужно подтвердить (commit) изменения. SQLite3 и другие транзакционные базы данных не подтверждают операций без Вашего особого распоряжения.

Теперь у нас появилась возможность создать базу данных в оболочке Python, импортировав и вызвав созданную функцию:

>>> from flaskr import init_db
>>> init_db()

Поиск и устранение неисправностей[править]

Получив сообщение об ошибке, что таблица не может быть найдена, убедитесь, что сделан вызов функции init_db() и правильно указаны названия таблиц (наиболее частая ошибка — путаница с множественным и единственным числом в названиях таблиц).

Шаг 4: Запрос подключения к базе данных[править]

Теперь мы знаем, как открыть соединения с базой данных и использовать их в скриптах, но как сделать эти запросы элегантно? Нам понадобится подключение к базе данных во всех наших функциях, так что имеет смысл инициализировать соединения перед каждым запросом и закрыть их после него.

Flask предоставляет такую возможность декораторами before_request(), after_request() и teardown_request():

@app.before_request
def before_request():
    g.db = connect_db()

@app.teardown_request
def teardown_request(exception):
    g.db.close()

Функции, отмеченные before_request() срабатывают до запроса и передаются без аргументов. Функции, отмеченные after_request() вызываются после запроса и передают ответ для отправки клиенту. Они должны вернуть объект-ответ (response object) или другой объект. Но их исполнение не гарантировано при возникновении исключительной ситуации, в таких случаях работают функции с декоратором teardown_request(). Их вызов происходит после того, как ответ был построен. Они не имеют права изменять запрос, их возвращаемые значения игнорируются. Если исключение произошло во время обработки запроса, оно передается для каждой функции, в противном случае, пропускается (передаётся None).

Мы сохраняем наше текущее подключение к базе данных в специальном объекте g, предоставляемым Flask. Этот объект хранит информацию только для одного запроса и доступен внутри каждой функции. Никогда не храните такие вещи в других объектах, иначе это не будет работать в многопоточной среде. Этот специальный объект g творит некую скрытую магию для самопроверки.

Совет[править]

Где поместить этот код?
Если Вы следили за статьей, то, возможно, были озадачены, где поместить свой код на этом шаге и последующих? Логичным было бы сгруппировать вместе все функции на уровне модуля и поставить новые функции before_request и teardown_request ниже существующей функции init_db. Чтобы сориентироваться, посмотрите, как это организовано в оригинале. Во Flask Вы можете поместить весь код Вашего приложения в один модуль Python. Но Вы не обязаны этого делать. Лучше этого не делать, если приложение становится большим и объемным.

Шаг 5: Функции представления[править]

Теперь, когда у нас уже есть соединения с базой данных, мы можем приступить к написанию функций представления. Нам понадобится четыре:

Вывод записей[править]

Этот вывод показывает все записи сохраненные в базе данных. Он мониторит корневой каталог приложения и выбирает по заголовку и тексту из базы данных. Запись с наибольшим идентификатором (новейшая запись) будет сверху. Ряды возвращаемые от курсора являются кортежем с колонками, и выбираются специфично по запросу. Это хорошо для небольших приложений, как здесь, но вы можете конвертировать их в словарь. Если вы заинтересованы, чтобы сделать это, то посмотрите как это сделано в Easy Querying.
Эта обзорная функция пропускает записи через словари в шаблоне show_entries.html и возвращает обработанное значение:

@app.route('/')
def show_entries():
    cur = g.db.execute('select title, text from entries order by id desc')
    entries = [dict(title=row[0], text=row[1]) for row in cur.fetchall()]
    return render_template('show_entries.html', entries=entries)

Добавление новых записей[править]

Эта страницы позволяет залогиненному пользователю добавлять новые записи. Это просто ответ на POST-запрос. Форма для данных показывается на странице отображения записей (show_entries). Если все сработало как надо, мы показываем (flash()) сообщение и производим перенаправление на страницу отображения записей:

@app.route('/add', methods=['POST'])
def add_entry():
    if not session.get('logged_in'):
        abort(401)
    g.db.execute('insert into entries (title, text) values (?, ?)',
                 [request.form['title'], request.form['text']])
    g.db.commit()
    flash('New entry was successfully posted')
    return redirect(url_for('show_entries'))

Обратите внимание, что мы проверяем, залогинен ли пользователь, по ключу (ключ logged_in существует в пределах сессии и равен True).

Замечание о безопасности:
убедитесь, что вы используете знаки вопроса (?) при составлении SQL-запроса, аналогично примеру выше. Если при составлении запроса вы используете форматированные строки, ваше приложение может быть уязвимо к атакам типа sql-injection. См. Using SQLite 3 with Flask

Функции login и logout[править]

Эти функции используются для входа и выхода пользователей сайта. Login проверяет имя пользователя и его пароль и устанавливает для сессии параметр logged_in. Если вход пользователя произошел успешно, то значение этого параметра устанавливается равным True, и пользователь перенаправляется обратно на страницу show_entries. Кроме того, пользователю выдается сообщение о том, что он успешно зашел на сайт. Если же происходит ошибка, то шаблон страницы логина выдает сообщение об этом, и запрос к пользователю повторяется.

@app.route('/login', methods=['GET', 'POST'])
def login():
    error = None
    if request.method == 'POST':
        if request.form['username'] != app.config['USERNAME']:
            error = 'Invalid username'
        elif request.form['password'] != app.config['PASSWORD']:
            error = 'Invalid password'
        else:
            session['logged_in'] = True
            flash('You were logged in')
            return redirect(url_for('show_entries'))
    return render_template('login.html', error=error)

В свою очередь, функция logout удаляет параметр logged_in из сессии. Здесь мы использовали следующий трюк: если мы будем использовать метод словаря (dict) pop(), и передадим в него второй параметр (значение по умолчанию), то метод удалит ключ из словаря, если он там есть, и не сделает ничего, если его нет. Трюк полезен, потому что нам не нужно проверять, был залогинен ли пользователь.

@app.route('/logout')
def logout():
    session.pop('logged_in', None)
    flash('You were logged out')
    return redirect(url_for('show_entries'))

Шаг 6: Шаблоны[править]

Теперь мы можем начать работу с шаблонами. Если мы будем переходить по URL примера прямо сейчас, мы получим исключение, из-за того что Фласк не может найти шаблоны. Шаблоны используют синтаксис Jinja2, по умолчанию включено автоматическое экранирование символов. Это значит, что если вы не разметите значения в коде метками Markup или фильтром |safe в шаблоне, то Jinja2 будет обеспечивать экранирование спецсимволов вроде < или > их XML-эквивалентами.

Мы так же будем пользоваться наследованием шаблонов, что делает возможным повторное использование одного макета сайта на всех его страницах.

Поместите следующие шаблоны в папку templates:

layout.html[править]

Этот шаблон содержит основную структуру html, шапку и ссылку на вход на сайт (или на выход, если пользователь уже залогинен). Так же он показывает всплывающие сообщения, если таковые появятся.
Блок {% block body %} может быть заменен блоком с таким же именем (body) в шаблоне-наследнике.

Словарь session доступен на уровне шаблонов, и вы можете использовать его для проверки, залогинен ли пользователь. Обратите внимание, что в Jinja вы можете получать отсутствующие атрибуты и элементы объектов/словарей, так что следующий код будет работать, даже если в сессии отсутствует параметр logged_in:

<!doctype html>
<title>Flaskr</title>
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
<div class=page>
  <h1>Flaskr</h1>
  <div class=metanav>
  {% if not session.logged_in %}
    <a href="{{ url_for('login') }}">log in</a>
  {% else %}
    <a href="{{ url_for('logout') }}">log out</a>
  {% endif %}
  </div>
  {% for message in get_flashed_messages() %}
    <div class=flash>{{ message }}</div>
  {% endfor %}
  {% block body %}{% endblock %}
</div>

show_entries.html[править]

Этот шаблон расширяет предыдущий шаблон layout.html и показывает сообщения нашего блога. Обратите внимание, что цикл for перебирает сообщения, которые мы передали через функцию render_template(). Так же мы выводим форму для функции add_entry и используем POST-запрос.

{% extends "layout.html" %}
{% block body %}
  {% if session.logged_in %}
    <form action="{{ url_for('add_entry') }}" method=post class=add-entry>
      <dl>
        <dt>Title:
        <dd><input type=text size=30 name=title>
        <dt>Text:
        <dd><textarea name=text rows=5 cols=40></textarea>
        <dd><input type=submit value=Share>
      </dl>
    </form>
  {% endif %}
  <ul class=entries>
  {% for entry in entries %}
    <li><h2>{{ entry.title }}</h2>{{ entry.text|safe }}
  {% else %}
    <li><em>Unbelievable.  No entries here so far</em>
  {% endfor %}
  </ul>
{% endblock %}

login.html[править]

И наконец, шаблон для входа на сайт, который просто показывает пользователю форму логина:

{% extends "layout.html" %}
{% block body %}
  <h2>Login</h2>
  {% if error %}<p class=error><strong>Error:</strong> {{ error }}{% endif %}
  <form action="{{ url_for('login') }}" method=post>
    <dl>
      <dt>Username:
      <dd><input type=text name=username>
      <dt>Password:
      <dd><input type=password name=password>
      <dd><input type=submit value=Login>
    </dl>
  </form>
{% endblock %}

Шаг 7. Добавляем стиль[править]

Теперь, когда все работает, самое время сделать наше приложение стильным. Просто создайте страницу стилей с названием style.css в папке static, которую мы создали раньше:

body            { font-family: sans-serif; background: #eee; }
a, h1, h2       { color: #377BA8; }
h1, h2          { font-family: 'Georgia', serif; margin: 0; }
h1              { border-bottom: 2px solid #eee; }
h2              { font-size: 1.2em; }

.page           { margin: 2em auto; width: 35em; border: 5px solid #ccc;
                  padding: 0.8em; background: white; }
.entries        { list-style: none; margin: 0; padding: 0; }
.entries li     { margin: 0.8em 1.2em; }
.entries li h2  { margin-left: -1em; }
.add-entry      { font-size: 0.9em; border-bottom: 1px solid #ccc; }
.add-entry dl   { font-weight: bold; }
.metanav        { text-align: right; font-size: 0.8em; padding: 0.3em;
                  margin-bottom: 1em; background: #fafafa; }
.flash          { background: #CEE5F5; padding: 0.5em;
                  border: 1px solid #AACBE2; }
.error          { background: #F0D6D6; padding: 0.5em; }

Бонус: тестируем приложение[править]

Теперь, когда вы закончили работу над этим приложением, и все работает как надо, будет хорошей идеей добавить автоматизированные тесты, чтобы упростить будущие модификации. Это приложение используется как пример для выполнения юнит-тестирования в разделе документации Testing Flask Applications. Сходите, посмотрите, как просто тестировать приложения на Flask-e

Ссылки[править]

  • http://flask.pocoo.org/docs/tutorial/ — оригинал приведенного здесь туториала
  • http://flask.pocoo.org/docs/ — официальный вебсайт Flask
  • http://groups.google.com/group/flask-russian — русскоязычная группа обсуждения Flask

Понравилась статья? Поделить с друзьями:
  • Фуразолидон инструкция по применению взрослым при цистите у мужчин
  • Инструкция по охране труда при работе на обдирочно заточном станке
  • Афлубин инструкция по применению для детей капли инструкция по применению
  • Bgr 34 инструкция по применению на русском языке
  • Снежинки из салфеток шаблоны для вырезания пошаговая инструкция