ManyToManyField


crud(2)owner-dogテーブルで1:N関係データベースのBackend APIを実現した.今回はN:N関係テーブルでManyToManyFieldを用いてBackend APIを実装する.

Model


これはN:N関係なので、ActorとMovieテーブルをForeignKeyに接続する中間テーブルを作成します.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を使用して再モデリングします.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では、関連するnameの設定が必要で、同じクラスのプロパティでは、関連するnameを設定せずに互いに同じモデルを参照すると、移行は発生せず、エラーが発生します.
私の場合もエラーが発生し、migartinにはならず、関連するnameを設定してスムーズに移行しました.
次に、Database Tableを作成し、各Tableにデータを入力しようとします.

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理解ではactormoviesを参照せずに直接参照することができ,コードの長さが短くなり,可読性が良くなると判断できる.