Introduction

In real Django applications, models rarely exist alone. A blog post has an author. A student belongs to a class. An order contains products. A user may have one profile. These connections between models are called relationships.

Django provides three main types of model relationships:

Understanding these relationships is essential because they are the foundation of real database design. Once you master them, you can build applications such as blogs, e-commerce stores, school systems, social platforms, and management dashboards much more efficiently.

In this tutorial, you will learn how relationships work in Django, how to create them, how to query related data, and how to choose the correct relationship type for each situation.

What You Will Learn

By the end of this tutorial, you will understand:

Prerequisites

Before starting, you should already know:

1. What Are Relationships in Django?

A relationship connects one model to another.

For example:

These are different kinds of connections, so Django provides different field types for each one.

2. The Three Main Relationship Types

ForeignKey

Used for one-to-many relationships.

Example:

OneToOneField

Used for one-to-one relationships.

Example:

ManyToManyField

Used for many-to-many relationships.

Example:

3. ForeignKey — One-to-Many Relationship

This is the most common relationship in Django.

Example: Category and Post

models.py

from django.db import models

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

    def __str__(self):
        return self.name


class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    category = models.ForeignKey(Category, on_delete=models.CASCADE)

    def __str__(self):
        return self.title

4. Understanding on_delete

When using ForeignKey, Django requires the on_delete argument.

Example:

category = models.ForeignKey(Category, on_delete=models.CASCADE)

This means: if the category is deleted, all related posts are also deleted.

Common on_delete options

models.CASCADE

Delete related objects too.

models.SET_NULL

Set the field to NULL when the related object is deleted.

Requires:

category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True)

models.PROTECT

Prevent deletion if related objects exist.

models.SET_DEFAULT

Set a default value.

models.DO_NOTHING

Do nothing. Use with caution.

For beginners, CASCADE, SET_NULL, and PROTECT are the most important to know.

5. Creating and Using a ForeignKey

Create objects

category = Category.objects.create(name="Python")

post = Post.objects.create(
    title="Django Basics",
    content="Learning Django is fun.",
    category=category
)

Access the related object

post.category

This gives the category of the post.

Access a field from the related object

post.category.name

6. Reverse Relationship with ForeignKey

Django also lets you go backward.

From a category, you can access all related posts.

category.post_set.all()

By default, Django uses:

modelname_set

So from Category, the reverse name for Post becomes:

post_set

7. Using related_name

Instead of using post_set, you can define a cleaner reverse name.

Example

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    category = models.ForeignKey(
        Category,
        on_delete=models.CASCADE,
        related_name='posts'
    )

    def __str__(self):
        return self.title

Now you can write:

category.posts.all()

This is much more readable than category.post_set.all().

<hr>

8. OneToOneField — One-to-One Relationship

This relationship means one object is connected to exactly one other object.

Common Example: User and Profile

In many Django projects:

models.py

from django.db import models
from django.contrib.auth.models import User

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    bio = models.TextField(blank=True)
    city = models.CharField(max_length=100, blank=True)

    def __str__(self):
        return self.user.username

9. Using a OneToOneField

Create a profile for a user

from django.contrib.auth.models import User

user = User.objects.create(username='alice')

profile = Profile.objects.create(
    user=user,
    bio='Django developer',
    city='Casablanca'
)

Access profile from user

user.profile

Access user from profile

profile.user

This is a direct one-to-one connection.

10. Why Use OneToOneField?

Use OneToOneField when:

Example:
Instead of placing bio, city, avatar, and phone directly inside the user model, you can place them in a Profile model.

11. ManyToManyField — Many-to-Many Relationship

This relationship is used when both sides can have many related objects.

Example: Student and Course

models.py

from django.db import models

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

    def __str__(self):
        return self.title


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

    def __str__(self):
        return self.name

Django automatically creates an intermediate table behind the scenes to manage this relationship.

12. Using a ManyToManyField

Create objects

python_course = Course.objects.create(title="Python")
django_course = Course.objects.create(title="Django")

student = Student.objects.create(name="Youssef")

Add courses to a student

student.courses.add(python_course, django_course)

Get all courses of a student

student.courses.all()

Remove a course

student.courses.remove(python_course)

Clear all courses

student.courses.clear()

13. Reverse Relationship with ManyToManyField

From a course, you can access all students:

django_course.student_set.all()

