クラスベースビュー入門¶
クラスベースビューはビューを実装するもう一つの手段で、関数の代わりに Python のオブジェクトとしてビューを定義します。クラスベースビューは関数ベースのビューを完全に置き換えるものではありませんが、関数ベースのビューと比較して、以下のような違いと利点があります。
- 特定の HTTP メソッド (
GET
、POST
など) に関連するコードの集まりを、条件分岐を使ってかき分けるのではなく、それぞれに独立したメソッドを割り当てることができる。 - ミックスイン (多重継承) などのオブジェクト指向のテクニックを使って、コードを再利用可能なコンポーネントに分解できる。
ジェネリックビュー、クラスベースビュー、クラスベースジェネリックビューの関係と歴史的経緯¶
まず初めに存在したのは、ビュー関数の規約だけでした。Django は、定義された関数に HttpRequest
を渡して、HttpResponse
が返ってくることを期待していました。Django が提供する機能はこの範囲まででした。
早い内に、ビューの開発には共通のイディオムやパターンが存在することが認識されるようになりました。こうしたパターンを抽象化し、共通ケースに当てはまるようなビューの開発を楽にするために導入されたのが、関数ベースのジェネリックビューでした。
The problem with function-based generic views is that while they covered the simple cases well, there was no way to extend or customize them beyond some configuration options, limiting their usefulness in many real-world applications.
クラスベースのジェネリックビューは関数ベースのジェネリックビューと同様に、ビューの開発を楽にすることを目的に作成されました。しかし、ミックスインを使用するなどの実装方法の工夫により、ツールキットを提供することができ、結果として、関数ベースのジェネリックビューに比べて、より拡張性が高く、柔軟なものにすることができました。
If you have tried function based generic views in the past and found them lacking, you should not think of class-based generic views as a class-based equivalent, but rather as a fresh approach to solving the original problems that generic views were meant to solve.
The toolkit of base classes and mixins that Django uses to build class-based
generic views are built for maximum flexibility, and as such have many hooks in
the form of default method implementations and attributes that you are unlikely
to be concerned with in the simplest use cases. For example, instead of
limiting you to a class-based attribute for form_class
, the implementation
uses a get_form
method, which calls a get_form_class
method, which in
its default implementation returns the form_class
attribute of the class.
This gives you several options for specifying what form to use, from an
attribute, to a fully dynamic, callable hook. These options seem to add hollow
complexity for simple situations, but without them, more advanced designs would
be limited.
クラスベースのビューを使用する¶
中核となる機能として、クラスベースのビューでは、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')
Because Django's URL resolver expects to send the request and associated
arguments to a callable function, not a class, class-based views have an
as_view()
class method which returns a
function that can be called when a request arrives for a URL matching the
associated pattern. The function creates an instance of the class, calls
setup()
to initialize its attributes, and
then calls its dispatch()
method.
dispatch
looks at the request to determine whether it is a GET
,
POST
, etc, and relays the request to a matching method if one is defined,
or raises HttpResponseNotAllowed
if not:
# 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})
This is a minimal case, but you can see that you would then have the option
of customizing this view by overriding any of the class attributes, e.g.
form_class
, via URLconf configuration, or subclassing and overriding one or
more of the methods (or both!).
クラスベースのビューをデコレーションする¶
クラスベースのビューを拡張する方法は、ミックスインの使用にとどまりません。デコレータも使用できます。クラスベースのビューは関数ではないので、as_view()
を使用した場合とサブクラスを作成した場合では、デコレータは違った動作をします。
URLconf でデコレーションする¶
You can adjust class-based views by decorating the result of the
as_view()
method. The easiest place to do
this is in the URLconf where you deploy your view:
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()
の前にリクエストを処理します。
In this example, every instance of ProtectedView
will have login
protection. These examples use login_required
, however, the same behavior
can be obtained by using
LoginRequiredMixin
.
注釈
method_decorator
は、クラスのデコレートするメソッドに *args
と **kwargs
を引数として渡します。定義されているメソッドが互換性のある引数のセットを受け取れない場合には TypeError
例外が発生します。