基于类的视图

本页介绍如何使用 ViewMethodView 类来编写基于类的视图。

基于类的视图是充当视图函数的类。因为它是一个类,所以可以使用不同的参数创建类的不同实例,以更改视图的行为。这也称为通用、可重用或可插入的视图。

这很有用的一个示例是定义一个类,该类根据初始化时使用的数据库模型创建API。

有关更复杂的API行为和定制,请查看FlaskAPI的各种扩展。

基本可重用视图

让我们演练一个将视图函数转换为视图类的示例。我们从一个查询用户列表的视图函数开始,然后呈现一个模板来显示该列表。

@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_requestFalse ,它将只创建类的一个实例,并将其用于每个请求。在本例中,向 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 调用,但不是针对这些视图的每个请求。

视图装饰器

View类本身不是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 Style,这相当于:

@app.route("/users/")
@login_required
@cache(minutes=2)
def user_list():
    ...

方法提示

一种常见的模式是将视图注册到 methods=["GET", "POST"] ,然后勾选 request.method == "POST" 来决定要做什么。设置 View.methods 等同于将方法列表传递给 add_url_ruleroute

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

方法

说明

/users/

GET

列出所有用户

/users/

POST

创建新用户

/users/<id>

GET

显示单个用户

/users/<id>

PATCH

更新用户

/users/<id>

DELETE

删除用户

/stories/

GET

列出所有故事

/stories/

POST

创造一个新的故事

/stories/<id>

GET

展示一个故事

/stories/<id>

PATCH

更新故事

/stories/<id>

DELETE

删除一篇报道