DetailViewの使い方をマスターしよう

detailviewを使いこなすクラスベースビューをマスターしよう

↓↓↓はじめて講座を受ける方はこちら↓↓↓
開発環境を作ろう

ウサギ
ウサギ

前回はListVIewを使って日記の一覧の表示方法を勉強したな

今回はDetailViewを使って、日記の詳細画面を作る方法をやっていくぞ。

完成形の確認

ウサギ
ウサギ

下記が完成した詳細画面だ

DetailViewを使った詳細画面

仕様はこんな感じだ。

  • http://127.0.0.1:8000/<日記のID>/ にアクセスすると詳細画面が表示される
  • 詳細画面には
    • タイトル、本文、投稿者、投稿日が表示される
    • ログイン済みの場合、その日記の編集リンクがつく

DetailViewを使うとどれだけ分かりやすく、簡単に書けるかやってみるぞ!

ベースアプリの準備

前回の講座で構築済みの方は読み飛ばしてください

リポジトリのクローン

ウサギ
ウサギ

まずは日記アプリのリポジトリをクローンする

GitHub - yheihei/base_diary: ウサでも分かる中級Django講座の教材
ウサでも分かる中級Django講座の教材. Contribute to yheihei/base_diary development by creating an account on GitHub.
$ 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
日記編集画面

完成版ソースだけ見る場合

ウサギ
ウサギ

今回も完成形を書いたブランチも用意しておいた。

答えだけ先に見たいやつは下記コマンドでブランチをチェックアウトしてくれ

GitHub - yheihei/base_diary at feature/#14
ウサでも分かる中級Django講座の教材. Contribute to yheihei/base_diary development by creating an account on GitHub.
# 完成版ブランチを見たい場合はこちら
$ 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)であればcontextpost内に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フレームワークを徹底解説!

動画講座で手を動かしながら、ほとんどのことが学べます。

ウサギもここから始めました。

コメント

タイトルとURLをコピーしました