Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 14

Django Model Relationships

Recommended resources

Hello! Welcome back to our journey through the Django framework.

In our last lesson, we laid the foundation for our application's data layer by learning how to define Django models and their basic fields. You now know how to create a model that acts as a blueprint for a database table, using fields like CharField, IntegerField, and DateTimeField to define its structure.

Today, we'll build on that foundation to explore one of the most powerful features of a relational database: relationships. An application's data is rarely isolated; it's interconnected. This lesson will teach you how to create those connections within Django.

Your familiarity with SQL means you already understand concepts like foreign keys and join tables. Today's goal is to see how Django's ORM elegantly abstracts these database-level concepts into simple, Pythonic field types. By the end of this lesson, you will be able to establish relationships between your models using ForeignKey, OneToOneField, and ManyToManyField.

The Three Types of Model Relationships

In Django, as in most relational database systems, there are three primary ways to relate models to each other:

  1. One-to-Many: One object is related to many other objects. For example, one Author can write many Blog Posts, but each Blog Post has only one Author.
  2. One-to-One: One object is related to exactly one other object. For example, a UserProfile might extend the built-in User model, with each user having one and only one profile.
  3. Many-to-Many: An object can be related to many other objects, and vice-versa. For example, a Blog Post can have many Tags, and a Tag can be applied to many Blog Posts.

This image provides a great visual summary of how these concepts map to Django's fields.

Django Relationship Fields: One-To-Many, One-To-One, and Many-To-Many
This diagram illustrates the three main relationship types and their corresponding Django fields: `ForeignKey` (one-to-many), `OneToOneField` (one-to-one), and `ManyToManyField` (many-to-many).

To get a solid conceptual understanding of how these relationships work at a database level, let's watch the beginning of the following video.

Database Relationships | One To Many & Many to Many | Django (3.0) Crash Course Tutorials (pt 6)

The video 'Database Relationships' by Dennis Ivy offers clear, diagram-based explanations of one-to-many and many-to-many relationships before diving into the Django implementation.

Watch from 00:27 to 04:49. Pay close attention to: How a one-to-many relationship uses a foreign key column. Why a many-to-many relationship requires a third, intermediary table (also known as a join table or through table).

Now that you have the conceptual framework, let's see how to implement each of these in Django.


One-to-Many Relationships: ForeignKey

A one-to-many relationship is the most common type. You define it using models.ForeignKey.

You place the ForeignKey field on the "many" side of the relationship. In our Author and Post example, a post has one author, so the ForeignKey goes in the Post model.

# models.py
from django.db import models

class Manufacturer(models.Model):
    name = models.CharField(max_length=100)
    # ... other fields

    def __str__(self):
        return self.name

class Car(models.Model):
    name = models.CharField(max_length=100)
    manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)

    def __str__(self):
        return self.name

The on_delete Argument

The on_delete argument is required for ForeignKey and is critical for database integrity. It tells Django what to do when the object on the "one" side of the relationship is deleted.

  • models.CASCADE: This is the most common behavior. If you delete a Manufacturer, all Car objects associated with it will also be deleted.
  • models.PROTECT: Prevents the deletion of the referenced object. If you try to delete a Manufacturer that still has Cars associated with it, Django will raise a ProtectedError.
  • models.SET_NULL: Sets the ForeignKey to NULL. This requires the field to have null=True set. For example, if you delete a Manufacturer, the manufacturer field on its cars becomes NULL.
  • models.SET_DEFAULT: Sets the ForeignKey to its default value. This requires a default value to be set for the field.
  • models.RESTRICT: Similar to PROTECT, but the specific error and timing can depend on the database backend. PROTECT is generally preferred.

For a detailed walkthrough of implementing and querying ForeignKey relationships, the following video is excellent.

Understanding Django Model Relationships

The 'Understanding Django Model Relationships' video by Pretty Printed provides a thorough, code-centric explanation of ForeignKey.

Watch the first part of the video (00:28 - 12:59). The presenter does a great job of showing: How to define the ForeignKey. What happens in the database (the _id column is created). How to create related objects. How to query across relationships using the double-underscore (__) syntax, a powerful feature of the ORM.


Many-to-Many Relationships: ManyToManyField

To define a many-to-many relationship, use models.ManyToManyField. As you saw in the conceptual video, Django handles the creation of the intermediary table for you automatically.

You can place the ManyToManyField on either of the two related models, but not both. By convention, you place it on the object that will be edited in forms. For example, when creating a Post, you would select its Tags, so we put the field on the Post model.

