🤔

[Django]クラスベースビューと関数ベースビューの使い分けを考える

2021/12/24に公開

はじめに

これは Django Advent Calendar 2021 17日目の記事です(空いていたので飛び入りで)。

Djangoチュートリアルでは関数ベースビューの紹介から始まりますが、実際に使うのはクラスベースビューがほとんどです。この記事では、まず原則としてクラスベースビューを使うべきという話、そして関数ベースビューを使う方がいい場面の説明をします。

原則=クラスベースビュー

まず、Djangoは原則、クラスベースビューを使った方がいいです。その理由は2つあります。

  1. コード量がほとんど変わらない
  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 メソッドくらいは知っておいた方がいいと思いますが、驚きを生じる書き方なので、自分は好みません。

https://github.com/django/django/blob/4.0/django/views/generic/base.py#L93-L101

そして、クラスベースビューを使って実現しようとすると、どの書き方でもしっくりきません。なので 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のクラスベースビューは強力ですが、継承を多用しているためクラス階層が分かりづらいです。そのときには自分が書いたこの記事を読んでみてください。

https://www.membersedge.co.jp/blog/completely-guide-for-django-class-based-views/

Discussion