Understanding intermediate model with many-to-many relationship in Django

Many-to-many relationship in Django generate third (intermediate) model which store key-to-key pairs from each of many-to-many models. Because there is no other way to store many-to-many relationship.

If your many-to-many pair don’t need to have additional information, you can just add ManyToManyField to one of the model. Which one? To anyone, because there is no difference: many-to-many relationship is symmetric. For example, you have Post and Tag models:

class Post(models.Model):  
    title = models.CharField()
    text = models.TextField()

class Tag(models.Model):  
    title = models.CharField()
    posts = models.ManyToManyField(Post, related_name='posts')

You can put ManyToManyField in Post class, or in Tag class. And in example above intermediate model don’t have any additional options. There is no “post-tag” object you can manage or use somehow. It’s just relationship you want to designate and that’s all. Note, you don’t need to add on_delete param because key-to-key pair in intermediate models will be deleted automatically when you delete row in one of the many-to-many relationship.

Another examlpe, we have Artist, Movie and intermediate Role models:

class Artist(models.Model):  
    name = models.CharField()

class Movie(models.Model):  
    title = models.CharField()
    artists = models.ManyToManyField(artist, related_name = 'actor')

And we need to add some custom fields to intermediate model. Every artist in movie has it’s own role, so the intermediate model is a artist’s role. Role has a lot of additional info, for example, role name. Django allows you to manually create intermediate model with custom fields. First, you should add through param in ManyToManyField and set it’s value to name of intermediate model. Second, in this third model you need to specify two one-to-many fields for each of many-to-many models:

class Artist(models.Model):  
    name = models.CharField()

class Movie(models.Model):  
    title = models.CharField()
    artists = models.ManyToManyField(artist, related_name = 'actor', through='Role')

class Role(models.Model):  
    role_name = models.CharField()
    artist = models.ForeignKey(artist, on_delete=models.CASCADE)  
    movie = models.ForeignKey(movie, on_delete=models.CASCADE)

Note, here we should set on_delete param for third model because it’s one-to-many fields ,not many-to-many.

Now we have intermediate model with custom fields and we can add roles in admin panel using Movie and Artist models. And this is complete different example, because Role is independent entity having it’s own properties. While “post-tag” is something like more technical relationship, in this example “artist-movie” relationship create new object - role with it’s own properties.