Spec-Zone.ru › Flask 2.3

Основанные на классах представления

Эта страница знакомит с использованием классов View и MethodView для написания представлений, основанных на классах.

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

Пример, где это полезно, — определение класса, который создаёт API, основанный на модели базы данных, с которой он инициализирован.

Для более сложного поведения API и кастомизации ознакомьтесь с различными расширениями API для Flask.

Базовое многоразовое представление

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

@app.route("/users/")
def user_list():
    users = User.query.all()
    return render_template("users.html", users=users)

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

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

from flask.views import View

class UserList(View):
    def dispatch_request(self):
        users = User.query.all()
        return render_template("users.html", objects=users)

app.add_url_rule("/users/", view_func=UserList.as_view("user_list"))

Метод View.dispatch_request() эквивалентен функции представления. Вызов метода View.as_view() создаст функцию представления, которую можно зарегистрировать в приложении с помощью метода add_url_rule(). Первый аргумент для as_view — имя, используемое для ссылки на представление с помощью url_for().

Примечание

Вы не можете декорировать класс с помощью @app.route() так же, как это делали с обычной функцией представления.

Далее нам нужно будет зарегистрировать тот же класс представления для разных моделей и шаблонов, чтобы сделать его более полезным, чем исходная функция. Класс будет принимать два аргумента — модель и шаблон — и сохранять их в self. Затем dispatch_request сможет ссылаться на них вместо жёстко заданных значений.

class ListView(View):
    def __init__(self, model, template):
        self.model = model
        self.template = template

    def dispatch_request(self):
        items = self.model.query.all()
        return render_template(self.template, items=items)

Помните, что мы создаём функцию представления с помощью View.as_view() вместо непосредственного создания класса. Все дополнительные аргументы, переданные as_view, передаются при создании класса. Теперь мы можем зарегистрировать одно и то же представление для обработки нескольких моделей.

app.add_url_rule(
    "/users/",
    view_func=ListView.as_view("user_list", User, "users.html"),
)
app.add_url_rule(
    "/stories/",
    view_func=ListView.as_view("story_list", Story, "stories.html"),
)

Переменные URL

Любые переменные, захваченные URL, передаются в качестве ключевых аргументов в метод dispatch_request, как и в случае с обычной функцией представления.

class DetailView(View):
    def __init__(self, model):
        self.model = model
        self.template = f"{model.__name__.lower()}/detail.html"

    def dispatch_request(self, id)
        item = self.model.query.get_or_404(id)
        return render_template(self.template, item=item)

app.add_url_rule(
    "/users/<int:id>",
    view_func=DetailView.as_view("user_detail", User)
)

Срок жизни представления и self

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

Однако, если вашему классу представления нужно выполнять много сложной инициализации, делать это для каждого запроса не нужно и может быть неэффективно. Чтобы этого избежать, установите View.init_every_request в значение False, что позволит создать только один экземпляр класса и использовать его для каждого запроса. В этом случае запись в self небезопасна. Если вам нужно хранить данные во время запроса, используйте g вместо этого.

В примере ListView ничего не записывается в self во время запроса, поэтому эффективнее создать один экземпляр.

class ListView(View):
    init_every_request = False

    def __init__(self, model, template):
        self.model = model
        self.template = template

    def dispatch_request(self):
        items = self.model.query.all()
        return render_template(self.template, items=items)

Разные экземпляры всё равно будут создаваться каждый для каждого вызова as_view, но не для каждого запроса к этим представлениям.

Декораторы представлений

Сам класс представления не является функцией представления. Декораторы представлений необходимо применять к функции представления, возвращаемой as_view, а не к самому классу. Установите View.decorators в список декораторов для применения.

class UserList(View):
    decorators = [cache(minutes=2), login_required]

app.add_url_rule('/users/', view_func=UserList.as_view())

Если вы не установили decorators, вы можете применить их вручную вместо этого. Это эквивалентно:

view = UserList.as_view("users_list")
view = cache(minutes=2)(view)
view = login_required(view)
app.add_url_rule('/users/', view_func=view)

Обратите внимание, что порядок имеет значение. Если вы привыкли к @decorator стилю, это эквивалентно:

@app.route("/users/")
@login_required
@cache(minutes=2)
def user_list():
    ...

Подсказки методов

Распространённый подход — зарегистрировать представление с помощью methods=["GET", "POST"], а затем проверить request.method == "POST" чтобы решить, что делать. Установка View.methods эквивалентна передаче списка методов в add_url_rule или route.

class MyView(View):
    methods = ["GET", "POST"]

    def dispatch_request(self):
        if request.method == "POST":
            ...
        ...

app.add_url_rule('/my-view', view_func=MyView.as_view('my-view'))

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

app.add_url_rule(
    "/my-view",
    view_func=MyView.as_view("my-view"),
    methods=["GET", "POST"],
)

Обработка методов и API

Для API может быть полезно использовать разные функции для каждого HTTP-метода. MethodView расширяет базовый View, чтобы направлять вызовы к разным методам класса в зависимости от метода запроса. Каждый HTTP-метод отображается на метод класса с тем же (строчным) именем.

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

Мы можем создать обобщённый класс ItemAPI, который предоставляет методы get (детали), patch (редактирование) и delete для заданной модели. Класс GroupAPI может предоставить методы get (список) и post (создание).

from flask.views import MethodView

class ItemAPI(MethodView):
    init_every_request = False

    def __init__(self, model):
        self.model = model
        self.validator = generate_validator(model)

    def _get_item(self, id):
        return self.model.query.get_or_404(id)

    def get(self, id):
        item = self._get_item(id)
        return jsonify(item.to_json())

    def patch(self, id):
        item = self._get_item(id)
        errors = self.validator.validate(item, request.json)

        if errors:
            return jsonify(errors), 400

        item.update_from_json(request.json)
        db.session.commit()
        return jsonify(item.to_json())

    def delete(self, id):
        item = self._get_item(id)
        db.session.delete(item)
        db.session.commit()
        return "", 204

class GroupAPI(MethodView):
    init_every_request = False

    def __init__(self, model):
        self.model = model
        self.validator = generate_validator(model, create=True)

    def get(self):
        items = self.model.query.all()
        return jsonify([item.to_json() for item in items])

    def post(self):
        errors = self.validator.validate(request.json)

        if errors:
            return jsonify(errors), 400

        db.session.add(self.model.from_json(request.json))
        db.session.commit()
        return jsonify(item.to_json())

def register_api(app, model, name):
    item = ItemAPI.as_view(f"{name}-item", model)
    group = GroupAPI.as_view(f"{name}-group", model)
    app.add_url_rule(f"/{name}/<int:id>", view_func=item)
    app.add_url_rule(f"/{name}/", view_func=group)

register_api(app, User, "users")
register_api(app, Story, "stories")

Это даёт следующие представления — стандартный REST API!

URL

Метод

Описание

/users/

GET

Список всех пользователей

/users/

POST

Создание нового пользователя

/users/<id>

GET

Отображение одного пользователя

/users/<id>

PATCH

Обновление пользователя

/users/<id>

DELETE

Удаление пользователя

/stories/

GET

Список всех историй

/stories/

POST

Создание новой истории

/stories/<id>

GET

Отображение одной истории

/stories/<id>

PATCH

Обновление истории

/stories/<id>

DELETE

Удаление истории

© 2007–2022 Pallets
Licensed under the BSD 3-clause License.
https://flask.palletsprojects.com/en/2.3.x/views/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API