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