このページでは、Django フレームワークで定義される ListView
の使い方について説明していきます。
この ListView
は View
というクラスのサブクラスであり、ビューをクラスベースで作成する際に利用するクラスとなります。この View のサブクラス
を継承し、さらにクラス変数を定義したりメソッドをオーバーライドすることで、あなたが開発したいアプリに応じたビューを作成することが可能となります。
この辺りのクラスベースビューやクラスベースビューの作り方については下記ページで解説していますので、詳しくはこちらをご参照ください。
【Django入門15】クラスベースビューの基本上記ページでも解説していますが、クラスベースビューを作成していくにあたって重要になるのが、あなたが実現したいビューに合わせて継承する 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
メソッドの動作を変化させることができます。
この辺りの、クラス変数やメソッドの定義によりカスタマイズが可能である理由については下記ページで解説していますので、詳しくは下記ページを参照してください。
【Django入門15】クラスベースビューの基本スポンサーリンク
クラス変数 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
さて、ここから紹介するクラス変数は全て、前述でも紹介した下記ページで説明しているページネーションを実現するためのクラス変数となります。
【Django入門13】ページネーションの基本ページネーションを実現するためには、ここから紹介するクラス変数を必要に応じて定義する必要があります。特に、この節で扱う 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
のデータ属性からも利用可能です
少し説明が長くなりましたが、ページネーション自体やページネーション利用時のテンプレートファイルの作り方に関しては下記ページで説明していますので、詳しくは下記ページを参考にしていただければと思います。
【Django入門13】ページネーションの基本例えば下記のように 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'
:ページ分割されたかどうか(True
orFalse
)'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
のオーバーライドを利用してコンテキストの要素を追加する必要があります。
もう少し正確に言えば、各キーにセットされるデータはインスタンスそのものではなくクエリセットになるのですが、テンプレートから参照することを考えればインスタンスと捉えても問題ないかと思います
テンプレートファイルからクエリセットを利用しようとした時(クエリセットの評価が行われる時)にクエリセットのクエリがデータベースに発行され、そこでインスタンスがデータベースから取得されることになります
この辺りは下記ページで解説していますので、詳細は下記ページを参照いただければと思います
【Django入門14】N+1問題とselect_related・prefetch_relatedでの解決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',
]
この辺りのプロジェクトやアプリの関係性に関しては下記ページで解説していますので、詳しく知りたい方は下記ページをご参照いただければと思います。
【Django入門2】Djangoの全体像・ファイル構成・動作の仕組みモデルの作成とマイグレーション
続いて 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 のサブクラス
についても説明していますので、是非他のページも読んでみてください!