Классы-представления
Эта страница знакомит с использованием классов 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 |
Метод |
Описание |
|
|
Список всех пользователей |
|
|
Создание нового пользователя |
|
|
Просмотр одного пользователя |
|
|
Обновление пользователя |
|
|
Удаление пользователя |
|
|
Список всех историй |
|
|
Создание новой истории |
|
|
Просмотр одной истории |
|
|
Обновление истории |
|
|
Удаление истории |
© 2007–2022 Pallets
Licensed under the BSD 3-clause License.
https://flask.palletsprojects.com/en/2.2.x/views/