基于类的视图¶
本页介绍使用 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 |
方法 |
说明 |
|
|
列出所有用户 |
|
|
创建新用户 |
|
|
显示单个用户 |
|
|
更新用户 |
|
|
删除用户 |
|
|
列出所有故事 |
|
|
创建新故事 |
|
|
显示单个故事 |
|
|
更新故事 |
|
|
删除故事 |