한국어로 옮기기 어색한 단어들은 영문으로 혹은 해석이 애매한 구절은 직역한 그대로 사용 하였습니다.
다대다 관계에서 추가적인 필드
당신이 many-to-many 관계를 피자와 토핑을 매칭하고 믹싱하는것처럼 다룬다면, 보통의 ManyToManyField
로 충분하다. 그러나 가끔 두개의 모델간에 관계에 대해서 연관된 데이터가 필요할 때가 있다
예를들면, 뮤지션이 속한 뮤지컬 그룹을 트래킹 하는 어플리케이션 케이스를 고려해 보자. 사람과 그룹간에 다대다 관계가 있다. 당신은 ManyToManyField
를 관계를 표현하는데 사용할 수 있다. 그러나 해당 사용자가 그룹에 가입 한 날짜와 같이 수집 할 수있는 멤버십에 대한 세부 정보가 많이 있다.
이러한 상황들을 위해서, 장고는 다 대 다 관계를 관리하는 데 사용할 모델을 지정할 수 있다. 당신은 중간 모델에 추가적인 필드를 놓을 수 있다. 중간 모델은 매개자 역할을 할 모델을 가리 키기 위해 through
인수를 사용하여 ManyToManyField
와 연결됩니다. musician 예제를 위해서 아래와 같이 볼 수 있다.
xxxxxxxxxx
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self):
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
당신이 중간 모델을 만들었을 때, 당신은 명시적으로 외래키 관계와 연관된 모델에 대한 외래키를 지정한다 이 명시적인 표시는 어떻게 두개 모델이 관련이 있을 지를 정의한다
중간 모델에 대한 몇가지 제한 사항이 있다.
- 중간 모델은 소스 모델에 대해서 무조건 딱 1개의 외래키를 포함해야한다(위 예제에서는
Group
) 아니면,ManyToManyField.through_fields
를 사용하여 장고가 관계에 사용해야하는 외래 키를 명시 적으로 지정해야합니다. 만약 1개 이상의 외래키를 가지고through_fields
가 지정되지 않으면, 유효성 에러가 발생할 것이다. 비슷한 제한이 타겟 모델에 대한 외래 키에 적용된다. (이 예에서는Person
). - 중개 모델을 통해 자체적으로 다 대다 관계가있는 모델의 경우 동일한 모델에 대한 두 개의 외래 키가 허용되지만, 이 두개는 다대다 관계에서 두개의 다른 side 로써 다루어진다. 만약 2개의 외래키가 있다면, 당신은
through_fields
를 지정해주어야만 한다 그렇지 않으면 유효성 에러가 발생될 것이다.
지금까지 당신은 ManyToManyFied
를 중개자 모델을 사용하기 위해서 세팅하였고,(이 경우엔 Membership
) 이제 many-to-many 관계를 생성할 준비가 되었다. 이것을 중개자 모델의 인스턴스를 생성함으로서 할 수 있다.
xxxxxxxxxx
>>> ringo = Person.objects.create(name="Ringo Starr")
>>> paul = Person.objects.create(name="Paul McCartney")
>>> beatles = Group.objects.create(name="The Beatles")
>>> m1 = Membership(person=ringo, group=beatles,
... date_joined=date(1962, 8, 16),
... invite_reason="Needed a new drummer.")
>>> m1.save()
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>]>
>>> ringo.group_set.all()
<QuerySet [<Group: The Beatles>]>
>>> m2 = Membership.objects.create(person=paul, group=beatles,
... date_joined=date(1960, 8, 1),
... invite_reason="Wanted to form a band.")
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>]>
당신은 through_defaults
를 지정한다면, add()
, create()
or set()
을 관계를 생성하기 위해서 사용할 수 있다
xxxxxxxxxx
>>> beatles.members.add(john, through_defaults={'date_joined': date(1960, 8, 1)})
>>> beatles.members.create(name="George Harrison", through_defaults={'date_joined': date(1960, 8, 1)})
>>> beatles.members.set([john, paul, ringo, george], through_defaults={'date_joined': date(1960, 8, 1)})
당신은 아마도 중개자 모델의 인스턴스를 직접적으로 생성하는 것을 선호 할 수 있다.
중개 모델에서 정의한 커스텀 테이블이 (model1, model2) 쌍에 고유성을 적용하지 않는 경우, 여러 값을 허용하고 remove () 호출은 모든 중간 모델 인스턴스를 제거합니다.
xxxxxxxxxx
>>> Membership.objects.create(person=ringo, group=beatles,
... date_joined=date(1968, 9, 4),
... invite_reason="You've been gone for a month and we miss you.")
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>, <Person: Ringo Starr>]>
>>> # This deletes both of the intermediate model instances for Ringo Starr
>>> beatles.members.remove(ringo)
>>> beatles.members.all()
<QuerySet [<Person: Paul McCartney>]>
clear()
메소드는 인스턴스를 위한 모든 다대다 관계를 삭제하는데 사용될 수 있다.
xxxxxxxxxx
>>> # Beatles have broken up
>>> beatles.members.clear()
>>> # Note that this deletes the intermediate model instances
>>> Membership.objects.all()
<QuerySet []>
일단 many-to-many 관계를 만들면, 쿼리가 이슈가 될 수 있다. 일반적인 다 대다 관계와 마찬가지로 다 대다 관련 모델의 속성을 사용하여 쿼리 할 수 있다:
xxxxxxxxxx
# Find all the groups with a member whose name starts with 'Paul'
>>> Group.objects.filter(members__name__startswith='Paul')
<QuerySet [<Group: The Beatles>]>
중간 모델을 사용하고 있으므로 해당 속성에 대해 쿼리 할 수도 있다.
xxxxxxxxxx
# Find all the members of the Beatles that joined after 1 Jan 1961
>>> Person.objects.filter(
... group__name='The Beatles',
... membership__date_joined__gt=date(1961,1,1))
<QuerySet [<Person: Ringo Starr]>
만약 멤버쉽 정보에 직접 접근 해야한 다면, 당신은 멤버쉽 모델로 부터 직접 쿼리할 수도 있다
x
>>> ringos_membership = Membership.objects.get(group=beatles, person=ringo)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'
같은 정보에 접근하는 또다른 방법은 Person
객체로 부터 다대다 역 관계 로 쿼리하는 것이다.
xxxxxxxxxx
>>> ringos_membership = ringo.membership_set.get(group=beatles)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'
내 마음대로 해설
노멀하지 않은(?) 다대다 관계
문서에서는 위에서 예를 든, Pizza와 Topping의 다대다 관계가 아닌, 두 모델 간에 연관된 데이터가 필요한 경우 이를 중개해주는 모델이 필요하다.
문서에서는 Person
, Group
사이에 Membership
이라는 중개 모델을 추가하였다. 이 중개모델에서는 가입날짜와 가입이유에 대한 추가적인 필드를 가진다. 여기서 date_joined
, invite_reason
는 Person
혹은 Group
어느쪽에도 속하지 않는 필드들인데, 이런 경우가 필요할 때는 노멀하지 않은(?) 이런 중개모델을 도입하는 다대다 관계를 고려하면 좋겠다? 라는 생각이들었다.
위와 관련된 내용들은 아래 항목으로 정리해보았다 자세한 내용은 아래 링크를 참조하자
-through, through_fields, through_defaults
'개발 > 장고' 카테고리의 다른 글
[DRF] Serializers - Serializers 클래스 선언하기 (0) | 2021.01.31 |
---|---|
[DRF] Serializers - 개요 (0) | 2021.01.31 |
[Django] 다대다 관계에서 through, through_fields, through_defaults (1) | 2021.01.30 |
[Django] 다대다 관계의 모델을 직접 활용해 보자 (0) | 2021.01.30 |
[Django] 다대다 관계에서 중개 모델의 제한 사항 (0) | 2021.01.30 |