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

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:

# 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

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:

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':

# 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:

# 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:

# 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:

# 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')
# 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:

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:

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

Now you can use:

# 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:

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.

💡 Found a typo or mistake? Click "Edit this page" to suggest a correction. Your feedback is greatly appreciated!