クラスベースビュー入門

クラスベースビューはビューを実装するもう一つの手段で、関数の代わりに Python のオブジェクトとしてビューを定義します。クラスベースビューは関数ベースのビューを完全に置き換えるものではありませんが、関数ベースのビューと比較して、以下のような違いと利点があります。

  • 特定の HTTP メソッド (GETPOST など) に関連するコードの集まりを、条件分岐を使ってかき分けるのではなく、それぞれに独立したメソッドを割り当てることができる。
  • ミックスイン (多重継承) などのオブジェクト指向のテクニックを使って、コードを再利用可能なコンポーネントに分解できる。

ジェネリックビュー、クラスベースビュー、クラスベースジェネリックビューの関係と歴史的経緯

まず初めに存在したのは、ビュー関数の規約だけでした。Django は、定義された関数に HttpRequest を渡して、HttpResponse が返ってくることを期待していました。Django が提供する機能はこの範囲まででした。

早い内に、ビューの開発には共通のイディオムやパターンが存在することが認識されるようになりました。こうしたパターンを抽象化し、共通ケースに当てはまるようなビューの開発を楽にするために導入されたのが、関数ベースのジェネリックビューでした。

関数ベースの汎用ビューの問題点は、単純なケースを十分にカバーしていたものの、設定オプションを超えて拡張したりカスタマイズしたりする方法がなく、多くの実世界のアプリケーションでの有用性が制限されていたことです。

クラスベースのジェネリックビューは関数ベースのジェネリックビューと同様に、ビューの開発を楽にすることを目的に作成されました。しかし、ミックスインを使用するなどの実装方法の工夫により、ツールキットを提供することができ、結果として、関数ベースのジェネリックビューに比べて、より拡張性が高く、柔軟なものにすることができました。

過去に関数ベースのジェネリック・ビューを試してみて、それが不足していることに気づいた場合、クラスベースのジェネリック・ビューをクラスベースの等価物と考えるのではなく、ジェネリック・ビューが解決しようとしていた本来の問題を解決するための新鮮なアプローチと考えるべきでしょう。

Django がクラスベースのジェネリックビューを構築するために使用する基底クラスと mixin のツールキットは、最大限の柔軟性を求めて構築されており、デフォルトのメソッド実装や、最も単純なユースケースでは気にすることのない属性の形で多くのフックを持っています。例えば、 form_class``のクラスベースの属性に制限する代わりに、実装では ``get_form メソッドを使用しており、 get_form_class メソッドを呼び出します。これにより、どのフォームを使用するかを指定するために、属性から完全に動的で呼び出し可能なフックまで、いくつかのオプションが用意されています。これらのオプションは、単純な状況では中途半端な複雑さを追加しているように見えますが、これがなければ、より高度なデザインは制限されてしまいます。

クラスベースのビューを使用する

中核となる機能として、クラスベースのビューでは、HTTP リクエストのメソッドに応じてクラスインスタンスの異なるメソッドを呼び出させることができるため、1つのビュー関数の内部で条件分岐を使わずにすみます。

そのため、ビュー関数の場合に HTTP GET をハンドリングするコードが次のようになるとすると、

from django.http import HttpResponse

def my_view(request):
    if request.method == 'GET':
        # <view logic>
        return HttpResponse('result')

クラスベースのビューでは以下のようになります。

from django.http import HttpResponse
from django.views import View

class MyView(View):
    def get(self, request):
        # <view logic>
        return HttpResponse('result')

Django の URL リゾルバはリクエストと関連する引数をクラスではなく呼び出し可能な関数に送ることを期待しているため、クラスベースのビューには as_view() クラスメソッドがあり、関連するパターンにマッチする URL へのリクエストが来た時に呼び出される関数を返します。この関数は、クラスのインスタンスを作成し、 setup() を呼び出してその属性を初期化し、その後 dispatch() メソッドを呼び出します。 dispatch はリクエストを調べて GETPOST などであるかどうかを判断し、マッチするメソッドが定義されていればそのリクエストをリレーします。

# urls.py
from django.urls import path
from myapp.views import MyView

urlpatterns = [
    path('about/', MyView.as_view()),
]

メソッドが関数ベースのビューが返すもの、つまり何らかの形の HttpResponse と同等のものしか返せないなら意味はありません。これが意味するのは、http shortcutsTemplateResponse オブジェクトはクラスベースのビューの内部でも使えるということです。

最小限のクラスベースのビューでは、ジョブを実行するのにどんなクラス属性も必要としませんが、クラスベースの設計をする場合にはクラス属性が役に立つことが多いです。クラス属性のカスタマイズと設定を行うには2つの方法があります。

第1の方法は、通常の Python のサブクラス化を行い、サブクラス上で属性やメソッドを上書きするという方法です。たとえば、次のように親クラスが greeting という属性を持っていたとすると、

from django.http import HttpResponse
from django.views import View

class GreetingView(View):
    greeting = "Good Day"

    def get(self, request):
        return HttpResponse(self.greeting)

サブクラスでは次のように属性を上書きできます。

class MorningGreetingView(GreetingView):
    greeting = "Morning to ya"

もう一つの方法は、URLconf 内での as_view() の呼び出し時に、クラス属性をキーワード引数として指定する方法です。

urlpatterns = [
    path('about/', GreetingView.as_view(greeting="G'day")),
]

注釈

定義したクラスはリクエストが発行されるごとにインスタンス化されますが、as_view() エントリーポイントで指定したクラス属性が設定されるのは、URL がインポートされる際の1回だけです。

ミックスインを使用する

