このページでは、Django フレームワークで定義される ListView の使い方について説明していきます。
この ListView は View というクラスのサブクラスであり、ビューをクラスベースで作成する際に利用するクラスとなります。この View のサブクラス を継承し、さらにクラス変数を定義したりメソッドをオーバーライドすることで、あなたが開発したいアプリに応じたビューを作成することが可能となります。
この辺りのクラスベースビューやクラスベースビューの作り方については下記ページで解説していますので、詳しくはこちらをご参照ください。
上記ページでも解説していますが、クラスベースビューを作成していくにあたって重要になるのが、あなたが実現したいビューに合わせて継承する View のサブクラス を適切に選択し、さらにクラス変数の定義やメソッドのオーバーライドを行なってクラスをカスタマイズしていくことになります。
そして、これらを行うためには、各 View のサブクラス の特徴を理解して継承するクラスとして選択することと、その View のサブクラス で定義されているクラス変数やメソッドを理解した上でカスタマイズを行なっていくことが重要となります。
このページでは、この View のサブクラス の中から ListView に焦点を当て、このクラスの特徴や定義されているクラス変数・メソッドの紹介を行なっていきたいと思います!
Contents
ListView
ListView は特定のモデルクラスのインスタンスの一覧リストを表示するページを実現する View のサブクラス となります。
ListView は django.views.generic から import して利用します。
from django.views.generic import ListView
皆さんも、ウェブアプリで何かしらの一覧を表示するページを作成した経験があるのではないでしょうか?
例えば掲示板アプリでの投稿コメント一覧、SNS アプリでのユーザー一覧、ゲームアプリでのランキング表などといった、特定のモデルクラスのインスタンスの一覧リストを表示するページは ListView を継承することで簡単に作成することが可能です。

ListView は前述の通り、特定のモデルクラスのインスタンスの一覧リストを表示するページを実現するクラスとなります。そして、この表示するインスタンスはデータベースの特定のテーブルから取得してから表示されることになりますので、表示対象となるインスタンスの取得先のテーブル(モデルクラス)を指定する必要があります。もしくは、モデルクラスそのものを指定するのではなく、表示対象となるインスタンスを取得するためのクエリの指定を行うことも可能です。
いずれにせよ、こういった表示対象となるインスタンスを特定するための設定さえ行なってやれば、後は ListView が自動的にインスタンスをデータベースから取得し、それをコンテキストにセットしてテンプレートに渡すようになっています。
また、ListView は複数のインスタンスの一覧リストを表示を実現する View のサブクラス となりますが、逆に1つのインスタンスの詳細を表示する際には下記ページで紹介している DetailView を継承することになります。
これらの ListView や DetailView は一緒に覚えておくと良いと思います。クラスベースでビューを作成する場合、これらは必ずと言っていいほど利用する機会の多い View のサブクラス になると思います。
ListView でのクラスベースビューの作り方
次は ListView でクラスベースビューを作成する手順を説明します。
基本的に、クラスベースビューは views.py に View のサブクラス を継承するクラスを定義することで作成していくことになります。そして、views.py に定義したクラスにクラス変数やメソッドを定義することで、継承した View のサブクラス の特徴を活かしながら自身のウェブアプリに応じたビューにカスタマイズしていくことになります。

もう少し具体的に言えば、ListView ではメソッドが GET のリクエストを受けとった際に get メソッドが実行されるようになっており、ListView のクラス変数や他のメソッドは、この get メソッドから利用されるようになっています。

