Many-to-many relationships in Django explained
Tags: django, python, webdev, backend
Published: 2022-06-19 06:06:15

Django provides us the [ManyToMany](https://docs.djangoproject.com/en/4.0/ref/models/fields/#django.db.models.ManyToManyField) field to define [many-to-many relationships](https://docs.djangoproject.com/en/4.0/topics/db/examples/many_to_many/) between our models. So for example, if we need to define a many-to-many relationship between two models named Books and Authors, we can do it as follows:

```py
from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=30)

    def __str__(self):
        return self.name


class Book(models.Model):
    title = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)

    def __str__(self):
        return self.title
```
This relationship tells us that an author can write more than one book, while a book can be written by one or more authors.
Now, let's populate our models with some data to understand how many-to-many relationships work in Django.

Let's add the author J.K Rowling and 3 novels authored by her to our database. I'll be using the relational database SQLite.

```py
>>> a1 = Author(name="J.K. Rowling")
>>> a1.save()

>>> b1 = Book(title="Harry Potter and the Philosopher's Stone")
>>> b2 = Book(title="Harry Potter and the Chamber of Secrets")
>>> b3 = Book(title="Harry Potter and the Goblet of Fire")
>>> b1.save()
>>> b2.save()
>>> b3.save()

>>> b1.authors.add(a1)
>>> b2.authors.add(a1)
>>> b3.authors.add(a1)
```
The novels titled "The Talisman" and "Black House" were written by Stephen King and Peter Straub. Let's add this data as well.

```py
>>> a2 = Author(name="Stephen King")
>>> a3 = Author(name="Peter Straub")
>>> a2.save()
>>> a3.save()

>>> b4 = Book(title = "The Talisman")
>>> b4.save()
>>> b4.authors.add(a2)
>>> b4.authors.add(a3)

>>> b5 = Book(title = "Black House")
>>> b5.save()
>>> b5.authors.add(a2)
>>> b5.authors.add(a3)
```
Suppose I want to fetch the authors of the book "The Talisman". I can do that as follows:

```py
>>> book = Book.objects.get(title="The Talisman")
>>> book.title
'The Talisman'
>>> book.authors.all()
<QuerySet [<Author: Stephen King>, <Author: Peter Straub>]>
```
Now, I want to retrieve all the books authored by J.K. Rowling. I can do that as follows:

```py
>>> author = Author.objects.get(name = "J.K. Rowling")
>>> author.name
'J.K. Rowling'
>>> author.book_set.all()
<QuerySet [<Book: Harry Potter and the Philosopher's Stone>, <Book: Harry Potter and the Chamber of Secrets>, <Book: Harry Potter and the Goblet of Fire>]>
```

Now, let's look at our database to see how these relationships are actually defined. I'll be using DB Browser for SQLite for browsing through the database.

Our authors' table looks as follows:

![Authors](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s0pbhqkjyoag6b74wbw2.png)

And the books' table looks like below:

![Books](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cexj7do8wa2kptps33fl.png)
 
Note that we had defined a ManyToManyField named "authors" in our Books model, and we are retrieving the authors of a particular book using that field. However, that field cannot be seen in our books table. Why is that so?

This is because Django has created another table in our database to define the relationship between books and authors. The table looks as follows:


![book-authors table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nmbjowstxwdbn4bvpyye.png)
 
This table stores the relationships between books and authors. The author_id 1 corresponds to J.K. Rowling and book_ids 1,2 and 3 correspond to the three books written by her. Similarly, 
author_ids 2 and 3 correspond to Stephen King and Peter Straub respectively, and book_ids 4 and 5 correspond to the novels "The Talisman" and "Black House" respectively.
 
Defining these relationships using pure SQL queries would have been a more difficult job, however the Django ORM makes it easy for the developer to define such relationships while at the same time ensuring that the database is [normalized](https://en.wikipedia.org/wiki/Database_normalization).