Представления на основе классов¶
На этой странице рассказывается об использовании классов 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
¶
По умолчанию новый экземпляр класса view создается каждый раз, когда обрабатывается запрос. Это означает, что во время запроса можно безопасно записывать другие данные в 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):
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 |
Метод |
Описание |
|
|
Список всех пользователей |
|
|
Создание нового пользователя |
|
|
Показать одного пользователя |
|
|
Обновление пользователя |
|
|
Удалить пользователя |
|
|
Список всех историй |
|
|
Создайте новую историю |
|
|
Показать одну историю |
|
|
Обновление истории |
|
|
Удалить историю |