오늘도 개발
ManyToManyField VS ForeignKey 본문
두 모델이 다대다 관계인 경우 ManyToManyField를 사용할 수도 ForeignKey를 사용할 수도 있다.
ManyToManyField를 사용지 않는 경우엔 직접 ForeignKey를 사용하여 연결 테이블을 만들어야 한다.
이 경우 views.py에서 연결 테이블을 불러와 직접 작업을 수행해야 한다.
ManyToManyField를 사용하는 경우 두 모델 중 하나에만 ManyToManyField를 정의한다.
이 경우 따로 연결 테이블을 만들지 않아도 DB에 자동으로 연결 테이블(through table)이 생성된다.
(연결 테이블을 커스터마이징 해야 하는 경우, Foreign키로 된 연결 테이블을 직접 만들고
ManyToManyField의 옵션 through=에 지정할 수 있다.)
views.py에서 연결 테이블을 따로 불러올 필요 없이
한 테이블에서 바로 연결된 항목에 접근할 수 있어 직관적이며 편리하다.
아래 예시를 통해 두 경우를 더 자세히 살펴보자.
영화, 배우 예시 1
한 영화에는 여러 배우가 등장할 수 있고, 한 배우는 여러 영화에 등장할 수 있으므로 영화-배우는 다대다 관계이다.
백엔드에서 모든 배우 정보를 json으로 만들어서 다음과 같이 프론트엔드에게 보내주어야 하는 상황을 가정해보자.
한 배우 안에는 배우가 출연한 모든 영화 제목이 들어가야 한다.
{
"results": [
{
"date_of_birth": "1990-01-10",
"first_name": "봄",
"last_name": "김",
"starred_in": [
"인터스텔라",
"그래비티"
]
},
{
"date_of_birth": "1982-08-04",
"first_name": "여름",
"last_name": "이",
"starred_in": [
"인터스텔라"
]
}
}
1. ForeignKey로 연결 테이블을 만드는 경우
<models.py> : 직접 연결테이블 Actor_movie를 정의한다.
class Actor(models.Model):
id = models.AutoField(primary_key=True)
first_name = models.CharField(max_length=45)
last_name = models.CharField(max_length=45)
date_of_birth = models.DateField()
class Meta:
db_table = 'actors'
class Movie(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=45)
release_date = models.DateField()
running_time = models.IntegerField()
class Meta:
db_table = 'movies'
class Actor_movie(models.Model):
id = models.AutoField(primary_key=True)
actor = models.ForeignKey(Actor, on_delete=models.CASCADE)
movie = models.ForeignKey(Movie, on_delete=models.CASCADE)
class Meta:
db_table = 'actors_movies'
<views.py>
1) all()로 모든 배우 쿼리셋을 불러온다.
2) for문으로 각 배우 오브젝트에 접근한다.
- 각 배우 오브젝트에서 : 연결 테이블에서 현재 배우와 연결된 영화-배우 쿼리셋을 직접 불러온다.
- 영화-배우 쿼리셋에서 for문으로 각 영화-배우 오브젝트에 접근한다.
- 각 영화-배우 오브젝트에서 : 해당 영화-배우 오브젝트와 연결된 영화 오브젝트를 가져온다.
- 영화 오브젝트를 결과 리스트에 추가한다.
class ActorsView(View):
def get(self, request):
results = []
# 배우 쿼리셋 생성
actors = Actor.objects.all()
# for문으로 배우 쿼리셋의 각 배우 오브젝트에 접근
for actor in actors:
# 연결 테이블에서 현재 배우가 포함된 row 모두 선택
# 영화-배우 쿼리셋 생성
actor_movies = Actor_movie.objects.filter(actor=actor)
starred_in = []
# for문으로 영화-배우 쿼리셋의 각 영화-배우 오브젝트에 접근
for actor_movie in actor_movies:
# 현재 영화-배우 오브젝트와 연결된 영화 오브젝트 생성
movie = actor_movie.movie.title
# 생성한 영화 오브젝트를 starred_in 리스트에 추가
starred_in.append(movie)
results.append({
"first_name": actor.first_name,
"last_name": actor.last_name,
"date_of_birth" : actor.date_of_birth,
"starred_in" : starred_in
})
return JsonResponse({'results' : results}, status = 200)
2. ManyToManyField를 사용하는 경우
<models.py>
from django.db import models
# Create your models here.
class Actor(models.Model):
id = models.AutoField(primary_key=True)
first_name = models.CharField(max_length=45)
last_name = models.CharField(max_length=45)
date_of_birth = models.DateField()
class Meta:
db_table = 'actors'
class Movie(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=45)
release_date = models.DateField()
running_time = models.IntegerField()
# ManyToManyField는 다대다 관계의 테이블 중 하나에만 설정하면 된다
actor = models.ManyToManyField(Actor)
class Meta:
db_table = 'movies'
클래스를 두 개만 만들어도 DB에는 세 개의 테이블(movies, actors, movies_actor)이 생성된다.
ManyToManyField를 설정한 테이블 Movie에는 actor 칼럼이 생성되지 않는다.
연결 테이블에는 다음과 같이 데이터를 입력한다.
m1 = Movie.objects.get(id=1)
a1 = Actor.objects.get(id=1)
a2 = Actor.objects.get(id=2)
# Movie 클래스에 actor라는 ManyToManyField를 설정했으므로
# Movie 인스턴스에 actor.add()로 Actor 인스턴스 추가
m1.actor.add(a1)
m1.actor.add(a2)
입력을 완료하면 다음과 같이 된다.

