前回はListVIewを使って日記の一覧の表示方法を勉強したな
今回はDetailViewを使って、日記の詳細画面を作る方法をやっていくぞ。
完成形の確認
下記が完成した詳細画面だ
仕様はこんな感じだ。
- http://127.0.0.1:8000/<日記のID>/ にアクセスすると詳細画面が表示される
- 詳細画面には
- タイトル、本文、投稿者、投稿日が表示される
- ログイン済みの場合、その日記の編集リンクがつく
DetailViewを使うとどれだけ分かりやすく、簡単に書けるかやってみるぞ!
ベースアプリの準備
リポジトリのクローン
まずは日記アプリのリポジトリをクローンする
$ git clone https://github.com/yheihei/base_diary.git
$ cd base_diary
マイグレーションと初期データ投入
次にマイグレーションとloaddataによる初期データの投入だ
$ python manage.py migrate
$ python manage.py loaddata initial.json
この状態でhttp://127.0.0.1:8000/
を開くと。。。
よし、日記一覧が表示されたね
日記の記事を追加したり、編集したりする場合はhttp://127.0.0.1:8000/admin/diary/post/
に入って行う。
ログインできるユーザー名とパスワードは下記だ。
UserName: yhei
Password: password
完成版ソースだけ見る場合
今回も完成形を書いたブランチも用意しておいた。
答えだけ先に見たいやつは下記コマンドでブランチをチェックアウトしてくれ
# 完成版ブランチを見たい場合はこちら
$ git fetch && git checkout feature/#14
DetailViewで機能を作ってみる
表示したい投稿を取得する
まずは表示したい投稿(Post)をDBから引っ張ってくる。
と言っても記述は簡単だ。
下記をdiary/views.py
に追加してくれ。
from .models import Post
from django.views.generic.detail import DetailView
class PostDetailView(DetailView):
model = Post
DetailViewを継承したクラスを作り、model変数にPostモデルを追加するだけだ。
次はルーティングの設定だ。
diary/urls.py
を下記のように記載する。
from django.urls import path
from . import views
from diary.views import PostDetailView
app_name = "diary"
urlpatterns = [
path("", views.index, name="index"),
# これを追加
path('<int:pk>/', PostDetailView.as_view(), name='post-detail'),
]
これで、http://127.0.0.1:8000/<投稿のID>/にアクセスすると、PostDetailViewの処理にたどり着く。
DBからの取得はDetailViewが全部やってくれる。
テンプレートを用意する
DetailViewで取得したPostを表示するテンプレートを用意する。
テンプレートの命名は、クラス名で勝手に決まる。
PostDetailView
だったら、テンプレートはアプリ名/post_detail.html
になる。
下記のように、template_name
変数を使って、直接指定することも可能だ。
class PostDetailView(DetailView):
model = Post
# テンプレートの名前を指定する
template_name = 'diary/post_detail.html' # 未指定の場合 アプリ名/post_detail.html
ではテンプレートを作っていく。
diary/templates/diary/post_detail.html
を作成し、下記を記述する。
<html>
<head>
<title>とあるエンジニアの日記帳</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
</head>
<body>
{% load static %}
<header class="container pt-3">
<h1>とあるエンジニアの日記帳</h1>
</header>
<main role="main" class="container pt-3 pb-3">
<article>
<h2>{{ object.title }}</h1>
<div class="mt-3">{{ object.body |safe }}</div>
<div class="mt-3">
<div>投稿者 : {{ object.user }}</div>
<div>{{ object.updated_at }}</div>
<div>カテゴリー :
{% for category in object.categories.all %}
<span>{{ category }},</span>
{% endfor %}
</div>
</div>
</article>
</main>
</body>
</html>
object
という名前で、投稿の情報がPostモデルでやってくる。
あとは一覧表示と同じように、各コンテンツをobject.xxxx
で表示していけば良い。
では、http://127.0.0.1:8000/1/
にアクセスしてみよう。
できた! 記述少ねえ〜〜!
今はobject
という名前でPostの情報がやってくるが、
下記のようにcontext_object_name
を指定して任意の名前に変更可能だ。
class PostDetailView(DetailView):
model = Post
# objectの名前を変更する
context_object_name = 'post' # 指定しない場合 object という名前でcontextに渡される
こっちの方が可読性が高くて俺は好きだな。
クエリを動的に変更する
で、だ。
ListViewの講座の時もやったが、カテゴリーの部分がN+1問題を起こしている。
クエリにprefetch_relatedを指定して、カテゴリーテーブルをJOINしておいたほうがいいな。
取得時のクエリを操作する場合は、queryset
変数で指定する。
class PostDetailView(DetailView):
model = Post
...
# 取得するクエリを指定
queryset = Post.objects.select_related(
'user', # あらかじめuserテーブルをjoinしておく
).prefetch_related(
'categories', # あらかじめcategoryテーブルをjoinしておく
)
今回は使わないが、リクエストに応じてクエリを変更したい場合、get_queryset
関数で変更可能だ。
class PostDetailView(DetailView):
...
def get_queryset(self):
'''
querysetの編集
'''
queryset = super().get_queryset()
# 条件によってquerysetを変えたければここに書く
if self.request.GET.get('何がしかのクエリストリング'):
queryset.filter(hoge=self.request.GET.get('何がしかのクエリストリング'))
return queryset
contextを編集する
ラストはcontextに編集リンクを追加するぞ。
その投稿に紐ついた編集リンクを画面に表示する。
そのためには、まずはget_context_data関数をオーバーライドする。
class PostDetailView(DetailView):
...
def get_context_data(self, **kwargs):
'''
contextを編集する
'''
# contextの取得
context = super().get_context_data(**kwargs)
# 編集ページのURLを追加
if self.request.user.is_authenticated and context[self.context_object_name]:
post = context[self.context_object_name]
post.edit_url = reverse(f'admin:{post._meta.app_label}_{post._meta.model_name}_change', args=[post.id] )
return context
get_context_data
中で、ログイン済み(user.is_authenticated
)であればcontext
のpost
内にedit_url
を追加して返却する。
これで、テンプレートにedit_url
が渡る。
あとはテンプレート側で表示するようにしてやれば。。。
<article>
...
<div>カテゴリー :
{% for category in object.categories.all %}
<span>{{ category }},</span>
{% endfor %}
</div>
{% if post.edit_url %}
<a href="{{ post.edit_url }}">編集</a>
{% endif %}
</div>
</article>
うおお、編集リンクが出てきた
まとめ
DetailViewの使い方をまとめるぞ
model
変数で取得したいモデルを指定する- テンプレート名はクラス名から勝手に決まる。指定もできる
- 取得するクエリは
queryset
変数で決める。動的に変更したい場合はget_queryset
関数中で変える - contextを修正、追加したい場合は
get_context_data
関数で行う
こうして見ると、ListVIewと被っているところが多いな
そうだ。
だからこの先どのクラスベースビューを見ても、だいたい似たような仕組みが出てくる
想像を働かせながら、引き続きクラスベースビューをマスターしていくぞ
\ 講座で学んだことを即アウトプットしよう /
次の講座
Djangoの基礎を学びたい方は
Djangoの基礎を固めたい方はこちら(セール時に買うのがおすすめ)
『Djangoパーフェクトマスター』〜インスタ映えを支えるPython超高速開発Webフレームワークを徹底解説!
動画講座で手を動かしながら、ほとんどのことが学べます。
ウサギもここから始めました。
コメント