Spec-Zone.ru › Flask 2.2

Классы-представления

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

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

    def get(self, id):
        user = 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, url):
    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.2.x/views/

Spec-Zone.ru

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