[Django] M to M model과 ManyToManyField

^_^·2022년 7월 6일
0

Django

목록 보기
4/4
post-thumbnail

과제 진행중 문제가 생겼던 부분에 대해 남긴다.

ManyToMany model

아래와 같이 ManyToManyField를 Actors 모델에 작성했고 Movies모델은 일반 모델과 같이 작성했다. 다대다 관계를 정의할때 따로 Forignkey를 받을 모델 클래스를 작성하지 않고 ManyToManyField를 클래스 속성으로 포함해 사용할 수 있다. 다른 속성들과 다르게 ManyToManyField를 사용하면moveis는 컬럼이 생성되는게 아니고 매니저클래스가 된다. 즉, movies를 데이터를 참조해 가져올 수 있다.


class Actors(models.Model):
    first_name = models.CharField(max_length=45)
    last_name = models.CharField(max_length=45)
    date_of_birth = models.DateField(blank=True, null=True)
    movies = models.ManyToManyField(
        'Movies', related_name="owo"
        )

    class Meta:
        db_table = 'actor'


class Movies(models.Model):
    title = models.CharField(max_length=45)
    release_date = models.DateField(blank=True, null=True)
    running_time = models.IntegerField()

    class Meta:
        db_table = 'movie'

ManyToManyField를 사용해 아래와 같은 다대다관계 테이블을 만들 수 있다.


a1 = Actors.objects.get(last_name="Ahn")
m1 = Movies.objects.get(title = "설국열차")
m2 = Movies.objects.get(title = "겨울왕국")
출연 영화 추가
a1.movies.add(m1, m2)

위 models.py를 기준으로 views.py를 작성했다. httpie를 사용해 GET으로 응답 받기 위해 actors에 Actors 모델로 생성한 데이터 객체를 담고 for 문으로 객체를 하나씩 가져오고 ManyToManyField의 movies에서 영화 정보를 가져왔다.


import json

from django.http import JsonResponse
from django.views import View

from movies.models import Actors, Movies

class ActorsView(View):
    def get(self, request):
        actors = Actors.objects.all()
        results=[]
        for actor in actors:
            movies = actor.movies.all()
            results.append(
                {
                    "first_name" : actor.first_name,
                    "last_name" : actor.last_name,
                    "movies" : [i.title for i in [movie for movie in movies]]
                }
            )
        return JsonResponse({'resutls':results}, status=200)
        

ActorsView클래스를 이용해 GET으로 데이터를 받아왔다. 잘된다. 여기선 MoviesView도 그냥 하면 되겠지 생각했다.

 http -v GET http://127.0.0.1:8000/movies/actors



HTTP/1.1 200 OK
Content-Length: 369
Content-Type: application/json
Cross-Origin-Opener-Policy: same-origin
Date: Tue, 05 Jul 2022 13:21:20 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.8.13
Vary: Origin
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

{
    "resutls": [
        {
            "first_name": "SangHyun",
            "last_name": "Ahn",
            "movies": [
                "겨울왕국",
                "설국열차"
            ]
        },
        {
            "first_name": "SangHyun",
            "last_name": "Kim",
            "movies": [
                "겨울왕국",
                "기생충"
            ]
        },
        {
            "first_name": "JY",
            "last_name": "P",
            "movies": [
                "겨울왕국",
                "설국열차",
                "기생충"
            ]
        }
    ]
}

MoviesView에서 역참조를 받기위해 actors_set을 사용해 데이터를 가져오려고 했다.

class MoviesView(View):
    def get(self, request):
        movies = Movies.objects.all()
        results=[]
        for movie in movies:
            actors = movie.actors_set.all()
            results.append(
                {
                    "title" : movie.title,
                    "running_time" : movie.running_time,
                    "actors" : [i.first_name for i in [actor for actor in actors]]
                }
            )
        return JsonResponse({'resutls':results}, status=200)

그런데 계속 에러가 났다. 왜그럴까... 쉘창에서 actors_set을 사용해서 데이터를 참조받으려 해도 되지 않았다.


Python 3.8.13 (default, Jun  6 2022, 19:35:52) 
[Clang 13.1.6 (clang-1316.0.21.2.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from movies.models import Actors, Movies
>>> m1 = Movies.objects.get(title="기생충")
>>> m1.actor_set.all()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'Movies' object has no attribute 'actor_set'

꽤오래 찾아본거 같다. 안되던 이유는 related_name 옵션 때문이였는데 이 옵션을 쓰게 되면 역참조할때 _set이 아니라 related_name에 지정한 값으로 데이터를 참조 받아야 했다. 나는 related_name="movies"로 해놨으니 정참조할때 movies로 데이터를 가져오겠다고 지정한줄 알았다. 바보같다.
같은 값으로 데이터를 가져오면 오해할 수 있으니 related_name="owo"로 바꿔 다시 시도했다.


class MoviesView(View):
    def get(self, request):
        movies = Movies.objects.all()
        results=[]
        for movie in movies:
            actors = movie.owo.all()
            results.append(
                {
                    "title" : movie.title,
                    "running_time" : movie.running_time,
                    "actors" : [i.first_name for i in [actor for actor in actors]]
                }
            )
        return JsonResponse({'resutls':results}, status=200)

출력전 쉘창에서 데이터를 가져오는지 확인했다. 너무 잘나온다.


>>> a1.movies.all()
<QuerySet [<Movies: Movies object (1)>, <Movies: Movies object (2)>, <Movies: Movies object (3)>]>

이제 영화데이터의 title, running_time, actors(출연배우 목록)을 가져와보자.

http -v GET http://127.0.0.1:8000/movies/movies

HTTP/1.1 200 OK
Content-Length: 289
Content-Type: application/json
Cross-Origin-Opener-Policy: same-origin
Date: Tue, 05 Jul 2022 13:16:36 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.8.13
Vary: Origin
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

{
    "resutls": [
        {
            "actors": [
                "SangHyun",
                "SangHyun",
                "JY"
            ],
            "running_time": 102,
            "title": "겨울왕국"
        },
        {
            "actors": [
                "SangHyun",
                "JY"
            ],
            "running_time": 126,
            "title": "설국열차"
        },
        {
            "actors": [
                "SangHyun",
                "JY"
            ],
            "running_time": 131,
            "title": "기생충"
        }
    ]
}

원하는 데이터를 잘 가져온다.

ManyToManyField 장점

ManyToManyField를 사용하면 장고가 자동으로 테이블을 만들어준다. 참조를 받을 모델을 작성하지 않아도 된다는 말이다. 하지만 내가만든게 아니기 때문에 커스터 마이징이 불편할 수있다. 이떄 through를 사용하면 내가 사용할 다대다 관계 테이블을 지정할 수 있다.

a1 = Actors.objects.get(last_name="Ahn")
m1 = Movies.objects.get(title = "설국열차")
m2 = Movies.objects.get(title = "겨울왕국")
출연 영화 추가
a1.movies.add(m1, m2)
ManyToManyField 사용안할때
a1 = Actors.objects.get(last_name="Ahn")
m1 = Movies.objects.get(title = "설국열차")
m2 = Movies.objects.get(title = "겨울왕국")

# ActorsMovies라는 중간테이블생성을 위한 model클래스가 있다고 가정
# Foriegnkey를 가져올 변수는 actors, movies라고 하자
ActorsMovies.objects.create(actors= a1, movies=m1,m2)

그리고 ManyToManyField를 사용하면 중간테이블에 접근하지 않고 movies를 사용해 다른테이블로 정참조 역참조가 가능하다.

0개의 댓글