Чертежи и виды

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

Создайте план

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

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

flaskr/auth.py
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(). Поместите новый код в конец функции фабрики перед возвратом приложения.

flaskr/__init__.py
def create_app():
    app = ...
    # existing code omitted

    from . import auth
    app.register_blueprint(auth.bp)

    return app

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

Первый взгляд: Зарегистрируйтесь

Когда пользователь переходит по URL /auth/register, представление register возвращает HTML с формой для заполнения. Когда пользователь отправит форму, она проверит его ввод и либо снова покажет форму с сообщением об ошибке, либо создаст нового пользователя и перейдет на страницу входа.

Сейчас вы просто напишете код представления. На следующей странице вы напишете шаблоны для генерации HTML-формы.

flaskr/auth.py
@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:

  1. @bp.route связывает URL /register с функцией представления register. Когда Flask получает запрос к /auth/register, он вызывает представление register и использует возвращаемое значение в качестве ответа.

  2. Если пользователь отправил форму, request.method будет 'POST'. В этом случае начните валидацию ввода.

  3. request.form - это особый тип dict сопоставления ключей и значений представленной формы. Пользователь вводит свои username и password.

  4. Убедитесь, что username и password не пусты.

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

    • db.execute принимает SQL-запрос с ? заполнителями для любого пользовательского ввода и кортеж значений для замены заполнителей. Библиотека базы данных позаботится об экранировании значений, чтобы вы не были уязвимы к атаке SQL injection.

    • В целях безопасности пароли никогда не должны храниться в базе данных напрямую. Вместо этого используется generate_password_hash() для безопасного хэширования пароля, и этот хэш сохраняется. Поскольку этот запрос изменяет данные, после него необходимо вызвать db.commit(), чтобы сохранить изменения.

    • Если имя пользователя уже существует, произойдет ошибка sqlite3.IntegrityError, которая должна быть показана пользователю как еще одна ошибка валидации.

  6. После сохранения пользователя перенаправляют на страницу входа в систему. url_for() генерирует URL для представления входа в систему на основе его имени. Это предпочтительнее, чем писать URL напрямую, так как позволяет изменить URL позже без изменения всего кода, который на него ссылается. redirect() генерирует ответ перенаправления на сгенерированный URL.

  7. Если валидация не прошла, пользователю показывается ошибка. flash() хранит сообщения, которые могут быть извлечены при рендеринге шаблона.

  8. Когда пользователь первоначально переходит на auth/register, или произошла ошибка валидации, должна быть показана HTML-страница с формой регистрации. В render_template() будет выведен шаблон, содержащий HTML, который вы напишете на следующем шаге учебника.

Вход в систему

Это представление следует той же схеме, что и представление register выше.

flaskr/auth.py
@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:

  1. Сначала запрашивается пользователь и сохраняется в переменной для последующего использования.

    fetchone() возвращает одну строку из запроса. Если запрос не дал результатов, возвращается None. Позже будет использоваться fetchall(), который возвращает список всех результатов.

  2. check_password_hash() хэширует отправленный пароль так же, как и сохраненный хэш, и безопасно сравнивает их. Если они совпадают, пароль действителен.

  3. session - это dict, который хранит данные при разных запросах. Когда проверка проходит успешно, данные id пользователя сохраняются в новой сессии. Данные хранятся в cookie, который отправляется в браузер, а браузер затем отправляет его обратно при последующих запросах. Flask надежно подписывает данные, чтобы их нельзя было подделать.

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

flaskr/auth.py
@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 не будет загружать пользователя при последующих запросах.

flaskr/auth.py
@bp.route('/logout')
def logout():
    session.clear()
    return redirect(url_for('index'))

Требование аутентификации в других представлениях

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

flaskr/auth.py
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'.

Продолжить Шаблоны.

Вернуться на верх