Django | 다대다(many-to-many) 관계 만들기

2022. 7. 5. 17:29·Django
반응형

actors-movies 테이블 사이 이런 관계가 있을 때

 

ForeignKey 사용

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 ActorMovie(models.Model):
    movie = models.ForeignKey('Movie', on_delete=models.CASCADE)
    actor = models.ForeignKey('Actor', on_delete=models.CASCADE)
    
    class Meta:
        db_table = 'actors_movies'

관계테이블을 따로 정의하고 두 테이블이랑 일대다 관계로 각각 이어줘야 한다.

 

ManyToManyField

장고에서 제공하는 ManyToManyField를 이용하면 관계테이블을 직접 만들지 않고 간단하게 작성할 수 있다.

 

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') # 이 속성은 데이터베이스에 생기지 않는다 movies는 참조를 위한 매니저 클래스가 된다

    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'

데이터베이스에도 자동으로 <m2m관계를 적어준 테이블이름>_<다대다관계인 테이블이름>으로 관계 테이블이 생성된다

mysql> show tables;
+---------------------+
| Tables_in_crud2     |
+---------------------+
| actors              |
| actors_movies       |
| django_content_type |
| django_migrations   |
| django_session      |
| movies              |
+---------------------+

Actor에 준 movies라는 속성은 데이터베이스에 컬럼을 만들지 않는다. 

외래키만 가지고 다대다 관계를 만들면 Actor->Movie, Movie->Actor에 접근하려면 관계 테이블을 통해 접근해야 한다.

Movie.objects.filter(movieactor__actor=actor)

그런데 장고의 매니투매니필드를 이용하면 movies라는 속성이 many to many관계에서 매니저클래스가 된다. 그래서 중간 테이블을 타고 들어가지 않아도 서로에게 접근이 가능하다.

 

Actor에서는 movies를 통해서(정참조),

Movie에서는 actor_set(역참조)를 통해서 관계있는 테이블에 접근이 가능하다.

Actor.movies.all()
Movie.actor_set.all()

역참조에서 매니저클래스는 상대클래스 이름의 소문자_set으로 자동으로 정해지는데

그게 싫으면 related_name이라는 속성을 주면 된다.

movies = models.ManyToManyField('Movie', related_name='actors')

이렇게 하면 Movie에서 Actor객체에 접근할때 Movie.actors.get.....이런식으로 접근이 가능하다

 

관계 만들기

테이블에 넣은 데이터는 아래와 같다.

mysql> table movies;
+----+---------------------------------+--------------+--------------+
| id | title                           | release_date | running_time |
+----+---------------------------------+--------------+--------------+
|  1 | 장고: 분노의 추적자             | 2013-03-21   |          165 |
|  2 | 바스터즈: 거친 녀석들           | 2009-10-29   |          153 |
|  3 | 토이스토리                      | 1995-12-23   |           81 |
|  4 | 위대한 개츠비                   | 2013-05-16   |          142 |
|  7 | 돈 룩 업                        | 2021-12-08   |          145 |
|  8 | 헝거 게임: 판엠의 불꽃          | 2012-04-05   |          142 |
|  9 | 레이디 버드                     | 2017-11-10   |           94 |
| 10 | 작은 아씨들                     | 2020-02-12   |          135 |
+----+---------------------------------+--------------+--------------+
8 rows in set (0.00 sec)

mysql> table actors;
+----+-----------------+-----------------+---------------+
| id | first_name      | last_name       | date_of_birth |
+----+-----------------+-----------------+---------------+
|  1 | 톰              | 행크스          | 1956-07-09    |
|  2 | Christoph       | Waltz           | 1956-10-04    |
|  3 | 브래드          | 피트            | 1963-12-18    |
|  4 | 레오나르도      | 디카프리오      | 1974-11-11    |
|  5 | Carey           | Mulligan        | 1985-05-28    |
|  7 | 제니퍼          | 로렌스          | 1990-08-15    |
|  8 | 티모시          | 샬라메          | 1995-12-27    |
|  9 | 플로렌스        | 퓨              | 1996-01-03    |
| 10 | Saoirse         | Ronan           | 1994-04-12    |
+----+-----------------+-----------------+---------------+
9 rows in set (0.00 sec)

둘을 관계 지을때는 객체를 이용한다.

 

정참조

>>> toys = Movie.objects.get(id=3)
>>> tomh = Actor.objects.get(id=1)
>>> tomh.movies.add(toys)

actors의 id 1인 톰 행크스와 movies에서 id=3인 토이스토리의 관계를 만들어주었다.

# 결과:
mysql> table actors_movies;
+----+----------+----------+
| id | actor_id | movie_id |
+----+----------+----------+
|  1 |        1 |        3 |
+----+----------+----------+

나머지도 관계를 설정해줬다.

