見出し画像

【サンプルコード付き】Django Viewをやさしく解説してみる②(View応用編 ~汎用ビューへの機能追加~)

1. ①のおさらい

Django Viewをやさしく解説してみる①の記事では、Djangoのバックエンド処理の肝であるViewの基礎知識を紹介しました。

紹介した内容は、HTTPリクエストの種類・関数ベースビュー・クラスベースビュー・汎用ビュー・継承・ミックスインという、Viewと関連の深い基礎知識やその使い方についてでした。まだ、読んでいない方で、上記の基礎的な使い方に自信がない方はぜひ読んでみて欲しいです。

さて、今回はViewの解説noteの第2弾です。
第2弾は、汎用ビューへの機能追加のテクニックについて解説します。

今回の解説を読んでいただくことで、
①解説する汎用ビューへの機能追加の方法を習得できる
②機能追加のための情報収集の流れを習得できる
という2点を、読者の皆さんに達成してもらえるよう解説していきます。

上記2点ができる様になれば、今回解説対象としない汎用ビューへの機能追加も皆さんご自身で行えるようになります。

Django Viewをやさしく解説してみる①の記事でも触れた通り、汎用ビューを使いこなすことで、よりスピーディな開発ができる様になります。それと併せて、洗練された分かりやすいコードを書ける様になるメリットもあるので、習得しない手はないと考えています。

本記事ではDetailView、ListViewの様な汎用ビューをより活用するために、理解しておくべきオーバーライドの基礎知識も併せて解説し、プログラミング初学者も分かりやすく説明していきます。
本記事が皆さんの学習の一助になれば大変嬉しいです。ぜひ最後まで読んでみてください!

それでは、解説していきましょう!

2. オーバーライドとは

初めに汎用ビューへの機能追加必須の手法、オーバーライドについて説明します。

オーバーライドとは、継承元のクラスで定義済みのメソッドと同名のメソッドを上書きすることを指します。この仕組みを上手く使うことで、継承したViewクラスの作成においても、機能を追加することができるようになります。

オーバーライドのポイントは、以下の2パターンで処理が異なる点ですね。
処理の変化の分かれ目となるポイントは、上書きのメソッド定義時に、super()の同名メソッドを呼び出すか否か
①super()のメソッドを呼び出さなければ、元の同名メソッドの処理は完全に消去された状態で上書きされ、②super()を呼び出していれば、元の同名メソッドの処理を残しつつ機能を追加できます。

上記のパターンの理解が完璧な方は以下の例は読み飛ばしてOKです。
以下に、オーバーライドした時の2パターンの挙動を理解できる簡単な例を記述しましたので、「理解が不十分だな」と感じた方はぜひ読んでみてください。

#継承元クラス
class Coffee(object):
    def __init__(self, price):
        self.price = price
       
    def drink(self):
        print(f'{self.price}円です。')

#継承先クラス1
class Mocha(Coffee):
    def __init__(self, color):
        self.color = color
        
    def drink(self):
        print(f'色は{self.color}です。')
        
#継承先クラス2
class Kirimanjaro(Coffee):
    def __init__(self, price, color):
        super().__init__(price)
        self.color = color
        
    def drink(self):
        super().drink()
        print(f'色は{self.color}です。')
        
#それぞれのクラスでdrinkを読んでみる
#継承元の挙動
obj_coffee = Coffee(100)
obj_coffee.drink()                            #--->'100円です。'
#継承先1の挙動(super()なし)
obj_mocha = Mocha('brown')
obj_mocha.drink()                             #--->'色はbrownです。'
#継承先2の挙動(super()あり)
obj_kirimanjaro = Kirimanjaro(100, 'brown')
obj_kirimanjaro.drink()                       #--->'100円です。'
                                              #--->'色はbrownです。'

3. 解説対象の汎用ビュー

次に、Django Viewをやさしく解説してみる①の記事で紹介した汎用ビューの中で、利用頻度の高いものをいくつか選び、機能追加のテクニックについて解説していきます。

