너무나 헷갈리는 related_name...
제대로 이해하지 않고 넘어가려다 호되게 당했다!
모델 작성 리뷰에서 related_name에 대한 코멘트를 받았다.
related_name이 언제 쓰이는 건지는 알고 있었는데,
related_name 안의 내용을 어떻게 지어줘야 할지 모르고 있었던 것이다.
(코멘트와 함께 빛소헌님의 블로그를 읽어보라고 추천하셨다! related_name이 어떻게 쓰이는지 알고 싶으면 이걸 보자. 엄청 도움이 된다.)
먼저 우리가 구현하고자 하는 걸 간단히 설명하면 로그인한 유저가 어떤 포스팅을 '좋아요'와 '스크랩'하는 기능이다. 이렇게!
user
와 posting
은 Many-to-many관계이다.
한 유저가 여러 개의 포스팅을 좋아요할 수 있고, 한 포스팅은 여러 유저로부터 좋아요를 받을 수 있기 때문이다.
이걸 모델링으로 짜면 아래와 같다.
posting_likes
와 posting_scraps
이라는 중간테이블을 만들어서 각각 users
테이블과 postings
테이블을 참조하게 했다.
그리고 posting/models.py에서 아래와 같이 작성했다. (User 클래스는 user앱에 있다.)
class Posting(models.Model):
(...생략)
like_user = models.ManyToManyField('user.User', through='PostingLike', related_name='posting_like_user')
scrap_user = models.ManyToManyField('user.User', through='PostingScrap', related_name='posting_scrap_user')
class PostingLike(models.Model):
user = models.ForeignKey('user.User', on_delete=models.CASCADE)
posting = models.ForeignKey('Posting', on_delete=models.CASCADE)
class PostingScrap(models.Model):
user = models.ForeignKey('user.User', on_delete=models.CASCADE)
posting = models.ForeignKey('Posting', on_delete=models.CASCADE)
원래 ManyToManyField
필드에서는 related_name을 지정해주지 않아도 된다. 하지만 지금 users
테이블을 참조하는(바라보는) 테이블이 2개일 경우에는 필수이다. users
테이블에서 자신을 바라보고 있는 테이블이 무엇인지 구별하지 못하기 때문이다. (자신을 바라보고 있는 테이블이 무엇인지 알아내는 것을 역참조라고 한다.)
따라서 related_name은 posting
이 아니라 user
입장에서 작성해줘야 한다!!!
(user
---> posting
'유저에서 포스팅을 찾는다, 시작은 유저고 마무리는 포스팅이다' 요걸 머리 속에 집어 넣자.)
이제 내가 원래 지어준 related_name이 잘못되었다는 것을 알 수 있다.
related_name='posting_like_user'
related_name='posting_scrap_user'
포스팅을 좋아요한 유저가 아니라 유저가 좋아요한 포스팅을 알아내야 한다!
즉, 'posting_like_user'가 아니라 'user_like_posting'으로 수정되어야 한다.
스크랩의 경우도 마찬가지이다.
related_name을 제대로 이해하고 넘어간 줄 알았지만 아니었다.. 또륵
이 질문을 받았을 때 내가 무엇을 의도하고 related_name을 작성했는지 전혀 설명할 수 없었다.
(인스타그램 클론 때 팔로우 기능을 구현했는데, 제대로 이해하지 않고 코드만 작성한 거였다. 확실히 이해하지 않고 대충 넘어간 나 자신에게 실망스러웠다. 하지만 이번 기회로 확실히 이해할 기회가 생겨 다행이다.)
팔로우 기능에서의 related_name은 처음 문제하고 비슷한데 좀 더 헷갈린다ㅠㅠ
요렇게 follows
라는 테이블이 users
테이블에서 FK를 두 개나 가져오기 때문이다. 즉. 같은 테이블이 두 번이나 하나의 테이블을 참조하고 있다.
이럴 땐 직접 쉘로 쳐보는 게 답이다!
+----+-------------+--------------+
| id | follower_id | following_id |
+----+-------------+--------------+
| 1 | 1 | 2 |
| 2 | 1 | 3 |
| 3 | 1 | 4 |
| 4 | 1 | 5 |
| 5 | 3 | 1 |
| 6 | 5 | 1 |
+----+-------------+--------------+
이런 데이터가 있다고 생각하고, User
에서 Follow
의 follower
필드를 역참조해보자.
In : user1 = User.objects.get(id=1)
In : user1.follower.values()
Out : <QuerySet [{'id': 1, 'follower_id': 1, 'following_id': 2},
{'id': 2, 'follower_id': 1, 'following_id': 3},
{'id': 3, 'follower_id': 1, 'following_id': 4},
{'id': 4, 'follower_id': 1, 'following_id': 5}]>
우리가 이 코드에서 얻을 수 있는 유의미한 정보는 following_id
이다. follower_id
가 1인 following_id
가 보인다고 생각할 수 있다.
follower
--->following
, follower
가 검색 기준이고, following
이 검색 결과라고 생각할 수 있겠다. 그리고 우리에게 유의미한 정보는 검색된 결과이다!
따라서 user1.follower.values()
가 아니라 user1.following.values()
으로 바꿔야 논리적으로 적합한 related_name이라고 볼 수 있다.
In : user1.following.values()
Out : <QuerySet [{'id': 5, 'follower_id': 3, 'following_id': 1},
{'id': 6, 'follower_id': 5, 'following_id': 1}]>
반대의 경우도 마찬가지이다.
following
에서 follower
로 바꿔줘야 한다.
# 수정 model
class Follow(models.Model):
follower = models.ForeignKey('User', on_delete=models.CASCADE, related_name='following')
following = models.ForeignKey('User', on_delete=models.CASCADE, related_name='follower')
자 이제 적합한 related_name을 지정해주었다.
여기에서 한 단계 더 나아가보자. follow의 방향성을 명확히 드러낼 수 있도록 필드명을 from_user
와 to_user
로 바꿔준다. 이렇게 하면 related_name과 겹치지 않아서 좋다.
from_user
는 follow를 요청한 사람, 쉽게 말해 지금 접속한 user, '나'를 의미한다.
to_user
는 follow를 당한 사람
# 최종 model
class Follow(models.Model):
from_user = models.ForeignKey('User', on_delete=models.CASCADE, related_name='following')
to_user = models.ForeignKey('User', on_delete=models.CASCADE, related_name='follower')