당신은 멋쟁이, 우리는 장고쟁이~

0%

Django Models 8편 - Relationships (Many-to-many) B

Many-to-Many-2편


Many-To-Many 인스턴스들 생성하기


지난 포스팅에서, through 를 사용하여, 중간 모델을 가진 Many-to-Many 관계에 대한 모델을 작성했고.


중간모델 Membership 을 사용하는 ManyToManyField 를 설정하였습니다.


이제 몇가지 many-to-many relationship 을 생성할 준비가 되어 있습니다. 중간 모델의 인스턴스들을 생성하면서 Membership 을 사용하는 Many-to-Many 관계를 생성해 봅시다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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')
# through="Membership" 을 넣어줌으로,
# Membership 이 Person 과 Group 사이의 중간 모델 역할을 하게 만들수 있음

def __str__(self):
return self.name


class Membership(models.Model):
# 중간 모델에서는, Person과 Group이 ForeignKey 로 등록 되어 있어야 함
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)

위 모델을 바탕으로, shell 에서 객체를 만들어 봅니다.


shell 에 접속하려면,

python manage.py shell 명령어를 커맨드 라인에서 실행 시키면 됩니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ringo = Person.objects.create(name="Ringo Starr")
paul = Person.objects.create(name="Paul McCartney")
beatles = Group.objects.create(name="The Beatles")

import datetime

m1 = Membership(person=ringo, group=beatles, date_joined=datetime.datetime(1962,8, 16), invite_reason="Needed a new drummer.")

m1.save()

In [7]: beatles.members.all()
Out[7]: <QuerySet [<Person: Ringo Starr>]>

In [8]: ringo.group_set.all()
Out[8]: <QuerySet [<Group: The Beatles>]>

In [9]: m2 = Membership.objects.create(person=paul, group=beatles, date_joined=datetime.datetime(1960, 8, 1), invite_reason="Wanted to form a band.")

In [10]: beatles.members.all()
Out[10]: <QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>]>

add(), create() 혹은 set() 을 사용해서 관계들을 생성할수도 있습니다.

요구되는 필드에 through_defaults 만 명시해주면 됩니다.


공식문서에는, 아래와 같은 예제가 있는데. shell 에서 쳐보면, 하나도 제대로 동작하지 않습니다.


공식문서를 무슨생각으로 만들어 놓은건지;;;


john 이라는 객체를 생성하면 잘 동작할것입니다만. 우선, 아래와 같이 연습 내역을 담습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
In [11]: beatles.members.add(john, through_defaults={'date_joined': date(1960, 8, 1)})                                                                                    
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-11-47e459b083b9> in <module>
----> 1 beatles.members.add(john, through_defaults={'date_joined': date(1960, 8, 1)})

NameError: name 'john' is not defined

In [12]: beatles.members.create(name="George Harrison", through_defaults={'date_joined': datetime.datetime(1960, 8, 1)}
...: )
Out[12]: <Person: George Harrison>

In [13]: beatles.members.set([john, paul, ringo, george], through_defaults={'date_joined': date(1960, 8, 1)})
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-13-cad5aee581b9> in <module>
----> 1 beatles.members.set([john, paul, ringo, george], through_defaults={'date_joined': date(1960, 8, 1)})

NameError: name 'john' is not defined


만약 중간 모델에 의해서, through 테이블이 커스텀 정의가 되었는데, (model1, model2 ) 페어에 강제적으로 고유성을 요구하지 않고, 다중의 값을 허용한다면, remove() 를 호출해서 모든 중간 모델 인스턴스들을 제거할수 있습니다.


1
2
3
4
5
6
7
8
9
10
In [14]: Membership.objects.create(person=ringo, group=beatles, date_joined=datetime.datetime(1968, 9, 4), invite_reason="You've been gone for a month and we miss you.") 
Out[14]: <Membership: Membership object (9)>

In [15]: beatles.members.all()
Out[15]: <QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>, <Person: George Harrison>, <Person: Ringo Starr>]>

In [16]: beatles.members.remove(ringo)

In [17]: beatles.members.all()
Out[17]: <QuerySet [<Person: Paul McCartney>, <Person: George Harrison>]>


clear() 메서드는,

하나의 인스턴스를 위한 모든 Many-to-Many 관계들을 없애줍니다.


1
2
3
4
5
6
7
8
In [18]: # 비틀즈가 해산                                                                                                                                                  

In [19]: beatles.members.clear()

In [20]: # 이 방식은 중간 모델 인스턴스를 삭제해줍니다

In [21]: Membership.objects.all()
Out[21]: <QuerySet []>

한번 many-to-many 관계를 성립하였으면, 쿼리들을 발행할수 있습니다. 보통의 many-to-many 관계와 같이, many-to-many 로 관계된 모델들의 속성들을 사용하여 쿼리를 사용할수 있습니다.


1
2
3
4
# 멤버의 이름이 'Paul' 로 시작하는 그룹 찾기                                                                                                                     

In [39]: Group.objects.filter(members__name__startswith='Paul')
Out[39]: <QuerySet [<Group: The Beatles>]>

중간 모델을 사용하면서, 중간 모델의 속성을 쿼리 조회 할수 있습니다.


1
2
3
4
5
6
# Person 중에, group 이름이 'The Beatles' 이면서 
# Membership에 든 날짜가 1961년 1월 1일 보다 이후인 객체를 조회
In [40]: Person.objects.filter(
...: group__name='The Beatles',
...: membership__date_joined__gt=datetime.datetime(1961, 1, 1))
Out[40]: <QuerySet [<Person: Ringo Starr>]>

membership 의 정보를 바로 접근하고 싶으면,

바로 Membership 모델에 쿼리를 보내면 됩니다.


1
2
3
4
5
6
7
In [41]: ringos_membership = Membership.objects.get(group=beatles, person=ringo)                                                                                          

In [42]: ringos_membership.date_joined
Out[42]: datetime.date(1962, 8, 16)

In [43]: ringos_membership.invite_reason
Out[43]: 'Needed a new drummer.'


Person 객체에서 부터 거꾸로 가는

Many-To-Many reverse 관계도 존재합니다


1
2
3
4
5
6
7
In [44]: ringos_membership = ringo.membership_set.get(group=beatles)                                                                                                      

In [45]: ringos_membership.date_joined
Out[45]: datetime.date(1962, 8, 16)

In [46]: ringos_membership.invite_reason
Out[46]: 'Needed a new drummer.'


마치며..


공식문서가 예시도 너무 불충분하고,, 뭐라 설명은 하는지는 잘 모르겠습니다.


다음 포스팅에서는, ManyToMany 예제를 좀 들어서, 데이터베이스 검색 및 쿼리문 사용을 좀 연습 해봐야 할것 같습니다.