오늘도 개발

정참조, 역참조, related_name 본문

웹 프로그래밍/Django

정참조, 역참조, related_name

Sueeeeeee 2022. 7. 6. 13:12

장고에서 일대다 관계는 ForeignKey,

다대다 관계는 ManyToManyField로 구현할 수 있다.

ForeignKey나 ManyToManyField에서 사용하는 정참조와 역참조의 개념에 대해 알아보자.

 

예시 모델

<models.py>

한 질문에는 여러 선택지가 있을 수 있지만, 한 선택지는 한 질문하고만 연결되는 일대다 관계의 모델이다.

OnetoMany 관계에서는 Many쪽 테이블에 ForeignKey를 넣는다.

아래 모델에서도 Choice에 question 속성을 추가하고 ForeignKey 필드를 생성했다.

# One
class Question(models.Model):
    question_text = models.CharField(max_length=200)

# Many
class Choice(models.Model):
    choice_text = models.CharField(max_length=200)
    question = models.ForeignKey("Question", on_delete=models.CASCADE)
    votes = models.IntegerField()

정참조 - Many쪽에서 One을 불러오기

ForeignKey 필드가 있는 테이블에서 연결된 테이블을 참조하는 것을 뜻한다.

한 Choice와 연결된 Question을 불러오는 것이 정참조이다.

 

예를 들어 id가 1번인 Choice가 갖는 Question은 다음과 같이 확인할 수 있다.

Choice에는 question 속성이 존재하므로, question을 사용하여 불러올 수 있다.

>>> Choice.objects.get(id=1).question
<Question: Question object (1)>

역참조 - One쪽에서 Many를 불러오기

ForeignKey 필드가 없는 테이블에서 연결된 테이블을 참조하는 것을 뜻한다.

 

한 Question과 연결된 Choice를 불러오는 것이 역참조이다.

예를 들어 id가 1번인 Question이 갖는 모든 Choice는 다음과 같이 확인할 수 있다.

Question에는 choice를 불러올 속성이 존재하지 않기 때문에, _set 매니저를 사용하여 불러와야 한다.

>>> Question.objects.get(id=1).choice_set.all()
<QuerySet [<Choice: Choice object (1)>, <Choice: Choice object (2)>, <Choice: Choice object (3)>]>

related_name

related_name 사용이 필수가 아닌 경우

역참조 시 _set 대신 사용할 이름을 related_name으로 설정할 수 있다. 

Choice 모델에서 related_name을 choices로 수정하면,

class Question(models.Model):
    question_text = models.CharField(max_length=200)

class Choice(models.Model):
    choice_text = models.CharField(max_length=200)
    
    # related_name 추가
    question = models.ForeignKey("Question", related_name='choices', on_delete=models.CASCADE)
    
    votes = models.IntegerField()

역참조 할 때 choice_set이 아니라 choices로 쓸 수 있다.

>>> Question.objects.get(id=1).choices.all()
<QuerySet [<Choice: Choice object (1)>, <Choice: Choice object (2)>, <Choice: Choice object (3)>]>

 

related_name를 필수로 사용해야 하는 경우

위의 예시에서는 related_name을 사용하지 않아도 되지만 related_name을 꼭 사용해야 하는 경우도 있다.

한 테이블에서 같은 테이블을 참조하는 칼럼이 두 개 이상 있는 경우엔 related_name을 사용하지 않으면 오류가 발생한다.

 

위 예시에 다음 상황을 추가해보자.

이제 사람들은 가장 중요한 Question 하나와 가장 중요하지 않은 Question 하나를 골라야 한다.

우리는 Importance 모델에 누가 어떤 모델을 가장 중요하게 생각했고, 가장 중요하지 않다고 생각했는지 저장할 것이다.

class Question(models.Model):
    question_text = models.CharField(max_length=200)

class Choice(models.Model):
    choice_text = models.CharField(max_length=200)
    question = models.ForeignKey("Question", on_delete=models.CASCADE, related_name='choices')
    votes = models.IntegerField()

class Importance(models.Model):
    name = models.CharField(max_length=200)
    important_q = models.ForeignKey("Question", on_delete=models.CASCADE)
    unimportant_q = models.ForeignKey("Question", on_delete=models.CASCADE)

 

important_q와 unimportant_q는 둘 다 같은 Question을 참조하는데 related_name을 지정하지 않았다.

이대로 마이그레이션을 하면 오류가 난다.

한 Question과 연관된 Importance를 가져오라는 역참조 명령을 내리는 경우

important_q를 가져오라는 건지 unimportant_q를 가져오라는 건지 불명확해지기 때문이다.

 

다음과 같이 related_name을 지정하면 제대로 마이그레이션을 할 수 있다.

class Question(models.Model):
    question_text = models.CharField(max_length=200)

class Choice(models.Model):
    choice_text = models.CharField(max_length=200)
    question = models.ForeignKey("Question", on_delete=models.CASCADE, related_name='choices')
    votes = models.IntegerField()

class Importance(models.Model):
    name = models.CharField(max_length=200)
    important_q= models.ForeignKey("Question", related_name='important', on_delete=models.CASCADE)
    unimportant_q= models.ForeignKey("Question", related_name='unimportant', on_delete=models.CASCADE)

이제 정참조는 다음과 같이 실행할 수 있고,

# 이름이 Snoopy인 사람이 중요하다고 고른 Question
>>> Importance.objects.get(name='Snoopy').important_q
<Question: Question object (1)>

역참조는 다음과 같이 실행할 수 있다.

# 1번 Question이 important_q에 들어있는 모든 Importance 오브젝트
>>> Question.objects.get(id=1).important.all()
<QuerySet [<Importance: Importance object (1)>, <Importance: Importance object (2)>, <Importance: Importance object (4)>]>

# 1번 Question이 important_q에 들어있는 Importance 오브젝트의 개수
>>>Question.objects.get(id=1).important.count()
3

 

 

참고

[Django] ManyToMany - related_name