Django : related_name 낯설다 너...

채록·2021년 2월 18일
6

Python & Django

목록 보기
25/34
post-thumbnail

유저-게시글 간의 좋아요 기능 관련

전에 여기서 내가 적은 내용은 다음과 같다.

하지만, 이미 게시글을 올리는 유저 라는 뜻으로 User를 FK로 지정해 줬었다. 한번 FK 지정에 사용한 class를 별도 옵션 없이 또 다시 지정하려니 다음과 같은 에러가 발생했다.
(대충 에러 메시지 사진)
django 에서는 한가지 class를 서로 다른 용도로 FK 지정을 해준다 하여도 그 두가지를 비교할 수 없다. User class에서 게시글을 올린 사람을 뜻하는 user인지, 그 게시글을 '좋아요'한 user인지 구분 X 이럴때 새로운 이름을 부여해 준다. related_name !!

class Posting(models.Model):
    user        = models.ForeignKey(User, on_delete=models.CASCADE)
    image_url   = models.URLField(max_length=2000)
    description = models.CharField(max_length=100, null=True)
    create_at   = models.DateTimeField(auto_now_add=True)
*   like_user   = models.ManyToManyField(User, through='Like', related_name='like_user')
    class Meta:
        db_table = 'postings'

게시글 올린이와 구분하기 위해 user가 아닌 like_user라고 이름지었다.
'Like' class를 통해 User와 MtoM 관계임을 명시하고, 이때 사용되는 user는 like_user라고 related_name을 지었다.

결론 = 틀렸다.

진정한 related_name의 의미를 반대로 생각했던 것.

장고쉘로 더 많이 두들겨봤으면 알 수 있었을텐데 말이다.




처음 "어랏...?" 을 느낀건 sweethome models.py 관련 리뷰를 받고나서이다..

왜 저런 질문을 하셨는지 유추부터

"그렇다면 related_name 이름을 잘못지정하셨습니다 하핫
여기 붙이는 이름은 해당 클래스를 지칭해야합니다."





개념을 참고하려면 제대로 참고해라 욘석아

I. related_name 개념 참고


참고 예시 코드

참고했던 소헌님의 예시를 다시 살펴보면 다음과 같다.

