[Django] ManyToManyField

이동명·2021년 6월 22일
1

관계형 데이터베이스(RDB)에서 Many to Many(N:N) 관계를 정규화하기 위해서는 서로간의 관계를 나타내는 중간의 관계테이블이 필요하다.

movies와 actors는 하나의 영화가 여러 배우를 포함하고, 하나의 배우가 여러 영화에 출연할 수 있어 N:N관계를 가진다.

ERD는 아래와 같다

django에서 이를 구현하기 위해서는 2가지 방법이 있다.
1. 중간테이블 actors_movies 클래스를 생성
2. Django에서 제공하는 ManyToManyField 이용하여 2개의 table을 연결

1. 중간테이블 클래스 생성

actormovie를 attribute로 갖는 ActorMovie 클래스를 별도로 생성하였다.
아래는 models.py와 그에 따른 mysql table이다.

# models.py
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 ActorMovie(models.Model):
    actor = models.ForeignKey('Actor', on_delete=models.CASCADE)
    movie = models.ForeignKey('Movie', on_delete=models.CASCADE)

    class Meta:
        db_table = 'actors_movies'

특정 actor instance와 연결된 모든 movie를 찾기 위해서는 역참조를 통해 중간테이블 actors_moviesQuerySet를 가져와, movie를 찾아야 한다.

actor = Actor.objects.get(id=1)			# actor: Instance
actormovies = actor.actormovie_set.all()	# actormovie: QuerySet
						# 역참조
movies = []
for actormovie in actormovies:
	movies.append(actormovie.movie)
# 해당 actor가 출연한 movie 인스턴스들을 담은 list

2. ManyToManyField 활용

Actor 클래스 생성시, movies라는 attribute를 아래와 같이 할당함.
movies = models.ManyToManyField('Movie')
migration시, 자동으로 actors_movies라는 database 중간table이 생성된다.

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')

    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'
  • ActorMovie 클래스를 따로 생성했을때와 동일한 table이 생성된 것을 확인할 수 있다.
  • Movie 클래스에서는 actors = ManyToManyField('Actor') 라는 attribute를 할당하지 않았다.
  • 만약 할당하게 되면, movies_actors라는 다른 이름의 관계테이블이 중복으로 생기기 때문에, 주의해야 한다.

특정 actor가 출연한 movie정보를 구하기 위해서는 기존에 actor 클래스에 할당한 movies attribute를 활용한다.

이때의 movies는 Django의 ManyRelatedManager objects를 반환한다.

actor = Actor.objects.get(id=1)			# actor: Instance
movies = actor.movie.all()			# actormovie: QuerySet
						# ManyRelatedManger 활용
# 해당 actor가 출연한 movie 인스턴스들을 담은 QuerySet

특정 movie에서 출연한 actors 정보를 구하기 위해서는 ManyToManyField를 통해 자동으로 생성된 ManyRelatedManager인 movie.actor_set을 활용한다.

movie = Movie.objects.get(id=1)			# movie: Instance
actors = movie.actor_set.all()	# actormovie: QuerySet
						# 역참조
# 해당 movie에 출연한 actor 인스턴스들을 담은 QuerySet

ManyToManyField를 쓰는 것의 장점은
1. 관계테이블을 따로 정의하지 않아도, Django에서 만들어주기 때문에 편하다.
2. 중간테이블의 related_name을 메소드로 사용하지 않고, 바로 N:N 테이블로 접근할 수 있어 코드가 직관적이다.

  • 그러나, 관계테이블에 대한 접근이 많이 필요한 데이터 구조의 경우, 관계테이블에 대한 클래스를 따로 정의하고, ManyToManyField의 argument로 through를 넘겨줘야하기 때문에, 1번의 장점은 사라진다.
  • through를 사용하면, 관계테이블에 데이터를 넣는 방법인 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', through="ActorMovie")

    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 ActorMovie(models.Model):
    actor = models.ForeignKey(Actor, on_delete=models.CASCADE)
    movie = models.ForeignKey(Movie, on_delete=models.CASCADE)

    class Meta:
        db_table = 'actors_movies'

0개의 댓글