jango_모델_다대다_관게설정

다대다 관계

ManyToManyField는 다대다 관계를 정의하기 위해 사용됩니다. 이 필드를 모델에 추가하는 방법은 다른 Field 유형과 유사합니다.

ManyToManyField에는 위치 매개변수 하나가 필요합니다. 이 매개변수는 이 모델이 연결될 클래스를 참조합니다.

예를 들어, 피자와 토핑의 관계를 표현할 때 ManyToManyField를 사용할 수 있습니다. 한 피자는 여러 가지 토핑을 가지고, 한 토핑은 여러 피자에 사용될 수 있습니다.

from django.db import models

class Topping(models.Model):
    # ...
    pass

class Pizza(models.Model):
    # ...
    토핑 = models.ManyToManyField(Topping)

ForeignKey와 마찬가지로, 자신과의 다대다 관계나 미리 정의되지 않은 모델과의 관계도 설정할 수 있습니다.

다대다 필드의 이름은 보통 관계 대상 모델의 복수형 이름을 사용합니다. 예를 들어, 위 예제에서 토핑이라는 이름이 사용되었습니다.

다대다 필드를 어느 모델에 설정하는지 중요하지 않습니다. 두 모델 중 하나에만 설정하면 됩니다. 양쪽에다 설정하면 오류가 발생합니다.

참고:

폼에서 편집할 대상 모델에 다대다 필드를 설정하는 것이 좋습니다. 예를 들어, 피자 폼에서 다양한 토핑을 선택할 수 있도록 Pizza 모델에 토핑 필드를 설정하는 것이 자연스럽습니다.

추가 정보는 다대다 관계 예제를 참조하세요.

ManyToManyField 필드는 선택적 인수도 받습니다. 이 인수들은 관계의 작동 방식을 결정하는데 사용됩니다. 이 인수들은 필수적이지 않습니다.

다대다 관계의 중간 모델

간단한 다대다 관계(예: 피자와 토핑)에서는 기본 ManyToManyField로 충분합니다. 그러나, 두 모델 사이의 관계에 추가 정보가 필요한 경우에는 중간 모델을 사용합니다.

예를 들어, 음악가와 음악 그룹의 관계를 기록하는 경우, 멤버십 정보(가입일, 초대사유 등)를 저장하기 위해 중간 모델을 사용할 수 있습니다.

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=128)

    def __str__(self):              # __unicode__ on Python 2
        return self.name

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(Person, through='Membership')

    def __str__(self):              # __unicode__ on Python 2
        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)

중간 모델을 사용할 때는, 양쪽 모델에 대한 외래 키를 명시적으로 지정해야 합니다.

중간 모델의 제한:

  • 중간 모델은 원본 모델(예: 그룹)에 대해 하나의 외래 키를 가지고 있어야 합니다. ManyToManyField.through_fields 인수를 사용하여 중간 모델의 외래 키를 지정할 수 있습니다. 만약 중간 모델에 두 모델에 대한 외래 키가 하나 이상일 경우, through_fields를 지정하지 않으면 오류가 발생합니다.
  • 자가 다대다 관계(모델이 자신과의 관계를 가지는 경우)에서는, 중간 모델이 원본 모델에 대한 두 외래 키를 가질 수 있지만, 이는 관계의 양방향을 나타냅니다. 만약 두 이상의 외래 키가 있을 경우, through_fields를 지정해야 합니다.
  • 자가 다대다 관계에서 중간 모델을 사용할 때, symmetrical=False를 설정해야 합니다.

다대다 필드를 통해 중간 모델을 사용하면, 중간 모델의 인스턴스를 생성해야 합니다.

>>> 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>]>

기본 다대다 필드와 달리, 중간 모델을 사용한 다대다 관계에서는 add(), create(), set()을 사용할 수 없습니다.

>>> # 다음 명령들은 실행할 수 없습니다
>>> beatles.members.add(john)
>>> beatles.members.create(name="George Harrison")
>>> beatles.members.set([john, paul, ringo, george])

이유는 중간 모델에 필요한 추가 정보를 제공할 수 없기 때문입니다.

remove() 메서드도 비활성化되어 있습니다. 중간 모델의 유일성 제약이 없을 경우, 어떤 중간 모델 인스턴스를 삭제해야 하는지 명확하지 않기 때문입니다.

>>> 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 will not work because it cannot tell which membership to remove
>>> beatles.members.remove(ringo)
그러나 `clear()` 메서드는 작동합니다. 이 메서드는 특정 인스턴스의 모든 다대다 관계를 삭제합니다.

>>> # Beatles가 해체되었습니다
>>> beatles.members.clear()
>>> # 이는 중간 모델 인스턴스를 삭제합니다
>>> Membership.objects.all()
중간 모델을 통해 다대다 관계를 설정한 후에는, 기본 다대다 필드와 마찬가지로 연결된 모델의 속성을 사용하여 쿼리를 수행할 수 있습니다.

# 'Paul'로 시작하는 이름을 가진 그룹 찾기
>>> Group.objects.filter(members__name__startswith='Paul')
<QuerySet [<Group: The Beatles>]>
중간 모델을 사용한 경우에는 중간 모델의 속성을 통해 쿼리도 수행할 수 있습니다.

# 1961년 1월 1일 이후에 그룹에 가입한 Beatles의 모든 멤버 찾기
>>> Person.objects.filter(
...     group__name='The Beatles',
...     membership__date_joined__gt=date(1961,1,1))
<QuerySet [<Person: Ringo Starr]>
멤버십 정보를 얻기 위해 중간 모델을 통해 접근할 수도 있습니다:

>>> 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 객체를 통해 멤버십 정보를 얻을 수 있습니다:

>>> 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.'

태그: Django models many-to-many

5월 28일 03:07에 게시됨