1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Django】親→子?子→親?モデルのリレーション取得方法を整理してみた

Posted at

はじめに

この記事では、コードレビューを受けた際に生まれた

  • この2つのコードって結局同じ結果じゃないの?
  • どうやって使い分ければいいの?

という疑問を整理しながら、「親→子」・「子→親」関係のアクセス方法についてアウトプットしていきます。

以下が今回のモデル構成です。
ユーザー、投稿、いいねの3つを用意しました。

class User(models.Model):
    name = models.CharField(max_length=100)


class Post(models.Model):
    user = models.ForeignKey(User, related_name="posts", on_delete=models.CASCADE)
    content = models.TextField()


class Like(models.Model):
    user = models.ForeignKey(User, related_name="likes", on_delete=models.CASCADE)
    post = models.ForeignKey(Post, related_name="likes", on_delete=models.CASCADE)

どっちも同じ結果になるコード

今から2つのコードを比較していきます。
以下のコードの目的は
プロフィールページにおいてユーザーがいいねした投稿一覧を取得することです。

① 子→親 (Like起点)

user = User.objects.get(id=1)
likes = Like.objects.filter(user=user).select_related("post")
posts = [like.post for like in likes]

実行順序は以下です。

  1. users テーブルから IDが「1」のユーザーを取得
  2. このユーザーが押した「いいね(Like)」をすべて取り出す
  3. Like に紐づく投稿(Post)も、DBから一緒に取得(select_related("post")
  4. for文で、各 Like に対して投稿を取得してリストにする

② 親→子 (User起点)

user = User.objects.get(id=1)
likes = user.likes.select_related("post")
posts = [like.post for like in likes]

実行順序は以下です。

  1. usersテーブルから IDが「1」のユーザーを取得
  2. このユーザーが持つ「いいね(user.likes)」をすべて取り出す
  3. Like に紐づく投稿(Post)を、DBから一緒に取得(select_related("post")
  4. for文で、各 Like から投稿を取り出してリスト化

細かく説明しているので2,3はあえて分けています

どのようなケースで利用するか

同じ結果を得られる2つのコードでも、「何を起点にしてデータを取得するか」で使い分けができます。

子→親

Like, Post, Comment など「アクション的なデータ」からユーザーや投稿にアクセスしたいときです。

具体例

  • 通知一覧:誰がどの投稿にいいね・コメントしたか
  • タイムライン:投稿+投稿者情報
  • リアクション履歴:最近のいいね履歴など
# 投稿+投稿ユーザー情報をタイムラインで取得
def get_post_list():
    return Post.objects.select_related('user')

posts = get_post_list()

for post in posts:
    print(post.message)           # 投稿本文
    print(post.user.username)     # 投稿したユーザー名
    print(post.user.profile_img)  # 投稿したユーザーのプロフィール画像

親→子

ユーザーを起点に、関連する投稿・いいね・コメントなどを取り出したいときです。

具体例

  • プロフィールページ
    • 投稿一覧
    • いいね一覧
user = User.objects.get(id=1)

# 「ユーザーが」.「いいねした」.「投稿一覧」
user.likes.select_related("post")
posts = [like.post for like in likes]

今回のケースでは

今回のように「特定のユーザーがいいねした投稿一覧を取得する」という目的では
親→子(User起点) でデータを取得するのがベターです。

理由は、そのユーザーに紐づいたデータ(いいね)を扱っているため、user.likes.select_related("post") のように記述することで、可読性が高くなり、将来的にコードの保守や拡張もしやすくなるからです。

最後に

今回のアウトプットでは
以下の3点が理解できました。

  • 「親→子」「子→親」は、目的や視点に応じて使い分ける
  • ユーザーが中心なら user.likes.select_related(...) のように親→子が自然
  • 通知や全体表示など、アクションを起点にするなら子→親も有効

もし、私のアウトプットで間違っている部分や
もっと別の書き方がいいんじゃないかと思う方は
是非コメントをお願い致します。

最後まで読んでいただきありがとうございました。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?