はじめに
Djangoでの開発時によく使うのが、クラスベースビューです。
クラスベースビューは「クラスベース汎用ビュー」または「汎用ビュー」と呼ばれることもありますが、データモデルのCRUDのような基本的な機能を簡単に実装できるように、ビューを再利用できるクラスとして提供したものです。
しかし現実のアプリケーションはデータモデルのCRUDのような基本的な機能だけではなく、例えば「2つのデータモデルを同時に編集させたい」「一覧を表示しつつ新規入力もさせたい」のような、単なるCRUDではない機能が要求されることもあります。そのような要求が来るたびに開発者は「これはどのクラスベースビューを使えばいいか」を考える必要があります。
このときに間違った判断をすると、後から見て何をやっているのか分からないコードになったり、実装はできたものの、コードが冗長になったりします。そのため、どのクラスをベースに作るかを判断するために、クラスベースビューの仕組みを理解しておく必要があります。
そしてクラスベースビューの仕組みを理解するためには、自分はソースコードを読むのが一番だと考えています。しかし、クラスベースビューはクラス構成が複雑なため、前提知識なしにソースコードを読むのは骨が折れます。
そのため、ソースコードを読むための前提知識として、クラスベースビューのクラス構成を解説するのがこの記事の目的です。そのため、各クラスが提供する機能については詳しく解説しません。
Djangoのクラスベースビューはどれだけあるか
Djangoのクラスベースビューを調べるときに役立つのが、Django Class-Based-View Inspectorです。このサイトでは、Djangoで使われているクラスベースビューを用途別にまとめています。また、クラス名をクリックすることで、クラスの詳細、具体的には継承関係、属性、メソッドが表示されます。
このサイトによれば、Djangoには24個のクラスベースビューがあります。しかしよく使うクラスベースビューは限られています。そのためこの記事では、よく使われる次のクラスベースビュー9個に限って解説します。
- Generic Base
- モジュール: django.views.generic.base
- クラス: View, RedirectView, TemplateView
- Generic Detail
- モジュール: django.views.generic.detail
- クラス: DetailView
- Generic List
- モジュール: django.views.generic.list
- クラス: ListView
- Generic Edit
- モジュール: django.views.generic.edit
- クラス: FormView, CreateView, UpdateView, DeleteView
Djangoのクラス構成
まず、9個のクラスベースビューには、親クラスが16個あります。クラスベースビューとその親クラスを合わせて25個のクラスの継承関係をクラス図にすると次のようになります。

見るのも嫌になりますよね。このように複雑な継承関係になる理由は、Pythonが多重継承を採用しているからです。
多重継承は名前こそシンプルですが、菱形継承問題などの問題を引き起こしやすい機能です。そのため、最近作られた言語では多重継承は採用されません(Pythonは1991年に誕生した、歴史ある言語です)。
Djangoでは多重継承に関する問題を避けるために、クラス構成を工夫することで、実質的に単一継承にしています。具体的には、Djangoのクラスベースビューで使われるクラスを「Viewクラス」と「Mixinクラス」の大きく2つに分けています。そして、木の幹となる「Viewクラス」は単一継承とし、木の枝となる「Mixinクラス」を複数付けられるようにしています。
Viewクラスは木の幹に相当するもので、次の特徴があります。
- django.views.generic.base.View クラスを頂点とした単一継承
- Viewクラスのみでビューが作れる
- クラス名の最後に「View」が付いている
- 頂点となる django.views.generic.base.View クラスを除いて、get(), post()など、HTTPメソッドに対応したメソッドのみオーバーライドされている
Viewクラスのみをクラス図にすると次のようになります。

一方でMixinクラスは木の枝に相当するもので、次の特徴があります。JavaのInterface、RubyのModuleと似たようなものです。
- 特定の機能だけを実装した「部品」を提供する
- Mixinクラスだけでビューは作れない
- クラス名の最後に「Mixin」が付いている
Mixinクラスだけをクラス図にすると次のようになります。