したがって、ListlView を継承するクラス側でクラス変数やメソッドを上書き・オーバーライドしてやれば、この get メソッドの動作を変化させることができます。
この辺りの、クラス変数やメソッドの定義によりカスタマイズが可能である理由については下記ページで解説していますので、詳しくは下記ページを参照してください。
スポンサーリンク
クラス変数 model or クラス変数 queryset を定義する
また、前述の通り、ListView はインスタンスの一覧を表示するページを作成する際に継承するクラスです。この ListView を継承する場合、表示するインスタンスを特定するための情報として、最低限クラス変数として model or queryset を定義する必要があります(get_queryset をオーバーライドする場合は、get_queryset の処理内容によってはこれらの定義も不要となる場合があります)。
従って、ListView を継承してクラスベースビューを作成する場合、最低限下記のようにクラスを定義してやれば良いことになります。この場合、model に指定したモデルクラスの全インスタンスが取得されて表示されることになります。
from django.views.generic import ListView
from .models import モデルクラス
class クラス名(ListView):
model = モデルクラス
もしくは、下記のように queryset の定義を行うのでも問題ないです。下記では all を実行しているので先ほど同様に モデルクラス の全インスタンスが取得されて表示されることになりますが、all を filter に置き換えれば、引数に指定した条件を満たすインスタンスのみが取得されて表示されるようになります。
from django.views.generic import ListView
from .models import モデルクラス
class クラス名(ListView):
queryset = モデルクラス.objects.all()
つまり、特定のモデルクラスの全インスタンスを取得して表示したい場合は model のみ定義してやれば良いのですが、条件等を指定してインスタンスを取得して表示したい場合は queryset を定義する必要があります。両方指定した場合は queryset 側が優先されますので、両方を指定しても問題ありません。
必要に応じて他のクラス変数を定義する
ということで、ListView を継承するクラス(ListView のサブクラス)を定義し、さらに model or queryset のクラス変数を定義すれば、とりあえず動作可能なクラスベースビューが出来上がることになります。
ただし、ListView のサブクラスでは、model や queryset 以外のクラス変数の定義も可能で、それらのクラス変数の定義によって ListView のサブクラスの動作の詳細な設定を行うことができます。そのため、実現したいビューに合わせて、適切に他のクラス変数を定義することが重要となります。
クラス変数 template_name の例
例えば、ListView のサブクラスに定義可能なクラス変数の1つに template_name が存在し、このクラス変数の定義によって、このクラスが HTML を生成するときに使用するテンプレートファイルのパスを設定することができます。逆に、この template_name を定義しなかった場合、このクラスが HTML を生成するときに使用するテンプレートファイルのパスは、ListView によって定められたデフォルト値となります。このデフォルト値は、具体的には 'アプリ名/モデルクラス名_list.html' となります。
モデルクラス名 は、モデルクラスのクラス名を全て小文字に変換したものになります
したがって、テンプレートファイルのパスをデフォルト値から変更したい場合は、クラス変数 template_name を ListView のサブクラスで定義する必要があります。
クラス変数 context_object_name の例
また、関数ベースのビューと同様に、ListView のサブクラスにおいても HTML を生成する際にはコンテキストが必要となります。このコンテキストは ListView のサブクラスによって自動的に生成されることになります。ListView はモデルクラスのインスタンスの一覧を表示することを目的とする View のサブクラス であるため、そのコンテキストには、”クラス変数 model に指定したモデルクラスの全インスタンスの集合” or “クラス変数 queryset に指定したクエリーの発行によって取得されたインスタンスの集合” がセットされることになります。そして、これらの集合は、コンテキストの モデルクラス名_list と object_list というキーの値としてセットされることになります(それぞれのキーに同じインスタンスの集合がセットされる)。
この モデルクラス名 も、モデルクラスのクラス名を全て小文字に変換したものになります
そのため、ListView のサブクラスが HTML を生成するときに使用するテンプレートファイルでは、モデルクラス名_list or object_list という変数を参照し、インスタンスの集合に含まれるインスタンスの集合を出力することができることになります。
たとえば、下記のようなテンプレートファイルを用意しておけば、ListView のサブクラスが用意したコンテキストから モデルクラス名_list キーの値を参照し、その集合に含まれる全インスタンスの データ属性 が1つ1つ出力されることになります。
<table>
<thead>
<th>ID</th><th>本文</th>
</thead>
<tbody>
{% for obj in モデルクラス名_list %}
<tr>
<td>{{ obj.データ属性 }}</td>
</tr>
{% endfor %}
</tbody>
</table>
で、この ListView のサブクラスによって生成されるコンテキストには、インスタンスの集合が モデルクラス名_list と object_list というキーの値としてセットされると説明しましたが、モデルクラス名_list というキー名に関してはクラス変数の定義によって変更可能です(object_list のキー名は変更不可)。
具体的には、ListView のサブクラスにクラス変数 context_object_name を定義することで、インスタンスの集合がセットされるキーの名称を モデルクラス名_list から別のものに変更することができます。例えば context_object_name = 'comments' を定義しておけば、インスタンスの集合がコンテキストの comments と object_list にセットされるようになります。そして、テンプレートファイルでは、comments or object_list を参照してインスタンスの集合を取得し、集合に含まれたインスタンスの情報を出力することができるようになります。
各種クラス変数・メソッドの定義によってクラスをカスタマイズ
ここまでの説明のように、ListView のサブクラスの場合、最低限定義が必要なクラス変数は model or queryset のみで、それ以外のクラス変数を定義しなかったとしても、ListView のサブクラスは、それぞれのクラス変数のデフォルト値に従って動作してくれます。ですが、クラス変数を定義することで、そのクラス変数をデフォルト値から上書きし、それによって ListView のサブクラスの動作を変化させることができます。なので、ListView のサブクラスの動作が気に入らないような場合は、適宜適切なクラス変数を定義して、ListView のサブクラスの動作の設定を変更する必要があります。
例えば、ListView のサブクラスにおいても、下記ページで紹介しているページネーションによるページ分割を行うことは可能です。ですが、デフォルトの設定では、ListView のサブクラスではページ分割が行われないようになっているため、ページ分割を行いたい場合は ListView のサブクラスに、ページ分割を行うためのクラス変数の定義が必要となります(この場合はクラス変数 paginate_by を定義してやれば良いです)。
同様に、ListView のサブクラスで、ListView のメソッドをオーバーライドすることによって ListView のサブクラスの動作を変化させるようなことも可能です。こういった、クラス変数の定義やメソッドのオーバーライドによって、ListView などのような View のサブクラス のサブクラスの動作を変化させることを、このサイトではカスタマイズと呼んでいます。
このカスタマイズを行う上で重要なのは、”View のサブクラス で定義されているクラス変数やメソッド” を定義する必要があるという点になります。これらを定義する目的は、View のサブクラス で定義されているクラス変数やメソッドを、そのサブクラス側で上書きして View のサブクラス とは異なる動作を実現させることにあります。従って、View のサブクラス で定義されていないクラス変数やメソッドを定義してもあまり意味がありません。前述で示した template_name や context_object_name に関しても ListView で定義されているクラス変数であるため、これらをサブクラス側で定義することで、使用するテンプレートファイルのパスやコンテキストのキー名が変化することになります。
なので、このカスタマイズを行うためには、View のサブクラス で定義されているクラス変数やメソッドについて理解しておく必要があります。では、このページで対象にしている ListView では、どんなクラス変数やメソッドが定義されているのでしょうか?そして、それらはそれぞれどんな役割のクラス変数やメソッドなのでしょうか?
このような疑問を解消するため、ここからは ListView で定義されているクラス変数やメソッドを紹介していきたいと思います。
ListView のクラス変数
では、まずは ListView で定義されているクラス変数の紹介を行なっていきます。これらのクラス変数を ListView を継承するクラスで定義し直して上書きすることで、そのクラスの動作のカスタマイズを行うことが可能となります。
ListView の場合は、GET メソッドのリクエストを受けとった際には ListView の get メソッドが実行されることになるため、この get メソッドの動作を変化させることを目的にクラス変数の定義を行なっていくことになります。
スポンサーリンク
ListView のクラス変数の一覧
その ListView で定義されるクラス変数には下記のようなものが存在します。
- model
- queryset
- ordering
- context_object_name
- template_name
- template_name_suffix
- template_engine
- response_class
- content_type
- allow_empty
- paginate_by
- paginate_orphans
- paginator_class
- page_kwarg
これらが ListView で定義されているクラス変数の全てというわけではないので注意してください。カスタマイズに利用する機会の多そうなもののみを紹介しています。全てのクラス変数を知りたい場合は、実際に ListView の定義をソースコードで確認していただくのが一番早いと思います。
また、ここからは、アプリの models.py で下記のような Comment が定義されていることを前提に解説を行なっていきます。
from django.db import models
class Comment(models.Model):
text = models.CharField(max_length=256)
では、先ほど挙げたクラス変数の役割や、これらを定義することで実現できることについて、1つ1つ説明していきます。
model
前述の通り ListView は特定のモデルクラスのインスタンスを取得して一覧リストとして表示するクラスになります。このインスタンスの取得先のモデルクラスを指定するクラス変数が model となります。
下記ページで説明しているとおり、Django においてはモデルクラスがデータベースのテーブルを表し、インスタンスが、そのテーブルのレコードを表しています。ですので、要は model にモデルクラスを指定することで、インスタンスの取得先のテーブルを指定することができることになります。
例えば、Comment のインスタンスを一覧を表示したいのであれば、ListView を継承したクラスに対して下記のように model を定義してやれば良いです。
from django.views.generic import ListView
from .models import Comment
class CommentList(ListView):
model = Comment
queryset
単に model を指定した場合は、model に指定したテーブル(モデルクラス)の全レコード(インスタンス)が取得されて表示されることになります(後述で説明するページネーションを利用して複数のページにレコードを割り付けて表示することも可能です)。
ただ、全レコードではなく、特定の条件を満たすレコードのみを取得して表示したいような場合も多いです。そんな時にはクラス変数 queryset を定義してやれば良いです。
queryset はデータベースに発行するクエリを指定するクラス変数であり、これを定義することでデータベースに発行するクエリを変更することが可能です。そして、データベースから取得されるレコードはクエリによって決まるため、クエリの変更によって全てのレコードではなく特定のレコードのみを取得するようなことが可能となります。そして、その取得されたレコードのみがページに表示されるようになります。
さらに、このクエリは、各モデルクラスの持つ objects にメソッドを実行させることで生成することができますので、queryset には objects のメソッドの実行結果を指定することになります。
queryset = モデルクラス.objects.メソッド(引数)
メソッド として all を実行させた場合は全てのレコードが取得されることになり、filter を実行させた場合は引数で指定した条件に合致するレコードのみが取得されるようになります。
例えば、Comment のテーブルの中から、text フィールドに Hello が含まれるレコードのみを取得して表示したいような場合は、ListView を継承したクラスに対して下記のように queryset を定義してやれば良いです。
from django.views.generic import ListView
from .models import Comment
class CommentList(ListView):
queryset = Comment.objects.filter(text__contains='Hello')
スポンサーリンク
ordering
ordering は、取得したインスタンスのソート条件を指定するクラス変数となります。ordering には、model or queryset に指定したモデルクラスの持つフィールドのフィールド名を文字列で指定します。これにより、データベースから取得されたインスタンスが、そのフィールドに対して “昇順” にソートされることになります。”降順” にソートしたい場合は、フィールド名の前に - (ハイフン) を付加して指定を行います。
例えば、Comment の全インスタンスを text フィールドに対して降順に並べて表示したい場合は、ListView を継承したクラスに対して下記のように ordering を定義してやれば良いです。
from django.views.generic import ListView
from .models import Comment
class CommentList(ListView):
model = Comment
ordering = '-text'
また、queryset を定義する場合は、ordering を定義しなくても、queryset への指定値によってインスタンスのソートを実現することが可能です。例えば下記のように queryset を定義した場合も、先ほどと同様に Comment の全インスタンスを text フィールドに対して降順に並べて表示することが可能となります。
from django.views.generic import ListView
from .models import Comment
class CommentList(ListView):
queryset = Comment.objects.order_by('-text')
context_object_name
context_object_name は、取得したインスタンスの集合をセットするコンテキストの “キー” を指定するクラス変数となります。
下記ページでも解説しているとおり、render 関数にコンテキストという辞書データを渡すことで、そのコンテキストのキーを変数名としてテンプレートファイルから参照することができるようになります。
ListView を継承した場合、基本的にコンテキストは ListView が自動的に作成してくれることになります。参考に、ListView (より正確にいうと MultiObjectMixin) でコンテキストの生成を行なっている箇所のソースコードを引用して掲載しておきます。
context_object_name = self.get_context_object_name(queryset)
if page_size:
paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size)
context = {
'paginator': paginator,
'page_obj': page,
'is_paginated': is_paginated,
'object_list': queryset
}
else:
context = {
'paginator': None,
'page_obj': None,
'is_paginated': False,
'object_list': queryset
}
if context_object_name is not None:
context[context_object_name] = queryset
context.update(kwargs)
上記のソースコードを確認していただければ分かるように、queryset に基づいて取得されたインスタンスの集合は、コンテキストの 'object_list' キーと、context_object_name に指定したキーそれぞれの値としてセットされることになります。
queryset をクラス変数として定義していない場合は、model.objects.all() が queryset にセットされた状態で上記が実行されることになります
そのため、クラス変数 context_object_name を定義してやれば、インスタンスの集合がセットされるキーを変更することができます('object_list' 側は変更不可)。
また、context_object_name を定義しなかった場合は、デフォルト値の 'モデルクラス名_list' が context_object_name として扱われるようになっています。つまり、context_object_name を定義しなかった場合、'モデルクラス名_list' と 'object_list' というキーの値としてインスタンスの集合がセットされるようになっています。
もし、これらのキーではなく、他のキーにインスタンスの集合をセットしたい場合は、クラス変数 context_object_name を定義して他のキー名を指定してやる必要があります。
例えば、context_object_name を指定しなかった場合、Comment のインスタンスの集合を扱うテンプレートファイルでは、そのインスタンスの集合は comment_list もしくは object_list という変数名で参照する必要があります。
<table>
<thead>
<th>ID</th><th>本文</th>
</thead>
<tbody>
{% for comment in comment_list %}
<tr>
<td>{{ comment.id }}</td>
<td>{{ comment.text|truncatechars:20 }}</td>
</tr>
{% endfor %}
</tbody>
</table>
それに対し、下記のように context_object_name を定義した場合、
from django.views.generic import ListView
from .models import Comment
class CommentList(ListView):
model = Comment
context_object_name = 'comments'
Comment のインスタンスの集合を扱うテンプレートファイルでは、そのインスタンスの集合は comments という変数名で扱うことができるようになります。
<table>
<thead>
<th>ID</th><th>本文</th>
</thead>
<tbody>
{% for comment in comments %}
<tr>
<td>{{ comment.id }}</td>
<td>{{ comment.text|truncatechars:20 }}</td>
</tr>
{% endfor %}
</tbody>
</table>
このように、クラス変数 context_object_name の定義によって、テンプレートファイルから参照する変数名を変更することができ、好きな変数名でインスタンスの集合を参照することができるようになります。例えば事前にテンプレートファイルを作っていた場合、テンプレートが参照する変数に合わせてコンテキストのキーを指定するようなことも可能です。
重要なのは、”インスタンスの集合がセットされるコンテキストのキー” と “テンプレートファイルからインスタンスの集合を参照する変数名” が一致していることであり、これらが一致するように context_object_name を定義する or テンプレートファイルを変更する必要があります。
extra_context
extra_context はコンテキストに任意の要素を追加するためのクラス変数となります。
各種 View のサブクラス ではコンテキストが自動的に生成されるようになっています。その際にコンテキストにセットされるキーと値は View のサブクラス によって異なります。ListView で生成されるコンテキストについては、後述の ListView が生成するコンテキスト で説明します。
そういった各種 View のサブクラス で生成されるコンテキストへ独自に要素を追加したい場合に定義するのがクラス変数 extra_context であり、このクラス変数に辞書形式のデータを指定すれば、その辞書に含まれる要素(キーと値)が View のサブクラス で生成されるコンテキストに追加されるようになります。
例えば下記のように extra_context を定義すれば、コンテキストに 'title' キーと 'message' キーが追加されることになり、これらの値をテンプレートファイルから参照することができるようになります。
from django.views.generic import ListView
from .models import Comment
class CommentList(ListView):
model = Comment
extra_context = {
'title': 'CommentList',
'message': 'コメント一覧を表示しています'
}
このように、クラス変数 extra_context の定義によってコンテキストの要素を追加し、それによりテンプレートファイルから参照可能なデータを増やすことができます。ListView が生成するコンテキスト で紹介するコンテキストに足りない要素があるのであれば、クラス変数 extra_context を定義して要素を追加するようにしてください。
スポンサーリンク
template_name
template_name は、ページ表示のための HTML を生成する際に使用するテンプレートファイルの名前を指定するクラス変数です。テンプレートファイルの名前というよりも、アプリ/templates からのテンプレートファイルへの相対パスと考えた方が分かりやすいかと思います。
関数ベースのビューで、HTML を生成してレスポンスとして返却する際に render 関数を利用した経験がある方もおられると思いますが、要は render 関数の第2引数に指定していたものと同じものを template_name に指定すれば良いです。また、第3引数で指定する context に関しては、 context_object_name で説明したように ListView によって生成されます。
return render(request, 'forum/comments.html', context)
template_name を定義しなかった場合は、アプリ名/モデルクラス名_list.html のテンプレートファイルが HTML 生成時に利用されることになります。ですので、template_name を定義しなくても、アプリ名/モデルクラス名_list.html を用意しておけば HTML の生成とレスポンスとしての返却を行うことは可能です。
例えば下記のように template_name を指定すれば、HTML 生成時に forum/comments.html のパスに存在するテンプレートファイルが利用されることになります。さらに、context_object_name でも説明したように、そのテンプレートファイルでインスタンスの集合を参照し、各インスタンスの情報を表示するようにしてやれば、インスタンスの一覧リストを表示する HTML が生成されることになります。
from django.views.generic import ListView
from .models import Comment
class CommentList(ListView):
model = Comment
template_name = 'forum/comments.html'
ちなみに、この アプリ名/モデルクラス名_list.html の _list 部分は、次に説明する template_name_suffix の指定によって変更することが可能です。
template_name_suffix
ということで、次は template_name_suffix について説明していきます。先ほど、template_name を定義しなかった場合は アプリ名/モデルクラス名_list.html というテンプレートファイルが HTML 生成時に利用されると説明しました。template_name_suffix は、その アプリ名/モデルクラス名_list.html の _list 部分を指定するクラス変数となります。したがって、template_name を定義しない場合に利用されるテンプレートファイルのパスの _list 部分を他のものに変更したい場合は template_name_suffix の定義が必要となります。
例えば下記のように template_name_suffix を指定した場合、HTML 生成時には アプリ名/comments.html というテンプレートファイルが利用されるようになります。
from django.views.generic import ListView
from .models import Comment
class CommentList(ListView):
model = Comment
template_name_suffix = 's'
template_engine
template_engine をクラス変数として定義することで、使用するテンプレートエンジンを変更することができます。
テンプレートファイルを解釈し、HTML を生成するのが基本的なテンプレートエンジンの役割であり、デフォルトでは Django に用意されているテンプレートエンジンが利用されるようになっています。
そのテンプレートエンジンをクラス変数として template_engine を定義してやることで別のものに変更することができます。
ただ、私も他のテンプレートエンジンは利用したことがないため、これ以上の詳細な説明は行いませんが、クラス変数の定義で使用するテンプレートエンジンを変更可能であることをは覚えておくと良いと思います。
スポンサーリンク
response_class
response_class をクラス変数として定義することで、ビューから Django フレームワークに返却するレスポンスを変更することができます。このレスポンスとは、具体的には HttpResponse というクラスのサブクラスのインスタンスになります。例えば関数ベースのビューで return render(略) といった処理を実装したことがあると思いますが、この際も render 関数の中で HttpResponse のサブクラスのインスタンスが生成され、それがビューからの return によって Django フレームワークに返却されるようになっています。
この HttpResponse のサブクラスには様々な種類のものが存在しますが、ListView のクラス変数 response_class にはデフォルトで TemplateResponse が指定されるようになっており、これがビューから Django フレームワークに返却されるようになっています。基本はデフォルトのまま TemplateResponse が返却されるようにしておくので良いですが、TemplateResponse をカスタマイズしたいような場合は、TemplateResponse を継承するクラスを定義してカスタマイズし、そのクラスを response_class に指定してやれば良いです。
例えば下記は response_class に MyTemplateResponse を指定する例となります。このように response_class を定義することで、ビューから Django フレームワークに対して MyTemplateResponse のインスタンスが返却されることになります。
この MyTemplateResponse は TemplateResponse を継承するクラスであり、単に print で 'MyTemplateResponse' と出力した後に TemplateResponse の __init__ を実行するだけの意味のないクラスになっていますが、それでも、この例で response_class の役割については理解していただけるのではないかと思います。
from django.views.generic import ListView
from .models import Comment
from django.template.response import TemplateResponse
class MyTemplateResponse(TemplateResponse):
def __init__(self, request, template, context=None, content_type=None,
status=None, charset=None, using=None, headers=None):
print('MyTemplateResponse')
super().__init__(request, template, context, content_type, status, charset, using, headers=headers)
class CommentList(ListView):
model = Comment
response_class = MyTemplateResponse
content_type
content_type はレスポンスのヘッダーにセットされるコンテンツタイプを指定するためのクラス変数となります。content_type を定義しなかった場合は text/html がコンテンツタイプとしてヘッダーにセットされることになります。このコンテンツタイプは、クライアントに対してレスポンスのボディが HTML であることを示す情報となっています。
このレスポンスのヘッダーにセットされるコンテンツタイプを text/html から変更したい場合は content_type をクラス変数として定義しておく必要があります。ただ、基本的には ListView はある程度 HTML を返却することを前提とした作りになっているので、content_type の定義が不要なケースが多いと思います。
例えばですが、下記のように content_type を定義した場合、
from django.views.generic import ListView
from .models import Comment
class CommentList(ListView):
model = Comment
content_type = 'text/plain'
レスポンスのボディとしては同じデータがクライアントに返却されることになりますが、レスポンスのヘッダーにセットされるコンテンツタイプが text/plain となるため、クライアントでは受け取ったレスポンスのボディがプレーンテキストとして扱われる可能性があります。例えば Chrome でレスポンスを受け取った場合には、下図のように HTML の中身そのものが表示されたりします。

