クラスに基づいたビュー(Class-based Views) Class-based Views

このページはViewMethodViewクラスを使った、クラスに基づいたviewを紹介します。 This page introduces using the :class:`View` and :class:`MethodView` classes to write class-based views.

クラスに基づいたviewは、view関数のように動くクラスです。クラスなので、異なるインスタンスを異なる引数で作成し、ビューの振る舞いを変更することができます。これは汎用的な(generic)、再利用可能な(reusable)、または差し込み可能な(pluggable)viewとして知られています。 A class-based view is a class that acts as a view function. Because it is a class, different instances of the class can be created with different arguments, to change the behavior of the view. This is also known as generic, reusable, or pluggable views.

これが便利な場所の例は、初期化するときに使ったデータベースのモデルに基づいてAPIを作成するクラスを定義する場合です。 An example of where this is useful is defining a class that creates an API based on the database model it is initialized with.

さらに複雑なAPIの振る舞いやカスタマイズについては、Flaskの様々なAPI拡張を調べてください。 For more complex API behavior and customization, look into the various API extensions for Flask.

基本的な再利用可能なview Basic Reusable View

view関数をviewクラスに変換する例を通して見てみましょう。ユーザのリストを問合せ、それからそのリストを表示するためにテンプレートを変換するview関数から始めます。 Let's walk through an example converting a view function to a view class. We start with a view function that queries a list of users then renders a template to show the list.

@app.route("/users/")
def user_list():
    users = User.query.all()
    return render_template("users.html", users=users)

この例はユーザのモデル向けに機能しますが、しかしリストのページが必要な、より多くのモデルもあるとします。モデルとテンプレートの名前を変更するだけであったとしても、各モデル用に別のview関数を書く必要がありそうです。 This works for the user model, but let's say you also had more models that needed list pages. You'd need to write another view function for each model, even though the only thing that would change is the model and template name.

(モデルごとにview関数を作る)代わりに、対象のモデルに問合せてテンプレートを変換するViewのサブクラスを書くことができます。最初の一歩として、何もカスタマイズはせずに、view(関数)をクラスに変換しましょう。 Instead, you can write a :class:`View` subclass that will query a model and render a template. As the first step, we'll convert the view to a class without any customization.

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関数と同等なものです。View.as_view()メソッドを呼び出すと、Flaskアプリのadd_url_rule()メソッドを使って登録できるview関数を作成します。as_viewの最初の引数は、url_for()を使ってviewを参照するときに使う名前です。 The :meth:`View.dispatch_request` method is the equivalent of the view function. Calling :meth:`View.as_view` method will create a view function that can be registered on the app with its :meth:`~flask.Flask.add_url_rule` method. The first argument to ``as_view`` is the name to use to refer to the view with :func:`~flask.url_for`.

注釈

基本的なview関数で行うような、@app.route()を使うやり方ではクラスは修飾(decorate)できません。 You can't decorate the class with ``@app.route()`` the way you'd do with a basic view function.

次に、元の関数よりもさらに便利にするために、同じviewクラスを異なる対象のモデルとテンプレート用に登録できる必要があります。(この例では)クラスは、対象のモデルとテンプレートの2つの引数を取り、それらをselfに格納します。そうすると、値を直接書く(hard-coded)代わりに、(以下の例のように)dispatch_requestはそれら(selfに格納した値)を参照できます。 Next, we need to be able to register the same view class for different models and templates, to make it more useful than the original function. The class will take two arguments, the model and template, and store them on ``self``. Then ``dispatch_request`` can reference these instead of hard-coded values.

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()を使ってview関数を作成することを覚えておいてください(訳注: この例では、app.ad_url_ruleで指定するview関数に、ListViewのインスタンスを直接作成してからdispatch_requestを指定するのではなく、ListViewの親クラスであるViewクラスのクラスメソッドであるas_view()を呼び出して作成したview関数を指定するということ)。as_viewに渡す追加の引数は、それからクラスを作成するとき(訳注: この例ではListViewクラスのインスタンスの作成時)に渡されます。この時点で、複数の対象のモデルを処理できる同じviewを登録できます。 Remember, we create the view function with ``View.as_view()`` instead of creating the class directly. Any extra arguments passed to ``as_view`` are then passed when creating the class. Now we can register the same view to handle multiple models.

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 Variables

URLから捉えたいかなる変数も、通常のview関数のときのように、dispatch_requestメソッドにキーワード引数として渡されます。 Any variables captured by the URL are passed as keyword arguments to the ``dispatch_request`` method, as they would be for a regular view function.

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"))