class User(models.Model):
    name = models.CharField(max_length = 50)
    job	 = models.ForeignKey( 
            'Occupation',
            on_delete    = models.CASCADE,
            related_name = 'appliers'

flow 설명 전 심호흡,,,

FK로 Occupation을 갖고있는 job의 입장에서 User는 지원자이다. 따라서 related_name = appliers

끝!




II. MtoM관계에서 related_name 수정

본래 내 코드는 다음과 같았다.

class Posting(models.Model):
    user = models.ForeignKey('user.User')
    #생략
    like_user = models.ManyToManyField('user.'User', through='Like' related_name='posting_like_user')

이걸 참고한 코드의 상황에 맞춰 생각하면, 그리고 "해당 클래스를 지칭해야합니다"에 맞다면

class Posting(models.Model):
    user = models.ForeignKey('user.User')
    #생략
    like_user = models.ManyToManyField('user.'User', through='Like' related_name='user_like_posting')

이 된다.




III. 하나의 class에서 동일한 class를 서로 다른 의미의 FK로 받아올때 related_name

본래 내 코드는 다음과 같다

class Follow(models.Model):
    follower = models.ForeignKey('User', on_delete=models.CASCADE, related_name='follower')
    following = models.ForeignKey('User', on_delete=models.CASCADE, related_name='following')

엉망 진창이라 설명할 수도 없다. 바로 수정!



1. 코드 변경 : 1st ver.

class Follow(models.Model):
    user = models.ForeignKey('User', on_delete=models.CASCADE)
    following = models.ForeignKey('User', on_delete=models.CASCADE, related_name='follower')

일단 related_name으로 follower를 쓸거기 때문에 기존에 follower라고 이름지어졌던 field의 이름을 user로 바꾸었다. (보기 편하려고)

User를 FK로 갖고 있는 following field (얘도 user임) 입장에서 Follow 의 내용(user field의 내용=user filed에 들어갈 user)은 follower를 의미한다.
따라서 following field의 related_name 은 follower가 된다.

dshell 결과

+----+--------------+---------+
| id | following_id | user_id |
+----+--------------+---------+
|  1 |            2 |       1 |
|  2 |            1 |       3 |
+----+--------------+---------+
>>> one = User.objects.get(id=1)
>>> two = User.objects.get(id=2)
>>> three = User.objects.get(id=3)
>>> one.follower.values()
<QuerySet [{'id': 2, 'user_id': 3, 'following_id': 1}]>
>>> one.follow_set.values()
<QuerySet [{'id': 1, 'user_id': 1, 'following_id': 2}]>
>>> two.follower.values()
<QuerySet [{'id': 1, 'user_id': 1, 'following_id': 2}]>
>>> two.follow_set.values()
<QuerySet []>

아무래도 user field의 related_name을 지정해주지 않았기 때문에 기본으로 follow_set이라고 명명되었다.

one의 follower를 무사히 불러왔다!!



2. 코드 변경 : 2nd ver.

그런데 field 이름이 user 인것이 좋아보이지 않는다. 다시 field 이름을 user에서 follower로 바꾸자

class Follow(models.Model):
    follower = models.ForeignKey('User', on_delete=models.CASCADE)
    following = models.ForeignKey('User', on_delete=models.CASCADE, related_name='following')

dshell 결과

+----+--------------+-------------+
| id | following_id | follower_id |
+----+--------------+-------------+
|  1 |            2 |           1 |
|  2 |            1 |           3 |
|  3 |            1 |           4 |
|  4 |            1 |           5 |
|  5 |            1 |           6 |
+----+--------------+-------------+
>>> one.follower.values()
<QuerySet [{'id': 2, 'follower_id': 3, 'following_id': 1}, {'id': 3, 'follower_id': 4, 'following_id': 1}, {'id': 4, 'follower_id': 5, 'following_id': 1}, {'id': 5, 'follower_id': 6, 'following_id': 1}]>
>>> one.follow_set.values()
<QuerySet [{'id': 1, 'follower_id': 1, 'following_id': 2}]>

에러는 다음과 같은 때에 발생했다.

>>> one.following.values()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'User' object has no attribute 'following'

following 이라는 개념은 User class에 없다. follower 가 된건 related_name 때문이라는 것!!!!



3. 코드 변경 : 3rd ver.

그런데 바꾸고 나니 여전히 follow_set이 사용되는게 불만이다.

class Follow(models.Model):
    follower = models.ForeignKey('User', on_delete=models.CASCADE)
    following = models.ForeignKey('User', on_delete=models.CASCADE, related_name='following')

그래서 지정해 줬다. related_name

dshell 결과

>>> one = User.objects.get(id=1)
>>> two = User.objects.get(id=2)
>>> three = User.objects.get(id=3)
>>> one.follower.values()
<QuerySet [{'id': 2, 'follower_id': 3, 'following_id': 1}, {'id': 3, 'follower_id': 4, 'following_id': 1}, {'id': 4, 'follower_id': 5, 'following_id': 1}, {'id': 5, 'follower_id': 6, 'following_id': 1}]>
>>> one.following.values()
<QuerySet [{'id': 1, 'follower_id': 1, 'following_id': 2}]>

one을 following하는 즉 one의 follower를 찾고 싶다면 one.follower 에서 follower_id를 본다.
one이 following하는 user를 찾고싶다면 one.following 에서 following_id를 본다.



4. 코드 변경 : 4rd ver.

follow 관계는 방향성이 있다고 생각한다.

누구를 향하면, 누구팔로잉
누가 향하면, 누가 팔로잉 = 는 그 누구팔로워

이를 적용해 field의 이름을 바꿔주었다.

class Follow(models.Model):
    from_user = models.ForeignKey('User', on_delete=models.CASCADE, related_name='follower')
    to_user = models.ForeignKey('User', on_delete=models.CASCADE, related_name='following')

dshell 결과

+----+--------------+------------+
| id | from_user_id | to_user_id |
+----+--------------+------------+
|  1 |            1 |          2 |
|  2 |            3 |          1 |
|  3 |            4 |          1 |
|  4 |            5 |          1 |
|  5 |            6 |          1 |
+----+--------------+------------+
>>> from user.models import *
>>> one = User.objects.get(id=1)
>>> two = User.objects.get(id=2)
>>> three = User.objects.get(id=3)
>>> one.follower.values()
<QuerySet [{'id': 2, 'from_user_id': 3, 'to_user_id': 1}, {'id': 3, 'from_user_id': 4, 'to_user_id': 1}, {'id': 4, 'from_user_id': 5, 'to_user_id': 1}, {'id': 5, 'from_user_id': 6, 'to_user_id': 1}]>
>>> one.following.values()
<QuerySet [{'id': 1, 'from_user_id': 1, 'to_user_id': 2}]>

one을 following하는 즉 one의 follower를 찾고 싶다면 one.follower 에서 from_user_id를 본다.
one이 following하는 user를 찾고싶다면 one.following 에서 to_user_id를 본다.
깔끔하다.!




IV. 끝

개념 이해 혼자 하려면 답없다. 함께해서 위코드...

ManyToMany 관계에서 related_name을 설정할땐 명확한 3개의 table이 있어 좀더 수월했는데 하나의 class 에서, 같은 class를 각각 다른 의미로 FK로써 받아오는 것에 대해 related_name을 설정하려니까 약간의 뇌정지가 왔던것 같다.
shell 로 다닥다닥 두들겨 보면서 이해하는데 정말정말 많은 도움이 되었다..!

profile
🍎 🍊 🍋 🍏 🍇

2개의 댓글

comment-user-thumbnail
2021년 2월 18일

굿~ 다시 한 번 정리하고 갑니다 채록님

1개의 답글