allow_empty
allow_empty は、データベースから取得されたインスタンスの数が 0 であることを許可するかどうかを指定するためのクラス変数になります。デフォルトでは allow_empty には True がセットされており、この場合はインスタンスの数が 0 でもページの表示が行われることになります(表示されるインスタンスの数が 0 個になるだけ)。
それに対し、allow_empty に False を指定した場合は、インスタンスの数が 0 個の場合に下図のような 404 エラーがレスポンスとして返却されるようになります。

なので、インスタンスの数が 0 個の場合にもページを表示するようにしたいか、それともエラーとして扱うようにしたいかによって allow_empty の定義の必要性が変わることになります。後者の場合はクラス変数として allow_empty = False を指定してやれば良いです。
例えば下記は、allow_empty を定義することで queryset で指定したクエリによって取得できるインスタンスの数が 0 個の場合に 404 エラーとして扱う例となります。
from django.views.generic import ListView
from .models import Comment
class CommentList(ListView):
queryset = Comment.objects.filter(text__contains='test')
allow_empty = False
スポンサーリンク
paginate_by
さて、ここから紹介するクラス変数は全て、前述でも紹介した下記ページで説明しているページネーションを実現するためのクラス変数となります。
ページネーションを実現するためには、ここから紹介するクラス変数を必要に応じて定義する必要があります。特に、この節で扱う paginate_by に関しては定義が必須となります。逆に、ページネーションが不要である場合、ここから説明するクラス変数に関しては定義が不要となります。
まず最初に紹介するのが paginate_by で、これは各ページに割り付けるインスタンスの個数を指定するためのクラス変数となります。paginate_by を指定しなかった場合、コンテキストにセットされてテンプレートファイルに渡されるインスタンスの集合には、取得されたインスタンスが全て含まれることになります。なので、基本的には取得されたインスタンスが1ページにまとめて表示されることになります。
それに対し、paginate_by に整数を指定すれば、取得されたインスタンスが paginate_by に指定された数ごとに分割されて各ページに割り付けられるようになります。下図は paginate_by = 4 を定義したときの例となります。