>>> a2=Actor.objects.get(id=2)
>>> movies=Movie.objects.all()
>>> movies[0]
<Movie: Movie object (1)>
>>> a2.movies.add(movies[0],movies[1])
>>> a=Actor.objects.all()
>>> a[2].movies.add(movies[1])
>>> a[3].movies.add(movies[0], movies[3])     #여러개일때는 ,로 구분해서 한번에 가능
>>> a[4].movies.add(movies[3])
# 결과
mysql> table actors_movies;
+----+----------+----------+
| id | actor_id | movie_id |
+----+----------+----------+
|  1 |        1 |        3 |
|  2 |        2 |        1 |
|  3 |        2 |        2 |
|  4 |        3 |        2 |
|  5 |        4 |        1 |
|  6 |        4 |        4 |
|  7 |        5 |        4 |
+----+----------+----------+
7 rows in set (0.00 sec)

역참조

Movie클래스에서 actor_set을 이용해서 역참조로 관계를 설정하기

In [23]: dlu=Movie.objects.get(id=7)
In [24]: actors=Actor.objects.all()
In [25]: dlu.actor_set.add(actors[3], actors[5], actors[6])
# 결과
mysql> table actors_movies;
+----+----------+----------+
| id | actor_id | movie_id |
+----+----------+----------+
|  1 |        1 |        3 |
|  2 |        2 |        1 |
|  3 |        2 |        2 |
|  4 |        3 |        2 |
|  5 |        4 |        1 |
|  6 |        4 |        4 |
|  9 |        4 |        7 |
|  7 |        5 |        4 |
| 10 |        7 |        7 |
|  8 |        8 |        7 |
+----+----------+----------+
10 rows in set (0.00 sec)
In [27]: Actor.objects.get(id=7).movies.add(Movie.objects.get(id=8))
In [28]: Movie.objects.get(id=9).actor_set.add(actors[6],actors[8])
In [29]: Movie.objects.get(id=10).actor_set.add(actors[6],actors[7],actors[8])
# 결과
mysql> table actors_movies;
+----+----------+----------+
| id | actor_id | movie_id |
+----+----------+----------+
|  1 |        1 |        3 |
|  2 |        2 |        1 |
|  3 |        2 |        2 |
|  4 |        3 |        2 |
|  5 |        4 |        1 |
|  6 |        4 |        4 |
|  9 |        4 |        7 |
|  7 |        5 |        4 |
| 10 |        7 |        7 |
| 11 |        7 |        8 |
|  8 |        8 |        7 |
| 12 |        8 |        9 |
| 14 |        8 |       10 |
| 15 |        9 |       10 |
| 13 |       10 |        9 |
| 16 |       10 |       10 |
+----+----------+----------+
16 rows in set (0.00 sec)

 

 

관계를 이용해서 데이터 불러오기

정참조

Actor->Movie를 참조할땐 매니저클래스 movies를 사용할 수 있다.

In [30]: a1=Actor.objects.get(first_name__contains='모시')

In [31]: m1=[movie.title for movie in a1.movies.all()]

In [32]: m1
Out[32]: ['돈 룩 업', '레이디 버드', '작은 아씨들']

 

역참조

ManyToManyField가 정의돼있지 않은 Movie 클래스에서 Actor 클래스를 참조할 때는 역참조이다.

actor_set을 이용해서 참조할 수 있다.

In [11]: movie1=Movie.objects.get(title__startswith='장고')

In [12]: movie1.title
Out[12]: '장고: 분노의 추적자'

In [13]: actors1=movie1.actor_set.all()

In [14]: for actor in actors1:
    ...:     print(actor.first_name + actor.last_name)
    ...:
ChristophWaltz
레오나르도디카프리오

 

 

 

참고자료

https://docs.djangoproject.com/en/4.0/topics/db/examples/many_to_many/

반응형
저작자표시 비영리 변경금지 (새창열림)

'Django' 카테고리의 다른 글

Django | models : verbose_name  (0) 2022.08.20
Django | ORM : on_delete=models.CASCADE  (0) 2022.08.14
Django | 프로젝트 초기세팅  (0) 2022.07.16
Django | 파이썬 쉘에서 db에 있는 데이터 id값 바꾸기  (0) 2022.07.06
Django | sql - 장고 모델 속성의 데이터타입  (0) 2022.07.05
'Django' 카테고리의 다른 글
  • Django | ORM : on_delete=models.CASCADE
  • Django | 프로젝트 초기세팅
  • Django | 파이썬 쉘에서 db에 있는 데이터 id값 바꾸기
  • Django | sql - 장고 모델 속성의 데이터타입
이라후
이라후
  • 이라후
    화이팅
    이라후
  • 전체
    오늘
    어제
    • 분류 전체보기 (133)
      • TIL (23)
      • 기타 (26)
      • Python (14)
      • Django (10)
      • JavaScript (8)
      • git & GitHub (8)
      • Web (10)
      • Go (3)
      • wecode (31)
  • 반응형
  • 인기 글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
이라후
Django | 다대다(many-to-many) 관계 만들기
상단으로

티스토리툴바