If you want a cleaner name, use related_name.

Example

class Student(models.Model):
    name = models.CharField(max_length=100)
    courses = models.ManyToManyField(Course, related_name='students')

    def __str__(self):
        return self.name

Now you can write:

django_course.students.all()

14. Relationship Comparison

ForeignKey

One object belongs to one parent, but the parent can have many children.

Example:

OneToOneField

One object matches exactly one other object.

Example:

ManyToManyField

Both sides can have many related objects.

Example:

15. Real Blog Example

Let us build a more realistic blog structure.

models.py

from django.db import models
from django.contrib.auth.models import User

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

    def __str__(self):
        return self.name


class Tag(models.Model):
    name = models.CharField(max_length=50)

    def __str__(self):
        return self.name


class Post(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
    category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='posts')
    tags = models.ManyToManyField(Tag, related_name='posts')
    title = models.CharField(max_length=200)
    content = models.TextField()

    def __str__(self):
        return self.title

16. Querying Related Data

Get all posts of a category

category.posts.all()

Get all posts written by a user

user.posts.all()

Get all tags of a post

post.tags.all()

Get all posts with a given tag

tag.posts.all()

Filter posts by category name

Post.objects.filter(category__name="Python")

Filter posts by author username

Post.objects.filter(author__username="alice")

Filter posts by tag name

Post.objects.filter(tags__name="django")

 

Notice the use of double underscores __ to go across relationships.

17. Querying Across Relationships

Django ORM lets you move through relationships very easily.

Example

Post.objects.filter(author__profile__city="Casablanca")

This means:

This is one of Django ORM’s strongest features.

18. Migrations with Relationships

After creating or changing relationships in models, run:

python manage.py makemigrations
python manage.py migrate

Django will generate the necessary database structure automatically.

For ManyToManyField, Django also creates the intermediate table for you.

19. Using Relationships in the Admin Panel

Django admin handles relationships very well.

If you register the models:

from django.contrib import admin
from .models import Category, Tag, Post, Profile, Course, Student

admin.site.register(Category)
admin.site.register(Tag)
admin.site.register(Post)
admin.site.register(Profile)
admin.site.register(Course)
admin.site.register(Student)

then in the admin panel you can:

This makes relationships much easier to manage visually.

20. Common Beginner Mistakes

Forgetting on_delete

A ForeignKey or OneToOneField requires on_delete.

Confusing ForeignKey and ManyToManyField

If one object can have many related objects on both sides, use ManyToManyField, not ForeignKey.

Forgetting null=True when using SET_NULL

This will cause errors.

Wrong:

category = models.ForeignKey(Category, on_delete=models.SET_NULL)

Correct:

category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True)

Accessing reverse relations with the wrong name

Without related_name, Django uses names like post_set.

Adding many-to-many relations before saving the object

You usually need to save the object first before using .add().

Example:

student = Student(name="Amine")
student.save()
student.courses.add(course)

21. Best Practices

22. Mini Project Example

Library System

A library system may have:

Example

from django.db import models

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

    def __str__(self):
        return self.name


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

    def __str__(self):
        return self.name


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

    def __str__(self):
        return self.name


class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')
    category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='books')
    readers = models.ManyToManyField(Reader, related_name='books')

    def __str__(self):
        return self.title

This small example combines all three major relationship types except OneToOneField.

23. Summary

In this tutorial, you learned that:

Once you understand relationships, you can design much more powerful and realistic databases.

 

24. Mini Quiz

1. Which field creates a one-to-many relationship?

A. OneToOneField
B. ManyToManyField
C. ForeignKey
D. CharField

2. Which field is best for extending the User model with profile information?

A. ForeignKey
B. OneToOneField
C. ManyToManyField
D. BooleanField

3. Which field is used when both models can have many related objects?

A. ForeignKey
B. OneToOneField
C. ManyToManyField
D. TextField

4. What does related_name do?

A. Deletes related objects
B. Renames a model
C. Defines a reverse access name
D. Creates a template

5. Which option deletes child objects when the parent is deleted?

A. SET_NULL
B. PROTECT
C. CASCADE
D. SET_DEFAULT

25. What Comes Next?

The next ideal tutorial is:

Tutorial 25: Django Model Methods and Properties
Subject: adding business logic inside models using methods, __str__, custom methods, and properties.

That tutorial will show you how to make your models smarter and more useful.