例えば、各ページにインスタンスを 10 個ずつ割り付けたいのであれば paginate_by = 10 を定義してやれば良いことになります。そして、これにより、コンテキストにセットされてテンプレートファイルに渡されるインスタンスの集合には、表示しようとしているページに割り付けられたインスタンスのみが含まれることになります。
また、paginate_by を定義した場合、特定のページに対応する Page クラスのインスタンスがコンテキストの 'page_obj' というキーにセットされることになります。そのため、テンプレートファイルからはページの情報やページに割り付けられたインスタンスの集合を page_obj を参照して扱うことが可能です(通常のインスタンスの集合同様に、page_obj に対して for ループを行えば各インスタンスを1つ1つ取得しながら情報の表示等を行うことが可能となります)。
paginate_by を定義しなかった場合同様、インスタンスの集合はコンテキストに context_object_name or object_list のキーにもセットされることになります
ただし、これらは page_obj と違って Page クラスのインスタンスではなく、ただのインスタンスの集合となります
したがって、ページの情報に関しては page_obj を利用して取得する必要があります
さらに、コンテキストには paginator キーに Paginator のインスタンスがセットされていますので、テンプレートファイルから paginator を利用してページの総数などを取得することも可能です。特に、ページネーションによるページの分割を行い、さらに分割後の最後のページに遷移するためのリンクを設置したいような場合、こういったリンクのリンク先をテンプレートファイルから指定するために paginator の利用が必要になります。
Paginator のインスタンスは page_obj のデータ属性からも利用可能です
少し説明が長くなりましたが、ページネーション自体やページネーション利用時のテンプレートファイルの作り方に関しては下記ページで説明していますので、詳しくは下記ページを参考にしていただければと思います。
例えば下記のように paginate_by を定義すれば、Comment のテーブルから全レコード(インスタンス)が取得され、取得されたインスタンスの数が 3 以上の場合、それらが 3 つずつ各ページに割り付けられることになります。
from django.views.generic import ListView
from .models import Comment
class CommentList(ListView):
model = Comment
paginate_by = 3
例えば、上記の CommentList によってインスタンスの一覧を表示するページが下記 URL に存在するとすると、
http://localhost:8000/forum/comments/
上記をそのままウェブブラウザで開いた場合は、1 ページ目が表示されることになります。が、下記のようにクエリパラメーターで ?page=2 を指定してやれば 2 ページ目が表示されることになります。
http://localhost:8000/forum/comments/?page=2
同様に、?page=n を指定すれば(n は整数)、n ページ目が表示されることになります。もし n ページ目が存在しないのであれば、下の図のように 404 エラーがレスポンスされることになります。

