Django ManyToManyField

Junyoung Lee·2021년 6월 22일
1

Django

목록 보기
3/4

이번에는 Django의 ManyToManyField에 대해서 적어보려고 합니다!


Django에서의 M2M 관계

Django에서 Many-To-Many를 표현하는 방법은 여러가지가 있다👇🏻

1. 중간 테이블 Class를 생성하여 FK연결

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 테이블이 ProductAllergy를 참조하게 만들어서 중간 테이블로 연결시킬 수 있는데 이럴 경우에는 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)
    

하지만 위의 경우와 같이 중간 테이블에 정보를 넣는 것이 필요할 경우에는 중간테이블 클래스를 선언해줘야 한다


2. ManyToManyField 사용

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'

ManyToManyFieldM2M관계 설정을 할 수 있는데 이런 식으로 모델을 작성하게 되면 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

profile
🎹재즈를 사랑하는 백엔드 개발자 이준영입니다🎷

0개의 댓글