<views.py>
1) all()로 모든 배우 쿼리셋을 불러온다.
2) for문으로 각 배우 오브젝트에 접근한다.
- 각 배우 오브젝트에서 : 현재 배우와 연결된 영화 쿼리셋을 불러온다.
- for문으로 영화 쿼리셋의 각 영화에 접근한다.
- 영화 오브젝트를 결과 리스트에 추가한다.
class ActorsView(View):
def get(self, request):
results = []
# 배우 쿼리셋 생성
actors = Actor.objects.all()
# for문으로 배우 쿼리셋의 각 배우 오브젝트에 접근
for actor in actors:
results.append({
"first_name": actor.first_name,
"last_name": actor.last_name,
"date_of_birth" : actor.date_of_birth,
# 현재 배우와 연결된 영화 쿼리셋 생성
# for문으로 영화 쿼리셋의 각 영화 오브젝트에 접근
"starred_in" : [movie.title for movie in actor.movie_set.all()]
})
return JsonResponse({'results' : results}, status = 200)
3. 결론
ForeignKey나 ManyToManyField 중 어느 것을 사용해도 상관없는 경우에는 ManyToMayField를 사용하는 것이 낫다.
코드를 더 쉽고 간결하게 짤 수 있기 때문이다.
영화, 배우 예시 2
배우는 영화마다 다른 배역을 연기한다.
한 배우가 각 영화에서 어떤 배역을 맡았는지도 보여주려면 어떻게 해야 할까?
1. ForeignKey로 연결 테이블을 만드는 경우
models.py의 Actor_movie 클래스에 필드를 하나 더 추가하고 위와 같은 방식으로 처리하면 된다.
<models.py>
class Actor(models.Model):
id = models.AutoField(primary_key=True)
first_name = models.CharField(max_length=45)
last_name = models.CharField(max_length=45)
date_of_birth = models.DateField()
class Meta:
db_table = 'actors'
class Movie(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=45)
release_date = models.DateField()
running_time = models.IntegerField()
class Meta:
db_table = 'movies'
class Actor_movie(models.Model):
id = models.AutoField(primary_key=True)
actor = models.ForeignKey(Actor, on_delete=models.CASCADE)
movie = models.ForeignKey(Movie, on_delete=models.CASCADE)
role = models.CharField(max_length=45)
class Meta:
db_table = 'actors_movies'
2. ManyToManyField를 사용하는 경우
<models.py>
ForeignKey를 사용하여 연결 테이블을 직접 만든다.
ManyToManyField에 인자 through로 연결 테이블을 넣는다.
from django.db import models
class Actor(models.Model):
id = models.AutoField(primary_key=True)
first_name = models.CharField(max_length=45)
last_name = models.CharField(max_length=45)
date_of_birth = models.DateField()
class Meta:
db_table = 'actors'
class Movie(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=45)
release_date = models.DateField()
running_time = models.IntegerField()
# Role 클래스를 연결 테이블로 사용하겠다는 뜻
actor = models.ManyToManyField(Actor, through='Role')
class Meta:
db_table = 'movies'
# 명시적으로 연결 테이블 정의
class Role(models.Model):
id = models.AutoField(primary_key=True)
actor = models.ForeignKey(Actor, on_delete=models.CASCADE)
movie = models.ForeignKey(Movie, on_delete=models.CASCADE)
character = models.CharField(max_length=45)
class Meta:
db_table = 'roles'
참고
'웹 프로그래밍 > Django' 카테고리의 다른 글
| 내 컴퓨터로 runserver 하기 (0) | 2022.07.13 |
|---|---|
| 정참조, 역참조, related_name (0) | 2022.07.06 |
| Django와 데이터베이스 - 2) 마이그레이션(migration) (0) | 2022.07.01 |
| Django와 데이터베이스 - 1) Django 모델(Model) (0) | 2022.06.30 |
| Django와 데이터베이스 - 0) Django ORM (0) | 2022.06.29 |