基于类的视图¶
本页介绍使用 View 和 MethodView 类编写基于类的视图。
基于类的视图是一个充当视图函数的类。因为它是一个类,所以可以创建类的不同实例,并使用不同的参数来更改视图的行为。这也称为通用、可重用或可插入视图。
此功能有用的一个示例是定义一个基于其初始化的数据库模型创建 API 的类。
对于更复杂的 API 行为和自定义,请查看 Flask 的各种 API 扩展。
基本可重用视图¶
让我们逐步介绍一个将视图函数转换为视图类的示例。我们从一个视图函数开始,该函数查询用户列表,然后呈现一个模板来显示该列表。
@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 |
方法 |
说明 |
|
|
列出所有用户 |
|
|
创建新用户 |
|
|
显示单个用户 |
|
|
更新用户 |
|
|
删除用户 |
|
|
列出所有故事 |
|
|
创建新故事 |
|
|
显示单个故事 |
|
|
更新故事 |
|
|
删除故事 |