Skip to main content

Django One-to-Many Relationship

One of the most common types of relationships in database design is the one-to-many relationship. In a relational database, this is a fundamental concept that allows you to connect related data across multiple tables. In Django, implementing these relationships is straightforward through the ORM (Object-Relational Mapping) system.

What is a One-to-Many Relationship?

A one-to-many relationship exists when a single record in one table can be linked to multiple records in another table. For example:

  • One author can write many books
  • One category can contain many products
  • One user can post many comments

The "one" side is the parent, and the "many" side contains the children records that reference the parent.

Implementing One-to-Many Relationships in Django

Django represents one-to-many relationships using the ForeignKey field. This field creates a database column that references the primary key of another table.

Basic Syntax

python
from django.db import models

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

class Child(models.Model):
name = models.CharField(max_length=100)
parent = models.ForeignKey(Parent, on_delete=models.CASCADE)

The ForeignKey field takes at least two arguments:

  1. The class it's relating to (or its name as a string)
  2. The on_delete parameter that defines what should happen when the referenced object is deleted

The on_delete Parameter

The on_delete parameter is required and determines what happens to related objects when the referenced object is deleted. Here are the common options:

python
# Delete all related objects when the parent is deleted
models.CASCADE

# Set the foreign key to NULL when the parent is deleted
models.SET_NULL # requires null=True

# Prevent deletion if related objects exist
models.PROTECT

# Set default value when parent is deleted
models.SET_DEFAULT # requires default=value

# Use a custom function to determine what happens
models.SET(function)

# Do nothing (not recommended, can lead to database integrity issues)
models.DO_NOTHING

Real-World Example: Blog Application

Let's build a practical example of a blog application where:

  • One author can publish many blog posts
  • One category can contain many blog posts

Step 1: Define the Models

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

class Category(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(blank=True)

def __str__(self):
return self.name

class Meta:
verbose_name_plural = "Categories"

class BlogPost(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
published_date = models.DateTimeField(auto_now_add=True)
updated_date = models.DateTimeField(auto_now=True)

# One-to-Many: One author can have many blog posts
author = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='blog_posts'
)

# One-to-Many: One category can have many blog posts
category = models.ForeignKey(
Category,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='posts'
)

def __str__(self):
return self.title

Step 2: Create and Apply Migrations

After defining your models, you need to create and apply migrations to update your database schema:

bash
python manage.py makemigrations
python manage.py migrate

When you define a ForeignKey, Django automatically creates a "reverse relationship" that allows the parent model to access its related children. By default, it uses the name of the child model lowercase + '_set':

python
# Get all blog posts by a specific author
author = User.objects.get(username='johndoe')
author_posts = author.blog_posts.all() # Using the related_name we set

# Get all posts in a category
category = Category.objects.get(name='Python')
category_posts = category.posts.all() # Using the related_name we set

Without a custom related_name, you would access the related objects like this:

python
# If we hadn't set related_name='blog_posts'
author_posts = author.blogpost_set.all()

# If we hadn't set related_name='posts'
category_posts = category.blogpost_set.all()

Accessing the Parent from a Child (Many Side)

Accessing the parent model from a child is straightforward - just use the name of the foreign key field:

python
# Get a blog post
post = BlogPost.objects.get(id=1)

# Access its parent objects
post_author = post.author # Returns the User object
post_category = post.category # Returns the Category object

Django makes it easy to filter across relationships:

python
# Find all posts by a specific author
johns_posts = BlogPost.objects.filter(author__username='johndoe')

# Find all posts in a specific category
python_posts = BlogPost.objects.filter(category__name='Python')

# Find all posts by authors who have 'john' in their username
john_posts = BlogPost.objects.filter(author__username__contains='john')
python
# Approach 1: Create and assign
author = User.objects.get(username='johndoe')
category = Category.objects.get(name='Django')

post = BlogPost(
title='Understanding Django ORM',
content='Django ORM makes database operations easy...',
author=author,
category=category
)
post.save()

# Approach 2: Using ID directly
post = BlogPost(
title='Django Tips and Tricks',
content='Here are some useful tips...',
author_id=1, # Directly using the author's ID
category_id=2 # Directly using the category's ID
)
post.save()

# Approach 3: Creating via the related manager
author = User.objects.get(username='johndoe')
post = author.blog_posts.create(
title='Working with Related Objects',
content='Django provides convenient ways to work with related objects...',
category_id=2
)

Advanced Features

The related_name parameter changes the name of the reverse relation:

python
class BlogPost(models.Model):
# This creates author.blog_posts instead of author.blogpost_set
author = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='blog_posts'
)

# This creates category.posts instead of category.blogpost_set
category = models.ForeignKey(
Category,
on_delete=models.SET_NULL,
null=True,
related_name='posts'
)

The related_query_name parameter defines the name to use for the reverse filter:

python
class BlogPost(models.Model):
author = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='blog_posts',
related_query_name='post'
)

Now you can use:

python
# Find users who have posts with 'Django' in the title
users = User.objects.filter(post__title__contains='Django')

Using limit_choices_to

You can limit the available choices for a foreign key using limit_choices_to:

python
class BlogPost(models.Model):
category = models.ForeignKey(
Category,
on_delete=models.SET_NULL,
null=True,
limit_choices_to={'is_active': True} # Only show active categories
)

Summary

One-to-many relationships in Django are implemented using the ForeignKey field, creating a connection between a "parent" model and multiple "child" models. The key points to remember are:

  1. Use ForeignKey in the "many" side of the relationship
  2. Always specify the on_delete behavior
  3. Consider using related_name to make reverse relations more intuitive
  4. Django provides convenient methods to work with related objects in both directions

By mastering one-to-many relationships, you'll be able to build complex data models that accurately represent real-world relationships between entities in your applications.

Additional Resources

Exercises

  1. Create a Product and Category model where one category can have many products.
  2. Implement a School Management system with models for School, Teacher, and Student, where a school can have many teachers and many students.
  3. Build a simple file organization system with Folder and File models, where a folder can contain many files.
  4. Extend the blog example to include Comments, where each BlogPost can have many comments.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)