内藤 裕二/ 2022年 8月 17日/ 技術

こんにちは!内藤です!
Djangoのクラスベースビューは便利です。
ただ、標準からはずれた動作を実現しようとすると、資料がなくて泣きながらソースコードを追いかける羽目になります。

幸いにも、泣きながら追いかける機会があったので、せっかくなので何回かに分けて、よく使うクラスの流れをまとめてみようと思います。
最終的には、ケーススタディまでまとめられたら、と思っているのですが、果たして・・・?

Index

  • 基本的なクラスのシーケンス(この記事)

    • View
    • RedirectView
    • TemplateView
    • DetailView
    • ListView
  • CRUDクラスのシーケンス(次の記事

    • FormView
    • CreateView
    • UpdateView
    • DeleteView

      前提条件

  • 対象のDjangoバージョンは、手元にあった 3.2.13 です

  • 現場で使える Django の教科書《基礎編》の内容程度が頭に入っている人向け

  • 対象とするクラスは、下記

    • View
    • RedirectView
    • TemplateView
    • DetailView
    • ListView
    • FormView
    • CreateView
    • UpdateView
    • DeleteView

Djangoのクラスベースビューの構成

こちらにクラス図を起こしている方がいますが、見るとクラクラします。
クラスビューはすべて django.views.generic.base.View クラスから派生しているのですが、各種 Mixin クラスが入り乱れていて、ソースコード上のどこで何をやっているのかを追いかけるのはなかなか大変です。
各クラスのそれぞれのメソッドがどのタイミングで呼ばれるのかを、ざっくりとまとめてみようと思います。

django.views.generic.base.View

すべての基本になるクラスです。
ソースコードはこちら

urls.py に記載する謎の呪文 as_view メソッドが実装されています。
URLディスパッチャから呼び出されて応答するまでの処理を、簡易的にコールグラフに起こしてみました。

Viewのコールグラフ

関数名 処理内容
View.as_view クラスメソッドとして実装されています。
Viewクラスのインスタンスを生成し、インスタンスのメソッドを呼び出します
setup 下記のインスタンス変数に値を設定します。
・self.request
・self.args
・self.kwargs
dispatch HTTPメソッドがクラス変数 View.http_method_not_allowed に存在する場合、HTTPメソッドと同名のメソッドを呼び出します。
View.http_method_not_allowed に存在しなかった場合、または、HTTPメソッドと同名のメソッドが定義されていない場合、 http_method_not_allowed メソッドを呼び出します

オーバーライドの例

dispatchメソッドは、以降のすべての処理の起点になっています。
この関数をオーバーライドすると、すべてのView派生クラスで実行する処理を記述できます。

View内の例外をキャッチ

下記のようなMixinを作成しておき、自作するすべてのViewをこのMixinから派生させると、例外発生時にエラーページに遷移するような動きを実現できます。

from django.http import HttpResponseRedirect
from logging import getLogger

logger = getLogger(__name__)

class CatchExceptionMixin:
    error_url = '<エラー時に遷移するURL>'

    def dispatch(self, request, *args, **kwargs):
        try:
            return super().dispatch(request, *args, **kwargs)
        except Exception as e
            logger.exception(e)
            return HttpResponseRedirect(self._get_error_url())

    def _get_error_url(self):
        return self.error_url

ここでは例外をキャッチしていますが、デバッグ用にパラメータをダンプする等の用途にも使用できます。

django.views.generic.base.RedirectView

別のURLにリダイレクトさせるViewです。
派生元のViewクラスのすべてのメソッドを持っています。
ソースコードはこちら
コールグラフは、dispatchより後ろ側について起こします。

RedirectViewのコールグラフ

関数名 処理内容
get GETメソッドでアクセスされた時に呼ばれます。
クラス変数 permanent に応じて、URLディスパッチャに HttpResponseRedirect(302) または HttpResponsePermanentRedirect(301) を返します。
図には載せていませんが、その他のHTTPメソッドでアクセスされた際もこのメソッドを呼び出すようになっており、すべて同様の動作をします。
get_redirect_url リダイレクト先のURLを返すメソッドです。
クラス変数 url またはクラス変数 pattern_name のうち、定義されている方を返します(urlが優先)
クラス変数 query_string が True の場合、リダイレクト先にクエリパラメータを引き継ぎます

表中にも記載しましたが、get以外のメソッドはすべてgetメソッドを呼び出すようになっており、すべてのHTTPメソッドで同一の処理を行うようになっています。

django.views.generic.base.TemplateView

テンプレートをレンダリングして表示するViewです。
派生元のViewクラスのすべてのメソッドを持っています。
ソースコードはこちら
コールグラフは、dispatchより後ろ側について起こします。

TemplateViewのコールグラフ

関数名 処理内容
ContextMixin.get_context_data テンプレートエンジンに渡すための context を生成します。
context には、ContextMixin.get_context_data 内部で 'view' というキー名でViewクラスのインスタンスが設定されます
TemplateResponseMixin.get_template_names レンダリングするテンプレート名をリストで返します。
クラス変数 template_name の内容を返します。
TemplateResponseMixin.render_to_response テンプレートをレンダリングしてレスポンスを返します。

get以外のメソッドは定義されていません。
その他のHTTPメソッドをサポートしたい場合は、post等のメソッドを自力で実装する必要があります。(他のクラスを使用する方が良いと思いますが・・・)

オーバーライドの例

状況によってテンプレートを変更する場合は、get_template_names メソッドをオーバーライドします。

from django.views.generic.base import TemplateView

class MyTemplateView(TemplateView):
    anonymous_template = '非ログイン時のテンプレート'
    authenticated_template = 'ログイン時のテンプレート'

    def get_template_names(self):
        if self.request.user.is_authenticated:
            return [self.authenticated_template]
        else:
            return [self.anonymous_template]

django.views.generic.detal.DetailView

あるひとつのデータモデルを表示するためのViewです。
ソースコードはこちら
コールグラフは、dispatchより後ろ側について起こします。

DetailViewのコールグラフ

関数名 処理内容
SingleObjectMixin.get_object 画面に表示するモデルオブジェクトを取得します。
内部で、モデルオブジェクトを取得するのに使用する queryset を取得するために、 get_querysetメソッドを呼び出します。
取得したモデルオブジェクトは self.object に設定されます。
また、get_context_data の中で 'object' というキー名で context にも設定されます。
SingleObjectMixin.get_queryset モデルオブジェクトを取得するための queryset を返します。
SingleObjectMixin.get_context_data テンプレートエンジンに渡すための context を生成します。
context には、'object' というキー名でモデルオブジェクトが設定されます
ContextMixin.get_context_data django.views.generic.base.TemplateViewに記述した内容です。
SingleObjectTemplateResponseMixin.get_template_names クラス変数 template_name が設定されていない場合、モデル名からテンプレート名を生成します。
TemplateResponseMixin.get_template_names django.views.generic.base.TemplateViewに記述した内容です。
TemplateResponseMixin.render_to_response django.views.generic.base.TemplateViewに記述した内容です。

get以外のメソッドは定義されていません。
その他のHTTPメソッドをサポートしたい場合は、post等のメソッドを自力で実装する必要があります。

django.views.generic.detal.ListView

一種類のモデルの複数のオブジェクトを表示するためのViewです。
ソースコードはこちら
コールグラフは、dispatchより後ろ側について起こします。

ListViewのコールグラフ

関数名 処理内容
BaseListView.get get_queryset を呼び出し、 self.object_list に格納します。
MultipleObjectMixin.get_queryset 画面に表示するモデルオブジェクトの一覧を、queryset の形で返します。
MultipleObjectMixin.get_ordering get_queryset で取得した queryset の表示順を返します。
クラス変数 ordering を返します。
MultipleObjectMixin.get_context_data get_pagenate_by を呼び出し、ページネーションをするかどうかを判断します。
context に、下記のキー名でデータを設定します。
'paginator' ... ページネーションする場合、pagenator オブジェクト
'page_obj' ... ページネーションする場合、表示対象のページオブジェクト
'is_paginated' ... ページネーションするかどうか
'object_list' ... 表示対象のqueryset
MultipleObjectMixin.get_paginate_by クラス変数 paginate_by を返します。
MultipleObjectMixin.paginate_queryset pagenatorオブジェクト や ページオブジェクトを生成します。
ページネーションする場合のみ呼び出されます。
ContextMixin.get_context_data django.views.generic.base.TemplateViewに記述した内容です。
MultipleObjectTemplateResponseMixin.get_template_names クラス変数 template_name が設定されていない場合、モデル名からテンプレート名を生成します。
TemplateResponseMixin.get_template_names django.views.generic.base.TemplateViewに記述した内容です。
TemplateResponseMixin.render_to_response django.views.generic.base.TemplateViewに記述した内容です。

get以外のメソッドは定義されていません。
その他のHTTPメソッドをサポートしたい場合は、post等のメソッドを自力で実装する必要があります。

参考URL