Lesson 3
Many-to-many Relationship: Basics
Introduction

In this lesson, we will explore the concept of many-to-many relationships in databases and their implementation in Django. A many-to-many relationship allows multiple records in one table to be associated with multiple records in another table. This type of relationship is crucial for various real-world applications, such as connecting tags to tasks where a single task can have multiple tags and a single tag can be associated with multiple tasks.

By the end of this lesson, you will be able to:

  • Define models in Django that use many-to-many relationships.
  • Create serializers to handle these relationships.
  • Load initial data using fixtures.
  • Write unit tests to verify many-to-many relationships.
Defining Models with Many-to-Many Relationship

Imagine you want to implement adding tags for your todo items. You can add multiple tags to each todo item, and each tag can be added to multiple todo items. This is called a many-to-many relationship.

We'll use two models: Tag and Todo. Each Todo task can have multiple Tags, and each Tag can be associated with multiple Todo tasks. We achieve this in Django using the ManyToManyField.

Here's how we define the Tag and Todo models:

Python
1from django.db import models 2 3class Tag(models.Model): 4 name = models.CharField(max_length=255) 5 6 def __str__(self): 7 return self.name 8 9class Todo(models.Model): 10 task = models.CharField(max_length=255) 11 completed = models.BooleanField(default=False) 12 priority = models.IntegerField() 13 assignee = models.CharField(max_length=255, blank=True, null=True) 14 group = models.CharField(max_length=255, blank=True, null=True) 15 tags = models.ManyToManyField(Tag, blank=True) 16 17 def __str__(self): 18 return self.task
  • Tag Model: Defines a simple tag with a name.
  • Todo Model: Defines a task with various fields such as task, completed, priority, assignee, and group.
  • tags: A ManyToManyField in the Todo model that links it to the Tag model. This field allows a task to have multiple tags, and a tag can be linked to multiple tasks.
How Many-to-Many Relationship Is Stored in SQL Database

In a relational database, a many-to-many relationship is typically stored using a third table, known as a "junction table" or "join table". This table holds references to the primary keys of the two tables involved in the relationship.

Here’s how the tables would look for the Todo and Tag models:

  • Todo
idtaskcompletedpriorityassigneegroup
1Task with tagsFalse1
2Another taskTrue2
  • Tag
idname
1Urgent
2Home
  • Todo_tags (Junction Table)
idtodo_idtag_id
111
212
321

In the Todo_tags table, each row represents a link between a Todo task and a Tag. For example, the first row shows that the Todo task with id=1 is associated with the Tag with id=1 (Urgent). We see that the first Todo item has tags Urgent and Home, and the second Todo item has tag Urgent.

Creating Serializers for Many-to-Many Relationship

Here's how we define serializers for our Tag and Todo models:

Python
1from rest_framework import serializers 2from .models import Todo, Tag 3 4class TagSerializer(serializers.ModelSerializer): 5 class Meta: 6 model = Tag 7 fields = ['id', 'name'] 8 9class TodoSerializer(serializers.ModelSerializer): 10 tags = TagSerializer(many=True) 11 12 class Meta: 13 model = Todo 14 fields = '__all__'
  • TagSerializer: Serializes the Tag model by including the id and name fields.
  • TodoSerializer: Serializes the Todo model by including all fields (__all__) and embeds the tag data using TagSerializer.

In the TodoSerializer, we use the TagSerializer for the tags field and set many=True, indicating that there will be multiple tags associated with each Todo.

Loading Initial Many-to-Many Data

Here's an example of a fixture file to preload some data:

JSON
1[ 2 { 3 "model": "myapp.tag", 4 "pk": 1, 5 "fields": { 6 "name": "Urgent" 7 } 8 }, 9 { 10 "model": "myapp.tag", 11 "pk": 2, 12 "fields": { 13 "name": "Home" 14 } 15 }, 16 { 17 "model": "myapp.todo", 18 "pk": 1, 19 "fields": { 20 "task": "Task with tags", 21 "completed": false, 22 "priority": 1, 23 "tags": [1, 2] 24 } 25 } 26]
  • This JSON fixture creates two tags ("Urgent" and "Home") and a task ("Task with tags") that is associated with both tags.
  • The fields key contains the actual data for the tag or task.
  • The tags field in the Todo model lists the primary keys of the tags it is associated with.
Testing Many-to-Many Relationships

Testing is essential to ensure that our models and relationships are correctly defined. Here’s a test case to verify our many-to-many relationship:

Python
1from django.test import TestCase 2from .models import Todo, Tag 3 4class ManyToManyRelationshipTestCase(TestCase): 5 fixtures = ['many_to_many.json'] 6 7 def test_todo_has_tags(self): 8 todo = Todo.objects.get(pk=1) 9 tags = todo.tags.all() 10 self.assertEqual(tags.count(), 2) 11 self.assertTrue(tags.filter(name='Urgent').exists()) 12 self.assertTrue(tags.filter(name='Home').exists())

This test:

  1. Retrieves the Todo task with pk=1.
  2. Fetches all the tags associated with this task. using todo.tags.all().
  3. Asserts that there are exactly 2 tags.
  4. Verifies that the tags named 'Urgent' and 'Home' are among the associated tags. This ensures that the many-to-many relationship is correctly established and can be queried as expected.
Summary and Next Steps

In this lesson, we covered:

  • The significance of many-to-many relationships.
  • How to define models with a many-to-many relationship using ManyToManyField.
  • How many-to-many relationships are stored in SQL databases using a junction table.
  • Creating serializers to handle many-to-many relationships.
  • Loading initial data using fixtures.
  • Writing test cases to verify many-to-many relationships.

With this knowledge, you are now equipped to manage many-to-many relationships in your Django applications. Let's get to practice!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.