[Django] Model - Many To Many
Many To Many
-
다대다 관계 (M : N)
-
Django 에서는 해당 관계를 정의하기 위해
django.db.models.ManyToManyField
를 사용 -
관계를 생성하게 되면 관계 연결을 위한 중간 테이블(Intermediate Table)이 자동생성된다.
- 이에따라
1 : M : 1
관계형태로 변형된다. ManyToManyField
의 속성through
로 중간 테이블을 직접 제작한 모델로 변경가능하다. (커스터마이징)
해당 내용은 [Django] Model - Many To Many (Intermediary Model) 포스트를 참조
- 이에따라
Exmaple
이 포스트에서는 마켓(market)에서 손님(Customer)과 과일(Fruit)을 예시로 사용한다.
상황가정
-
손님 a 의 구매목록 : apple, orange, grape
-
손님 b 의 구매목록 : orange, grape
Visualization
위의 관계는 중간 테이블 (Intermediate Table) 이 생성됨에 따라
1 : M : 1
형태가 된다. (아래 이미지 참조)
Django Model Code
ManyToManyField
를 Customer
또는 Fruit
어디에 추가하더라도 Many To Many 관계로써 동일하다.
Many To One 에 해당하는 ForeignKey 의 경우 관계설정이 단 한개만 되기 때문에
연결될 두 모델 중 어느곳에 추가되느냐에 따라 의미가 달라진다.
-
연결할 수 있는 관계의 수에 제한이 없다.
-
두 모델의 관계는 자동생성되는 Intermediate Table 에 의해 관리되기 때문에 해당 모델의 테이블에는 필드가 생성되지 않는다.
그래도 가능하면 의미상 어울리는 곳에 필드를 만들고 관계를 연결하는 것을 권장한다.
아래의 코드는 상대적으로 주체적인 Customer
모델에 Fruit
를 연결하는 ManyToManyField
를 추가하였다.
from django.db import models
class Customer(models.Model):
name = models.CharField(max_length=20)
choice = models.ManyToManyField('Fruit', related_name='buyers')
def __str__(self):
return self.name
class Fruit(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
-
choice = models.ManyToManyField('Fruit', related_name='buyers')
에서
Fruit
모델 클래스를 따음표로 감싸 문자열로 만든 뒤 변환한 이유는
Fruit
클래스가 정의되기전 할당되었기 때문이다. (Fruit
클래스는Customer
클래스 이후 정의되었다.)
이렇게 문자열로 바꾸어서 할당해주면 클래스 정의 순서에 제한받지 않고 작성할 수 있다. -
related_name='buyers'
: 역방향 참조의 기본값customer_set
에서buyers
로 변경
Practice
위의 구현된 모델을 통해 실제 인스턴스(객체)를 생성하고 확인해보자.
실습을 위해 market 이라는 이름을 가진 app을 생성하고 해당 앱의 models.py 에 구현코드를 작성한다.
이후makemigrations
와migrate
를 하였음을 가정한다.
from market.models import Customer, Fruit
a = Customer.objects.create(name='a') # 손님 a 생성
b = Customer.objects.create(name='b') # 손님 b 생성
apple = Fruit.objects.create(name='apple') # 과일 apple 생성
grape = Fruit.objects.create(name='grape') # 과일 grape 생성
orange = Fruit.objects.create(name='orange') # 과일 orange 생성
a.choice.add(apple) # a 의 선택에 apple 추가
a.choice.add(grape) # a 의 선택에 grape 추가
a.choice.add(orange) # a 의 선택에 orange 추가
b.choice.add(grape) # b 의 선택에 grape 추가
b.choice.add(orange) # b 의 선택에 orange 추가
a.choice.all() # a 의 선택목록
>>> <QuerySet [<Fruit: apple>, <Fruit: grape>, <Fruit: orange>]>
b.choice.all() # b 의 선택목록
>>> <QuerySet [<Fruit: grape>, <Fruit: orange>]>
apple.buyers.all() # apple 을 구매한 손님 목록
>>> <QuerySet [<Customer: a>]>
grape.buyers.all() # grape 를 구매한 손님 목록
>>> <QuerySet [<Customer: a>, <Customer: b>]>
orange.buyers.all() # orange 를 구매한 손님 목록
>>> <QuerySet [<Customer: a>, <Customer: b>]>
DataBase
Table Name은
AppName_ClassName
소문자화로 만들어진다.
Table List
-
market_customer
-
market_fruit
-
market_customer_choice (자동 생성된 Intermediate Table)
AppName_ClassName_FieldName
(소문자) 형태로 만들어진 것을 알 수 있다.
market_customer
id | name |
---|---|
1 | a |
2 | b |
market_fruit
id | name |
---|---|
1 | apple |
2 | grape |
3 | orange |
market_customer_choice
id | customer_id | fruit_id |
---|---|---|
1 | 1 | 1 |
2 | 1 | 2 |
3 | 1 | 3 |
4 | 2 | 2 |
5 | 2 | 3 |
-
customer_id
: market_customer 으로 연결된 ForeignKey -
fruit_id
: market_fruit 으로 연결된 ForeignKey -
해당 Table 을 통해
M : N
관계에서1 : M : 1
로 변형
Leave a comment