再度まとめると、全てのViewクラスは頂点となる django.views.generic.base.View クラスを除いて、ただ1個のViewクラスと、0個以上のMixinクラスを継承しています。複数のViewクラスを継承することはありません。
この仕組みが理解できると、Djangoのクラス構成が理解しやすくなります。
メソッド解決順序
また、クラスを多重継承する場合、同じメソッドが継承元の複数のクラスで定義されることがあります。そのため、どのクラスのメソッドが優先的に呼ばれるのか、その順番を決める必要があります。
そのメソッドが優先的に呼ばれる順番が「メソッド解決順序」です。英語ではMethod Resolution Orderで、MROと略します。
そして、Pythonのメソッド解決順序は次の2つの条件を満たしています。
- 派生クラスが基底クラスより優先される
- 継承の定義で左側に書いた基底クラスが右側に書いた基底クラスより優先される
例えば TemplateView は次のように定義されています。
class TemplateView(TemplateResponseMixin, ContextMixin, View):
このときに、メソッド解決順序は次のようになります。
Generic Base
まずは基本となるクラスを集めた、Generic Baseについて解説します。 Generic Baseで解説するクラスはモジュール django.views.generic.base で定義されています。
View
まずは全てのViewクラスの頂点となる、 django.views.generic.base.View クラスについて解説します。
これ以降のクラス図ではインスタンス変数、クラスメソッド、インスタンスメソッドについて記載しますが、メソッドの引数および戻り値は省略します。

大まかな処理の流れは次のようになっています。
- as_view(): 次の関数を定義して返す
- setup() で request, args, kwargs をインスタンス変数として登録
- dispatch() を呼び出す
- dispatch(): HTTPメソッドに対応するメソッドを探して呼び出す(例: GET→get()、POST→post())
- ただし次の場合は http_method_not_allowed() を呼び出す
- HTTPメソッドが http_method_names に未定義
- HTTPメソッドに対応するメソッドが見つからない
- OPTIONSについては定義ずみ(options())
- ただし次の場合は http_method_not_allowed() を呼び出す
RedirectView
次に、リダイレクトを行うためのクラスベースビューである、 RedirectView を解説します。クラス図は次のようになります。

いろいろメソッドが定義されていますが、 head() 以降は全て get() を呼び出しており、 get() メソッドではリダイレクトしています。すなわち、どのHTTPメソッドを使ってアクセスしてもリダイレクトされます。
TemplateView
次に、シンプルなクラスベースビューの1つである、 TemplateView を解説します。クラス図は次のようになります。

TemplateView はテンプレート機能に対応したクラスベースビューです。TemplateView は2つの機能からなっており、それぞれ継承している2つのMixinクラスで実装されています。
- TemplateResponseMixin: テンプレートファイルを指定する機能
- ContextMixin: テンプレートファイルに渡すコンテキストを指定する機能
そして、TemplateView の get() メソッドは次のように定義されています。コンテキストを取得して、テンプレートに渡すだけのシンプルな処理です。
def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) return self.render_to_response(context)
Generic Detail
次に、詳細を表示するクラスを集めた、Generic Detailについて解説します。Generic Detailで解説するクラスはモジュール django.views.generic.detail で定義されています。
DetailView
データモデルを表示するためのクラスベースビューである、 DetailView を解説します。クラス図は次のようになります。

ContextMixin, TemplateResponseMixin は TemplateView で出てきました。その他のMixinクラスについては次のような役割です。
- SingleObjectMixin: 単一のデータモデルを取得する
- SingleObjectTemplateResponseMixin: 単一のデータモデルに対応したテンプレート名を取得する
そしてこれがややこしいのですが、 SingleObjectTemplateResponseMixin を使うには次のいずれかが必須です。
- self.template_name が定義されていること
- メソッド get_template_names() が値を返すこと
- self.object が定義されていること
しかし、SingleObjectTemplateResponseMixin では self.object は定義されていません。この self.object はどこから来るのでしょうか。
それは BaseDetailView です。 get() メソッドは次のように定義されており、 self.object がここで設定されていること、コンテキスト取得時にそのオブジェクトを渡していることが分かります。
なお、 DetailView ではメソッドはオーバーライドされていません。
def get(self, request, *args, **kwargs): self.object = self.get_object() context = self.get_context_data(object=self.object) return self.render_to_response(context)
Generic List
次に、一覧表示を行うクラスを集めた、Generic Listについて解説します。Generic Listで解説するクラスはモジュール django.views.generic.list で定義されています。
ListView
データモデルを一覧表示するためのクラスベースビューである、 ListView を解説します。クラス図は次のようになります。

