배우와 영화 정보를 M2M(Many To Many)로 관계를 가진 테이블을 django의 models.py
로 작성했다.
models.py
from django.db import models
class Actor(models.Model):
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):
title = models.CharField(max_length=45)
release_date = models.DateField()
running_time = models.IntegerField()
class Meta:
db_table = 'movies'
class Actor_Movie(models.Model):
actor = models.ForeignKey('Actor', on_delete=models.CASCADE)
movie = models.ForeignKey('Movie', on_delete=models.CASCADE)
class Meta:
db_table = 'actors_movies'
ForeignKey로 중간테이블을 만들어서 actor와 model을 참조하게 만들었다.
그 후에 views.py
에 등록된 배우 목록을 리턴하는 GET 메소드를 구현하였다.
(배우 목록 중에는 이름, 성 그리고 영화 목록을 포함)
views.py
...
class ActorsView(View):
def get(self, request):
actors = Actor.objects.all()
result = []
for actor in actors:
movies = actor.actor_movie_set.all() #역참조하는 _set매니저 사용
# movies = Actor_Movie.objects.filter(actor=actor) 이것도 가능!
movie_list = []
for movie in movies:
movie_list.append(
{'title' : movie.movie.title}
)
result.append(
{
'first_name' : actor.first_name,
'last_name' : actor.last_name,
'date_of_birth' : actor.date_of_birth,
'movies' : movie_list
}
)
return JsonResponse({'result':result},status=200)
_set 매니저
위 상황에서 Actor_Movie(참조하는 Model)에 있는 ForeignKey가 각각 Actor와 Model(참조되는 Model)의 테이블을 바라보고 있는데, 실제로 이 ForeignKey를 바탕으로 해당하는 객체에 접근이 가능하다.
하지만, 반대상황으로 Actor와 Model에서 Actor_Movie의 객체를 호출하기 위해서는 역참조가 필요하다.
역참조의 방법으로는 여러가지가 있지만,
Django에서는_set
매니저를 통해 이에 쉽게 접근이 가능하다.
actor
라는 루프 변수를 역참조하여 Actor_Movie 테이블에서
그에 해당하는 객체들을 all()
메소드로 movies
변수로 저장하였다.
Actor_Movie에서는 Movie로 참조가 가능하기 때문에,
movie.movie.title
과 같이 호출이 가능하다.
하지만 이 방법으로는 테이블을 2번 호출해야 한다는 단점이 있다.
(중간 테이블, Movie 테이블)
이런 번거러움을 해결하기 위해 ManyToManyField
를 사용하면 된다.
models.py
from django.db import models
class Actor(models.Model):
first_name = models.CharField(max_length=45)
last_name = models.CharField(max_length=45)
date_of_birth = models.DateField()
movies = models.ManyToManyField('Movie', related_name='movies')
def __str__(self):
return self.last_name + self.first_name
class Meta:
db_table = 'actors'
class Movie(models.Model):
title = models.CharField(max_length=45)
release_date = models.DateField()
running_time = models.IntegerField()
def __str__(self):
return self.title
class Meta:
db_table = 'movies'
views.py
...
class ActorsView(View):
def get(self, request):
actors = Actor.objects.all()
result = []
for actor in actors:
movies = actor.movies.all()
movies_list = []
for movie in movies:
movies_list.append(
{'title' : movie.title}
)
result.append(
{
'first_name' : actor.first_name,
'last_name' : actor.last_name,
'movies' : movies_list
}
)
return JsonResponse({'result':result}, status=200)
ManyToManyField
를 사용하여 다시 models.py
, views.py
를 작성하였다.
ManyToManyField
를 사용하면, django에서는 두 테이블을 연결하는 중간테이블을 자동으로 생성해준다.
그리고 Actor 테이블에 Movie 테이블을 참조하는 필드가 존재하기 때문에 더 쉽게 접근이 가능하다.
ex)
>>> actor = Actor.objects.get(first_name='정민', last_name='황') >>> actor.movies.all() <QuerySet [<Movie: 베테랑>, <Movie: 신세계>, <Movie: 다만 악에서 구하소서>]>
반대의 경우에는 역참조로 호출이 가능하다.
>>> movie = Movie.objects.get(title='베테랑') >>> movie.actor_set.all() Traceback (most recent call last): File "<console>", line 1, in <module> AttributeError: 'Movie' object has no attribute 'actor_set' >>> movie.actors.all() <QuerySet [<Actor: 황정민>, <Actor: 유해진>]>
여기서
_set
이 실행되지 않는 이유는,
필드 속성에related_name
을 actors로 정의하였기 때문이다.
ManyToManyField
는 add()
메소드를 통해 중간 테이블 내 데이터를 생성할 수 있다.
ManyToManyField
은 직접 중간 테이블을 설정하고 through
속성으로 지정할 수 있다.
자동으로 생성되는 중간 테이블을 왜 직접 설정해야 할까?
추가적인 데이터를 기록하고 싶을때 사용할 수 있기 때문이다.
(자동으로 생성하면 pk와 두 테이블의 id만 생성됨)
그러나 더 이상 add()
메소드는 사용할 수 없다.
직접 중간 테이블에 데이터를 넣어주어야 한다.
예를 들어, 둘의 생성 날짜를 추가하여 관리하고 싶다면
class Actor(models.Model):
first_name = models.CharField(max_length=45)
last_name = models.CharField(max_length=45)
date_of_birth = models.DateField()
movies = models.ManyToManyField('Movie', related_name='movies', through='ActorMovie')
def __str__(self):
return self.last_name + self.first_name
class Meta:
db_table = 'actors'
class Movie(models.Model):
title = models.CharField(max_length=45)
release_date = models.DateField()
running_time = models.IntegerField()
def __str__(self):
return self.title
class Meta:
db_table = 'movies'
class ActorMovie(models.Model):
actor = models.ForeignKey('Actor', on_delete=CASCADE)
movie = models.ForeignKey('Movie', on_delete=CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
#생성할때, 시간날짜를 자동으로 입력해줌
class Meta:
db_table = 'actors_movies'
이런식으로 해당 테이블에 더 자세한 정보들을 담을 수 있기 때문에 이런 경우에는 사용하는 것이 더 좋다.
참조
(https://docs.djangoproject.com/en/3.2/topics/db/examples/many_to_many/)
(https://velog.io/@jiffydev/Django-9.-ManyToManyField-1)