TIL #14 CRUD(M2M)

tycode·2021년 6월 22일
0

TIL

목록 보기
16/30

구현하고자 하는 테이블의 형태는 같지만, 필자는 데이터 변수와 타입 등을 다르게 예시로 했다.


ForeignKey로 M2M

모델 필드에 ForeignKey를 사용한다면 두 테이블을 이어주기 위해 중간테이블 모델을 추가해줘야 한다.

models.py

일단 두 테이블을 연결하기 위해서는 class에 중간테이블 모델을 선언해야 된다.

class Actor(models.Model):
    name = models.CharField()
    age  = models.IntegerField()
    
class Movie(models.Model):
    title = models.CharField()
    score = models.IntegerField()

class MovieActor(models.Model):      # 중간 테이블
    actor = models.ForeignKey('Actor', on_delete=models.CASCADE)
    movie = models.ForeignKey('Movie', on_delete=models.CASCADE)
  • MovieActor라는 중간테이블을 만들어주고 Actor와 Movie를 FK로 연결한다.
shell CREATE
In [1]: a1 = Actor.objects.create(name='Bob', age=12)
In [2]: m1 = Movie.objects.create(title='Armageddon',score='5')
In [3]: MovieActor.objects.create(actor=a1, movie=m1)
  • 이렇게 각 클래스를 a1, m2에 담고 그 변수들을 MovieActor에 모델을 선언한다.
shell READ
In [1]: a1 = Actor.objects.get(id=1)

#중간테이블 이름 together라고 가정.
#_set은 역참조를 의미
In [2]: a1.together_set.all()
Out[3]: <QuerySet [<MovieActor: MovieActor object (1), <MovieActor: MovieActor object (2)>]

In [4]: a1.together_set.get(id=1).movie
Out[5]: <Movie: Movie object (1)>

In [6]: a1.together_set.get(id=1).movie.name
Out[7]: 'Armageddon'
  • actor의 아이디로 >> together 중간테이블의 movie ForegnKey를 통해 >> movie클래스 접근 >> name을 찾아서 반환
  • 중간 테이블을 거쳐야 하는 번거로움이 있다.

ManyToManyField로 M2M

이 필드를 사용하면 두 테이블을 이어주는 중간테이블을 선언하지 않아도 된다.

models.py
class Actor(models.Model):
    name = models.CharField()
    age  = models.IntegerField()

class Movie(models.Model):
    title  = models.CharField()
    score  = models.IntegerField()
    actors = models.ManyToManyField(Actor, related_name='movies')
  • Movie가 Actor를 참조하는 함수를 actors라고 선언한다.
  • related_name은 optional이지만 넣는게 편하다. GET할 때, Actor가 Movie를 역참조할 때 해당 부분을 공란으로 두면 _set.all()로 불러야 한다. 밑에 views.py에 예시를 보자.
shell CREATE
In [1]: a1 = Actor.objects.create(name='Bob', age=12)
In [2]: m1 = Movie.objects.create(title='Armageddon',score='5')
In [3]: m1.actors.add(a1) #Movie 아마게돈에 Actor 밥을 넣어주었다.
In [4]: m1.save()         ###### 잊지말고 하자.. 안그럼 저장이 안됨.
  • 반대로 a1.movies.add(m1)도 가능하다.
    만약 related_name을 안했다면 a1.movie_set.add(m1)
SHELL READ
In [1]: m1 = Movie.objects.get(id=1)
In [2]: m1.actors.get()   #하나의 actor를 참조했을 경우 가능. 만약 둘 이상이면 get()안에 특정 actor의 id값을 넣어줘야 한다.
Out[3]: <Actor: Actor object (1)>
-------또는-------
In [2]: m1.actors.all()   #이렇게하면 m1에 해당하는 actor들을 불러온다.
Out[3]: <QuerySet [<Actor: Actor object (1)>, <Actor: Actor object (2)>]>

In [4]: m1.actors.get().name   #값이 하나일 경우.
Out[5]: 'Bob'
-------또는--------
In [4]: m1.actors.get(id=1).name  #2개 이상일시 지정함.  or m1.actors.get(name='Bob')
Out[5]: 'Bob'                                       or <Actor: Actor object (1)>
views.py (GET)

Actor가 Movie의 title을 정참조하려고 한다.

class ActorView(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_info = {
                	'title' : movie.title
                    }
                movies_list.append(movies_info)
         actor_info = {
            'name'   : actor.name
            'age'    : actor.age
            'movies' : movies_list
            }
         result.append(actor_info)
     return JsonResponse({'result'}, result}, status=200)
  • 만약 위에 related_name을 정하지 않았을 경우, 해당 부분은 movies = actor.actor_set.all()되야 한다. actor변수를 actor(클래스지만 앞에 소문자로 씀)를 역참조하겠다는 의미.
  • 반면, Movie가 Actor를 정참조할 때는 이미 변수로 actors라고 정했기 때문에 actors = movie.actors.all()로 불러오면 된다.
  • 주의할점: ManyToManyField는 참조하려는 클래스 함수보다 아래 클래스에 위치해 있어야 한다.
    즉, class ActorView(View)에 ManyToManyField를 넣는다면, class MovieView(View)가 위에 먼저 선언되있어야 한다.

결론

  • ForeignKeyField를 사용할 경우, 중간테이블을 만들어야되는 번거로움이 조금 있다.

  • python shell에서 데이터를 CRUD할 때, 중간테이블을 거쳐서 해당 테이블로 가야 된다.

  • ManyToManyKey로 정/역참조할 경우, 중간테이블이 자동으로 생성된다.

  • 중간테이블을 CRUD할 수는 없지만, 참조한 테이블의 값을 부를 때 중간테이블을 거치지 않아서 shell에서 데이터를 상대적 쉽게 추가 및 삭제할 수 있다.

0개의 댓글