ManyToManyField

최창환·2022년 3월 20일
0
post-thumbnail

crud (2) owner - dog 테이블에서는 1:N 관계의 데이터베이스에 대한 Backend API를 구현해 보았다. 이번에는 N:N 관계의 테이블에서 ManyToManyField를 사용하여 Backend API를 구현해보자.

Model

이번에는 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를 입력해 보았다.

View

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를 참조하지않고 바로 참조할 수 있어서 코드의 길이가 짧아지고 가독성이 좋아진것을 확인할 수 있다.

profile
포기하지 않는 개발자

0개의 댓글