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
을 사용할 수 없게 되지만, 같은 기능을 하며 훨씬 직관적으로 이해할 수 있는 기능을 한다.
예를 들어, 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
이 필수인 경우가 언제일까?
바로 한 클래스에서 서로 다른 두 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이 job
과 choice_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은 같은 원리로 동작하기 때문에 혼동하지 말도록 하자