クラスベースビュー入門¶
クラスベースビューはビューを実装するもう一つの手段で、関数の代わりに Python のオブジェクトとしてビューを定義します。クラスベースビューは関数ベースのビューを完全に置き換えるものではありませんが、関数ベースのビューと比較して、以下のような違いと利点があります。
- 特定の HTTP メソッド (
GET
、POST
など) に関連するコードの集まりを、条件分岐を使ってかき分けるのではなく、それぞれに独立したメソッドを割り当てることができる。 - ミックスイン (多重継承) などのオブジェクト指向のテクニックを使って、コードを再利用可能なコンポーネントに分解できる。
ジェネリックビュー、クラスベースビュー、クラスベースジェネリックビューの関係と歴史的経緯¶
まず初めに存在したのは、ビュー関数の規約だけでした。Django は、定義された関数に HttpRequest
を渡して、HttpResponse
が返ってくることを期待していました。Django が提供する機能はこの範囲まででした。
早い内に、ビューの開発には共通のイディオムやパターンが存在することが認識されるようになりました。こうしたパターンを抽象化し、共通ケースに当てはまるようなビューの開発を楽にするために導入されたのが、関数ベースのジェネリックビューでした。
関数ベースのジェネリックビューには問題点があり、シンプルなケースは概ねカバーしていたものの、シンプルな設定オプションを超えた拡張やカスタマイズの方法がなく、現実世界で使われる様々なアプリケーションでの利便性を制限してしまっていました。
クラスベースのジェネリックビューは関数ベースのジェネリックビューと同様に、ビューの開発を楽にすることを目的に作成されました。しかし、ミックスインを使用するなどの実装方法の工夫により、ツールキットを提供することができ、結果として、関数ベースのジェネリックビューに比べて、より拡張性が高く、柔軟なものにすることができました。
もしあなたが昔、関数ベースのジェネリックビューを使用しようとして、不十分なものだと考えたことがあったのなら、クラスベースのジェネリックビューを単なるクラスベースビューと同等のものとは考えないでください。むしろ、ジェネリックビューが解決しようとしていた元々あった問題を解くための新しいアプローチとして考えてください。
Django がクラスベースのジェネリックビューを作成する時に使う、ベースクラスやミックスインのツールキットは、最高の柔軟性を持つように設計されており、最も単純なユースケースではおそらく気に掛けることもないような多数のフックを、デフォルトのメソッド実装や属性という形で持っています。たとえば、クラスベースの属性 form_class
だけを使用するよう制限する代わりに、実際の実装では get_form
メソッドを呼び出し、ここからさらに、デフォルトでは単にクラスの form_class
属性を返すだけの 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 へのリクエストが届いた時に呼ばれる関数を返します。そして、この関数はクラスのインスタンスを作成して、dispatch()
メソッドを呼びます。dispatch
はリクエストを見て GET
や POST
などの HTTP メソッドを判定し、マッチするメソッドが定義されていればリクエストを受け渡します。もし定義されていない場合には、HttpResponseNotAllowed
例外が発生します。
# urls.py
from django.urls import path
from myapp.views import MyView
urlpatterns = [
path('about/', MyView.as_view()),
]
メソッドが関数ベースのビューが返すもの、つまり何らかの形の HttpResponse
と同等のものしか返せないなら意味はありません。これが意味するのは、http shortcuts ` や :class:`~django.template.response.TemplateResponse オブジェクトはクラスベースのビューの内部でも使えるということです。
最小限のクラスベースのビューでは、ジョブを実行するのにどんなクラス属性も必要としませんが、クラスベースの設計をする場合にはクラス属性が役に立つことが多いです。クラス属性のカスタマイズと設定を行うには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つです。
For example, in the generic class-based views there is a mixin called
TemplateResponseMixin
whose primary purpose
is to define the method
render_to_response()
.
When combined with the behavior of the View
base class, the result is a TemplateView
class that will dispatch requests to the appropriate matching methods (a
behavior defined in the View
base class), and that has a
render_to_response()
method that uses a
template_name
attribute to return a TemplateResponse
object (a behavior defined in the TemplateResponseMixin
).
Mixins are an excellent way of reusing code across multiple classes, but they come with some cost. The more your code is scattered among mixins, the harder it will be to read a child class and know what exactly it is doing, and the harder it will be to know which methods from which mixins to override if you are subclassing something that has a deep inheritance tree.
Note also that you can only inherit from one generic view - that is, only one
parent class may inherit from View
and
the rest (if any) should be mixins. Trying to inherit from more than one class
that inherits from View
- for example, trying to use a form at the top of a
list and combining ProcessFormView
and
ListView
- won't work as expected.
クラスベースのビューでフォームを扱う¶
フォームを扱う基本の関数ベースのビューは、次のようなコードになります。
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})
これは非常にシンプルなケースですが、クラス属性を上書きするなどの方法でこのビューをカスタマイズする手段が用意されていることがわかります。たとえば、form_class
を URLconf 設定で指定したり、サブクラス化して1つ以上のメソッドを上書きしたりすることもできます。
クラスベースのビューをデコレーションする¶
クラスベースのビューを拡張する方法は、ミックスインの使用にとどまりません。デコレータも使用できます。クラスベースのビューは関数ではないので、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
のすべてのインスタンスがログインをプロテクトされます。
注釈
method_decorator
は、クラスのデコレートするメソッドに *args
と **kwargs
を引数として渡します。定義されているメソッドが互換性のある引数のセットを受け取れない場合には TypeError
例外が発生します。