ミックスインは、複数の親クラスのメソッドや属性を混合することができる多重継承の形式の1つです。

たとえば、ジェネリッククラスベースのビューには、 TemplateResponseMixin と呼ばれるミックスインがあり、その主な目的はメソッド render_to_response() を定義することです。 View 基底クラスの動作と組み合わせると、適切なマッチングメソッド( View クラス基底クラスで定義されている動作)にリクエストをディスパッチする TemplateView メソッドクラスとなり、 template_name 属性を使用してTemplateResponseオブジェクト(TemplateResponseMixin で定義されている動作)を返す render_to_response() メソッドを持っています。

ミキシンは複数のクラス間でコードを再利用するための優れた方法ですが、いくつかのコストがかかります。あなたのコードがミックスインに分散すればするほど、子クラスを読み込んでそれが何をしているのかを正確に知ることは難しくなります。

また、1つの汎用ビューからしか継承できないことにも注意してください - つまり、親クラスは1つだけ View を継承することができ、残りのクラスは(もしあれば)ミックスインでなければなりません。View を継承している複数のクラスから継承しようとすると - たとえば、リストの先頭にあるフォームを使用して ProcessFormViewListView を組み合わせようとすると - 期待通りには動作しません。

クラスベースのビューでフォームを扱う

フォームを扱う基本の関数ベースのビューは、次のようなコードになります。

from django.http import HttpResponseRedirect
from django.shortcuts import render

from .forms import MyForm

def myview(request):
    if request.method == "POST":
        form = MyForm(request.POST)
        if form.is_valid():
            # <process form cleaned data>
            return HttpResponseRedirect('/success/')
    else:
        form = MyForm(initial={'key': 'value'})

    return render(request, 'form_template.html', {'form': form})

同等のクラスベースのビューは、次のようになるでしょう。

from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.views import View

from .forms import MyForm

class MyFormView(View):
    form_class = MyForm
    initial = {'key': 'value'}
    template_name = 'form_template.html'

    def get(self, request, *args, **kwargs):
        form = self.form_class(initial=self.initial)
        return render(request, self.template_name, {'form': form})

    def post(self, request, *args, **kwargs):
        form = self.form_class(request.POST)
        if form.is_valid():
            # <process form cleaned data>
            return HttpResponseRedirect('/success/')

        return render(request, self.template_name, {'form': form})

これは最低限のケースですが、URLconfの設定で``form_class`` などのクラス属性をオーバーライドしたり、サブクラス化してメソッドをオーバーライドしたり(もしくはその両方)して、このビューをカスタマイズするオプションがあることがわかります。

クラスベースのビューをデコレーションする

クラスベースのビューを拡張する方法は、ミックスインの使用にとどまりません。デコレータも使用できます。クラスベースのビューは関数ではないので、as_view() を使用した場合とサブクラスを作成した場合では、デコレータは違った動作をします。

URLconf でデコレーションする

as_view() メソッドの結果を装飾することにより、クラスベースのビューを調整できます。これを行う最も簡単な場所は、ビューをデプロイするURLconfです:

from django.contrib.auth.decorators import login_required, permission_required
from django.views.generic import TemplateView

from .views import VoteView

urlpatterns = [
    path('about/', login_required(TemplateView.as_view(template_name="secret.html"))),
    path('vote/', permission_required('polls.can_vote')(VoteView.as_view())),
]

このアプローチではデコレータはインスタンスごとに適用されます。もしあるビューのすべてのインスタンスをデコレーションしたいばあいは、別のアプローチを取る必要があります。

クラスをデコレーションする

クラスベースのビューのすべてのインスタンスをデコレーションするには、クラスの定義自体をデコレーションする必要があります。そのためには、クラスの dispatch() メソッドにデコレータを付けます。

クラス上のメソッドはスタンドアロンの関数と完全に同じではないため、関数デコレータを単純にそのままメソッドに適用することはできません。適用前にメソッドデコレータに変換する必要があります。method_decorator デコレータを使えば、関数デコレータをメソッドデコレータに変換し、インスタンスのメソッドのデコレーションに使えるようにできます。たとえば、次のように使用します。

from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView

class ProtectedView(TemplateView):
    template_name = 'secret.html'

    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)

あるいは、より簡潔に、クラスを代わりにデコレートして、デコレーション対象のメソッド名をキーワード引数 name に渡すという方法もあります。

@method_decorator(login_required, name='dispatch')
class ProtectedView(TemplateView):
    template_name = 'secret.html'

共通のデコレータ群が複数の場所で呼ばれる場合には、デコレータのリストまたはタプルを定義して、これを method_decorator() を複数回呼ぶ代わりに使用できます。以下の2つのクラスは同じになります。

decorators = [never_cache, login_required]

@method_decorator(decorators, name='dispatch')
class ProtectedView(TemplateView):
    template_name = 'secret.html'

@method_decorator(never_cache, name='dispatch')
@method_decorator(login_required, name='dispatch')
class ProtectedView(TemplateView):
    template_name = 'secret.html'

デコレータは、デコレータに渡された順番でリクエストを処理します。上の例では、never_cache()login_required() の前にリクエストを処理します。

この例では、 ProtectedView のすべてのインスタンスにログイン保護があります。これらの例では login_required, を使用していますが、 LoginRequiredMixin を使用しても同じ動作を得ることができます。

注釈

method_decorator は、クラスのデコレートするメソッドに *args**kwargs を引数として渡します。定義されているメソッドが互換性のある引数のセットを受け取れない場合には TypeError 例外が発生します。