以前Qiitaに投稿したのですが、Djangoにはクラスベース汎用ビュー(CBV)というのがあります。
いつか書き直したいなぁと思っていたのですが、新しいサイトを立ち上げたのを機にもう一度書いてみます。
Djangoのバージョンも上がりましたしね。
Djangoでいうところのビューとは
ビューというのはMVCで言うところのコントローラーであり、MTVで言うところのビューです。
Djangoを使った人がある人もない人も、何かの開発に関わればMVCという言葉は聞いたことがあると思います。
MVCはざっくり言うと機能や責任の分離であり、その名称を表しています。
モデル・ビュー・コントローラーですね。
ご存知の方も多いと思います。
DjangoではそのことをMTVと呼んでいます。
モデル・テンプレート・ビューです。
DjangoではMVCでいうビューはテンプレートのことですし、MVCでいうコントローラーはビューと言います。
この三つは実際にDjangoの中核を成しています。
私の講座でもモデルを作り、ビューを作り、そしてテンプレートを作ります。
Djangoにおいてビューの作り方は関数ベースのものとクラスベースのものがあります。
関数のビュー
私はクラスベース汎用ビューを推していますが、機能として単純な方がいい場合は関数ベースの方が良いときもあります。
Djangoのビューはリクエストを受け取ってレスポンスを返せばよいだけなので、書き方として関数とクラスのどちらが良いかはありません。
def index(request):
return HttpResponse('Hello, World!)
このような一般的なPythonの関数でもビューを作ることができます。
request
という引数はリクエストオブジェクトで、アクセスされたページの情報などが入っています。
そして、HttpResponseはレスポンスオブジェクトを返す関数です。
Djangoではこの二つ、引数としてリクエストオブジェクトを受け取る(一般的にはrequest
とする)ことと、レスポンスオブジェクトを返すことだけがビューとして動作する条件となっています。
汎用ビューとはなにか
ビューとはコントローラーの事です。
汎用ビューとはその名の通り、一般的なコントローラーのことです。
例えば、普通にウェブサイトを作成すると
- データを取り出すモデルが違うので指定する必要がある
- データを供給するテンプレートのデザインが違うのでテンプレートを指定する必要がある
という処理が必要なだけで、内部的なロジックは変わらないことが多いです。
例えば、
- ブログのトップページは記事の一覧を表示する
- ページネーションしているので、一度につき10件
- ブログのトップページ用のデザインがあるので、テンプレートの指定は必要
と
- 商品のトップページは商品の一覧を表示する
- ページネーションしているので、一度につき10件
- 商品のトップページ用のデザインがあるので、テンプレートの指定は必要
などです。大体同じようなことをしています。
同じようなことをとは
- 一覧ページはモデルからデータを取得する
- ページ分割に応じた件数だけ取得する
- テンプレートを指定する必要がある
といったことです。
これらの処理をはじめから作っておいて、適切なデフォルト値を設定しておこう、
そして、カスタマイズが必要であればデフォルト値を上書きしてもらおう、
というのが汎用ビューの考え方です。
クラスベースビューではデフォルト値が用意されていて、必要であればデフォルト値を変更する
クラスベース汎用ビューとは
上記の汎用ビューの実装がクラスベースになったものです。
要はMixinとして実装している汎用ビューのことを言います。
もちろん、関数ベースでの汎用ビューもあります(ありました)が、いま使っている人は少ないのではないかと思います。
のっけから長かったですが、これが汎用ビューの存在意義です。
クラスベース汎用ビューの使い方
クラスベース汎用ビューは、先ほど書いてあるとおりMixinです。
従って、自分で作成したクラスで継承して組み込みます。
では、サンプルを書いてみたいと思います。
下準備
ここでは、
django-admin startproject foo
のようにして、プロジェクトはすでに作ってあるものとします。
では、アプリケーションを作っていきましょう。
試しに、ブログ記事のようなものを作ってみたいと思います。
django-admin startapp article
これで、article
アプリケーションが作成されました。
つぎの、モデルを作成します。
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
簡単にするため、フィールドの数は2つにしました。
マイグレーションなどは割愛することにします。
汎用ビューの使い方
続いてviews.py
にクラスベース汎用ビューを使って一覧ページを作ってみたいと思います。
from django.views.generic import ListView
from .models import Article
class ArticleListView(ListView):
model = Article
基本的にはこれだけでテンプレートにデータが供給されます。
上から説明すると
from django.views.generic import ListView
クラスベース汎用ビューは django.views.generic
に入っていることが多いです。
別のモジュールからインポートする必要があるものもありますが、8割ここに入っています。
次に、
from .models import Article
は自分で作ったモデルですね。
どのモデルからデータを取得するのか指定する必要があるのでここでインポートしています。
次の2行がクラスベース汎用ビューの使い方です。
class ArticleListView(ListView):
でクラスを定義しています。
もちろん、クラスベース汎用ビューである ListView
を継承しています。
ビューの名前はモデル+汎用ビューの名前であることが多いです。
次に、 model = Article
となっています。
ここに指定されたモデルからデータを読み込みます。
デフォルトでは全件読み出してくるので注意が必要です。
テンプレートの指定
さて、ここにテンプレートの指定がありません。
クラスベース汎用ビューではテンプレート名がモデルと汎用ビューの種類で決まります。
今は、
モデル → Article、汎用ビュー → ListView
ですので
article_list.html
という名前のテンプレートが指定されています。通常(モデル名)_(汎用ビューの種類).html
という名称が自動で選ばれます。
これはデフォルト値ですので変更することも可能です。
テンプレートのパスですが、実は settings.py
の設定に依存しています。
settings.py
に自動生成されている TEMPLATE
というリストがあります。
要素は辞書になっていて、テンプレートの設定が記述されています。
そこに APP_DIR
というキーがあるのですが、この値が True
であれば、各アプリケーションの内部の templates
というフォルダを探索します。
今回のケースで言えば foo/article/templates
フォルダの中身を探索する、という事です。
もしtemplate
フォルダが無くても探索しないだけでエラーなどにはなりません。
templates
フォルダを作成しましょう。
Pythonモジュールなどではありませんので、ただのフォルダでOKです。__init__.py
は必要ありません(あっても問題ありません)。
次が重要です。
クラスベース汎用ビューでは、templates
フォルダの中のモデル名のフォルダを探索します。
つまり、今回の場合、templates
フォルダの中の
article/article_list.html
を探します。
ですから、まずはfoo/artifle/templates/article
フォルダを作成して、その中にテンプレートファイルであるarticle_list.html
を作成してください。
urls.pyの設定
urls.py
は実はとても重要です。
DjangoはRailsのようにURLを自動で生成しません。
自分でURLを設定する必要があります。
そして、そのURLとビューを紐つけるのがこのurls.py
です。
アクセスできるURLを指定し、そこにアクセスされたらどのビューが反応するかをここで指定します。
クラスベースビューであれば、ここに、as_view()
というメソッドを指定します。
そうすれば、あとはクラスベースビューがよしなに処理してくれます。
ListView
from django.views.generic import ListView
from .models import Article
class ArticleListView(LisView):
model = Article
のように、としてインポートして使います。model = Article
は、リストとして取得したいモデルを指定しておけば、自動で取得してくれます。
しかし、ここでは全件取得してしまいます。ページネーションなどで分割したい場合は、パラメーターを足します。
class ArticleListView(ListView):
model = Article
paginated_by = 10
paginated_by
は取得するデータを文字通り分割して取得します。
今回は10件にしてみました。ListView
は分割取得もはじめから組み込まれています。
Viewはページネーションに対応していますが、テンプレートにはページネーションのコードを書かなければなりません。
ページネーションついては別の記事で解説しようと思います。
DetailView
リスト表示があるのであれば個別表示もありますよね。
それがDetailView
です。
from django.views.generic import DetailView
class ArticleDetailView(DetailView):
model = Article
相変わらずモデルは指定が必要です。
get_context_dataメソッド
クラスベースビューを使っていて、クラスベースビューで管理されている以外のデータを挿入したいというニーズはよくあります。
そんなときは、フックに使える(自分の処理を差し込める)メソッドがあるのでそこにデータを挿入します。
クラスベースビューに定義されているメソッドをオーバーライドすることで、そこに処理を差し込む、という処理をします。
オーバーライドするメソッドですが、大体のクラスベース汎用ビューには get_context_dataメソッドが定義されています。
このメソッドは、テンプレートに渡すデータを取得するメソッドです。
ですので、このメソッドをオーバーライドして、自身の処理を挿入しましょう。
from django.views.generic import ListView
from .models import Article
class ArticleListView(ListView):
model = Article
def get_context_data(self, **args):
context = super().get_context_data(**args)
context['name'] = 'Dai Takahashi'
return context
のようにします。
super
関数で親クラスの同メソッドを呼び出すと、ListView
がテンプレートに渡す前のデータを返します。
これは辞書ですので、キーを指定して、値を入れておけばテンプレートで呼び出すことができるようになります。
今回の場合、context
という変数で辞書を受け取りました。
続いて、name
というキーで、Dai Takahashi
という値を入れています。
最後にreturn context
で変数を返せば良いです。
こうすることにより、テンプレートでは {{ name }}
という形で使用できます。
もちろん、他のモデルのデータを呼び出したりすることもできるので、上手に使ってください。
まとめ
こういった表示系の汎用ビューはあまりカスタマイズすることがありません。
しかし、同じページに他のデータを挿入しておきたいケースはよくあると思います。
get_context_data
でほとんど対応できると思います。
全てのページに同じ変数をセットしておきたい場合は、コンテキストプロセッサーというのがあるので、そちらを使うと良いと思います。