Frequently most challenging part of creating scalable and reliable applications in web development is making a good database schema. Often entities can contain shared data and could not be represented with the classic OneToMany relationship explained in the ForeignKey article. That’s why Django has built-in tools to help us represent ManyToMany relationships.
What is Many To Many relationship?
Many to Many is a way of connecting entities with one additional table (associative table). Associative table points to entities it connects. The associative table contains primary keys of entities it connects and additional optional parameters.
Django Many To Many relationship can be easily represented in 2 different ways that we will cover in this article:
- Using two
Django Many To Many relationship using ManyToManyField
Django ManyToManyField is a field that helps you create many to many relationships in Django. It’s also the most obvious and fastest way.
ManyToManyField syntax is as follows:
class ManyToManyField(to, **options)
to– Related model class
**options– optional arguments explained in official Django documentation
ManyToManyField in Database
ManyToManyField between two models creates a third table in the database. That table contains the primary key of both models.
Let’s check out the following models in which we assume that the Book can have multiple Authors:
class Author(models.Model): first_name = models.CharField(max_length=512) last_name = models.CharField(max_length=512) class Book(models.Model): title = models.CharField(max_length=512) authors = models.ManyToManyField(Author)
Often, developers are confused in what model to put ManyToManyField. From the database point of view, it doesn’t matter. Tables are equally connected no matter where you place it. But Django admin and ORM have slight changes in behavior.
Django admin interface adds a widget for choosing related objects to a model where ManyToManyField is set. In other words, it requires adding referenced objects first. For our example, you could choose authors only when adding books, but not another way around.
After migrating the above models to the database, we get the following database schema:
Our associate table contains book_id and author_id referencing objects from connected tables.
For example, let’s check out the book “The Talisman” written by Stephen King and Peter Straub.
>>> from app.models import Author, Book >>> a1 = Author(first_name="Stephen", last_name="King") >>> a2 = Author(first_name="Peter", last_name="Straub") >>> a1.save() >>> a2.save() >>> b1 = Book(title="The Talisman") >>> b1.save() # Can’t associate book with authors if it is not yet saved! >>> b1.authors.add(a1, a2)
Here we can see a few crucial steps. Firstly we create two authors, both of them have to be saved (if we don’t save it,
ValueError appears because the object still doesn’t have id value). Then we create Book, Book also needs to be saved, or
ValueError appears. Only then, we access the authors’ field using Django ORM and add both authors, affecting the book_authors table.
NOTE - Multiple many to many relations can be created using the ManyRelatedManager object and its add() method.
Our database looks like this:
Last but not least, table that connects these two tables, book_authors table. Django by default names this table in format <model_name>_<many_to_many_field_name>. Its columns are named <model_name>_id by default.
Now we can see how data is connected. In this example, the first row represents Book with id=1 and Author with id=1. Row two represents Book with id=1 and Author with id=2. This way of saving resolves the problem of duplicate data in Book or Author tables.
Django provides powerful tools to query models. That tool is called ORM (object-relational mapping). Django ORM is a tool used to write queries using Python instead of SQL. Python queries are eventually translated to SQL that then queries the database.
In the following examples I’ll show you some of the queries over ManyToManyField:
# We can use lookups on many to many field >>> Book.objects.filter(authors__id=1) <QuerySet [<Book: Book object (1)>]> # This equals above example >>> Book.objects.filter(authors=1) <QuerySet [<Book: Book object (1)>]> # Fetching all Authors connected to Book >>> Book.objects.get(id=1).authors.all() <QuerySet [<Author: Author object (1)>, <Author: Author object (2)>]> # Removing book_authors connections from b1 >>> b1.authors.clear() # Removing a1 from book_authors table connected to b1 >>> b1.authors.remove(a1)
NOTE - Documentation on more operations can be found in official Django documentation on many-to-many relationships.
Django Many To Many relationship using ForeignKey
Many To Many relationships and representative associative table consists of ForeignKeys, Django only made it easier to implement it with ManyToManyField. Implementing many to many relationship with ForeignKeys requires manual implementation, but it gives us more implementation possibilities such as adding more fields.
NOTE - ForeignKey is explained in the Complete guide to Django ForeignKey, and it’s recommended to understand its usage before continuing reading!
ForeignKey in Django represents one-to-many relationships, so how can we use it to make many-to-many relationships?
First, let’s define models.
class Author(models.Model): first_name = models.CharField(max_length=512) last_name = models.CharField(max_length=512) class Book(models.Model): title = models.CharField(max_length=512) class BookAuthor(models.Model): author = models.ForeignKey(Author, on_delete=models.CASCADE) book = models.ForeignKey(Book, on_delete=models.CASCADE)
We had to define one more model in our models.py file. BookAuthor table in the database is automatically created when we define ManyToManyField.
Using ForeignKey implementation, we must manually define two ForeignKey fields, one connecting table to Author and one connecting table to Book. With these connections, we managed to recreate many-to-many relationship using ForeignKey.
Why use ForeignKey instead of ManyToManyField?
ForeignKey implementation of many-to-many relationship allows us to add extra fields into our model. For example, maybe we would need to keep track of the word count of every Author in the Book they wrote. This requires an additional field in the BookAuthor connection. With manual implementation, we would simply rewrite the BookAuthor model:
class BookAuthor(models.Model): author = models.ForeignKey(Author, on_delete=models.CASCADE) book = models.ForeignKey(Book, on_delete=models.CASCADE) word_count = models.IntegerField()
Many to Many relationships are industry standards and are widely used in relational databases. It’s necessary to master this concept to become a good web developer. Since Django helps us a lot, we need to know how to use these basic concepts to make suitable, functional web applications with the complex database schema.
I hope this article helped you! 🙂