FOSSTER by Ashwin Shenoy

Django Admin: Setting up Many-to-Many Relationships with Intermediate Models

The Scenario

Lets 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).

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 is 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.

2. 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.

3. 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.

We will infact be also be able to create and edit artist details while movie creation, and vice-versa. When an artist is added to a movie, the movie is also automatically added to the artist (i.e. the relation is bidirectional).

TRY THESE OUT

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

More Extensive Reference
[1] Many to Many Relationships - https://docs.djangoproject.com/en/dev/ref/contrib/admin/#working-with-many-to-many-intermediary-models
[2] Inline Admin - https://docs.djangoproject.com/en/2.0/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin
[3] Model Admin - https://docs.djangoproject.com/en/2.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin