このページでは、Django フレームワークで定義される DetailView
の使い方について説明していきます。
この DetailView
は View
というクラスのサブクラスであり、ビューをクラスベースで作成する際に利用するクラスとなります。この View のサブクラス
を継承し、さらにクラス変数を定義したりメソッドをオーバーライドすることで、あなたが開発したいアプリに応じたビューを作成することが可能となります。
この辺りのクラスベースビューやクラスベースビューの作り方については下記ページで解説していますので、詳しくはこちらをご参照ください。
【Django入門15】クラスベースビューの基本このページでは、前述の通り View のサブクラス
の1つである DetailView
に焦点を当てて解説を行なっていきます。
また、View のサブクラス
には ListView
というクラスも存在しており、下記ページで ListView
を継承したクラスベースビューの作り方について詳しく解説しています。ListView
は View のサブクラス
の中でも最も基本的なクラスの1つであり、この ListView
の解説ページを読んでいただければ大体 View のサブクラス
を継承したクラスの使い方については理解していただけると思います。
DetailView
に関する解説も、適宜上記の ListView
の解説ページを参照しながら解説を行なっていきますので、事前に上記ページを読んでおいていただくことをオススメします。
Contents
DetailView
DetailView
は特定のモデルクラスにおける1つのインスタンスの詳細を表示するページを実現する View のサブクラス
となります。
DetailView
は django.views.generic
から import
して利用します。
from django.views.generic import DetailView
例えば SNS アプリでのユーザーの詳細ページ、ショッピングアプリでの商品の紹介ページといった、特定のモデルクラスにおける1つのインスタンスの詳細を表示するページは DetailView
を継承することで簡単に作成することが可能です。
基本的には、DetailView
が対応しているのは表示だけであり、HTTP のリクエストメソッドとしては GET
のみに対応しています。そして、リクエストを受け取った際には、この DetailView
の get
メソッドが実行されるようになっています。
同様に、下記ページで解説している ListView
も特定のモデルクラスのインスタンスを表示するのが得意なクラスとなります。が、ListView
の場合は複数のインスタンスの一覧を表示するビューを実現するために継承するのに対し、DetailView
の場合は1つのインスタンスの詳細を表示するビューを実現するために継承するクラスとなります。
そのため、ListView
の場合は表示対象となるモデルクラス or 表示対象となるインスタンスを取得するためのクエリの指定を行うことが重要でしたが、DetailView
の場合は、それらに加え、表示対象となる1つのインスタンスを特定するための情報が必要となります。
この辺りが ListView
と DetailView
の大きな違いとなるのですが、定義されているクラス変数やメソッドに共通なものも多いため、共通している部分は ListView
の解説ページへのリンクを示すだけで、本ページでの詳細な解説は省略させていただこうと思います。
DetailView
でのクラスベースビューの作り方
次は DetailView
でクラスベースビューを作成する手順を説明します。
DetailView
でのクラスベースビューの作り方は、大まかに言えば ListView でのクラスベースビューの作り方 で説明した ListView
でのクラスベースビューの作り方と同様となります。
つまり、views.py
に DetailView
を継承するクラスを定義し、そのクラスでクラス変数やメソッドを定義して DetailView
側で定義されているクラス変数の上書き・メソッドのオーバーライドを行なうことで、ビューを作成していきます。これにより、DetailView
の特徴を活かしながら自身のウェブアプリに応じたビューにカスタマイズしていくことが可能となります。
もう少し具体的に言えば、前述の通り、DetailView
ではメソッドが GET
のリクエストを受けとった際に get
メソッドが実行されるようになっており、DetailView
のクラス変数や他のメソッドは、この get
メソッドから利用されるようになっています。
したがって、DetailView
を継承するクラス側でクラス変数やメソッドを上書き・オーバーライドしてやれば、この get
メソッドの動作を変化させることができます。そして、それによって、開発するウェブアプリに適したビューに仕立てていくことができます。
また、DetailView
の場合は ListView
の時同様に最低限 model
or queryset
を定義する必要があります。これらの定義により、インスタンスの取得先のテーブルやインスタンスを取得する際に発行するクエリを指定することができます。
従って、DetailView
を継承してクラスベースビューを作成する場合も、最低限下記のようにクラスを定義してやれば良いことになります。model
の代わりに queryset
を定義するのでも良いです。
from django.views.generic import DetailView
from .models import モデルクラス
class クラス名(DetailView):
model = モデルクラス
スポンサーリンク
インスタンスを1つに特定する
ここまでに関しては ListView
と全く同じです。
ですが、DetailView
の場合、ListView
とは異なり、上記に加えて取得するインスタンスを1つに特定することが必要となります。そのため、DetailView
は URL でプライマリーキー (pk
) もしくはスラッグ (slug
) が指定されることを期待した作りとなっています。まずは、URL でプライマリーキーが指定されることを前提に解説していきます。スラッグについては後述で解説します。
URL でプライマリーキーが指定された場合、DetailView
は model
で指定したモデルクラスの全インスタンス or queryset
で指定されたクエリで取得される全インスタンスの中から、プライマリーキーのフィールドの値が URL で指定されたプライマリーキーと一致するインタンスのみを取得するようになっています。そして、それをコンテキストにセットしてテンプレートファイルに渡すようになっています。
このプライマリーキーは各インスタンスを特定するための識別子ですので、プライマリーキーが指定されることで1つのインスタンスのみが取得されるようになります(そのプライマリーキーを持つインスタンスが存在しなければ取得に失敗する)。また、特に設定を変更していない場合は各インスタンスの id
フィールドがプライマリーキーとなっています。また、プライマリーキーとなっているフィールドは pk
データ属性からも参照可能となっています。
DetailView
が上記のような作りとなっているため、DetailView
を継承してビューを作成する場合、基本的には下記の形式の URL に対するリクエストを受け取れるようにしておく必要があります。
/パス/プライマリーキー/
そして、これは urls.py
(アプリ側) の urlpatterns
を下記のように定義することで実現できます。
from django.urls import path
from . import views
urlpatterns = [
path('パス/<int:pk>/', views.DetailViewのサブクラス.as_view()),
]
つまり、DetailView
のサブクラスを動作させるためには、基本的には上記のように path
関数の第1引数で指定する URL パターンに <int:pk>
が含まれるようにしておく必要があります。そして、この <int:pk>
部分で指定された整数がプライマリーキーとして扱われ、そのプライマリーキーのインスタンスが取得されるようになります。
実は、この pk
の部分はクラス変数の定義により変更することも可能です。これに関しては後述で解説します。また、先ほども少し触れたように、プライマリーキーではなくスラッグを指定して表示対象のインスタンスを1つに特定するようなことも可能です。これに関しても後述で解説します。
このように、DetailView
を継承する場合は表示対象のインスタンスを1つに特定することが重要であり、DetailView
が期待する情報が URL に指定可能となるように urls.py
を作る必要があります。この辺りに関しては ListView
にはない DetailView
ならではの特徴であると言っていいと思います。そして、こういった特徴があるため、DetailView
には ListView
に定義されていないクラス変数が存在し、この辺りの設定をクラス変数の定義で変更可能となっています。
特に、この辺りに注目しながら、次の DetailView のクラス変数 で DetailView
に定義されているクラス変数を紹介していきたいと思います。
また、DetailView
においてもページの表示を実現するためにはテンプレートファイルが必要となります。この際に利用されるテンプレートファイルは ListView
同様に template_name
で指定したパスのテンプレートファイル or DetailView
でデフォルトで設定されているパスのテンプレートファイルとなります。
したがって、これらのパスには “1つのインスタンスの詳細情報を表示するためのテンプレートファイル” を設置しておく必要があります。ListView
の場合と違って複数のインスタンスの情報を表示するテンプレートファイルではなく、1つのインスタンスのみの情報を表示するテンプレートファイルを用意しておく必要がある点に注意してください。
DetailView
のクラス変数
では、次は DetailView
で定義されているクラス変数の紹介を行なっていきます。これらのクラス変数を DetailView
を継承するクラスで定義し直して上書きすることでクラスの動作のカスタマイズを行うことが可能となります。
DetailView
の場合、GET
メソッドのリクエストを受け取った際に DetailView
の get
メソッドが実行されることになるため、この get
メソッドの動作を変化させることを目的にクラス変数の定義を行なっていくことになります。
DetailView
のクラス変数の一覧
その DetailView
で定義されるクラス変数には下記のようなものが存在します。
- model
- queryset
- pk_url_kwarg
- slug_url_kwarg
- slug_field
- context_object_name
- extra_context
- template_name
- template_name_suffix
- template_engine
- response_class
- content_type
特に extra_context・template_engine・response_class・content_type に関しては ListView
のクラス変数の役割が全く同じなので ListView
の解説ページを参照していただければと思います(これら4つの各リンクには ListView
の解説ページでの説明部分へのリンクを設定しています)。
また、これらが DetailView
で定義されているクラス変数の全てというわけではないので注意してください。カスタマイズに利用する機会の多そうなもののみを紹介しています。全てのクラス変数を知りたい場合は、実際に DetailView
の定義をソースコードで確認していただくのが一番早いと思います。
また、ここからは、アプリの models.py
で下記のような Comment
が定義されていることを前提に解説を行なっていきます。
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
は URL でスラッグを指定して1つのインスタンスに特定するためのフィールドとなります。これに関しては後述の slug_url_kwarg で説明します。
では、先ほど挙げたクラス変数の役割や、これらを定義することで実現できることについて、1つ1つ説明していきます。
スポンサーリンク
model
前述の通り、DetailView
は特定のモデルクラスの1つのインスタンスを取得して表示するクラスになります。このインスタンスの取得先のモデルクラスを指定するクラス変数が model
となります。
考え方は基本的には ListView の model と同様になります。
例えば、Comment
から1つのインスタンスを取得して表示したいのであれば、ListView
を継承したクラスに対して下記のように model
を定義してやれば良いです。
from django.views.generic import DetailView
from .models import Comment
class CommentDetail(DetailView):
model = Comment
これにより、プライマリーキーが “URL で指定された pk
” と一致するインスタンスが取得されるようになります。そして、そのインスタンスがコンテキストとしてテンプレートファイルに渡され、テンプレートファイルでインスタンスの情報を出力できるようになります。
queryset
model
のみを定義した場合、テーブル全体の中からプライマリーキーが URL で指定された pk
と一致するインスタンスが検索されて取得されることになります。それに対し、queryset
を定義した場合は、テーブル全体ではなく queryset
に指定されたクエリによって取得されるインスタンスの集合の中からプライマリーキーが URL で指定された pk
と一致するインスタンスが検索されて取得されるようになります。
queryset
の定義の仕方やクエリの指定の仕方に関しては ListView の queryset と基本的な考え方は同じなので、ここでの詳しい説明は省略します。
pk_url_kwarg
さて、ここまで URL ではプライマリーキーが指定され、そのプライマリーキーから1つのインスタンスが特定されることを前提に説明をしてきました。そして、その URL で指定されたプライマリーキーは pk
という変数名で扱う必要があると説明してきました。
前述の通り、この場合、urls.py
(アプリ側) では下記のように変数名を pk
として定義する必要があります。
from django.urls import path
from . import views
urlpatterns = [
path('パス/<int:pk>/', views.CommentDetail.as_view()),
]
なぜ上記のように変数名を pk
として定義する必要があるかというと、DetailView
が、プライマリーキーを受け取る変数名が pk
であることを前提に作られているからです。そのため、たとえば下記のように異なる変数名でプライマリーキーを受け取るように urls.py
を変更してしまうと、DetailView
を継承したビューが動作する際に例外が発生することになります。
from django.urls import path
from . import views
urlpatterns = [
path('パス/<int:comment_id>/', views.CommentDetail.as_view()),
]
発生する例外は下記のようなものになります。
Generic detail view CommentDetail must be called with either an object pk or a slug in the URLconf.
このように、DetailView
ではプライマリーキーが pk
という変数名で扱われることを前提に動作しています。これ以外にも、DetailView
は様々な前提のもとに動作するようになっています。そのため、DetailView
を継承したビューを扱う際には、その前提に合わせてアプリを作成する or その前提を変更する必要があります。
そして、この前提はクラス変数の定義で変更することが可能で、上記の URL で指定されるパラメーターの変数名に関しては pk_url_kwarg
によって変更可能です。この pk_url_kwarg
は URL で指定されるプライマリーキーの変数名を定義するクラス変数であり、デフォルトでは 'pk'
となっています。従って、pk_url_kwarg
を定義しなかった場合、プライマリーキーの変数名は pk
である必要があります。
ですが、pk_url_kwarg
を定義してやれば、プライマリーキーの変数名は pk
から変更することが可能となります。例えば下記のように pk_url_kwarg
を定義した場合、CommentDetail
はプライマリーキーの変数名が comment_id
であることを前提に動作するようになります。
from django.views.generic import DetailView
from .models import Comment
class CommentDetail(DetailView):
model = Comment
pk_url_kwarg = 'comment_id'
したがって、urls.py
においては、下記のようにプライマリーキーを comment_id
という変数名で指定できるようにしておく必要があります。
from django.urls import path
from . import views
urlpatterns = [
path('パス/<int:comment_id>/', views.CommentDetail.as_view()),
]
結局、DetailView
を継承するクラスで受け取るプライマリーキーの変数名と urls.py
で定義するプライマリーキーの変数名が一致していれば問題なく動作しますし、一致していなければ例外が発生するという単純な話です。これは実は関数ベースビューの場合も同様で、関数ベースビューの場合も urls.py
で定義する変数名と、関数がそれを受け取る引数名が一致している必要があります。
ただ、関数ベースビューの場合は、その変数名も引数名も開発者自身が必ず定義する必要があるため、単に両方を同じものに合わせて定義してやれば良いだけです。ですが、クラスベースビューの場合は、先ほどの例の pk
のように、View のサブクラス
で受け取ることを前提としている変数名などが決まっているため、それに合わせて他の部分を作成する必要がある or その前提を変更してやる必要があるため、View のサブクラス
の作りをある程度理解しておく必要があります。この点がクラスベースビューの難しいところかなぁと思います。
スポンサーリンク
slug_url_kwarg
ここまでプライマリーキーでインスタンスを1つに特定することを前提に説明をしてきましたが、DetailView
ではスラッグでインスタンスを1つに特定することも可能となっています。
例えば、下記のように URL でプライマリーキーではなくスラッグの文字列を指定することで、このスラッグと slug
フィールドが一致するインスタンスのみを取得して情報を表示するようなことが可能です。
/パス/スラッグ/
ですが、これも今までの説明の通りで、このようにスラッグを指定できるようにするためには urls.py
もプライマリーキーではなくスラッグを指定可能なように変更する必要があります。
例えば下記のように urls.py
を変更すればスラッグを URL で指定できるようになります。そして、この際、URL で指定されたスラッグは 変数名
という変数名で扱われることになります。
from django.urls import path
from . import views
urlpatterns = [
path('パス/<slug:変数名>/', views.DetailViewのサブクラス.as_view()),
]
それに対し、DetailView
は、URL で指定されたスラッグをクラス変数 slug_url_kwarg
で定義される変数名で扱うようになっています。そして、slug_url_kwarg
のデフォルト設定は 'slug'
となっていますので、slug_url_kwarg
を定義しない場合は、URL で指定されたスラッグを slug
という変数名で扱えるよう urls.py
を作成しておく必要があります。
そのため、例えば下記のように、urls.py
でスラッグを扱う変数名を slug
として定義する必要があります。
from django.urls import path
from . import views
urlpatterns = [
path('パス/<slug:slug>/', views.DetailViewのサブクラス.as_view()),
]
これにより、URL で指定されたスラッグを DetailView
が受け取り、そのスラッグと slug
フィールドが一致するインスタンスが取得され、そのインスタンスがコンテキストとしてテンプレートファイルに渡されるようになります。また、この slug
フィールドとは、具体的にはモデルクラスに定義するslug
フィールドのことになります。
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)
要は、この Comment
の場合、URL で指定したスラッグと、 slug
フィールドに格納されている文字列とが一致するインスタンスが Comment
のテーブルから検索され、見つかった場合にそのインスタンスが取得されることになります。この時、各インスタンスの slug
フィールドが参照されることになりますので、スラッグを URL で指定できるように urls.py
を作成した場合は、インスタンス取得先のモデルクラスには slug
フィールドが必要になることになります。このフィールドが存在しない場合は下記のような例外が発生することになります(後述で説明しますが、スラッグを扱うフィールド名は slug
から別のものに変更することも可能です)。
Cannot resolve keyword 'slug' into field. Choices are: date, id, text
ここまでが、スラッグで取得するインスタンスを1つに特定する際の説明になります。で、ここで注目しているクラス変数 slug_url_kwarg
は、前述の通り DetailVew
のサブクラスがスラッグを受け取る変数名を指定するクラス変数となります。したがって、この slug_url_kwarg
を変更することで、DetailView
のサブクラスが受け取る変数名を変更することが可能です。
例えば、slug_url_kwarg
を下記のように定義すれば、CommentDetail
は comment_slug
という変数名でスラッグを受け取るようになります。そして、この comment_slug
という変数名の文字列が slug
フィールドと一致するインスタンスが Comment
のテーブルから取得されるようになります。
from django.views.generic import DetailView
from .models import Comment
class CommentDetail(DetailView):
model = Comment
slug_url_kwarg = 'comment_slug'
で、この場合、CommentDetail
は comment_slug
という変数名でスラッグを受け取るのですから、urls.py
では下記のようにスラッグを comment_slug
という変数名で指定できるようにしておく必要があります。
from django.urls import path
from . import views
urlpatterns = [
path('パス/<int:comment_slug>/', views.CommentDetail.as_view()),
]
slug_field
slug_url_kwarg
が “DetailView
のサブクラスがスラッグを受け取る変数名” を指定するクラス変数であるのに対し、ここで紹介する slug_field
は、スラッグを格納するフィールド名を指定するクラス変数となります。
前述の通り、DetailView
のサブクラスはスラッグを受け取り、そのスラッグと slug
フィールドとが一致するインスタンスをテーブルから取得します。つまり、スラッグの文字列が格納されているフィールドが slug
であることを前提に動作します。
このように、スラッグの文字列が格納されているフィールドが slug
であることを前提に動作するのは、クラス変数 slug_field
のデフォルトが 'slug'
となっているためになります。逆に言えば、slug_field
を定義し直してやれば、他のフォールドをスラッグとして扱うことが可能となります。
例えば、slug_url_kwarg
と slug_field
を下記のように定義すれば、CommentDetail
は comment_slug
という変数名でスラッグを受け取るようになります。そして、この comment_slug
という変数名の文字列が slug_field
で指定するフィールド、下記の場合は text_slug
フィールドと一致するインスタンスを Comment
のテーブルから取得するようになります。
from django.views.generic import DetailView
from .models import Comment
class CommentDetail(DetailView):
model = Comment
slug_url_kwarg = 'comment_slug'
slug_field = 'text_slug'
この場合、Comment
の text_slug
フィールドがスラッグとして扱われることになるため、下記のように Comment
には text_slug
フィールドを定義しておく必要があります。
from django.db import models
class Comment(models.Model):
text = models.CharField(max_length=256)
date = models.DateField(auto_now_add=True)
text_slug = models.SlugField(max_length=256, null=True, unique=True)
この辺りも、結局は View のサブクラス
と他の部分(models.py
や urls.py
等)とで話が合うように開発していくことが重要となります。
context_object_name
context_object_name
に関しては ListView の context_object_name と同様のクラス変数となります。要は、データベースから取得したインスタンスをコンテキストにセットするキーを指定するクラス変数となります。そして、この context_object_name
に指定した文字列を変数名としてテンプレートファイルから参照可能となります。
また、コンテキストには context_object_name
で指定されるキーだけでなく、'object'
というキーにもインスタンスがセットされることになります。なので、テンプレートファイルからは context_object_name
に指定した変数名 or object
を参照して情報を出力するようにしてやれば良いことになります。
ただし、DetailView
の場合はコンテキストにセットされるデータが1つのインスタンスのみとなります。ListView
の場合はコンテキストにセットされるデータはインスタンスの集合(クエリセット)であり、イテラブルなオブジェクトですが、DetailView
の場合は単なるインスタンスなので、そのデータに対して for
ループを実行するようなことはできません。
また、context_object_name
を定義しなかった場合、context_object_name
にはデフォルトでインスタンスの取得先のモデルクラスの 'モデルクラス名'
(小文字) が設定されることになっています。
したがって、例えば下記のようにモデルクラスを定義した場合、
from django.views.generic import DetailView
from .models import Comment
class CommentDetail(DetailView):
model = Comment
context_object_name
が定義されていないため、テンプレートファイルからは下記のように comment
を参照する or object
を参照する必要があります。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Example of DetailView</title>
</head>
<body>
<main>
<h2>コメント({{ comment.id }})</h2>
<table>
<tbody>
<tr><td>ID :</td><td>{{ comment.id }}</td></tr>
<tr><td>TEXT :</td><td>{{ comment.text }}</td></tr>
<tr><td>DATE :</td><td>{{ comment.date }}</td></tr>
</tbody>
</table>
</main>
</body>
</html>
それに対し、例えば下記のように context_object_name
を定義した場合、
from django.views.generic import DetailView
from .models import Comment
class CommentDetail(DetailView):
model = Comment
context_object_name = 'comment_obj'
今度はテンプレートファイルからは下記のように comment_obj
を参照する or object
を参照する必要があります。comment
では参照できなくなります。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Example of DetailView</title>
</head>
<body>
<main>
<h2>コメント({{ comment_obj.id }})</h2>
<table>
<tbody>
<tr><td>ID :</td><td>{{ comment_obj.id }}</td></tr>
<tr><td>TEXT :</td><td>{{ comment_obj.text }}</td></tr>
<tr><td>DATE :</td><td>{{ comment_obj.date }}</td></tr>
</tbody>
</table>
</main>
</body>
</html>
基本的には、context_object_name
は定義せずにテンプレートファイルからは object
or モデルクラス名
(小文字) を参照するようにしてやれば良いと思いますが、既にテンプレートファイルを用意しており、参照する変数名が他のものである場合は、その変数名に合わせて context_object_name
を変更してやればテンプレートファイル側の変更なしにアプリを動作させることも可能となります。
スポンサーリンク
template_name
ここからは、ここまで話の中にも挙がっていたテンプレートファイルに関するクラス変数について説明していきます。まず template_name
はビューから利用するテンプレートファイルのパスを指定するクラス変数となります。このパスには、アプリの templates
フォルダから見た相対パスを指定します。
DetailView
の場合、デフォルトでは使用するテンプレートファイルのパスとして アプリ名/モデルクラス名_detail.html
が設定されるようになっているため、DetailView
を継承するクラスを動作させる際は、あらかじめこのパスにテンプレートファイルを用意しておくか、template_name
の定義を行い、template_name
に指定したパスにテンプレートファイルを用意しておく必要があります。
例えば、forum
というアプリの views.py
で下記のように CommentDetail
を定義した場合、
from django.views.generic import DetailView
from .models import Comment
class CommentDetail(DetailView):
model = Comment
template_name ='forum/comment.html'
テンプレートファイルは、forum/templates/forum/comment.html
に用意しておく必要があります。さらに、前述の通り、このテンプレートファイルでは context_object_name
に指定した変数名 or context_object_name
のデフォルト設定の変数名 or object
という変数名のいずれかでインスタンスを参照するようにしておく必要があります。
template_name_suffix
先ほど、template_name
を定義しなかった場合、テンプレートファイルのパスとして アプリ名/モデルクラス名_detail.html
がデフォルトで設定されると言いましたが、この _detail
はクラス変数 template_name_suffix
を定義することで変更することも可能です。
template_name_suffix
は、その名の通りデフォルトのテンプレートファイルのパスにおけるファイル名の末尾を指定するクラス変数であり、デフォルトで '_detail'
が指定されています。
例えば、forum
というアプリの views.py
で下記のように CommentDetail
を定義した場合、
from django.views.generic import DetailView
from .models import Comment
class CommentDetail(DetailView):
model = Comment
template_name_suffix ='info'
template_name
を指定していないため、デフォルト設定のテンプレートファイルが利用されることになり、そのファイルのパスは forum/commentifno.html
となりますので、このパスにテンプレートファイルを用意しておく必要があることになります。
DetailView
のメソッド
次に DetailView
の持つメソッドを紹介していきます。DetailView
を継承したクラスを定義し、そのクラスで DetailView
の持つメソッドをオーバーライドしてやることで DetalView
とは異なる動作のクラスを実現することができるようになります。
まずはメソッド一覧を示し、続いて、その中からオーバーライドする機会が多そうなメソッドのみの詳細を説明するようにしたいと思います。
スポンサーリンク
DetailView
のメソッド一覧
DetailView
の持つメソッドの一覧は下記のようになります。あくまでもクラスのカスタマイズ目的でオーバーライドを行う可能性のあるものを挙げており、as_view
などのカスタマイズは行わないであろうメソッドは省略しています。
また、DetailView
は get
メソッドを持っており、メソッドが GET
のリクエストを受け取った場合は get
が実行され、この get
の中から各種メソッドが実行されるようになっています。
get
:リクエストのメソッドがGET
の場合の処理を実行するget_context_data
:テンプレートに渡すコンテキストを生成するget_context_object_name
:context_object_name
への指定値を取得するget_queryset
:インスタンスの集合(クエリセット)を取得するget_object
:インスタンスを取得するget_slug_field
:slug_field
を取得するget_template_names
:テンプレートファイルの名前を取得するrender_to_response
:レスポンスを返却する
この中でオーバーライドする可能性が高そうなのは get_object
くらいかなぁと思います。
get_object
ということで、ここでは get_object
のオーバーライドについてのみ解説を行なっていきます。get_object
はページに表示する1つのインスタンスを取得するメソッドになります。そして、ここで取得されたインスタンスがコンテキストにセットされてテンプレートファイルに渡され、テンプレートファイルからそのインスタンスの情報を出力することができるようになります。
ここまでの説明を読んでいただければ分かると思いますが、基本的に DetailView
は、URL で指定されるプライマリーキー or スラッグと一致するインスタンスを取得するようになっています。したがって、これら以外のインスタンスを取得してページに表示するようにしたい場合は、この get_object
をオーバーライドする必要があります。
例えば、下記のように get_object
をオーバーライドすれば、URL で指定されたスラッグが latest
である場合に投稿日が一番新しい Comment
のインスタンスが get_object
で取得されるようになります。例としてはイマイチかもしれませんが、get_object
のオーバーライドで様々なインスタンスの取得が可能となることは理解していただけるのではないかと思います。
class CommentDetail(DetailView):
model = Comment
def get_object(self, queryset=None):
slug = self.kwargs.get(self.slug_url_kwarg)
if slug != 'latest':
return super().get_object(queryset)
# 引数のquerysetをクラス変数のquerysetより優先する
if queryset is None:
queryset = self.get_queryset()
# querysetが定義されていない場合
if queryset is None:
queryset = self.model.objects.all()
# dateに対して降順に並べる
queryset = queryset.order_by('-date')
# 先頭の要素を取得
obj = queryset[0]
return obj
DetailView
が生成するコンテキスト
続いて DetailView
が生成するコンテキストについて説明しておきます。
DetailView
が生成するコンテキストには下記の要素が含まれます。2つの要素が存在しますが、これらの値は両方とも同じデータとなります。
'object'
: データベースから取得されたインスタンスcontext_object_name
: データベースから取得されたインスタンス
context_object_name
に関しては context_object_name で説明したようにクラス変数の定義で変更することも可能ですし、デフォルトは モデルクラス名
(小文字) となっています。モデルクラス名が Comment
であれば 'comment'
となります。
要は、DetailView
が生成するコンテキストには、プライマリーキーやスラッグ等で特定される1つのインスタンスのみしかセットされていません。他のデータをテンプレートファイルから参照したい場合は、extra_context で説明しているクラス変数 extra_context
を定義したり、get_context_data
のオーバーライドを行なったりしてコンテキストの要素を追加する必要があります。
スポンサーリンク
DetailView
の利用例
最後に、ここまでの説明のまとめとして、DetailView
を継承するクラスでのビューの作成例を示していきたいと思います。
プロジェクトとアプリの作成やモデルの定義・マイグレーションの手順に関しては下記ページの ListView の利用例 と同じとなりますので、ここでは説明を省略させていただきます。ListView
の解説ページの利用例を読んでいない方は、下記ページの プロジェクトとアプリの作成 と モデルの作成とマイグレーション を読んで手順を進めていただければと思います。
クラスベースビューの作成
プロジェクトやアプリ、モデルの準備が済んだ後は、本題のクラスベースビューの作成を行っていきます。今回は、ここまでの説明の通り DetailView
を継承するクラスを定義し、そのクラスをクラス変数の定義とメソッドのオーバーライドによってカスタマイズしていきます。
今回は、下記のように views.py
を変更することでクラスベースビューを作成したいと思います。一部関数ベースのビューがありますが、これは動作確認を楽に行うために複数の Comment
のインスタンスをデータベースに新規作成するものとなっています。
from django.views.generic import DetailView
from .models import Comment
from django.http import HttpResponse
class CommentDetail_pk(DetailView):
model = Comment
pk_url_kwarg = 'comment_id'
template_name_suffix = 'info'
class CommentDetail_slug(DetailView):
model = Comment
slug_url_kwarg = 'comment_slug'
template_name_suffix = 'info'
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.lower()
slug = ''.join(filter(str.isalnum, tmp))
comment = Comment(text=text, slug=slug)
comment.save()
return HttpResponse(str(len(texts)) + '個のレコードを作成しました')
この views.py
においては、DetailView
を継承するクラスとして CommentDetail_pk
と CommentDetail_slug
の2つを定義しています。クラス名が示す通り、前者は表示するインスタンスをプライマリーキーで指定し、後者はスラッグで指定した場合に動作させるクラスとして定義しています。なので、urls.py
において、プライマリーキーが指定された際には CommentDetail_pk
が、スラッグが指定された際には CommentDetail_slug
が動作するように urlpattens
を定義する必要があります。これに関しては後述で解説します。
2つクラスを定義しているところは複雑かもしれませんが、これらのクラスで定義しているクラス変数に関しては DetailView のクラス変数 で紹介したものばかりですので、詳細な説明は不要だと思います。
また、test_view
関数においては、Comment
のインスタンスのデータベースへの新規登録を行なっています。今回、特に CommentDetail_slug
では URL で指定されたスラッグが slug
フィールドと一致する Comment
のインスタンスを取得することになるため、Comment
のインスタンスには slug
フィールドが必要となります。そのため、test_view
では、Comment
のインスタンスに slug
フィールドをセットしてから save
メソッドによりデータベースへの新規登録を行うようにしています。具体的には、text
フィールドの文字列から半角英数字以外を削除したものを slug
フィールドにセットするようにしています。
したがって、例えば下記のように URL が指定されれば、slug
フィールドに hello
がセットされているインスタンスの情報が表示されることになります。
/パス/hello/
また、DetailView
を継承するクラスにおいては、インスタンスが複数取得できてしまう場合は下記のような例外が発生するようになっています。
get() returned more than one Comment -- it returned 3!
プライマリーキーに関しては重複しないようになっていますが、slug
フィールドに関してはフィールドの定義の仕方によっては重複する可能性があります。URL で指定されたスラッグと一致する slug
フィールドを持つインスタンスが複数存在する場合は上記のような例外が発生することになるため、slug
フィールドも重複しないように定義しておくことをお勧めします。
今回は、Comment
を下記のように定義し、slug
フィールドに unique=True
を指定しているため、slug
フィールドに同じスラッグがセットされた際に例外発生するようになっています。したがって、slug
フィールドが重複するようなことはありません。また、slug
フィールドをこのように定義しているため、test_view
が複数回実行されると例外が発生することになるので注意してください。
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)
ビューと 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('comment/<int:comment_id>/', views.CommentDetail_pk.as_view()),
path('comment/<slug:comment_slug>/', views.CommentDetail_slug.as_view()),
path('test/', views.test_view)
]
これらの定義より、ルートパス指定で /forum/comment/整数/
へのリクエストがあった場合に CommentDetail_pk.as_view()
の get
メソッドが実行され、/forum/comment/整数以外の文字列/
へのリクエストがあった場合に CommentDetail_slug.as_view()
の get
メソッドが実行されるようになります。
スポンサーリンク
テンプレートの作成
次はテンプレートファイルを作成します。ソースコードの変更としては、これが最後となります。
まず、CommentDetail_pk
でも CommentDetail_slug
でもクラス変数 template_name
が定義されていないため、これらのビューの両方からは DetailView
でデフォルトで設定されているパスのテンプレートファイルが利用されることになります。また、template_name_suffix = 'info'
が定義されているため、このデフォルトのパスは下記となります。このパスは templates
フォルダから見た相対パスとなります。
forum/commentinfo.html
そのため、まず forum
フォルダの下に templates
フォルダを、さらに templates
フォルダの下に forum
フォルダを作成します。そして、その最後に作成した forum
フォルダの下に commentinfo.html
を作成します。これで、CommentDetail_pk
と CommentDetail_slug
の期待するパスにテンプレートファイルが作成されたことになります。
さらに、commentinfo.html
の中身を下記のように変更して保存します。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Example of DetailView</title>
</head>
<body>
<main>
<h2>コメント({{ comment.id }})</h2>
<table>
<tbody>
<tr><td>ID :</td><td>{{ comment.id }}</td></tr>
<tr><td>TEXT :</td><td>{{ comment.text }}</td></tr>
<tr><td>DATE :</td><td>{{ comment.date }}</td></tr>
<tr><td>SLUG :</td><td>{{ comment.slug }}</td></tr>
</tbody>
</table>
</main>
</body>
</html>
上記のテンプレートファイルでは次の1つの変数を参照しています。
comment
:Comment
のインスタンス
comment
部分は object
に変更しても OK です。ですが、comment
や object
以外の他の変数名で Comment
のインスタンスを参照したい場合は context_object_name
をクラス変数として定義する必要があります。
また、テンプレートファイルから Comment
のインスタンス以外のデータを参照したい場合は、クラス変数 extra_context
の定義や get_context_data
のオーバーライドを行なってコンテキストの要素を追加する必要があります。
動作確認
最後に動作確認を行なっておきましょう!
まずは、manage.py
が存在するフォルダで下記コマンドを実行します。
% python manage.py runserver
これにより、開発用ウェブサーバーが起動しますので、ウェブブラウザから作成したアプリのページを表示することが可能となります。
最初に下記 URL をウェブブラウザで表示してみてください。この URL をウェブブラウザで表示すれば、12個分の Comment
のインスタンスが生成され、スラッグがセットされたのちにデータベースに新規登録されるようになっています。
http://localhost:8000/forum/test/
次は、下記 URL をウェブブラウザで表示しましょう!
http://localhost:8000/forum/comment/1/
URL の最後の部分には整数を指定しているため、この URL は urlpatterns
における下記の上側の行の URL パターンに一致することになります。
path('comment/<int:comment_id>/', views.CommentDetail_pk.as_view()), # こっちと一致
path('comment/<slug:comment_slug>/', views.CommentDetail_slug.as_view()),
そのため、上記の URL をウェブブラウザで表示する際には CommentDetail_pk
の get
メソッドが実行されることになります。そして、上記の URL における 1
の部分はプライマリーキーとして解釈され、これと一致するプライマリーキーを持つ Comment
のインスタンスがデータベースから取得されます。そして、そのインスタンスの情報が commentinfo.html
に埋め込まれる形で HTML が生成され、それがページとして表示されることになります。
ウェブブラウザに指定する URL の 1
部分を他の整数(12
以下)に変更すれば、また異なるページ表示が行われることが確認できると思います。これは URL で指定するプライマリーキーが変化することで、データベースから取得されるインスタンスも変化するからになります。
次は、ウェブブラウザで下記 URL を開いてみましょう!
http://localhost:8000/forum/comment/goodbye/
今度は URL の最後に文字列を指定するため、
この URL は urlpatterns
における下記の下側の行の URL パターンに一致することになります。
path('comment/<int:comment_id>/', views.CommentDetail_pk.as_view()),
path('comment/<slug:comment_slug>/', views.CommentDetail_slug.as_view()), # こっちと一致
そのため、上記の URL をウェブブラウザで表示する際には CommentDetail_slug
の get
メソッドが実行されることになります。そして、上記の URL における goodbye
の部分はスラッグとして解釈され、これと一致する slug
フィールドを持つ Comment
のインスタンスがデータベースから取得されます。さらに、そのインスタンスの情報が commentinfo.html
に埋め込まれる形で HTML が生成され、それがページとして表示されることになります。
Comment
ではスラッグを扱うフィールドのフィールド名を slug
としていますが、異なるフィールドでスラッグを扱う場合は、それに合わせて CommentDetail_slug
にクラス変数 slug_field
を定義する必要があります。
動作確認に対する説明は以上となります。
まとめ
このページでは、DetailView
および DetailView
を継承したクラスベースビューの作り方について解説しました!
DetailView
は1つのインスタンスの詳細を表示するための View のサブクラス
であり、このクラスを継承することでインスタンスの詳細を表示するページを簡単に実現できます。
DetailView
を継承するクラスを利用する際の一番のポイントは、DetailView
の期待する変数名でプライマリーキーやスラッグを渡せるように urls.py
を作成する必要があるという点になると思います。ここさえ理解すれば DetailView
を継承するクラスでのビューの実現は簡単ですので、是非この点は覚えておいてください!
このサイトでは他の View のサブクラス
についても説明していますので、是非他のページも読んでみてください!
[…] https://daeudaeu.com/django-detailview/#queryset […]
[…] だえうホームページ【Django】DetailViewの使い方(クラスベースビューでの詳… ウサでも分かる中級Django講座DeleteViewの使い方をマスターしよう – […]
[…] 環境 Py yu-nix.comユーニックス総合研究所技術記事の公開など。 だえうホームページ【Django】DetailViewの使い方(クラスベースビューでの詳… ウサでも分かる中級Django講座DeleteViewの使い方をマスターしよう – […]