Чертежи и виды¶
Функция представления - это код, который вы пишете для ответа на запросы к вашему приложению. Flask использует шаблоны для сопоставления URL входящего запроса с представлением, которое должно его обработать. Представление возвращает данные, которые Flask превращает в исходящий ответ. Flask также может действовать в обратном направлении и генерировать URL-адрес представления на основе его имени и аргументов.
Создайте план¶
Blueprint
- это способ организации группы связанных представлений и другого кода. Вместо того чтобы регистрировать представления и другой код непосредственно в приложении, они регистрируются в чертеже. Затем чертеж регистрируется в приложении, когда он становится доступен в функции фабрики.
Flaskr будет иметь два чертежа, один для функций аутентификации и один для функций записей в блоге. Код для каждого чертежа будет находиться в отдельном модуле. Поскольку блог должен знать об аутентификации, сначала вы напишете модуль аутентификации.
import functools
from flask import (
Blueprint, flash, g, redirect, render_template, request, session, url_for
)
from werkzeug.security import check_password_hash, generate_password_hash
from flaskr.db import get_db
bp = Blueprint('auth', __name__, url_prefix='/auth')
Это создает объект Blueprint
с именем 'auth'
. Как и объекту приложения, чертежу необходимо знать, где он определен, поэтому в качестве второго аргумента передается __name__
. Значение url_prefix
будет добавлено ко всем URL, связанным с чертежом.
Импортируйте и зарегистрируйте blueprint из фабрики с помощью app.register_blueprint()
. Поместите новый код в конец функции фабрики перед возвратом приложения.
def create_app():
app = ...
# existing code omitted
from . import auth
app.register_blueprint(auth.bp)
return app
Проект аутентификации будет содержать представления для регистрации новых пользователей, а также для входа и выхода из системы.
Первый взгляд: Зарегистрируйтесь¶
Когда пользователь переходит по URL /auth/register
, представление register
возвращает HTML с формой для заполнения. Когда пользователь отправит форму, она проверит его ввод и либо снова покажет форму с сообщением об ошибке, либо создаст нового пользователя и перейдет на страницу входа.
Сейчас вы просто напишете код представления. На следующей странице вы напишете шаблоны для генерации HTML-формы.
@bp.route('/register', methods=('GET', 'POST'))
def register():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
db = get_db()
error = None
if not username:
error = 'Username is required.'
elif not password:
error = 'Password is required.'
if error is None:
try:
db.execute(
"INSERT INTO user (username, password) VALUES (?, ?)",
(username, generate_password_hash(password)),
)
db.commit()
except db.IntegrityError:
error = f"User {username} is already registered."
else:
return redirect(url_for("auth.login"))
flash(error)
return render_template('auth/register.html')
Вот что делает функция представления register
:
@bp.route
связывает URL/register
с функцией представленияregister
. Когда Flask получает запрос к/auth/register
, он вызывает представлениеregister
и использует возвращаемое значение в качестве ответа.Если пользователь отправил форму,
request.method
будет'POST'
. В этом случае начните валидацию ввода.request.form
- это особый типdict
сопоставления ключей и значений представленной формы. Пользователь вводит своиusername
иpassword
.Убедитесь, что
username
иpassword
не пусты.Если проверка прошла успешно, вставьте данные нового пользователя в базу данных.
db.execute
принимает SQL-запрос с?
заполнителями для любого пользовательского ввода и кортеж значений для замены заполнителей. Библиотека базы данных позаботится об экранировании значений, чтобы вы не были уязвимы к атаке SQL injection.В целях безопасности пароли никогда не должны храниться в базе данных напрямую. Вместо этого используется
generate_password_hash()
для безопасного хэширования пароля, и этот хэш сохраняется. Поскольку этот запрос изменяет данные, после него необходимо вызватьdb.commit()
, чтобы сохранить изменения.Если имя пользователя уже существует, произойдет ошибка
sqlite3.IntegrityError
, которая должна быть показана пользователю как еще одна ошибка валидации.
После сохранения пользователя перенаправляют на страницу входа в систему.
url_for()
генерирует URL для представления входа в систему на основе его имени. Это предпочтительнее, чем писать URL напрямую, так как позволяет изменить URL позже без изменения всего кода, который на него ссылается.redirect()
генерирует ответ перенаправления на сгенерированный URL.Если валидация не прошла, пользователю показывается ошибка.
flash()
хранит сообщения, которые могут быть извлечены при рендеринге шаблона.Когда пользователь первоначально переходит на
auth/register
, или произошла ошибка валидации, должна быть показана HTML-страница с формой регистрации. Вrender_template()
будет выведен шаблон, содержащий HTML, который вы напишете на следующем шаге учебника.
Вход в систему¶
Это представление следует той же схеме, что и представление register
выше.
@bp.route('/login', methods=('GET', 'POST'))
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
db = get_db()
error = None
user = db.execute(
'SELECT * FROM user WHERE username = ?', (username,)
).fetchone()
if user is None:
error = 'Incorrect username.'
elif not check_password_hash(user['password'], password):
error = 'Incorrect password.'
if error is None:
session.clear()
session['user_id'] = user['id']
return redirect(url_for('index'))
flash(error)
return render_template('auth/login.html')
Есть несколько отличий от представления register
:
Сначала запрашивается пользователь и сохраняется в переменной для последующего использования.
fetchone()
возвращает одну строку из запроса. Если запрос не дал результатов, возвращаетсяNone
. Позже будет использоватьсяfetchall()
, который возвращает список всех результатов.check_password_hash()
хэширует отправленный пароль так же, как и сохраненный хэш, и безопасно сравнивает их. Если они совпадают, пароль действителен.session
- этоdict
, который хранит данные при разных запросах. Когда проверка проходит успешно, данныеid
пользователя сохраняются в новой сессии. Данные хранятся в cookie, который отправляется в браузер, а браузер затем отправляет его обратно при последующих запросах. Flask надежно подписывает данные, чтобы их нельзя было подделать.
Теперь, когда информация о пользователе id
хранится в session
, она будет доступна при последующих запросах. В начале каждого запроса, если пользователь вошел в систему, его информация должна быть загружена и сделана доступной для других представлений.
@bp.before_app_request
def load_logged_in_user():
user_id = session.get('user_id')
if user_id is None:
g.user = None
else:
g.user = get_db().execute(
'SELECT * FROM user WHERE id = ?', (user_id,)
).fetchone()
bp.before_app_request()
регистрирует функцию, которая запускается перед функцией view, независимо от того, какой URL запрашивается. load_logged_in_user
проверяет, хранится ли идентификатор пользователя в session
и получает данные этого пользователя из базы данных, сохраняя их в g.user
, который длится в течение всего времени запроса. Если id пользователя нет, или если id не существует, g.user
будет None
.
Выход из системы¶
Чтобы выйти из системы, необходимо удалить идентификатор пользователя из session
. Тогда load_logged_in_user
не будет загружать пользователя при последующих запросах.
@bp.route('/logout')
def logout():
session.clear()
return redirect(url_for('index'))
Требование аутентификации в других представлениях¶
Создание, редактирование и удаление записей блога потребует, чтобы пользователь вошел в систему. Декоратор* может быть использован для проверки этого для каждого вида, к которому он применяется.
def login_required(view):
@functools.wraps(view)
def wrapped_view(**kwargs):
if g.user is None:
return redirect(url_for('auth.login'))
return view(**kwargs)
return wrapped_view
Этот декоратор возвращает новую функцию представления, которая обертывает исходное представление, к которому он применяется. Новая функция проверяет, загружен ли пользователь, и в противном случае перенаправляет на страницу входа. Если пользователь загружен, вызывается исходное представление и продолжает работу в обычном режиме. Вы будете использовать этот декоратор при написании представлений блога.
Конечные точки и URL-адреса¶
Функция url_for()
генерирует URL-адрес представления на основе имени и аргументов. Имя, связанное с представлением, также называется конечной точкой, и по умолчанию оно совпадает с именем функции представления.
Например, представление hello()
, которое было добавлено в фабрику приложений ранее в учебнике, имеет имя 'hello'
и может быть связано с url_for('hello')
. Если бы оно принимало аргумент, что вы увидите позже, его можно было бы связать с помощью url_for('hello', who='World')
.
При использовании чертежа имя чертежа добавляется к имени функции, поэтому конечная точка для функции login
, которую вы написали выше, будет 'auth.login'
, потому что вы добавили ее в чертеж 'auth'
.
Продолжить Шаблоны.