この記事は Django Advent Calendar 2023 4日目です。
1日目〜3日目など空きがありますので、Djangoに関係するアウトプットネタがある方はぜひどうぞ〜
はじめに
今できないことがあっても大丈夫。nikkieです。
変更しやすいコードに関心があり、ちょうぜつ本を読んでいます。
デザインパターンの章でテンプレートメソッドパターンを学びました。
テンプレートメソッドパターンの例として浮かんだのが、Djangoの汎用クラスベースビューでした。
テンプレートメソッドパターンの実装例として、Djangoの汎用クラスベースビューを見ていきます。
目次
Djangoのビュー
ビュー はアプリのロジックを書いていくところです。
もう少し詳細化すると、ビューはリクエストオブジェクトを受け取り、ロジックで処理して、レスポンスオブジェクトを返します。
ビューの実装方法は関数による方法とクラスによる方法の2通りがあります。
関数によるビュー
関数による方法はリクエストオブジェクトを受け取り、レスポンスオブジェクトを返す関数を定義します。
Django Girls Tutorialなどのチュートリアルでよく見かける印象です1。
def post_new(request): if request.method == "POST": # POSTリクエストの場合の処理 else: # GETリクエストを想定した処理 # レスポンスオブジェクトを返すショートカット return render(request, 'blog/post_edit.html', {'form': form})
クラスによるビュー
クラスによる方法では、HTTPリクエストメソッドに応じたメソッドを実装します。
from django.views import View class PostNewView(View): def get(self, request): # GETリクエストの処理 # レスポンスオブジェクトを返す def post(self, request): # POSTリクエストの処理 # レスポンスオブジェクトを返す
クラスベースビュー入門 | Django ドキュメント | Django より
特定の HTTP メソッド (GET 、 POST など) に関連するコードの集まりを、条件分岐を使ってかき分けるのではなく、それぞれに独立したメソッドを割り当てることができる。
例えば『実践 Django』や『現場で使える Django の教科書《基礎編》』で紹介されています。
汎用クラスベースビュー
クラスによるビューの実装では継承が使えます。
Djangoは定型的な処理を実装したビューのクラスを用意しており、それを継承することでわずかなコード量で定型処理を実装できます。
ドキュメントにあるListView(一覧処理のビュー)を使った例です:
https://docs.djangoproject.com/ja/4.2/topics/class-based-views/generic-display/#generic-views-of-objects
from django.views.generic import ListView from books.models import Publisher class PublisherListView(ListView): model = Publisher
たったこれだけ!
PublisherListViewはmodel属性しか指定していません。
これでPublisherを一覧するビューは機能します!
上の設定例では
- テンプレートは books/publisher_list.html
- テンプレート中でPublisherを指す変数は
object_list
と設定したことになります。
ListViewを継承したクラスでそれぞれtemplate_name
・context_object_name
を指定してカスタマイズできます。
ListView
ドキュメントはこちら:
https://docs.djangoproject.com/ja/4.2/ref/class-based-views/generic-display/#listview
継承元やメソッドのフローチャートが掲載されています。
公式ドキュメントとは別に、Classy Class-Based Viewsというサイトもあります。
継承関係の図が見えるなど、公式ドキュメント+αが嬉しいですね
getメソッド
一覧するGETリクエストを受けたときのビューの処理を覗いてみましょう。
Classy Class-Based Viewsではgetメソッドの実装も確認できます
呼ばれているメソッドをざっくり挙げると
- get_queryset
- get_allow_empty
- get_context_data
- render_to_response
といった感じですね(条件分岐のif文は省略しました)
get_queryset
get_queryset
ではmodel属性を見て、全レコード取得する実装があります。
# https://github.com/django/django/blob/4.2.8/django/views/generic/list.py#L33-L34 elif self.model is not None: queryset = self.model._default_manager.all()
先のPublisherListViewが動いたのはこれによるのですね。
クエリセットを返しています。
get_allow_empty
get_allow_empty
は、一覧が空のときに404(Not Found)を返すかを設定するメソッドのようです。
https://github.com/django/django/blob/4.2.8/django/views/generic/list.py#L106-L111
get_context_data
get_context_data
はテンプレート用のコンテキスト(辞書)を作って返しています。
https://github.com/django/django/blob/4.2.8/django/views/generic/list.py#L122
キーは
- paginator
- page_obj
- is_paginated
- object_list
context_object_name
属性を見て2、コンテキストに追加していますね。
# https://github.com/django/django/blob/4.2.8/django/views/generic/list.py#L144-L145 if context_object_name is not None: context[context_object_name] = queryset
コンテキストにはobject_list
というキーの他にcontext_object_name
というキーもできて、この2つは同じオブジェクト(クエリセット)を指します。
テンプレートではどちらの変数を使っても同じになるわけですね
render_to_response
render_to_response
ではレスポンスオブジェクトを返しています。
https://github.com/django/django/blob/4.2.8/django/views/generic/base.py#L190
ここでget_template_names
メソッドを呼び出しています。
return self.response_class( # https://github.com/django/django/blob/4.2.8/django/views/generic/base.py#L200 template=self.get_template_names(), )
この呼び出しの中でtemplate_name
属性が参照されます。
テンプレートメソッドパターンの例として見たときの所感
テンプレートメソッドとしてうまいな〜と思います。
- getメソッドを見ましたが、穴埋め問題になっていますよね(=差分プログラミング!)
- 属性を穴埋め
- getメソッドで呼ぶ各メソッドを穴埋め
- 穴埋めしてビューの動きをカスタマイズできる!
- ビューはリクエストオブジェクトを受け取り、レスポンスオブジェクトを返すと決まっている
- 思うにこれはインターフェースのようなもの
- ビューという抽象の中の定型処理だけを汎用クラスベースビューに切り出したのでは
- つまり、テンプレートメソッドで全てのビューに対処しようとしていない!
- テンプレートメソッド(汎用クラスベースビューを継承)にしないで済む方法が常に提供されている
- Viewクラスを継承して自分で書いていく
- 関数による方法
Djangoは時の試練に耐えており、多くの人の目が入ったことでこんなふうに洗練しているのかなと思いました。
終わりに
GoFのデザインパターン「テンプレートメソッド」の例として浮かんだDjangoの汎用クラスベースビューを見てきました。
- 一覧処理を提供する
ListView
、getメソッドはテンプレートメソッド - 属性の指定やメソッドのオーバーライドでカスタマイズできる
- Classy Class-Based Viewsで実装を見るのが捗る!
課題全体をテンプレートメソッドパターンで解こうとしたというよりは、Webアプリの定型処理だけにテンプレートメソッドパターンを提供したということではと気付きがありました。
課題全体をテンプレートメソッドパターンで解こうとして手痛い失敗をした身としては非常に納得です。
参考文献
DjangoCongress 2023より
リファレンスへのリンクが大変充実しており
- ドキュメントのクラスベースビュー入門
- Classy Class-Based Views
を知りました(多謝)
- Djangoドキュメントのpollアプリでも。はじめての Django アプリ作成、その 3 | Django ドキュメント | Django (クラスによるビューも紹介されます)↩
-
get_context_object_name
メソッドでcontext_object_name
属性を見ます↩