Spec-Zone.ru › Flask 3.0

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

Эта страница знакомит с использованием классов 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().

Примечание

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

Далее, нам нужно будет регистрировать тот же класс представления для разных моделей и шаблонов, чтобы сделать его более полезным, чем исходная функция. Класс будет принимать два аргумента: модель и шаблон и сохранит их в 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")

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

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

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

© 2010 Pallets
Licensed under the BSD 3-clause License.
https://flask.palletsprojects.com/en/3.0.x/views/

Spec-Zone.ru

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