ForeignKey: One-to-Many Relationships
A ForeignKey field establishes a many-to-one relationship: many Book rows can point to one Author row. Django requires an on_delete argument specifying what happens to the child rows when the referenced row is deleted — common choices include CASCADE (delete children too), PROTECT (block deletion), and SET_NULL (requires null=True). The related_name argument controls the reverse accessor name on the other side, so author.books.all() reads more naturally than the default author.book_set.all().
Cricket analogy: A ForeignKey from Innings to Match is like every innings scorecard pointing back to exactly one match — many innings can reference the same match, but each innings belongs to only one, mirroring the many-to-one direction of a ForeignKey.
ManyToManyField and OneToOneField
A ManyToManyField models relationships where both sides can relate to multiple rows on the other side, such as a Book having multiple Author objects and each Author having multiple Book objects; Django creates a hidden junction (through) table automatically, or you can specify through=YourModel to add extra fields like a contribution_role on the relationship itself. A OneToOneField enforces exactly one matching row on each side, commonly used to extend Django's built-in User model with a Profile model without touching the auth app.
Cricket analogy: A ManyToManyField between Match and Umpire is like a tournament roster where each match has multiple officiating umpires and each umpire officiates multiple matches across the season — a genuine many-to-many web.
class Author(models.Model):
name = models.CharField(max_length=100)
country = models.CharField(max_length=50)
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(
Author, on_delete=models.CASCADE, related_name="books"
)
co_authors = models.ManyToManyField(
Author, through="Contribution", related_name="contributed_books"
)
class Contribution(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
book = models.ForeignKey(Book, on_delete=models.CASCADE)
role = models.CharField(max_length=50) # e.g. "editor", "illustrator"
class Profile(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE
)
bio = models.TextField(blank=True)on_delete is a required positional argument for every ForeignKey and OneToOneField in modern Django — there is no default, unlike most other field options. Forgetting it raises a TypeError at import time, not a silent bug.
Using through on a ManyToManyField disables Django's automatic add()/remove()/set() shortcuts on the manager — once you add extra fields via a through model, you must create and delete rows on that through model directly instead.
Choosing the Right Relationship
Picking the correct relationship type is a modeling decision, not just a syntax choice: ask whether one side can legitimately have more than one of the other (many-to-many), whether one side is strictly capped at one (one-to-one, often used for optional model extension), or whether the relationship is inherently directional with a clear 'many' side pointing to a clear 'one' side (foreign key). Getting this wrong early is expensive because changing a ManyToManyField to a ForeignKey later requires a data migration to reshape the junction table into a plain column.
Cricket analogy: Deciding whether Umpire-to-Match should be ForeignKey or ManyToMany is like a modeler asking 'can one match have multiple umpires and one umpire officiate multiple matches?' — yes to both means ManyToMany, not ForeignKey.
- ForeignKey models a many-to-one relationship and requires an explicit on_delete argument (CASCADE, PROTECT, SET_NULL, etc.).
- related_name customizes the reverse accessor name; without it, Django defaults to modelname_set.
- ManyToManyField models relationships where both sides can have multiple matches, backed by an automatic or custom junction table.
- Use through=YourModel on a ManyToManyField to attach extra fields to the relationship itself, such as a role or date joined.
- OneToOneField enforces exactly one matching row per side and is the standard way to extend Django's built-in User model.
- Adding through to a ManyToManyField disables the automatic add()/remove()/set() manager shortcuts.
- Choosing the wrong relationship type early is costly to fix, since it requires a data migration to reshape the underlying tables.
Practice what you learned
1. Which argument is required (with no default) on every ForeignKey field?
2. What does related_name='books' on Author's ForeignKey to Book allow you to write?
3. What is the idiomatic way to extend Django's built-in User model with extra profile fields?
4. What happens to a ManyToManyField's manager when you add a through model?
5. Which on_delete option would you choose to prevent deletion of an Author row while Book rows still reference it?
Was this page helpful?
You May Also Like
Defining Models
How to declare Django models as Python classes that map to database tables, including field types, options, and Meta configuration.
Django ORM QuerySets
How QuerySets let you retrieve, filter, and chain database queries lazily using Python instead of raw SQL.
Migrations in Django
How Django's migration system tracks model changes over time and applies them safely to the database schema.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentNext.js Study Notes
JavaScript · 30 topics