筆者のポートフォリオ作成で追加した機能は、主にHTML埋め込み用のオブジェクト作成引き渡しでした。WEB開発に必要なスキルとしては、このデータベースとHTMLファイルの連携ができれば、基本的にはカバーできます。今回の記事でも、HTML埋め込みのためのデータ準備と引き渡しに焦点を当てて解説していきます。

今回取り上げる汎用ビューは、TemplateViewDetailViewListViewの3つです。その他のCreateView、UpdateView、DeleteViewに関しては、筆者の主観でポートフォリオ作成において使用頻度が低かった上に、機能追加をして利用するシーンが少ないと捉えているため、今回は解説から外します。

それでは早速TemplateViewから解説していきましょう!

4.1 TemplateViewへの機能追加

①機能追加の方法
機能追加にはget_context_dataオーバーライドして、処理を追加します。

super()付きでオーバーライドすることで、汎用ビューとしての機能も損なわずに機能追加できます。逆にsuper()無しのオーバーライドは元々の汎用ビューの機能を失う可能性があるので、オススメしません。

以下の例を通して、機能追加のイメージをつかみましょう。

#TemplateViewを継承したビューを作成
class ToppageView(TemplateView):
   template_name = 'toppage.html'    #使用テンプレートを指定

   #get_context_dataをオーバーライド
   def get_context_data(self, **kwargs):
       context = super().get_context_data(**kwargs)        #super()で呼び出し
       posts = Post.objects.all().order_by('-created_at')  #HTMLに引き渡すオブジェクトを作成
       context['posts'] = posts                            #contextにキーを指定して渡す

       #用意したデータを追加したcontextを返す
       #HTMLでは{{ posts }}でデータ操作→表示可能
       return context

簡単に解説します。
が、template_name=...の行までは解説を割愛します。
Django Viewをやさしく解説してみる①の記事で解説済みのため)

def get_context_data(self, **kwargs):以降では、TemplateViewで既存のget_context_dataをオーバーライドしています。
デフォルトのget_context_dataの挙動は、URL等からキーと値の決まる辞書型変数をHTMLに渡す働きをしています。例えば、'/blogs/<int:pk>/'というURLの場合、{'pk': pk}がHTMLに渡されています。
つまり、HTMLで{{ pk }}と記述すればURLに含まれる整数を表示させることができます。

上記の例ではその辞書型変数contextに、"posts"というキーでPostモデルから作成したオブジェクトリストを追加しています。

これによって、toppage.html側で埋め込みのPython構文を書くことで、渡したpostsを1件ずつ表示させたりできる様になりました。1件ずつの表示方法は詳細な説明は割愛しますが、{% for post in posts %}〜{% endfor %}構文を使えば表示可能です。

②機能追加までの情報収集の流れ
どの様に情報収集して、get_context_dataのオーバーライドによって機能追加できると分かるのか、解説していきます。

重要なのは、TemplateViewがデフォルトでどんなメソッドを備えているのか。それを紐解くためにTemplateViewの継承関係を把握しましょう。
以下は、TemplateViewの定義文となるソースコードです。ブラウザで「Django TemplateView git」と検索すれば確認可能です。

TemplateViewソース

こうして確認してみると、TemplateViewはGETリクエストでURLアクセスした際の動作が既に定義されています。
デフォルトで実行される動作には、get_context_dataの呼び出しでcontextを作成する処理と、render_to_responseの呼び出しで表示するHTMLファイルを指定する処理が含まれています。
次に、これら2つのメソッドがどういう処理をしているのか、確認していきます。継承やミックスインされているどこかのクラスに定義されているため、同じくソースを検索しつつ探していきます。

■get_context_dataソース
URLに含まれる変数をkwargsとして、オブジェクトの辞書型変数にして戻り値にセットする処理を行なっています。なので、キーを追加して値を追加することも可能です。

get_context_dataソース

■render_to_responseソース
contextを引数として、HTMLファイルにrenderする処理を行なっています。なので、contextの内容を変更してもTemplateViewそのものの処理は変わらない様です。

render_to_responseソース

という流れで解読を進めて、get_context_dataをオーバーライドする中でcontextをアップデートして、HTMLに埋め込みたいデータを付け加えるという方針に至っています。

