[Django]クラスベースビューと関数ベースビューの使い分けを考える
はじめに
これは Django Advent Calendar 2021 17日目の記事です(空いていたので飛び入りで)。
Djangoチュートリアルでは関数ベースビューの紹介から始まりますが、実際に使うのはクラスベースビューがほとんどです。この記事では、まず原則としてクラスベースビューを使うべきという話、そして関数ベースビューを使う方がいい場面の説明をします。
原則=クラスベースビュー
まず、Djangoは原則、クラスベースビューを使った方がいいです。その理由は2つあります。
- コード量がほとんど変わらない
- HTTPメソッドを指定する必要がない
1つ目は、クラスベースビューの共通の祖先であるViewクラスを使えば、関数ベースビューとほぼ同等のことが簡単にできるからです。例えば次の2つは(ほぼ?)同等です。
from django.http import HttpResponse
def hello(request):
return HttpResponse("hello")
class HelloView(View):
def get(self, request, *args, **kwargs):
return HttpResponse("hello")
もしテンプレートを使いたければ TemplateView を使うなど、リファクタリングも可能です。関数ベースビューを使うと、そこで発展が止まってしまいます。なので、原則はクラスベースビューを使った方がいいです。
2つ目は、HTTPメソッドを指定する必要がないことです。これは分かりにくいので実際のコードで示します。
def hello(request):
# ここらへんになんか重い処理を入れる
return HttpResponse("hello")
「ここらへんになんか重い処理を入れる」は例えばPDFを作るとか、適当な重い処理を想像してください。その場合、上のメソッドは大きな問題があります。それは何でしょうか?
それは、GETメソッドで重い処理が行われてしまうことです。
GETメソッドは通常、副作用が生じるべきでないとされています。なので、何か重い処理を行うならPOSTメソッドに制限すべきです。この場合、次のようにする必要があります。
from django.views.decorators.http import require_http_methods
@require_http_methods(["POST"])
def hello(request):
# ここらへんになんか重い処理を入れる
return HttpResponse("hello")
しかし require_http_methods
をわざわざ指定する必要があるため、忘れる可能性が高くなります。クラスベースビューなら、 post()
メソッドを実装すればいいので、忘れる可能性が低いです。
class HelloView(View):
def post(self, request, *args, **kwargs):
# ここらへんになんか重い処理を入れる
return HttpResponse("hello")
関数ベースビューの方がいい場合
逆に関数ベースビューの方がいい場面はないでしょうか。自分は少なくとも1つは思いつきます。それは、 @csrf_exempt
を使うときです。 @csrf_exempt
はCSRF検証を無効化するときに使うデコレータで、例えば Webhook の実装で使います。
この場合、次のようなコードになります。
@csrf_exempt
@require_http_methods(["POST"])
def hello(request):
return HttpResponse("hello")
これをクラスベースビューで実装すると次のようになります。
@method_decorator(csrf_exempt, name="dispatch")
class HelloView(View):
def post(self, request, *args, **kwargs):
return HttpResponse("hello")
@csrf_exempt
でなく @method_decorator
を使います。それはまだいいのですが、 name="dispatch"
って何?と思うかもしれません。
この dispatch
は Viewクラスに定義されているメソッドで、全てのHTTPメソッドの処理の前に呼ばれます。 dispatch
メソッドくらいは知っておいた方がいいと思いますが、驚きを生じる書き方なので、自分は好みません。
そして、クラスベースビューを使って実現しようとすると、どの書き方でもしっくりきません。なので Webhook など、CSRF検証を無効化する場合は関数ベースビューでいいのではと考えています。 @require_http_methods(["POST"])
が必要というのは一緒ですが、 @csrf_exempt
をつけるのは例外なのでまあいいかなと。
# これはダメ
@method_decorator(csrf_exempt, name="post")
class HelloView(View):
def post(self, request, *args, **kwargs):
return HttpResponse("hello")
# これもダメ
class HelloView(View):
@method_decorator(csrf_exempt)
def post(self, request, *args, **kwargs):
return HttpResponse("hello")
# これならOKだが冗長
class HelloView(View):
@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return HttpResponse("hello")
おわりに
Djangoのクラスベースビューは強力ですが、継承を多用しているためクラス階層が分かりづらいです。そのときには自分が書いたこの記事を読んでみてください。
Discussion