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