[Перевод] Мега-Учебник Flask, Часть 5: Вход пользователей

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

Цель данного руководства — разработать довольно функциональное приложение-микроблог, которое я за полным отсутствием оригинальности решил назвать microblog.

Оглавление Часть 1: Привет, Мир! Часть 2: ШаблоныЧасть 3: ФормыЧасть 4: База данныхЧасть 5: Вход пользователей (данная статья)Часть 6: Страница профиля и аватарыЧасть 7: Unit-тестированиеЧасть 8: Подписчики, контакты и друзьяЧасть 9: ПагинацияЧасть 10: Полнотекстовый поискЧасть 11: Поддержка e-mailЧасть 12: РеконструкцияЧасть 13: Дата и времяЧасть 14: I18n and L10nЧасть 15: AjaxЧасть 16: Отладка, тестирование и профилированиеЧасть 17: Развертывание на Linux (даже на Raspberry Pi!)Часть 18: Развертывание на Heroku Cloud Краткое повторение В предыдущей части мы создали базу данных и научились заполнять её пользователями и постами, однако этот функционал еще не реализован в нашем приложении. Две главы назад мы узнали, как создавать веб-формы и создали форму для авторизации.В этой статье мы объединим наши знания о веб-формах и базах данных и напишем свою систему для входа пользователей. В конце данного руководства наше небольшое приложение будет регистрировать новых пользователей и проводить их авторизацию.

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

Конфигурация Как и в предыдущих главах, мы начнём с настройки расширений, которые будем использовать. Для авторизации нам понадобятся два расширения — Flask-Login и Flask-OpenID. Настроим их следующим образом (файл app/__init__.py): import os from flask.ext.login import LoginManager from flask.ext.openid import OpenID from config import basedir

lm = LoginManager () lm.init_app (app) oid = OpenID (app, os.path.join (basedir, 'tmp')) Расширению Flask-OpenID нужно где-то хранить свои временные файлы, для этого при инициализации ему передаётся путь до папки tmp.Функция представления авторизации Давайте обновим нашу функцию представления (файл app/views.py): from flask import render_template, flash, redirect, session, url_for, request, g from flask.ext.login import login_user, logout_user, current_user, login_required from app import app, db, lm, oid from forms import LoginForm from models import User, ROLE_USER, ROLE_ADMIN

@app.route ('/login', methods = ['GET', 'POST']) @oid.loginhandler def login (): if g.user is not None and g.user.is_authenticated (): return redirect (url_for ('index')) form = LoginForm () if form.validate_on_submit (): session['remember_me'] = form.remember_me.data return oid.try_login (form.openid.data, ask_for = ['nickname', 'email']) return render_template ('login.html', title = 'Sign In', form = form, providers = app.config['OPENID_PROVIDERS']) Обратите внимание, мы импортировали несколько новых модулей, некоторые из которых будут использованы позднее.

Отличий от предыдущей версии немного. Мы добавили новый декоратор для функции отображения. Благодаря oid.loginhandler Flask-OpenID теперь знает, что это — функция для авторизации.

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

Функция url_for, которую мы использовали при вызове redirect, предоставляет возможность получения URL для переданного ей имени функции представления. Вы, конечно же, можете использовать redirect ('/index'), однако есть весьма веские причины поручить построение URL специально предназначенной для этого функции.

Мы также обновили код, обрабатывающий данные полученные из формы авторизации. Здесь мы делаем две вещи. Во-первых, мы сохраняем значение поля remember_me в сессии Flask (не путайте с db.session — сессией, предоставленной расширением Flask-SQLAlchemy). Как было сказано выше, объект flask.g может хранить данные только во время жизни запроса. В то время как flask.session является более сложным хранилищем. Данные, сохраненные в сессии, будут также доступны во время всех последующих запросов от одного клиента. Информация хранится до тех пор, пока не будет явно удалена. Такое поведение возможно благодаря тому, что Flask хранит отдельные сессии для каждого клиента.

Вызов oid.try_login запускает процесс авторизации с помощью Flask-OpenID. Эта функция принимает два аргумента: openid, полученный из веб-формы и список полей, которые мы хотели бы получить от провайдера OpenID. Так как наша модель User имеет атрибуты nickname и email, именно эти данные мы и будем запрашивать.

Аутентификация через OpenID проводится асинхронно. Если получен положительный ответ от провайдера, Flask-OpenID вызовет функцию, объявленную с помощью декоратора oid.after_login. В противном случае пользователь снова вернётся на страницу авторизации.

