헷갈리는 related_name.. 확실히 알고 가자

Sua·2021년 2월 18일
0

Django

목록 보기
18/23
post-thumbnail

너무나 헷갈리는 related_name...
제대로 이해하지 않고 넘어가려다 호되게 당했다!

모델 작성 리뷰에서 related_name에 대한 코멘트를 받았다.

related_name이 언제 쓰이는 건지는 알고 있었는데,
related_name 안의 내용을 어떻게 지어줘야 할지 모르고 있었던 것이다.

(코멘트와 함께 빛소헌님의 블로그를 읽어보라고 추천하셨다! related_name이 어떻게 쓰이는지 알고 싶으면 이걸 보자. 엄청 도움이 된다.)

먼저 우리가 구현하고자 하는 걸 간단히 설명하면 로그인한 유저가 어떤 포스팅을 '좋아요'와 '스크랩'하는 기능이다. 이렇게!

userpostingMany-to-many관계이다.
한 유저가 여러 개의 포스팅을 좋아요할 수 있고, 한 포스팅은 여러 유저로부터 좋아요를 받을 수 있기 때문이다.

이걸 모델링으로 짜면 아래와 같다.
posting_likesposting_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에서 Followfollower 필드를 역참조해보자.

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_userto_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')

결론

  • related_name은 ForeignKey나 ManyToManyField를 가진 클래스를 정의할 때, 이 클래스가 정참조하고 있는 클래스로부터 어떤 이름으로 호출당할 지 정해주는 이름이다.
  • 깔끔하게 설명할 수 있을 정도로 완벽히 내 것으로 만들지 못했다. 장고쉘을 많이 쳐보면서 익숙해지자.
profile
Leave your comfort zone

0개의 댓글