最終的には①で記述した様に、オーバーライドしたget_context_dataのなかでcontextにpostsというキーを追加してHTMLに埋め込みたいデータを渡して、機能を追加することにしています。

以降の汎用ビューへの機能追加もまた、基本的には②で紹介した流れ(GETリクエスト時に呼ばれるメソッドの確認⇨メソッドの挙動確認⇨機能追加に適したメソッドをオーバーライドして更新)で進めていくと良いかと思います。

4.2 DetailViewへの機能追加

以降は②の情報収集の流れは割愛しつつ、①の機能追加の方法を解説していきましょう。

①機能追加の方法
機能追加にはget_object、またはget_context_dataをオーバーライドして、処理を追加します。

細かい解説は割愛しますが、以下の例はマイページ用のビューとする前提で作成しています。

#マイページ用のビュー
class Mypage(DetailView):
    model = CustomUser
    template_name = 'mypage.html'
    context_object_name = 'user'
    
    #URLから判定されるアクセスユーザーと、ログインユーザーが異なれば403エラーページを出力
    def get_object(self, request, queryset=None):
        user = super().get_object()
        if user != self.request.user:
            raise Http403
        return user
        
    #HTMLにログインユーザーに紐づくLike('お気に入り'という設定)を全て渡す処理を追加
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['like_posts'] = Like.objects.filter(user=self.request.user)
        return context

これで処理を追加できる理由は、TemplateViewの解説の時と変わらずデフォルトのDetailViewのソースでGETリクエストの際にこれらのメソッドが呼び出されているためですね。

ただ、機能追加をする際、get_objectとget_context_dataのどちらに追加すればいいか迷うかと思います。
参考までに筆者の使い方を説明しておきます。筆者は、get_context_dataHTMLに引き渡す追加データの設定のために使います。一方、get_objectアクセス制限を設定するために使います。
アクセス制限の例としては、非公開ページへのアクセス拒否・ログインユーザーのみのアクセス許可・特定ユーザーのみのアクセス許可などがあります。(本節のサンプルコード↑の例は、特定ユーザーのみのアクセス許可に該当)

DetailViewソース

4.3 ListViewへの機能追加

最後にListViewの解説です。同様に①機能追加の方法を解説していきます。

①機能追加の方法
機能追加にはget_context_dataをオーバーライドして、処理を追加します。

DetailViewと同様、細かい解説は割愛しますが、以下の例は記事投稿サイトの閲覧ランキングページの前提で作成しています。

#ランキングページ用のビュー
class Ranking(ListView):

    template_name = 'ranking.html'
    model = BlogPost
    context_object_name = 'blog_posts'
    paginate_by = 10
    
    #HTMLにログインユーザーに紐づくRecommend('オススメ記事'という設定)を全て渡す処理を追加
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['recommends'] = Recommend.objects.filter(user = self.request.user)
        return context

こちらもTemplateViewの解説と同様、デフォルトのListViewのソースでGETリクエストの際にこのメソッドが呼び出されるため、get_context_dataのオーバーライドで機能追加可能という仕組みでした。

5. 最後に

いかがでしたか?

Djangoに限らずWeb開発を始めたばかりだと、どうしてもクラス・継承といった難しそうな分野の理解を避けて実装したくなると思います。ただ、大前提で押さえて欲しいのは”使えると便利だから”こういう仕組みが備わっています。

使いこなせると開発がもっとスピーディになりますし、より分かりやすいコードになっていくと思うので、ぜひ汎用ビューをうまく活用した開発ノウハウを習得していただけると嬉しいです!

また、別のDjangoファイルの解説記事も充実させていきます!
お楽しみに!

6. おまけ

今回解説に当たって、汎用ビューの継承関係を情報収集するために、筆者がよく参考にしているサイトがあるので、共有しておきます。↓
こちらのサイトがDjangoの汎用ビューの継承関係や、デフォルトで備わっているメソッド・オプション世界一分かりやすくまとめているので、参考にしない手はないです。


よろしければサポートをお願いします🙇‍♂️ いただいたサポートは、クリエイターの活動資金として使わせていただきます😌 活動を通してえた経験を、また記事として皆さんと共有します👍