差し込み可能なview(Pluggable Views) Pluggable Views

Changelog

バージョン 0.7 で追加.

Flask 0.7では、Django由来で、関数の代わりにクラスに基づいた汎用的なviewに発想を得た、差し込み可能なview(pluggable views)を導入しています。その第一の意図は、実装の一部が置き換え可能であり、その方法(実装の置き換え)でpluggable viewをカスタマイズできるようにすることです。 Flask 0.7 introduces pluggable views inspired by the generic views from Django which are based on classes instead of functions. The main intention is that you can replace parts of the implementations and this way have customizable pluggable views.

基本原理(Basic Principle) Basic Principle

データベースからオブジェクトのリストを読み込み、テンプレート内で表示する(以下のような)関数があるとします: Consider you have a function that loads a list of objects from the database and renders into a template::

@app.route('/users/')
def show_users(page):
    users = User.query.all()
    return render_template('users.html', users=users)

これはシンプルで柔軟性もありますが、もしもこのviewを他のモデルやテンプレートへも適応できるような汎用的なやり方で提供したい場合、もっと柔軟性が欲しくなるかもしれません。これは、クラスに基づいた差し込み可能なview(pluggable class-based views)が出てくるところです。これをクラスに基づくviewへと変換する最初の一歩として、以下のようにできるでしょう: This is simple and flexible, but if you want to provide this view in a generic fashion that can be adapted to other models and templates as well you might want more flexibility. This is where pluggable class-based views come into place. As the first step to convert this into a class based view you would do this::

from flask.views import View

class ShowUsers(View):

    def dispatch_request(self):
        users = User.query.all()
        return render_template('users.html', objects=users)

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

見てわかるように、flask.views.Viewのサブクラスを作成し、dispatch_request()を実装する必要があります。それから、クラスメソッドのas_view()を使って、そのクラスを実際のview関数へ変換する必要があります。その関数(as_view関数)へ渡す文字列は、それ以降viewが持つエンドポイントの名前になります。しかしながら、これ(訳注: 上記のコード例のことだと思います)自体が役立つわけではないので、少しコードをリファクタリングしましょう: As you can see what you have to do is to create a subclass of :class:`flask.views.View` and implement :meth:`~flask.views.View.dispatch_request`. Then we have to convert that class into an actual view function by using the :meth:`~flask.views.View.as_view` class method. The string you pass to that function is the name of the endpoint that view will then have. But this by itself is not helpful, so let's refactor the code a bit::

from flask.views import View

class ListView(View):

    def get_template_name(self):
        raise NotImplementedError()

    def render_template(self, context):
        return render_template(self.get_template_name(), **context)

    def dispatch_request(self):
        context = {'objects': self.get_objects()}
        return self.render_template(context)

class UserView(ListView):

    def get_template_name(self):
        return 'users.html'

    def get_objects(self):
        return User.query.all()

これ(クラスに基づいたview)は、もちろんこのような小さな例ではそれほど役立つものではありませんが、基本原理を説明するには十分です。クラスに基づくviewを持つときはselfが何を指すかという質問が上がってきます。この(クラスに基づいたviewの)働き方は、リクエストが割り振られると必ずクラスの新しいインスタンスが作成され、dispatch_request()メソッドがURLルール由来のパラメーターを使って呼ばれるように働きます。クラス自体はas_view()関数に渡されたパラメーターを使ってインスタンス化されます。例えば、以下のようにクラスを書くことができます: This of course is not that helpful for such a small example, but it's good enough to explain the basic principle. When you have a class-based view the question comes up what ``self`` points to. The way this works is that whenever the request is dispatched a new instance of the class is created and the :meth:`~flask.views.View.dispatch_request` method is called with the parameters from the URL rule. The class itself is instantiated with the parameters passed to the :meth:`~flask.views.View.as_view` function. For instance you can write a class like this::

class RenderTemplateView(View):
    def __init__(self, template_name):
        self.template_name = template_name
    def dispatch_request(self):
        return render_template(self.template_name)

それから、以下のようにこのクラスを登録できます: And then you can register it like this::

app.add_url_rule('/about', view_func=RenderTemplateView.as_view(
    'about_page', template_name='about.html'))

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

pluggable viewはroute()またはより好ましいadd_url_rule()のいずれかを使用して、普通の関数のようにアプリケーションへ取り付けられます。しかしながら、これはviewを取り付けるときは、viewがサポートするHTTPメソッド名の提供が必要になりそうなことを意味します。サポートするメソッドの情報をクラスへ移動するためには、その情報を持ったmethods属性を提供できます: Pluggable views are attached to the application like a regular function by either using :func:`~flask.Flask.route` or better :meth:`~flask.Flask.add_url_rule`. That however also means that you would have to provide the names of the HTTP methods the view supports when you attach this. In order to move that information to the class you can provide a :attr:`~flask.views.View.methods` attribute that has this information::

class MyView(View):
    methods = ['GET', 'POST']

    def dispatch_request(self):
        if request.method == 'POST':
            ...
        ...

app.add_url_rule('/myview', view_func=MyView.as_view('myview'))

メソッドに基づいた割り振り(Method Based Dispatching) Method Based Dispatching

RESTful APIではHTTPメソッドごとに異なる関数を実行すると特別に役立ちます。flask.views.MethodViewを使ってそれを容易に実行できます。各HTTPメソッドは同じ名前(小文字のみ)の関数に対応づきます: For RESTful APIs it's especially helpful to execute a different function for each HTTP method. With the :class:`flask.views.MethodView` you can easily do that. Each HTTP method maps to a function with the same name (just in lowercase)::