# models.py
from django.db import models

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

    def __str__(self):
        return self.name

class Pizza(models.Model):
    name = models.CharField(max_length=50)
    toppings = models.ManyToManyField(Topping, blank=True) # blank=True makes it optional in forms

    def __str__(self):
        return self.name

The next section of the Pretty Printed video continues with a great explanation of ManyToManyField.

Understanding Django Model Relationships

Continuing with the same video, we now move to many-to-many relationships.

Watch from 12:59 to 25:57. Focus on: How Django automatically creates a third table for the relationship. The .add(), .create(), and .all() methods for managing related objects. The similarity in query syntax (using __) to ForeignKey.

Advanced: The through Argument

Sometimes you need to store extra information about a many-to-many relationship. For example, in a relationship between a Musician and a Group, you might want to store the date the musician joined the group. In this case, you can create a custom intermediary model yourself and link it using the through argument on the ManyToManyField. This is an advanced technique, but it's good to know it exists. You can read more about it in the Django documentation on extra fields on many-to-many relationships.


One-to-One Relationships: OneToOneField

A one-to-one relationship is defined with models.OneToOneField. It's essentially a ForeignKey with a unique=True constraint, ensuring that each object can only be related once.

The most common use case is to extend an existing model, particularly Django's built-in User model, to create a user "profile" with additional information.

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

class Profile(models.Model):
    # This creates a one-to-one link to Django's User model
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    
    bio = models.TextField(blank=True)
    location = models.CharField(max_length=100, blank=True)
    birth_date = models.DateField(null=True, blank=True)

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

Here, every User can have exactly one Profile, and every Profile is associated with exactly one User. Just like ForeignKey, OneToOneField also requires the on_delete argument.

For a definitive textual reference on all three relationship types, the official Django documentation is the best source.

Models | Django documentation

The official Django documentation provides concise and accurate definitions for all relationship fields. It's a great resource to solidify your understanding.

Skim through the sections on 'Many-to-one relationships', 'Many-to-many relationships', and 'One-to-one relationships'. You don't need to read every detail, but notice the examples and the structure. This will serve as a good reference.

Test your understanding!

You are designing models for a simple course enrollment system. You need three models: Student, Course, and Enrollment.

  • A Student can enroll in many Courses.
  • A Course can have many Students.
  • The Enrollment model should track which Student is in which Course, and also store the enrollment_date.

How would you structure these models and their relationships? Think about which relationship fields you would use and where you would place them.

Show answer

This is a classic case for a many-to-many relationship with extra data, which requires a custom through model.

from django.db import models

class Student(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)

    def __str__(self):
        return f'{self.first_name} {self.last_name}'

class Course(models.Model):
    name = models.CharField(max_length=200)
    # We define the ManyToManyField here, and tell it to use
    # the Enrollment model as the intermediary table.
    students = models.ManyToManyField(Student, through='Enrollment')

    def __str__(self):
        return self.name

class Enrollment(models.Model):
    # This is the intermediary model.
    student = models.ForeignKey(Student, on_delete=models.CASCADE)
    course = models.ForeignKey(Course, on_delete=models.CASCADE)
    enrollment_date = models.DateField(auto_now_add=True)

    class Meta:
        # Ensures a student can't enroll in the same course twice
        unique_together = ('student', 'course')
  • The Course model has a ManyToManyField pointing to Student.
  • We specify through='Enrollment' to tell Django that we are using a custom model to manage the relationship.
  • The Enrollment model itself contains a ForeignKey to Student, a ForeignKey to Course, and the extra data field (enrollment_date).

Conclusion

In this lesson, we bridged the gap between individual data tables and a fully relational database schema. You've learned how to use Django's ORM to define the connections that give your application's data structure and meaning.

Key Takeaways:

  • One-to-Many (ForeignKey): Used when one object can be related to many others. It's placed on the "many" side of the relationship and requires an on_delete policy.
  • Many-to-Many (ManyToManyField): Used when objects on both sides can be related to multiple objects on the other side. Django automatically creates the necessary join table.
  • One-to-One (OneToOneField): A restrictive version of ForeignKey that ensures an object is linked to only one other object. It's perfect for extending existing models, like creating user profiles.
  • The ORM's double-underscore syntax (__) allows you to intuitively query across these relationships.

You now have the complete toolkit to design a robust data model for a complex application. But defining models is only the first step. In our next lesson, we will learn how to translate these model definitions into an actual database schema using migrations, with the makemigrations and migrate commands.