[Django] ManyToMany - related_name

haejun-kim·2020년 8월 23일
5

[Django]

목록 보기
20/20

중간 테이블을 생성해주어야 하는가?

ORM 은 객체가 아닌 것을 객체로 사용하기 위해서 사용한다. Django에서는 ManyToMany Field를 사용할 경우 별도로 중간 테이블을 설정해주지 않아도 자동으로 중간테이블을 설정해준다. 이 경우 ManyToMany Field에 through 옵션을 사용하지 않을 경우 중간 테이블에 직접 접근하여 객체로 사용하는 것이 불가능하다.

이럴 경우 어떤 객체를 생성 해 주고 그 생성한 객체를 통해서 중간 테이블에 접근하여 객체를 사용해야하기 때문에 loss가 발생한다.

그렇다면 중간테이블 객체로 직접 접근해야하는 경우는 무엇이 있나? 유저가 어떤 상품을 like 한 경우 유저 전체가 like 한 항목들을 전체 보고 싶을 때의 경우가 이에 해당한다.

따라서 ManyToMany Field를 사용하더라도 중간 테이블을 직접 설정해주길 바라며, 반드시 through 옵션을 통해서 생성 한 중간 테이블이 중간 테이블이란 것을 Django에 인식 시켜주도록 하자.

정참조와 역참조 객체 서로 호출

정참조의 경우 별다른 고민 할 필요가 없다. 정참조 관계에서는 속성 이름으로 바로 접근할 수 있기 때문이다.

하지만 역잠조의 경우는 바로 접근할 수가 없다. 이럴 때 사용하는게 _set 또는 related_name이다.

  • 역참조 할 때 _set 사용

ex) 나를 바라보고 있는 것_set

이 때, classname_set 대신 사용할 수 있는 것이 related_name이다. 역참조 대상인 객체를 부를 이름을 지정해주면 된다.

즉, 정참조 하고 있는 클래스의 인스턴스에서 어떤 명칭으로 거꾸로 호출 당할지 (역참조) 정해주는 이름이다.

related_name을 사용하면 _set 을 사용할 수 없게 되지만, 같은 기능을 하며 훨씬 직관적으로 이해할 수 있는 기능을 한다.

related_name

예를 들어, User 와 지원자인 Occupation의 테이블이 존재한다고 하자. 이 경우 Occupation의 입장에서는 입사 지원자들을 appliers라고 부르는 것이 더 직관적이고 편할 것이다.

class User(models.Model):
    name	= models.CharField(max_length = 50)
    job		= models.ForeignKey( 
            'Occupation',
            on_delete    = models.CASCADE,
            related_name = 'appliers' ------------------------- [Key Point !]
        )
    created_at	= models.DateTimeField(auto_now_add = True)
    	
class Occupation(models.Model):
    name = models.CharField(max_length = 50)

Key Point 부분을 확인해보면, User 객체를 정의 할 때, job이라는 속성에 Occupation 객체가 연결되어 정참조 하고 있다.

Occupation 객체의 인스턴스와 연결되어 있는 User 객체를 거꾸로 호출할 때, appliers 라는 이름으로 부르기 위해 job 속성에 related_name = appliers 를 함께 지정해주었다.

job1   = Occupation.objects.get(id = 1)
people = job1.appliers.all()
>>> <QuerySet[<Object User Object(1)>, <Object User Object(2)>]>

_set과 마찬가지로 잘 동작하는 것을 확인할 수 있다.

그렇다고 related_name을 남발하지는 말자. 때에 따라서 _set이 훨씬 직관적인 경우가 있다. 그렇다면 related_name이 필수인 경우가 언제일까?

Related_name이 필수인 경우

바로 한 클래스에서 서로 다른 두 column이 같은 table을 참조하는 경우이다.

위의 예시에서 지원자가 필수로 신청한 occupation 외에 2지망인 occupation도 받는다고 가정해보자.

class User(models.Model):
    name	= models.CharField(max_length = 50)
    job		= models.ForeignKey('Occupation', on_delete = models.CASCADE)
[*] choice_2nd  = models.ForeignKey('Occupation', on_delete = models.CASCADE, null = True)
    created_at	= models.DateTimeField(auto_now_add = True)
    	
class Occupation(models.Model):
    name = models.CharField(max_length = 50)

User 객체에서 Occupation 객체를 정참조 하는 속성이 두 개이다. 이 경우는 Occupation 중에 developer를 1지망으로 선택한 지원자와 2지망으로 선택한 지원자가 따로 있음을 의미한다.

user1 = User.objects.create(name = 'Nick', job_id = 1) #developer
user2 = User.objects.create(name = 'Sue', job_id = 2, choice_2nd_id = 1)

user1 의 1지망은 job으로 id=1인 developer이다.

user2 의 1지망은 job id = 2이고, 2지망이 developer이다.

job1 = Occupation.objects.get(id = 1)
job1.user_set.all()

오류가 발생한다.

오류가 발생하는 이유는?

Occupation 객체를 정참조 하고 있는 column이 jobchoice_2nd 두 개이기 때문에 user_set이라는 속성만으로는 자신을 바라보고 있는 두 개의 User 객체 중에 어떤 속성에 접근해야 할 지 알 수가 없기 때문에 발생했다.

즉, developer 를 1지망으로 고른 사람(Nick)을 가져와야할 지, 2지망으로 고른 사람들의 목록(Sue)를 가져와야 할 지 알 수가 없기 때문이다.

바로 이 경우 related_name이 필수로 필요하다

이제 related_name을 입력해보자.

job		= models.ForeignKey( 
            'Occupation',
            on_delete    = models.CASCADE,
            related_name = 'appliers' ------------------------- [Key Point !]
        )
    choice_2nd  = models.ForeignKey(
            'Occupation',
            on_delete    = models.CASCADE,
            null         = True
            related_name = 'second_appliers'
        )

이렇게 수정하면 developer를 1지망으로 지원한 사람과 2지망으로 지원한 사람을 구분하여 호출 할 수 있다.

job1 = Occupation.objects.get(id = 1)

job1.appliers.all()
>>> <QuerySet[<Object User Object(1)>]> # ---> Nick

job1.second_appliers.all()
>>> <QuerySet[<Object User Object(2)>]> # ---> Sue

Foreign Key 뿐만 아니라, ManyToMany 관계에 있을 때도 related_name은 같은 원리로 동작하기 때문에 혼동하지 말도록 하자

참고

Django와 Reverse relations과 Related_name

0개의 댓글