ここで重要なのは、クエリパラメーターでページ番号を指定することによって、ページネーションによって分割されたページの間を遷移することができるという点になります。
page_kwarg
で、先ほどページ番号をクエリパラメーターの page によって指定可能であると説明しました。ページ番号が page によって指定可能になっているのは、ここで紹介する page_kwarg のデフォルトが 'page' に設定されているからになります。
つまり、この page_kwarg はページ番号を指定するクエリパラメーターの変数名を指定するクラス変数になります。前述の通り、page_kwarg を定義しなかった場合、ページ番号は page によって指定できるようになっています。
例えば、下記のように page_kwarg を定義した場合、ページ番号をクエリパラメーター p で指定できるようになります。
from django.views.generic import ListView
from .models import Comment
class CommentList(ListView):
model = Comment
paginate_by = 3
page_kwarg = 'p'
したがって、先ほどの例で考えれば、2 ページ目を表示する際は下記のようにクエリパラメーターを指定することになります。
http://localhost:8000/forum/comments/?p=2
paginate_orphans
paginate_by の定義により、各ページに paginate_by に指定した個数のインスタンスが割り付けられていくことになります。この際、最後のページには中途半端に少ない個数のインスタンスのみが割り付けられる可能性があります。paginate_orphans は、そういった最後のページに割り付けられたインスタンスを、最後の1つ前のページにまとめて割り付けるようにしたい場合に定義するクラス変数になります。
より具体的には、 paginate_orphans に整数を指定して定義すれば、最後のページに割り付けられたインスタンスの数が paginate_orphans 以下の場合に、最後のページの1つ前のページにまとめて割り付けられるようになります。
例えば下記のように paginate_orphans を定義すれば、最後のページに割り付けられるインスタンスの数が 3 以下の場合は、それらのインスタンスが最後のページの1つ前のページにまとめて割り付けられるようになります。
from django.views.generic import ListView
from .models import Comment
class CommentList(ListView):
model = Comment
paginate_by = 5
paginate_orphans= 3
例えば、取得されたインスタンスの数が 8 の場合、paginate_orphans を定義しなければ 1 ページ目に 5 つ、2 ページ目に 3 つのインスタンスが割り付けられることになりますが上記のように paginate_orphans を定義すれば、1 ページ目に 8 つのインスタンスが全て割り付けられることになります。
スポンサーリンク
paginator_class
paginator_class はページネーションを実現するクラスを指定するクラス変数になります。paginator_class を定義しなかった場合はページネーションを実現するクラスとして Django フレームワークで定義される Paginator というクラスが利用されることになります。もし、ページネーションを Paginator 以外のクラスで実現したいのであれば、paginator_class の定義が必要となります。
これも基本的には定義は不要だとは思いますが、Paginator を継承するクラスを定義してカスタマイズしたものを利用したいような場合は、このクラス変数の定義が必要となります。
ListView のメソッド
次に ListView の持つメソッドを紹介していきます。ListView を継承したクラスを定義し、そのクラスで ListView の持つメソッドをオーバーライドしてやることで ListView とは異なる動作のクラスを実現することができるようになります。
ただ、特に ListView の場合はメソッドをオーバーライドする機会は少ないと思います。なので、全てに対して解説をしてもしょうがないので、まずはメソッド一覧を示し、続いて、その中からオーバーライドする機会が多そうなメソッドのみの詳細を説明するようにしたいと思います。
ListView のメソッド一覧
ListView の持つメソッドの一覧は下記のようになります。あくまでもクラスのカスタマイズ目的でオーバーライドを行う可能性のあるものを挙げており、as_view などのカスタマイズは行わないであろうメソッドは省略しています。
また、ListView は get メソッドを持っており、メソッドが GET のリクエストを受け取った場合は get が実行され、この get の中から各種メソッドが実行されるようになっています。
get:リクエストのメソッドがGETの場合の処理を実行するget_allow_empty:allow_emptyへの指定値を取得するget_context_data:テンプレートに渡すコンテキストを生成するget_context_object_name:context_object_nameへの指定値を取得するget_ordering:orderingへの指定値を取得するget_paginate_by:paginate_byへの指定値を取得するget_paginate_orphans:paginate_orphansへの指定値を取得するget_paginator:paginator_classのインスタンスを生成するget_queryset:インスタンスの集合(クエリセット)を取得するget_template_names:テンプレートファイルの名前を取得するpaginate_queryset:インスタンスの集合をページに割り付けるrender_to_response:レスポンスを返却する
ご覧の通り、単にクラス変数への指定値を取得するだけのメソッドが多いです。例えば get_ordering は下記のように定義されており、単にクラス変数 get_ordering の値を取得するだけのメソッドとなっています。
def get_ordering(self):
"""Return the field or fields to use for ordering the queryset."""
return self.ordering
ですので、特に名前が get_クラス変数名 となっているメソッドに関しては、メソッドのオーバーライドではなくクラス変数の定義によってクラスの動作をカスタマイズすることもでき、そちらの方がカスタマイズを楽に行えると思います。
ただし、クラス変数の値はアプリ起動時に静的に決定されるため、動的にメソッドの返却値を変化させたいような場合はオーバーライドが必要になります。極端な例になりますが、例えば get_paginate_by の返却値をランダムにしたい場合は、下記のようなメソッドを、ListView を継承するクラス側に定義してオーバーライドしてやれば良いです。
import random
def get_paginate_by(self, queryset):
return random.randint(1, 10)
下記のようにクラス変数を定義した場合も上記と同じような結果が得られるようにも思えますが、この場合はアプリ起動時に random.randint が実行されて各ページに割り付けられるインスタンスの数が決まります。従って、一度アプリを起動すれば、その後はページ表示時に毎回同じ数のインスタンスが表示されることになります。
class CommentList(ListView):
model = Comment
ordering = 'text'
paginate_by = random.randint(1, 10)
それに対し、メソッドの場合はページ表示が行われるたびに実行されることになるため、ページを表示するたびに表示されるインスタンスの数が変化することになります。
基本はクラス変数の定義によるカスタマイズを行えば良いですが、上記の例のように、クラス変数の定義はアプリ起動時に静的に決まるため、アプリ起動後にユーザーの操作やリクエストされた URL 等に応じて動的に設定を変更したい場合はクラス変数の定義ではなくメソッドのオーバーライドで実現する必要があります。その一例として、次に get_query_set のオーバーライド例を示します。
スポンサーリンク
get_queryset
では get_queryset のオーバーライドについて説明していきます。get_queryset はその名の通り、データベースに発行するクエリ(セット)を取得するためのメソッドとなります。そして、ここで取得したクエリをデータベースに発行し、クエリに応じたインスタンスの集合(単体の場合もあり)が取得されることになります。
簡単に言えば、この get_queryset メソッドは下記を返却するメソッドとなります。
- クラス変数
querysetが定義されている場合:queryset - クラス変数
querysetが定義されていない場合:model.objects.all()
本当はクラス変数 ordering も考慮して返却値が決定されるようになっているのですが、説明を簡単にするため、ここではその点は考慮しないこととします。
ここで重要なのは、get_queryset はクラス変数 queryset でコントロール可能であるという点になります。queryset を定義しておけば、get_queryset の返却値はクラス変数 queryset に指定したクエリとなります。なので、クラス変数 queryset の定義で好きなクエリを指定してやれば、わざわざオーバーライドして get_queryset の動作を変化させる必要もありません。
ただし、前述の通りクラス変数は静的に決まるもの、もっと具体的に言えばアプリ起動時に決定可能なものしか指定できず、アプリ起動後に動的に変化させることができません。例えばですが、一覧リストを表示するページに検索フォームを追加し、ここにユーザーから指定された文字列が text フィールドに含まれる Comment のインスタンスのみを取得したい場合、クエリは下記のような処理で生成する必要があります。
Comment.objects.filter(text__contains=ユーザーから指定された文字列)
ですが、ユーザーが指定する文字列はアプリ起動時には分からないため、このようなクエリはクラス変数 queryset の定義では実現できないことになります。こういった動的に検索条件を変化させたいような場合は get_queryset をオーバーライドして get_queryset の動作を変化させる必要があります。
例えば、検索条件を動的に変化させる場合の get_queryset の定義例は下記のようなものになります。
from django.views.generic import ListView
from .models import Comment
class CommentList(ListView):
model = Comment
def get_queryset(self):
try:
target = self.request.GET['s']
self.queryset = self.model.objects.filter(text__contains=target)
except Exception:
pass
return super().get_queryset()
上記では、クエリパラメーターで ?s=検索文字列 が指定された際に、その 検索文字列 が text フィールドに含まれる Comment のインスタンスのみを取得するクエリを生成して上書きするようになっています。それ以外はスーパークラス(ListView)の get_queryset で Comment.objects.all() に対するクエリが生成されるようになっています。ですので、検索フォームへ入力された文字列がクエリパラメーターの ?s= の右辺に指定されるようにテンプレートファイルを作成しておけば、ユーザーが Comment のインスタンスの検索を行うことができるようになります。
このように、生成するクエリを動的に変更したい場合は get_queryset のオーバーライドが有効です。逆にクエリが静的に決まる場合はクラス変数 queryset の定義でクエリを指定してやれば良いことになります。
ListView が生成するコンテキスト
続いて ListView が生成するコンテキストについて説明しておきます。
ListView が生成するコンテキストはページネーションを行うかどうかによって異なり、ページネーションを行う場合は、コンテキストに下記のような要素が含まれます。
'paginator':Paginatorのインスタンス'page_obj':クエリパラメーターで指定されたページに対するPageのオブジェクト'is_paginated':ページ分割されたかどうか(TrueorFalse)'object_list':インスタンスの集合(クエリセット)context_object_name:インスタンスの集合(クエリセット)
それに対し、ページネーションを行わない場合は、コンテキストに下記のような要素が含まれます。
'paginator':None'page_obj':None'is_paginated':False'object_list':インスタンスの集合(クエリセット)context_object_name:インスタンスの集合(クエリセット)
context_object_name に関しては、context_object_name で説明したように、デフォルトは 'モデルクラス名_list' (小文字) となっています。モデルクラス名が Comment であれば 'comment_list' となります。そして、この context_object_name はクラス変数の定義で変更することも可能です。
こういったページネーションに関するデータやインスタンスの集合はコンテキストに含まれることになりますが、他のデータをテンプレートファイルから参照したい場合は、extra_context で紹介したクラス変数 extra_context を定義してコンテキストの要素を追加する必要があります。また、extra_context の定義では静的に決まる要素しか追加できないため、追加する要素を動的に変化させたいような場合は get_context_data のオーバーライドを利用してコンテキストの要素を追加する必要があります。
もう少し正確に言えば、各キーにセットされるデータはインスタンスそのものではなくクエリセットになるのですが、テンプレートから参照することを考えればインスタンスと捉えても問題ないかと思います
テンプレートファイルからクエリセットを利用しようとした時(クエリセットの評価が行われる時)にクエリセットのクエリがデータベースに発行され、そこでインスタンスがデータベースから取得されることになります
この辺りは下記ページで解説していますので、詳細は下記ページを参照いただければと思います
ListView の利用例
最後に、ここまでの説明のまとめとして、ListView を継承するクラスでのビューの作成例を示していきたいと思います。
スポンサーリンク
プロジェクトとアプリの作成
このページは、View のサブクラス の紹介ページの第1弾として作成しており、各 View のサブクラス の紹介ページの中でも最初に読んでいただくことを前提としているページのため、この作成例に関しても少し詳しく説明していこうと思います。
ということで、プロジェクトの作成の仕方から説明をしていきます。説明が不要である部分はどんどんスキップしながら読み進めていただければと思います。
まず、適当な作業フォルダに移動したのち、下記コマンドでプロジェクトを作成します。今回はプロジェクトを classviewproject としています。
% django-admin startproject classviewproject
続いてアプリを作成していきます。先ほどのコマンドの実行で classviewproject というフォルダが作成されたはずなので、この classviewproject に下記の cd コマンドで移動します。
% cd classviewproject
続いて、下記コマンドを実行してアプリを作成します。今回はアプリを forum としています。
% python manage.py startapp forum
また、先ほど cd コマンドで移動後のフォルダの下にも classviewproject というフォルダがあるはずなので、そのフォルダの下にある settings.py を開き、下記のように INSTALLED_APPS のリストを変更します。これにより、アプリ forum がプロジェクトに登録されることになります。
INSTALLED_APPS = [
'forum', # 追加
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
この辺りのプロジェクトやアプリの関係性に関しては下記ページで解説していますので、詳しく知りたい方は下記ページをご参照いただければと思います。
モデルの作成とマイグレーション
続いて forum フォルダの下にある models.py を下記のように変更します。
from django.db import models
class Comment(models.Model):
text = models.CharField(max_length=256)
date = models.DateField(auto_now_add=True)
slug = models.SlugField(max_length=256, null=True, unique=True)
slug フィールドに関しては今回は利用しませんが、DetailView の解説で利用しますので、使い回しが効くよううに定義のみを行なっています。
さらに、上記 models.py に定義したモデルクラスに対応するテーブルのデータベースへの作成を行います。テーブルは下記の2つのコマンドでマイグレーションを実施することで作成することができます。
% python manage.py makemigrations % python manage.py migrate
クラスベースビューの作成
続いて、このページの主題となっているクラスベースビューを作成していきます。今回は、ここまでの説明の通り ListView を継承するクラスを定義し、そのクラスをクラス変数の定義とメソッドのオーバーライドによってカスタマイズしていきます。
今回は、下記のように views.py を変更することでクラスベースビューを作成したいと思います。一部関数ベースのビューがありますが、これは動作確認を楽に行うために複数の Comment のインスタンスをデータベースに新規作成するものとなっており、今回の説明対象となるのは CommentList のみとなります。
from django.views.generic import ListView
from .models import Comment
from django.http import HttpResponse
class CommentList(ListView):
model = Comment
ordering = 'text'
paginate_by = 5
paginate_orphans = 2
template_name = 'forum/comments.html'
page_kwarg = 'p'
extra_context = {'title': 'コメント一覧'}
def get_queryset(self):
try:
search = self.request.GET['s']
self.queryset = self.model.objects.filter(text__contains=search)
except Exception:
pass
return super().get_queryset()
def test_view(request):
texts = [
'Hello', 'Good bye...', 'Excellent!', 'Good night!', 'Hi!',
'What is this?', 'Why?', 'Nice to meet you', 'Good morning',
'See you', 'Me too', 'Yes!'
]
for text in texts:
tmp = text.replace(' ', '').lower()
slug = ''.join(filter(str.isalnum, tmp))
comment = Comment(text=text, slug=slug)
comment.save()
return HttpResponse(str(len(texts)) + '個のレコードを作成しました')
CommentList の定義を見ていただければ分かるように、定義しているクラス変数は全て ListView のクラス変数 で紹介したものになりますし、定義しているメソッドも全て ListView のメソッド で紹介したものになります。ですので、詳細な説明はここでは省略させていただきます。
スポンサーリンク
ビューと URL とのマッピング
次は、views.py で定義したビューと URL とのマッピングを行なっていきます。
まず、classviewproject フォルダの下にある urls.py を下記のように変更してください。
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('forum/', include('forum.urls')),
]
続いて、forum フォルダの下に urls.py を新規作成し、中身を下記のように変更してください。
from django.urls import path
from . import views
urlpatterns = [
path('comments/', views.CommentList.as_view(), name='comments'),
path('test/', views.test_view)
]
これらの定義より、ルートパス指定で /forum/comments/ へのリクエストがあった場合に CommentList の get メソッドが実行されるようになります。
関数ベースビューの場合、上記の views.test_view のように path の第2引数には直接 views.py に定義した関数を指定すれば良いのですが、クラスベースビューの場合は上記の views.CommentList.as_view() のように views.py に定義したクラスの as_view() メソッドの実行結果を指定する必要があります。ここもクラスベースビューを扱う上でのポイントの1つとなります。
テンプレートの作成
次はテンプレートファイルを作成します。ソースコードの変更としては、これが最後となります。
クラスベースビューを扱う上ではテンプレートファイルの作り方も重要となってきます。テンプレートファイルとビューは密接に関わっており、双方で話が合うように両方を作成する必要があります。例えば、ビューが用意したコンテキストに存在しないキーの変数をテンプレートファイルが参照すると上手くデータの表示などができません。また、ビューが利用しようとしているテンプレートファイルが用意されていない場合もページの表示が出来なくなってしまいます。
ビューが期待するテンプレートを用意する or テンプレートが期待するビューを作成するように心がけることが重要です。
ということで、先ほど定義した CommentList と話が合うようなテンプレートファイルを作成していきたいと思います。
まず、CommentList では template_name = 'forum/comments.html' が定義されているため、templates フォルダから見た相対パスが forum/comments.html となる位置にテンプレートファイルを用意する必要があります。
そのため、forum フォルダの下に templates フォルダを、さらに templates フォルダの下に forum フォルダを作成します。さらに、その最後に作成した forum フォルダの下に comments.html を作成します。これで、CommentList の期待するパスにテンプレートファイルが作成されたことになります。
さらに、comments.html の中身を下記のように変更して保存します。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>ListViewの利用例</title>
</head>
<body>
<main>
<h2>{{ title }}(全{{ page_obj.paginator.count }}件)</h2>
<table>
<thead>
<tr>
<th>ID</th><th>本文</th>
</tr>
</thead>
<tbody>
{% for comment in page_obj %}
<tr>
<td>{{ comment.id }}</td>
<td>{{ comment.text }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div>
<span>
{% if page_obj.has_previous %}
<a href="?p=1">« first</a>
<a href="?p={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span>
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="?p={{ page_obj.next_page_number }}">next</a>
<a href="?p={{ page_obj.paginator.num_pages }}">last »</a>
{% endif %}
</span>
</div>
<div>
<form action="{% url 'comments' %}" method="get">
<p><input type="text" name="s"><input type="submit" value="検索"></p>
</form>
</div>
</main>
</body>
</html>
上記のテンプレートファイルでは次の2つの変数を参照しています。
title:ページの見出し用page_obj:表示中のページに対応するPageのインスタンス
つまり、これらの変数がテンプレートファイルから参照できるように、CommentList はコンテキストを準備する必要があります。ListView が生成するコンテキスト で説明したように、page_obj に関しては、クラス変数として paginate_by を定義しておけば自動的にコンテキストにセットされるようになっています(逆に paginate_by を定義しなければ page_obj はテンプレートファイルから利用できません)。
それに対し、title に関しては自動的にコンテキストにセットされるようになっていないため、CommentList ではクラス変数 extra_context の定義によって title をコンテキストにセットするようにしています。
また、上記テンプレートファイルの最後の <div> 〜 </div> では検索フォーム要素の表示を実現しています。この検索フォームの 検索 ボタンをクリックすれば、下記のようにフォームに入力した文字列がクエリパラメーターの変数 s にセットされた状態で /forum/comments/ にリクエストが送信されるようになっています。
/forum/comments/?s=入力された文字列
そして、このリクエストを受けった際には、urls.py の設定によって再度 CommentList の getメソッドが実行されるようになっています。
さらに、CommentList の getメソッドからは CommentList で定義した get_queryset が実行されます。受け取ったリクエストではクエリパラメーターとして s が指定されているため、get_queryset では “この s に指定された文字列を text フィールドに含むインスタンスのみ” を取得するためのクエリが生成されることになります。そのため、検索フォームで 検索 ボタンがクリックされた際には、”フォームに入力された文字列を text フィールドに含むインスタンスのみ” が表示されることになります。つまり、検索が実行されることになります。
この検索機能の実現に関しても、どんなクエリパラメーターで検索文字列を扱うのかをテンプレートファイルとビューとで上手く話を合わせるという点が重要となります。
ここまで説明してきたように、テンプレートファイルとビューとで上手く話が合うように双方を作成していくことが重要になります。特にクラスベースビューの場合は、継承する View のサブクラス (今回の場合は ListView) によってコンテキストなどが自動的に作成されるようになっているため、テンプレートファイルから参照する変数の変数名に関しては特に注意が必要です。
動作確認
最後に動作確認を行なっておきましょう!
まずは、manage.py が存在するフォルダで下記コマンドを実行します。
% python manage.py runserver
これにより、開発用ウェブサーバーが起動しますので、ウェブブラウザから作成したアプリのページを表示することが可能となります。
最初に下記 URL をウェブブラウザで表示してみてください。この URL をウェブブラウザで表示すれば、12個分の Comment のインスタンスがデータベースに新規作成されるようになっています。
http://localhost:8000/forum/test/
次は、下記 URL をウェブブラウザで表示しましょう!
http://localhost:8000/forum/comments/
この URL を表示することによって、views.py に定義した CommentList が動作し、Comment のインスタンス一覧が表示されることになります。
ただし、ここで表示されるのは 5 件分のインスタンスのみとなります。これは、paginate_by = 5 の定義によって各ページに 5 件分のインスタンスが割り付けられるようになっているためです。

また、各インスタンスは text に対してアルファベット順で昇順にソートされた状態で表示されるようになっています。これは ordering = 'text' を定義しているためになります。
ここで、next リンクをクリックすれば、2 ページ目が表示され、先ほどとは異なるインスタンスが表示されることになります。

nextリンクをクリックした後に URL を見てみると ?p=2 というクエリパラメーターが確認できると思います。CommentList ではクラス変数 page_kwarg = 'p' を定義しているため、クエリパラメーター p によって、表示されるページ(分割後のページ)が自動的に変化するようになっています。今回は ?p=2 が指定されているので、ページ番号 2 のページが表示されているというわけです。
また、next リンククリック後に表示されるインスタンスの数が 5 件ではなく 7 件になっているのは、paginate_orphans = 2 の定義により、最後のページに割り付けられるページが 2 件以下の場合は前のページにまとめて割り付けるよう指定されているからになります。
最後に、ページ下部にある検索フォームに Good を入力して 検索 ボタンをクリックしてみてください。これにより、本文に Good が含まれるインスタンスのみが表示されるようになったことが確認できると思います。

これは、CommentList で定義した get_queryset により、データベースから text フィールドに Good が含まれる Comment のインスタンスのみを取得するようクエリが生成されたからになります。
これらの動作確認結果より、CommentList で定義したクラス変数やメソッドに応じたページの表示が行われていることを確認できたと思います。こんな感じで、ListView で定義されているクラス変数やメソッドを、ListView を継承するクラス(今回の場合は CommentList)で上書きしてやれば、ListView の特徴を引き継いだまま、ListView とは異なる動作となるクラスを実現することができます。そして、これらを行うことで、自身が開発したいウェブアプリに応じたビューにカスタマイズしていくことが可能となります。
今回紹介した CommentList も、クラス変数とメソッドの定義の削除や変更を行えば、また異なったページの表示結果になることを確認できると思います。是非色んなカスタマイズを試してみてください!
スポンサーリンク
まとめ
このページでは、ListView および ListView を継承したクラスベースビューの作り方について解説しました!
ListView は特定のモデルクラスのインスタンス一覧を表示するための View のサブクラス であり、このクラスを継承することでインスタンス一覧を表示するページを簡単に実現できます。
また、ListView では様々なクラス変数・メソッドが定義されており、ListView を継承したクラスでこれらを上書き(オーバーライド)することでページの表示内容をカスタマイズすることも可能です。このカスタマイズの仕方は他の View のサブクラス でも同様ですので、是非この考え方は覚えておいてください!
このサイトでは他の View のサブクラス についても説明していますので、是非他のページも読んでみてください!
