[Django] ManyToManyField

minch·2021년 7월 20일
1

Django

목록 보기
8/16
post-thumbnail

배우와 영화 정보를 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를 사용하면 된다.

Use 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로 정의하였기 때문이다.

ManyToManyFieldadd() 메소드를 통해 중간 테이블 내 데이터를 생성할 수 있다.

through model

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)

0개의 댓글

관련 채용 정보