초보 개발자

many-to-many 본문

AI 웹개발 트랙 - 내배캠/6주차

many-to-many

taehyeki 2022. 1. 21. 12:47

ManyToMany

데이터베이스의 Many-to-Many relationship(이하 다대다 관계)는 처음 접하는 사람들을 힘들게 한다. 한 테이블의 여러 레코드가 다른 테이블의 여러 레코드와 연결되어 있는 관계. 말만 들어서는 감이 오지 않으니 예를 들자면, 피자와 토핑 사이의 관계라고 할 수 있을 것이다. '피자'라는 테이블의 피자(페퍼로니피자, 치즈피자 등)는 '토핑' 테이블의 토핑(치즈, 페퍼로니, 올리브, 양파 등)을 여러 개 가질 수 있고, 그 반대도 마찬가지이다. 장고에서는 이러한 데이터들의 관계를 ManyToManyField에서 정의할 수 있다.

class Pizza(models.Model):

    name = models.CharField(max_length=20)
    toppings = models.ManyToManyField('Topping')

class Topping(models.Model):

    name = models.CharField(max_length=20)

위의 예시처럼 피자 테이블과 토핑 테이블을 만들었다면, 둘 중 하나에서 상대방을 참조하는 필드를 만들고 ManyToManyField를 정의하면 된다. 여기서는 피자 테이블에서 토핑 테이블을 참조하는 toppings 필드를 생성했다.
장고 공식문서에서는 ManyToManyField를 정의하면, 자동으로 두 테이블 사이의 관계를 관리해주는 중간 테이블을 생성한다고 되어 있다. 이 테이블은 우리가 작성한 모델에는 없지만, 데이터베이스를 확인하면 다대다관계의 두 테이블 이름을 _로 이어준 별도의 테이블이 생성된 것을 볼 수 있다.
이 중간 테이블은 두 테이블의 id를 각각 필드로 가지고 있다. 그렇기 때문에 각 테이블에 데이터가 존재하지 않으면 중간 테이블에도 primary key가 존재하지 않는다.

 

위에서 작성한 예시를 보면, 피자 테이블에 토핑 테이블을 참조하는 필드가 존재한다. 그래서 한 피자에 있는 토핑들을 가져오는 것은 크게 문제가 없다.

#피자이름
Pizza.objects.all()
#피자에 올린 토핑 이름
Pizza.objects.get(name='파파존스').toppings.all()

하지만 토핑이 들어간 피자들을 가져오기 위해
cheeze.pizzas.all()을 하면, AttributeError: 'Topping' object has no attribute 'pizzas' 와 같이 '그런 속성 없다' 는 에러가 뜬다. 사실 조금 생각해보면 토핑 테이블에는 피자와 관련된 데이터는 아무것도 없다. 그렇기 때문에 직접적으로 가져올 수 없는 것이다.
그래서 참조되는 테이블(토핑)에서 참조하는 테이블(피자)의 데이터를 가져오기 위해, 장고에서는 참조하는 테이블의 이름 뒤에 _set를 붙여주어야 한다고 명시했다. 

즉 모델명(소문자)_set 이렇게 해줘야함 Topping.objects.get(topping_name='치즈').pizza_set.all()

 

>> pineapple.pizza_set.all()
<QuerySet [<Pizza: Hawaiian>]>

그런데 _set을 붙여주기 싫다면? 해결책은 있다. 바로 필드 속성에 related_name을 아래와 같이 정의해 주는 것이다.

toppings = models.ManyToManyField('Topping', related_name='pizzas')

처음에 related_name을 보면 뭔가 의아할 것이다. 필드명은 토핑인데 왜 이름을 'pizzas'라고 붙였을까? 그 이유는 related_name이 '참조되는 테이블이 참조하는 테이블의 데이터를 가져오고 싶을 때 사용하는 이름'을 정의하는 것이기 때문이다. 이 경우 토핑이 피자의 데이터를 가져오고 싶을 때 pizza_set 대신 pizzas를 쓸 수 있다는 것이다.

 

 

pineapple.pizzas.all()
<QuerySet [<Pizza: Hawaiian>]>

related_name을 정의해 주니 _set 없이도 토핑에서 피자의 데이터에 접근한 것을 볼 수 있다.

 

여기서 만약 related_name을 추가한다면 전에 사용하였던  모델(소문자)_set명령어는 더이상 유효하지 않게된다. 둘 중 하나만 선택하여 써야 한다.

 

 

데이터 간의 관계성을 가지게 하기 위해 hawaiian_pizza.toppings.add(pineapple)피자 테이블에서 토핑을 추가할 수도 있지만,

hawaiian_pizza.toppings.add(pineapple)

반대로 아래와 같이 토핑 테이블에서 토핑이 들어간 피자를 추가할 수도 있다.

 

즉 피자에서도 토핑을 추가할 수있고, 토핑에서도 피자를 추가할 수 있다는 말이다.

mozzarella = Topping.objects.create(name='mozzarella')
>> mozzarella.pizzas.add(cheese_pizza)

여기서 cheeze_pizza는 아무 문자나 적은 것이 아니라 Pizza모델의 객체이다. 

이 결과로 모짜렐라는 치즈피자의 토핑으로 올라가고, 피자치즈의 토핑에도 모짜렐라가 추가되었다.

따라서 피자치즈의 토핑을 검색하면 치즈도 나오고 치즈가 들어있는 피자들을 검색하면 피자치즈도 추가된 걸 볼 수 있을 것이다.

 

 

'AI 웹개발 트랙 - 내배캠 > 6주차' 카테고리의 다른 글

6주차 WIL  (0) 2022.01.22
django views class  (0) 2022.01.21
AWS EC2 배포 및 가비아 연결  (0) 2022.01.21
django {% url %}  (0) 2022.01.21
django sparta 강의 정리 및 의문점  (0) 2022.01.19