crud (2) owner - dog 테이블에서는 1:N 관계의 데이터베이스에 대한 Backend API를 구현해 보았다. 이번에는 N:N 관계의 테이블에서 ManyToManyField를 사용하여 Backend API를 구현해보자.
이번에는 N:N관계이므로 Actor와 Movie 테이블을 ForeignKey로 연결하는 중간테이블을 만들어 Models.py를 작성하였다.
from django.db import models
# Create your models here.
class Actor(models.Model):
first_name = models.CharField(max_length=20)
last_name = models.CharField(max_length=20)
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 = 'actor_movies'
위의 코드처럼 중간테이블을 사용하여 view로직을 만들면 코드가 너무 길어져서 가독성이 떨어지며 불필요하게 길어진다. 또한 값을 추가하거나 불러올 때 일일히 중간테이블을 이용해야 한다.
그래서 ForeignKey대신에 ManyToManyField를 사용하는데 이러면 중간테이블을 사용하지 않고(Django에서 자동으로 중간테이블을 만들어줌) 바로 N:N관계를 정의하여 데이터를 참조할 수 있다.
ManyToManyField를 사용하여 다시 Models.py를 작성하였다.
from django.db import models
# Create your models here.
class Actor(models.Model):
first_name = models.CharField(max_length=20)
last_name = models.CharField(max_length=20)
date_of_birth = models.DateField()
movies = models.ManyToManyField('Movie', through='Actor_Movie', related_name='actors')
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 = 'actor_movies'
중간테이블을 생략할 수 있지만 중간테이블에 column을 작성할경우가 있어서 남겨두었다.
through를 사용하여 중간테이블을 거치지 않고 N:N관계의 테이블에서 바로 상대 테이블의 참조가 가능하다.
그리고 related_name을 설정 해 주면 참조하고 있는 모델에서 역참조를 할 경우 직관적인 이름으로 설정하여 참조가 가능하다.
Django에서 related_name 설정이 반드시 되어야 하는 상황이 있는데 같은 클래스 내 attribute에서 서로 같은 모델을 참조하고 있을 경우에 related_name을 설정하지 않는다면 migration이 되지 않고 오류가 발생한다.
나의 경우에도 오류가 발생하여 migartion이 되지 않다가 related_name을 설정하여 문제없이 migration이 진행되었다.
이제 Database Table을 생성하고 각 Table에 data를 입력해 보았다.
import json
# from django.shortcuts import render
from django.http import JsonResponse
from django.views import View
from movies.models import Actor, Movie, Actor_Movie
# Create your views here.
class ActorView(View):
def get(self, request):
actors = Actor.objects.all()
# movies = Movie.objects.all()
# actor_movies = Actor_Movie.objects.all()
results = [{
"first_name": actor.first_name,
"last_name" : actor.last_name,
"movie" : [{
"title" : movie.title
} for movie in actor.movies.all()]
} for actor in actors]
# results = []
# for actor in actors:
# for actor_movie in actor_movies.filter(actor_id=actor.id):
# movie_list = []
# movie_list.append(Movie.objects.get(id=actor_movie.movie_id).title)
# results.append(
# {
# "first_name" : actor.first_name,
# "last_name" : actor.last_name,
# "title" : movie_list
# }
# )
return JsonResponse({'reults': results}, status=200)
class MovieView(View):
def get(self, request):
movies = Movie.objects.all()
results = [{
"title" : movie.title,
"running_time" : movie.running_time,
"actor" : [{
"first_name" : actor.first_name,
"last_name" : actor.last_name
} for actor in movie.actors.all()]
} for movie in movies]
return JsonResponse({'reults': results}, status=200)
더 이상 중간테이블과 Movie테이블을 가져와서 사용할 필요가 없어 주석처리를 해주었다.
list comprehension에서 actormovies를 참조하지않고 바로 참조할 수 있어서 코드의 길이가 짧아지고 가독성이 좋아진것을 확인할 수 있다.