이번에는 Django의 ManyToManyField에 대해서 적어보려고 합니다!
Django에서 Many-To-Many를 표현하는 방법은 여러가지가 있다👇🏻
class Product(models.Model):
category = models.ForeignKey('Category', on_delete=models.CASCADE)
korean_name = models.CharField(max_length=45)
english_name = models.CharField(max_length=45)
description = models.TextField(null=True)
class Meta:
db_table = 'products'
class Allergy(models.Model):
name = models.CharField(max_length=45)
class Meta:
db_table = 'allergy'
class ProductAllergy(models.Model):
product = models.ForeignKey('Product', on_delete=models.CASCADE)
allergy = models.ForeignKey('Allergy', on_delete=models.CASCADE)
이런 식으로
ProductAllergy
테이블이Product
와Allergy
를 참조하게 만들어서 중간 테이블로 연결시킬 수 있는데 이럴 경우에는M:N
관계에 있는 테이블로 직접 참조하지 못하고 중간테이블을 거쳐서 참조해야 하기 때문에 불편한 부분이 존재한다
class ProductSize(models.Model):
product = models.ForeignKey('Product', on_delete=models.CASCADE)
size = models.ForeignKey('Size', on_delete=models.CASCADE)
one_serving_kcal = models.DecimalField(max_digits=6, decimal_places=2)
하지만 위의 경우와 같이 중간 테이블에 정보를 넣는 것이 필요할 경우에는 중간테이블 클래스를 선언해줘야 한다
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()
actors = models.ManyToManyField(Actor)
class Meta:
db_table = 'movies'
ManyToManyField
로M2M
관계 설정을 할 수 있는데 이런 식으로 모델을 작성하게 되면 Django에서 자동으로 중간 테이블을 생성해준다
위 사진처럼 자동으로 테이블 이름이 정해지고 각 테이블의PK
를 참조하여 만들어지게 된다
M2M
관계의 테이블을 만들 때 한 쪽 클래스에서만 attribute를 넣어줘야 하며 양쪽에서 넣어 줄 경우 이름이 반대인 중간테이블이 중복으로 생성되기 때문에 문제가 생길 수 있다
ManyToManyField
에서 중간 테이블을 수동으로 정의하여 두 모델간의 관계에 데이터를 연결해야 할 경우 through
를 사용할 수 있다!
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()
actors = models.ManyToManyField(Actor, through='ActorMovie')
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)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = 'actormovie'
이런식으로 지정해주면 중간 테이블에 데이터를 연결도 가능하고 ManyToManyField
에서 through
를 사용했기 때문에 중간 테이블을 거치지 않아도 M:N
관계의 테이블에서 바로 상대 테이블의 참조가 가능하다!
더 나아가 related_name
을 설정 해 준다면 참조하고 있는 모델에서 역참조를 할 경우 직관적인 이름으로 설정하여 참조가 가능하다!
class Movie(models.Model):
title = models.CharField(max_length=45)
release_date = models.DateField()
running_time = models.IntegerField()
actors = models.ManyToManyField(Actor, through='ActorMovie', related_name='movies')
위 처럼 설정한 후
>>> a1 = Actor.objects.get(pk=1)
>>> a1.movies.all()
Actor
모델의 PK
값이 1인 인스턴스에 연결된 Movie
테이블의 값들이 출력되게 된다!
related_name
을 설정하지 않은 경우 Actor
모델에서 Movie
의 모델에 접근할 경우
>>> a2 = Actor.objects.get(pk=2)
>>> a2.movie_set.all()
위와 같이 중간 테이블에 지정한 이름으로 역참조를 하여야 된다!
Django에서 Related name 설정이 반드시 되어야 하는 상황이 있는데 같은 클래스 내 attribute에서 서로 같은 모델을 참조하고 있을 경우에 related_name
을 설정하지 않는다면 migration이 되지 않고 오류가 발생한다!
이런 경우에는 꼭 related_name
을 설정하여 문제없이 migration 진행을 해야한다!
잘못된 부분은 댓글로 Feedback 부탁드립니다! 👊🏻👊🏻👊🏻👊🏻👊🏻👊🏻👊🏻👊🏻👊🏻👊🏻👊🏻
Ref.
https://velog.io/@ssaboo/TIL-Wecode-Django-ORM-MN-%EA%B4%80%EA%B3%84