自作クラスの継承＆オーバーライドを実用した

# 今回の問題
みなさんこんにちは！
駆け出し前エンジニアのYuyaです。
所々ツッコミポイントがあるかもしれませんが
その場合は質問してくださると今後の学習の糧になりますので、よろしくお願い致します。

今、DjangoでXのクローンアプリを作成しています。
プロフィールページでユーザー情報はそのままで
タブでポスト、リポスト、いいね、コメント
この4つを効率良くコードにする方法を探していました。

![スクリーンショット 2025-03-30 21.45.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3918502/333d3614-49c2-444f-9638-43457d7efcf8.png)

# そもそも継承とは
端的に言うと、継承とはあるクラスが
別に存在しているクラスの性質を受け継いていることです。

一例としては犬があります。
以下がイメージです。
![スクリーンショット 2025-03-30 20.46.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3918502/f222572e-75e3-4577-bdb5-0b0a9e94fad9.png)
（細かい個体差はここでは考えません）
犬種ごとに毛色や大きさが異なりますが、以下の特徴や振る舞いを持っています。
- 足が四本
- 嗅覚が人間の約100万倍
- 嬉しいと尻尾を振る

# フォルダ構成
実際の例を見ていきましょう。

以下が今回の大まかなフォルダ構成です。
```
manage.py
config/
    settings.py
    urls.py
    
apps/
    accounts/
        urls.py
        models.py
        views.py
      
    posts/
        urls.py
        models.py
        views.py
        services.py
      
    profiles/
        urls.py
        views.py
        services.py
```
# 画面遷移イメージ
![スクリーンショット 2025-03-30 22.09.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3918502/d49cc856-d243-4e8f-93b1-2bb8c09263a8.png)


# とりあえずViewに殴り書き
:::note warn
Serviceクラスが書いてありますが
コメントアウトで概要を記載して詳細は割愛します。
:::
```profiles/views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView
from django.shortcuts import get_object_or_404
from apps.accounts.models import CustomUser
from .services import ProfileService
from apps.posts.services import PostService

PROFILE_PAGE = "profile.html"

# ユーザー情報を出力するView
class ProfileView(LoginRequiredMixin, TemplateView):
    template_name = PROFILE_PAGE

    def get_context_data(self, **kwargs):
        # 既存のget_context_dataをコール
        context = super().get_context_data(**kwargs)
        # ログインユーザーを取得
        user = get_object_or_404(CustomUser, id=self.request.user.id)
        # フォロー、フォロワー情報をcontextに渡す
        context['user_relatinships'] = ProfileService.count_following_followers(user)
        return context


# プロフィールユーザーのポスト一覧を出力するView
class PostView(LoginRequiredMixin, TemplateView):
    template_name = PROFILE_PAGE

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        user = get_object_or_404(CustomUser, id=self.request.user.id)
        context['posts'] = PostService.get_my_post_list(user)
        return context
```
今回の問題で記載がある画像の通り
`ProfileView`(ユーザー情報)はうまく出力されました。

ただ、ポストタブをクリックした際にユーザー情報が出力されなくなりました。
![スクリーンショット 2025-03-30 22.38.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3918502/aa88619a-3de0-4f61-be08-cecf62f4ee20.png)

# `ProfileView`の中で`PostView`を呼びたい
困りました。
これだとユーザー情報が出力されません。

ただ、やることは明確になりました。
`ProfileView`の中で`PostView`を呼び出せば解決しそうだと考えました！！

# ここで継承 & オーバーライド
ここでやっと本題の継承が登場です。
完成のコードをみていきましょう！

```profiles/views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView
from django.shortcuts import get_object_or_404
from apps.accounts.models import CustomUser
from .services import ProfileService
from apps.posts.services import PostService

class BaseProfileView(LoginRequiredMixin ,TemplateView):
    template_name = "profile.html"
    
    def get_user_activities(self, user):
        """ 子クラスでオーバーライドするためのメソッド """
        return []

    def get_context_data(self, **kwargs):
        # 既存のget_context_dataをコール
        context = super().get_context_data(**kwargs)
        # ログインユーザーを取得
        user = get_object_or_404(CustomUser, id=self.request.user.id)
        # ユーザーのプロフィール情報
        context['user_data'] = ProfileService.format_user(user)
        # ユーザーのフォロー、フォロワー情報
        context['user_relatinships'] = ProfileService.count_following_followers(user)
        # ユーザーの投稿データ
        context['activities'] = self.get_user_activities(user)
        return context


# 継承先の BaseProfileView の get_user_activities をオーバーライド
class PostView(BaseProfileView):
    def get_user_activities(self, user):
        return PostService.get_my_post_list(user)
```
以下の6ステップで実行されます
1. `PostView.get_context_data()` が呼ばれる
2. `BaseProfileView.get_context_data()` の `super().get_context_data()` で親の `TemplateView.get_context_data()` が実行
3. `self.request.user` でログインユーザーを取得
4. `context['activities'] = self.get_user_activities(user)` で `PostView.get_user_activities(user)` が実行される
5. `PostService.get_my_post_list(user)` で投稿データを取得
6. `context` を返し、テンプレート (`template_name`) に渡す


この実行順序でユーザー情報を保ったままタブの切り替えができそうです。

またこの書き方によって、ログインユーザーの取得や
`template_name`を複数回書かずに済むといったメリットも享受することができます。

![スクリーンショット 2025-03-31 0.58.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3918502/62a45771-99e9-42ed-919a-57049e334eed.png)

これでポスト一覧が出力できました。

これならリポスト、いいね、コメントの一覧も`get_user_activities`をオーバーライドすることで取得してテンプレートで出力できそうです。

# 最後に
この問題を解決した後に、私は自問自答しました。
いや、お前`LoginRequiredMixin` ,`TemplateView`とか既に継承してんじゃんw

要は継承やオーバーライドで何が行われているかという
基礎の理解はできているが
実用ができていないことが浮き彫りになりました。

この考えをストックしたということに着目して
これからも学習を続けていきます！