Many to Many Relationships

Goals

  1. Describe many-to-many relationships.
  2. Explain how to create M:M relationships in Django.
  3. Skillfully use M:M relationships: add, upsert, query, and such.

Many-to-Many Relationships

What Is a Many-to-Many Relationship?

A many-to-many (M:M) relationship means that:

Examples: - A student can take multiple courses. A course can have multiple students. - A tag can apply to multiple blog posts. A blog post can have multiple tags.

Creating Many-to-Many Relationships in Django

Option 1: Simple M:M with ManyToManyField

from django.db import models

class Student(models.Model):
    name = models.CharField(max_length=100)

class Course(models.Model):
    title = models.CharField(max_length=100)
    students = models.ManyToManyField(Student)

This automatically creates a hidden join table behind the scenes.

Option 2: Explicit Through Table for Custom Fields

Use this if you need to store extra information about the relationship.

from django.db import models

class Student(models.Model):
    name = models.CharField(max_length=100)

class Course(models.Model):
    title = models.CharField(max_length=100)
    students = models.ManyToManyField('Student', through='Enrollment')

class Enrollment(models.Model):
    student = models.ForeignKey('Student', on_delete=models.CASCADE)
    course = models.ForeignKey('Course', on_delete=models.CASCADE)
    enrollment_date = models.DateField()

Using Many-to-Many Relationships

Add a Relationship

Simple M:M:

student = Student.objects.get(id=1)
course = Course.objects.get(id=2)
course.students.add(student)

With a Through Table:

Enrollment.objects.create(
    student=student,
    course=course,
    enrollment_date="2025-06-11"
)

Upsert (Update if exists, otherwise insert)

enrollment, created = Enrollment.objects.update_or_create(
    student=student,
    course=course,
    defaults={'enrollment_date': '2025-06-11'}
)

Query Relationships

Simple M:M:

# Get all students in a course
course.students.all()

# Get all courses a student is in
student.course_set.all()  # reverse lookup using default naming

With Through Table:

# Get all enrollments for a student
Enrollment.objects.filter(student=student)

# Get all courses for a student through enrollment
Course.objects.filter(enrollment__student=student)

Remove or Clear Relationships

# Remove one student from a course (simple M:M only)
course.students.remove(student)

# Remove all students from a course
course.students.clear()

Customize Reverse Access

You can customize the reverse access name:

students = models.ManyToManyField(Student, related_name='enrolled_courses')

Then you can access:

student.enrolled_courses.all()

Migrations

After defining or updating your models:

python manage.py makemigrations
python manage.py migrate

Summary