Обработка ответа от провайдера OpenID Так выглядит реализация функции after_login (файл app/views.py): @oid.after_login def after_login (resp): if resp.email is None or resp.email == »: flash ('Invalid login. Please try again.') return redirect (url_for ('login')) user = User.query.filter_by (email = resp.email).first () if user is None: nickname = resp.nickname if nickname is None or nickname == »: nickname = resp.email.split ('@')[0] user = User (nickname = nickname, email = resp.email, role = ROLE_USER) db.session.add (user) db.session.commit () remember_me = False if 'remember_me' in session: remember_me = session['remember_me'] session.pop ('remember_me', None) login_user (user, remember = remember_me) return redirect (request.args.get ('next') or url_for ('index')) Аргумент resp, переданный функции after_login содержит в себе данные, полученные от провайдера OpenID.

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

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

Затем мы вызываем функцию login_user из модуля Flask-Login, чтобы наконец авторизовать пользователя в нашем приложении.

В конце концов мы перенаправляем пользователя по адресу, переданному в атрибуте next, или же на главную страницу, если такой параметр в запросе отсутствует. Идея параметра next весьма проста. Допустим, вы хотите сделать некоторые страницы доступными для просмотра только авторизованным пользователям. С помощью Flask-Login такие страницы могут быть обозначены с помощью декоратора login_required. Если анонимный пользователь попытается открыть такую страницу, он будет автоматически перенаправлен на страницу авторизации, при этом Flask-Login сохранит URL исходной страницы в параметре next. Нам останется только отправить пользователя по этому адресу после того, как авторизация будет пройдена.

Для того, чтобы Flask-Login знал куда отправлять пользователей для авторизации, мы должны сообщить ему об этом при инициализации (файл app/__init__.py):

lm = LoginManager () lm.init_app (app) lm.login_view = 'login' Глобальный объект g.user В функции представления login мы проверяли состояние g.user, для того, чтобы определить, не является ли текущий пользователь уже авторизованным. Чтобы это работало, мы используем событие Flask before_request. Все функции, объявленные с помощью декоратора before_request будут запущены непосредственно перед вызовом функции отображения каждый раз, когда получен запрос. Таким образом, вполне логичным будет устанавливать значение g.user именно здесь (файл app/views.py): @app.before_request def before_request (): g.user = current_user Это всё, что нам нужно. Flask-Login предоставляет нам доступ к переменной current_user, мы просто копируем в g ссылку на это значение, для удобства дальнейшего использования. Теперь текущий пользователь будет доступен везде, даже внутри шаблонов.

Отображение главной страницы В предыдущей главе мы использовали в функции index объекты-заглушки, так как у нас еще не было настоящих пользователей и постов. Теперь у нас есть пользователи, самое время это использовать: @app.route ('/') @app.route ('/index') @login_required def index (): user = g.user posts = [ { 'author': { 'nickname': 'John' }, 'body': 'Beautiful day in Portland!' }, { 'author': { 'nickname': 'Susan' }, 'body': 'The Avengers movie was so cool!' } ] return render_template ('index.html', title = 'Home', user = user, posts = posts) Мы сделали всего два изменения в этой функции. Во-первых, был добавлен декоратор login_required. Отныне мы можем быть уверены, что эту страницу увидят только зарегистрированные пользователи.

Во-вторых, мы передаём в шаблон непосредственно объект g.user вместо заглушки, используемой ранее.

Самое время запустить приложение.

Когда вы перейдете по адресу http://localhost:5000, вместо главной страницы вы увидите страницу для входа. Авторизация с помощью OpenID проходит с помощью URL, предоставляемого провайдером. Чтобы не вводить адрес вручную, можно использовать одну из ссылок под текстовым полем.

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

После этого вы окажетесь на главной странице, теперь уже в качестве авторизованного пользователя.

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

Выход из системы Мы реализовали вход, самое время добавить возможность выхода из системы. Это делается очень просто (файл app/views.py): @app.route ('/logout') def logout (): logout_user () return redirect (url_for ('index')) Помимо этого нам необходимо добавить соответствующую ссылку в шаблон. Расположим её вверху страницы, рядом с другими навигационными ссылками (файл app/templates/base.html):

{% if title %} {{title}} — microblog {% else %} microblog {% endif %}

Microblog: Home {% if g.user.is_authenticated () %} | Logout {% endif %}

{% with messages = get_flashed_messages () %} {% if messages %}
    {% for message in messages %}
  • {{ message }}
  • {% endfor %}
{% endif %} {% endwith %} {% block content %}{% endblock %} Видите, насколько это просто! Нам всего лишь надо проверить содержимое g.user, и если текущий пользователь авторизован, добавить ссылку для выхода. Не забывайте, что вместо прямых адресов лучше использовать url_for в таких случаях…

Заключительные слова Теперь у нас есть полноценная система для авторизации пользователей. В следующей главе мы создадим страницу профиля пользователя, и добавим возможность использования аватаров.Для экономии времени вы можете воспользоваться ссылкой и скачать код приложения, включающий в себя все изменения из данной статьи: Скачать microblog-0.5.zip

До новых встреч!

Мигель

© Habrahabr.ru