100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Python

Model Relationships

How ForeignKey, ManyToManyField, and OneToOneField model real-world relationships between Django models.

Models & ORMIntermediate9 min readJul 10, 2026
Analogies

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.

python
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

Was this page helpful?

Topics covered

#Python#DjangoStudyNotes#WebDevelopment#ModelRelationships#Model#Relationships#ForeignKey#One#StudyNotes#SkillVeris