Django Models: Setting up Many-to-Many Relationships through Intermediate Models

10/18/2018
4 min read

This is an archived post from my college days, which I originally published on Medium.

Original URL: https://medium.com/@aswinshenoy/django-admin-setting-up-many-to-many-relationships-with-intermediate-models-782895cb48b2

The Scenario

Let's consider a Django app with the following model to manage movie and artist details. It uses an intermediate model role to relate between movies and artists.

class Artist(models.Model):
    name = models.CharField(max_length=200)
    class movie(models.Model):
    title = models.CharField(max_length=100)
    artists = models.ManyToManyField(artist, related_name = 'actor', through='roles')
    class role(models.Model):
    role_name = models.CharField(max_length=100)
    artist = models.ForeignKey(artist, on_delete=models.CASCADE)
    movie = models.ForeignKey(movie, on_delete=models.CASCADE)

Though we have setup a many-to-many field, one would not find the ability to select/specify the artists while creating a movie (as well as to specify movies when creating artists).

Create Artist Page
Create Movie Page

The only way we can manage relations now is by creating roles (just like creating artists/movies), in which we can select artist and movie. However, this is not what we expect things to work.

Prerequisite

  • Understand and write Python code
  • Create and run Django app/project
  • Basic understanding about Models in Django

Our Goal

  • Achieve a movie-artist, bidirectional, many-to-many relationship (with intermediate model).
  • Ability to ‘select’ artists in movie page, and ‘select’ movies in artist page.
  • Specify role/character name of the artist in the movie (using a intermediate model).

Solution Steps

  1. Create an inline class for the intermediate model (i.e role)
class role_inline(admin.TabularInline):
   model = role
   extra = 1

add this code to your models.py (or anywhere with admin class imported)

What this does?

Creates a tabular form to manage the given model, which here is ‘role’ which holds the relationship.

Why doesn’t it show up? It will still not show up while creating a movie/artist, and you will see no observable changes after adding this code. For changes to come, we need to put this class as an inline argument inside ModelAdmin class of both the models.

What does ‘extra’ do?

Extra is nothing but a parameter which specifies how many empty role(movie/artist) slots are to be loaded when a movie/artist is being created.

For example when extra = 10, 10 blank slots are rendered to enter 10 artists in a movie creation page. Note that even if extra is set to 1, and only 1 blank slot is rendered, one can use a “add another” button to add any number of artists/movies.

  1. Define ModelAdmin class with the inline class
class artistAdmin(admin.ModelAdmin):
   inlines = (role_inline,)
   class movieAdmin(admin.ModelAdmin):
   inlines = (role_inline,)

add this code to your models.py (or anywhere with admin class imported)

What this does?

We have defined a ModelAdmin class for each model (i.e. a class that manages the admin end of the model), and added the TabularInline class we just defined above as a inline in the inlines option for the model admin class.

Why don’t I see changes yet?

Though we have defined new ModelAdmin classes for each of our models, and also included the inline to show up the roles field, we missed out something very essential.

Did we anywhere mention/refer to the actual model when creating the admin class for it? Nope. As the final step, we should add the newly created model admin classes while registering the model itself in the admin.py.

What does ‘inlines’ option do?

The admin interface has the ability to edit models (here, we want to edit the intermediate model role) on the same page as a parent model (i.e. movie and artist). This is achieved through the inlines option.

  1. Add ModelAdmin class while registering the models in admin
   admin.site.register(movie, movieAdmin)
   admin.site.register(artist, artistAdmin)
   admin.site.register(role)

add this code to your admin.py (or anywhere with admin class, and the models are imported. )

What this does ?

Each of the model is registered in the admin, along with their admin-class specifying how the model should be managed inside the admin.

Finally, we will see a table field showing the option to add/remove/edit artists/movies using selector while creating movies/artists, and also the field to specify the role name.

The Result

We will in-fact be also be able to add new artists and edit artist details through a pop-up while creating a movie profile, and vice-versa while creating an artist profile. When an artist is added to a movie, the movie is also automatically added to the artist (i.e. the relation is bidirectional).

Change Movie Page
Change Artist Page

Try these out

  • Can you use the same ModelAdmin class (say, artistAdmin) for both the models ?
  • Can you put the 3 admin.site.register() calls right inside the models.py ?
  • Try using StackedInline instead of TableInline and checkout the difference.

More Extensive Reference

Stay in touch

Be the first to hear when I post new stuff! Instead of filling your inbox with newsletters, I prefer using X to share all my updates and blog posts. If you find my content useful or interesting then please consider following me on X.


If you found this post helpful,
you will love these too.
5 min read
10/18/2018
My Google Summer of Code Experience
My experience and learnings from completing Google Summer of Code 2019 with Salesforce Open Source on the Design System React project.
4 min read
10/18/2018
Things that matter in a Hackathon, and why hackathons matter.
A reflection from first hackathon experience, why hackathons matter, and things that matter in a hackathon.