勘のいい人なら気づくかもしれませんが、 ListView は DetailView と構成がほぼ同じで、クラスが以下のように変わっただけです。
- SingleObjectMixin → MultipleObjectMixin
- SingleObjectTemplateResponseMixin → MultipleObjectTemplateResponseMixin
- BaseDetailView → BaseListView
- DetailView → ListView
主な違いは次の通りです。
- MultipleObjectTemplateResponseMixin は self.template_name, get_template_names() あるいは self.object_list の存在を仮定
- ページネーション機能がある
なお、 ListView ではメソッドはオーバーライドされていません。
Generic Edit
最後に、編集を行うクラスを集めた、 Generic Edit について解説します。 Generic Edit で解説するクラスはモジュール django.views.generic.edit で定義されています。
FormView
フォームを扱うためのクラスベースビューである、 FormView を解説します。最も複雑で、理解が難しいのがこの FormView です。クラス図は次のようになります。

FormMixin はフォーム作成に必要な情報を作成しています。フォームをカスタマイズするときは、この FormMixin で定義されているメソッドをオーバーライドすることが多いでしょう。そして処理の流れは ProcessFormView で定義されています。 BaseFormView と、 FormView ではメソッドはオーバーライドされていません。
CreateView
次に、データモデルを作成するためのクラスベースビューである、 CreateView を解説します。クラス図は次のようになります。

継承関係は複雑ですが、新規に出てくるMixinクラスは ModelFormMixin ただ1つです。このMixinクラスでは、データモデル固有の実装になるように、メソッドをオーバーライドしています。
ややこしいのは、 DetailView では self.object の設定は BaseDetailView が行っていましたが、 CreateView では self.object の設定は ModelFormMixin が行っていることです。
UpdateView
次に、データモデルを更新するためのクラスベースビューである、UpdateView を解説します。クラス図は次のようになります。

これは CreateView が理解できれば簡単です。なぜなら、継承関係がほぼ同じだからです。実装もほぼ同じで、 BaseCreateView が次のような実装なのに対し、
def get(self, request, *args, **kwargs): self.object = None return super().get(request, *args, **kwargs)
BaseUpdateView が次のような実装になっています。
def get(self, request, *args, **kwargs): self.object = self.get_object() return super().get(request, *args, **kwargs)
この実装の違いは、 CreateView が初期状態を持たないのに対して、 UpdateView が初期状態を持つ、それだけです。詳しくは ModelFormMixin の実装を確認してください。
DeleteView
最後に、データモデルの削除を行うためのクラスベースビューである、 DeleteView を解説します。クラス図は次のようになります。

DeleteViewの特徴は、 BaseDetailView を継承していることです。実際には BaseDeleteView では何もオーバーライドしていないため、 DetailView にDeletionMixin が追加されている、ほぼそれだけです。
DeletionMixinはその名のとおり、削除機能を実装していますが、 BaseDetailView を継承する理由は何でしょうか。それは、GETメソッドでアクセスすると、データモデルの内容が入った確認画面を表示し、削除ボタンを押してPOSTメソッドでアクセスすると削除される、それが DeleteView の標準の仕様だからです。
おわりに
Djangoのクラスベースビューについて一通り解説しました。
この記事を読んだあとは、Django公式サイトにあるドキュメント、 Using mixins with class-based views を読んでみてください。この公式ドキュメントでは、クラスベースビューやMixinを使ってどのように必要な機能を実装していくかの指針が書かれています。
逆に言えば、この指針が理解できれば「これはどのクラスベースビューを使えばいいか」を判断できるようになります。そしてこの記事が、この指針の理解の助けになれば幸いです。