Viewの生存期間とself(View Lifetime and self View Lifetime and ``self``

標準設定では、viewクラスの新しいインスタンスはリクエストが処理されるたびに作成されます。これは、リクエストを処理している間はselfに他のデータを書き込んでも、他の形式のグローバルな状態のデータとは違い、次のリクエストからは見られないため、安全であることを意味します。 By default, a new instance of the view class is created every time a request is handled. This means that it is safe to write other data to ``self`` during the request, since the next request will not see it, unlike other forms of global state.

しかしながら、もしviewクラスがたくさんの複雑な初期化をする必要がある場合、リクエストごとに初期化をするのは不要であり非効率な場合があります。これを避けるためには、クラスのインスタンスを1つだけ作成してすべてのリクエストで使用するように、View.init_every_requestFalseに設定します。 However, if your view class needs to do a lot of complex initialization, doing it for every request is unnecessary and can be inefficient. To avoid this, set :attr:`View.init_every_request` to ``False``, which will only create one instance of the class and use it for every request. In this case, writing to ``self`` is not safe. If you need to store data during the request, use :data:`~flask.g` instead.

ListViewの例の中では、リクエストの処理中はselfに何も書かないため、1つだけのインスタンスを作成するとより効率的です。 In the ``ListView`` example, nothing writes to ``self`` during the request, so it is more efficient to create a single instance.

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へのリクエストごとには作成されません(訳注: 先の例では、app.add_url_ruleで呼び出されるas_viewごとにListViewのインスタンスは作成されますが、そのインスタンスは実際のリクエストの処理では再利用されるような意味合い)。 Different instances will still be created each for each ``as_view`` call, but not for each request to those views.

Viewのデコレータ(View Decorators) View Decorators

viewクラス自体はview関数ではありません。viewデコレータはクラス自身に対してではなく、as_viewによって返されるview関数に対して適用される必要があります。適用するデコレータのリストをView.decoratorsに設定します。 The view class itself is not the view function. View decorators need to be applied to the view function returned by ``as_view``, not the class itself. Set :attr:`View.decorators` to a list of decorators to apply.

class UserList(View):
    decorators = [cache(minutes=2), login_required]

app.add_url_rule('/users/', view_func=UserList.as_view())

もしdecoratorsを設定しない場合、代わりにそれらを手作業で適用できます。上記の例は以下と同じになります: If you didn't set ``decorators``, you could apply them manually instead. This is equivalent to:

view = UserList.as_view("users_list")
view = cache(minutes=2)(view)
view = login_required(view)
app.add_url_rule('/users/', view_func=view)

順序が問題になることに留意してください。もし@decoratorのやり方を使う場合、上記の例は以下と同じになります: Keep in mind that order matters. If you're used to ``@decorator`` style, this is equivalent to:

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

メソッドの暗示(Method Hints) Method Hints

よくあるパターンは、methods=["GET", "POST"]を使ってviewを登録し、それから何をするか決定するために、request.method == "POST"をチェックします。View.methodsを設定することは、メソッドのリストをadd_url_ruleまたはrouteへ渡すことと同じです。 A common pattern is to register a view with ``methods=["GET", "POST"]``, then check ``request.method == "POST"`` to decide what to do. Setting :attr:`View.methods` is equivalent to passing the list of methods to ``add_url_rule`` or ``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'))

上の例は、さらなるサブクラスがメソッドを継承又は変更できること以外は、以下と同じになります。 This is equivalent to the following, except further subclasses can inherit or change the methods.

app.add_url_rule(
    "/my-view",
    view_func=MyView.as_view("my-view"),
    methods=["GET", "POST"],
)

メソッドの振り分けとAPI(Method Dispatching and API) Method Dispatching and APIs

API用にはHTTPメソッドごとに異なる関数を使うと便利な場合があります。MethodViewは、リクエストのメソッドに基づいてクラスの異なるメソッドに振り分けるために、基本的なViewを拡張します。各HTTPメソッドは、クラスで同じ(小文字の)名前を持つメソッドに対応付けられます。 For APIs it can be helpful to use a different function for each HTTP method. :class:`MethodView` extends the basic :class:`View` to dispatch to different methods of the class based on the request method. Each HTTP method maps to a method of the class with the same (lowercase) name.

MethodViewは、クラスによって定義されたメソッドに基づいて、自動的にView.methodsを設定します。MethodViewは、上書きしたり他のメソッドを定義したりするサブクラスをどのように処理するかも分かっています。 :class:`MethodView` automatically sets :attr:`View.methods` based on the methods defined by the class. It even knows how to handle subclasses that override or define other methods.

与えられた対象のモデル用に、get(詳細取得)、patch(編集)、そしてdeleteメソッドを提供する、汎用的なItemAPIクラスを作成できます。(以下の例の)GroupAPIはget(リスト取得)とpost(作成)メソッドを提供します。 We can make a generic ``ItemAPI`` class that provides get (detail), patch (edit), and delete methods for a given model. A ``GroupAPI`` can provide get (list) and post (create) methods.

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):
        user = 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, url):
    app.add_url_rule(f"/{name}/<int:id>", view_func=ItemAPI(f"{name}-item", model))
    app.add_url_rule(f"/{name}/", view_func=GroupAPI(f"{name}-group", model))

register_api(app, User, "users")
register_api(app, Story, "stories")

これは、標準的なREST API同様、以下のviewを生成します! This produces the following views, a standard REST API!

URL

メソッド Method

説明 Description

/users/

GET

すべてのユーザーをリスト List all users

/users/

POST

新規ユーザーを作成 Create a new user

/users/<id>

GET

ユーザーを表示 Show a single user

/users/<id>

PATCH

ユーザーを更新 Update a user

/users/<id>

DELETE

ユーザーを削除 Delete a user

/stories/

GET

すべてのストーリーをリスト List all stories

/stories/

POST

新規ストーリーを作成 Create a new story

/stories/<id>

GET

ストーリーを表示 Show a single story

/stories/<id>

PATCH

ストーリーを更新 Update a story

/stories/<id>

DELETE

ストーリーを削除 Delete a story