from flask.views import MethodView

class UserAPI(MethodView):

    def get(self):
        users = User.query.all()
        ...

    def post(self):
        user = User.from_form_data(request.form)
        ...

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

この方法ではmethods属性を提供する必要もありません。それはクラス内で定義されたメソッドに基づいて自動的に設定されます。 That way you also don't have to provide the :attr:`~flask.views.View.methods` attribute. It's automatically set based on the methods defined in the class.

viewの修飾(Decorating Views) Decorating Views

viewクラス自身は(Flaskの)ルーティングの仕組み(routing system)に追加されるview関数ではないため、クラス自体を修飾する(訳注: ここではPythonのdecoratorを適用すること)ことにそれほど意味はありません。代わりに、as_view()の戻り値を手作業で修飾する必要があります: Since the view class itself is not the view function that is added to the routing system it does not make much sense to decorate the class itself. Instead you either have to decorate the return value of :meth:`~flask.views.View.as_view` by hand::

def user_required(f):
    """Checks whether user is logged in or raises error 401."""
    def decorator(*args, **kwargs):
        if not g.user:
            abort(401)
        return f(*args, **kwargs)
    return decorator

view = user_required(UserAPI.as_view('users'))
app.add_url_rule('/users/', view_func=view)

Flask 0.8からは、クラス宣言の中で適用するデコレーターのリストを指定できる代替手段が存在します: Starting with Flask 0.8 there is also an alternative way where you can specify a list of decorators to apply in the class declaration::

class UserAPI(MethodView):
    decorators = [user_required]

しかしながらメソッド呼び出し側の視点からはselfが暗黙的であるため、viewの各メソッドへ普通のview用デコレーター(訳注: 関数の上に「@~」で適用するやり方を指していると思います)は使用できないので、忘れないでください。 Due to the implicit self from the caller's perspective you cannot use regular view decorators on the individual methods of the view however, keep this in mind.

API用のメソッドのview Method Views for APIs

Web APIはしばしばHTTPの動詞(訳注: ここでは「GET」「POST」などのメソッド名のこと)と密接しながら働くため、MethodViewに基づいてそのようなAPIを実装することはとても理にかなっています。その際、殆どのときに、同じメソッドのviewに対して異なるURLルールをAPIが要求する(訳注: 例えば、HTTPのGETメソッド用の同じview関数に対して、異なるURLルールを適用するようなことをいっています)であろうことに気づくでしょう。例えば(以下のように)web上でユーザーオブジェクトを公開することを考えてください: Web APIs are often working very closely with HTTP verbs so it makes a lot of sense to implement such an API based on the :class:`~flask.views.MethodView`. That said, you will notice that the API will require different URL rules that go to the same method view most of the time. For instance consider that you are exposing a user object on the web:

URL

Method

Description

/users/

GET

すべてのユーザーのリストを与えます Gives a list of all users

/users/

POST

新規ユーザーを作成します Creates a new user

/users/<id>

GET

単一ユーザーを表示します Shows a single user

/users/<id>

PUT

単一ユーザーを更新します Updates a single user

/users/<id>

DELETE

単一ユーザーを削除します Deletes a single user

それでは、MethodViewを使ってこれを行うにはどうするのでしょうか?その秘訣は、同じviewに対して複数のルールを提供できるという事実を利用することです。 So how would you go about doing that with the :class:`~flask.views.MethodView`? The trick is to take advantage of the fact that you can provide multiple rules to the same view.

一瞬だけviewが以下のようなものであると仮定しましょう: Let's assume for the moment the view would look like this::

class UserAPI(MethodView):

    def get(self, user_id):
        if user_id is None:
            # return a list of users
            pass
        else:
            # expose a single user
            pass

    def post(self):
        # create a new user
        pass

    def delete(self, user_id):
        # delete a single user
        pass

    def put(self, user_id):
        # update a single user
        pass

それでは、(Flaskの)ルーティングの仕組みにどうやって(直前に例示しているクラスに基づいたviewを)接続するのでしょうか?2つのルールを追加し、それぞれに対して明示的にメソッドを示します(訳注: 以下の例で、methodsが「['GET']」と「['GET', 'PUT', 'DELETE']」である2つの「add_url_rule」の部分が該当します): So how do we hook this up with the routing system? By adding two rules and explicitly mentioning the methods for each::

user_view = UserAPI.as_view('user_api')
app.add_url_rule('/users/', defaults={'user_id': None},
                 view_func=user_view, methods=['GET',])
app.add_url_rule('/users/', view_func=user_view, methods=['POST',])
app.add_url_rule('/users/<int:user_id>', view_func=user_view,
                 methods=['GET', 'PUT', 'DELETE'])

もしも似たようなAPIがたくさんある場合、その登録用コードを(以下のように)リファクタリングできます: If you have a lot of APIs that look similar you can refactor that registration code::

def register_api(view, endpoint, url, pk='id', pk_type='int'):
    view_func = view.as_view(endpoint)
    app.add_url_rule(url, defaults={pk: None},
                     view_func=view_func, methods=['GET',])
    app.add_url_rule(url, view_func=view_func, methods=['POST',])
    app.add_url_rule('%s<%s:%s>' % (url, pk_type, pk), view_func=view_func,
                     methods=['GET', 'PUT', 'DELETE'])

register_api(UserAPI, 'user_api', '/users/', pk='user_id')