Models & DatabaseExtra· 45 min read

Model Relationships

Link your tables together — one author with many posts, posts with many tags — using ForeignKey, ManyToMany and OneToOne.

What you will learn

  • Connect models with ForeignKey, ManyToMany and OneToOne
  • Choose the right ondelete and relatedname
  • Read data across the relationship

Real data is connected

A single flat table can only get you so far. Real apps have data that relates: one author writes many posts; one post has many tags; one user has exactly one profile. A relationship is a link between two models so the database knows which rows belong together. Django gives you three relationship fields, one for each shape of link.

FieldMeansEveryday example
ForeignKeyone-to-manyone Author → many Posts
ManyToManyFieldmany-to-manymany Posts ↔ many Tags
OneToOneFieldone-to-oneone User ↔ one Profile

ForeignKey: one-to-many

A ForeignKey lives on the "many" side and points to the "one" side. Each post belongs to exactly one author, but an author can have many posts. We put the ForeignKey on Post because a post is the "many":

blog/models.py — a post belongs to an author
# blog/models.py
from django.db import models

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

    def __str__(self):
        return self.name

class Post(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(
        Author,
        on_delete=models.CASCADE,
        related_name="posts",
    )

    def __str__(self):
        return self.title

Reading the ForeignKey arguments one by one:

  • Author — the model this points to (the "one" side).
  • on_delete=models.CASCADE — what to do if the author is deleted. CASCADE means "delete the author’s posts too." (Other choices below.)
  • related_name="posts" — the nickname you use to go backwards, from an author to all their posts: author.posts.all().

on_delete — the rule you must choose

on_delete answers a single question: when the row this points to is deleted, what happens to me? You must pick one — Django will not guess. The common choices:

on_delete valueWhen the author is deleted…
CASCADEdelete this post too (most common)
PROTECTblock the delete — refuse to remove an author who still has posts
SET_NULLkeep the post, set its author to empty (needs null=True)
SET_DEFAULTkeep the post, set author to a default value

Using the relationship

Once the models are linked, you walk the relationship in both directions with plain Python. Forwards (post → its author) uses the field name; backwards (author → its posts) uses the related_name:

Walk the relationship both ways
# in the Django shell:  python manage.py shell
asha = Author.objects.create(name="Asha")
Post.objects.create(title="Hello", author=asha)
Post.objects.create(title="Django tips", author=asha)

p = Post.objects.first()
p.author.name           # forwards: post -> author
asha.posts.all()        # backwards: author -> all her posts
asha.posts.count()      # how many posts Asha has

Note: Output: p.author.name -> 'Asha' asha.posts.all() -> <QuerySet [<Post: Hello>, <Post: Django tips>]> asha.posts.count() -> 2 Forwards used the field name author; backwards used the related_name we set to posts.

ManyToMany: many-to-many

A ManyToManyField is for links where each side can have many of the other: a post can have many tags, and a tag can be on many posts. You declare it on either model — Django builds the hidden link table for you:

A post can have many tags, a tag many posts
class Tag(models.Model):
    name = models.CharField(max_length=50)

class Post(models.Model):
    title = models.CharField(max_length=200)
    tags = models.ManyToManyField(Tag, related_name="posts")

# using it:
post.tags.add(python_tag, web_tag)   # attach two tags
post.tags.all()                      # this post's tags
python_tag.posts.all()               # every post with this tag

You do not pass on_delete to a ManyToManyField — there is no single "owner" row to cascade from. You attach and detach with .add() and .remove(), and read both directions just like before.

OneToOne: one-to-one

A OneToOneField is a ForeignKey with a "only one" rule: each row on this side links to exactly one row on the other, and vice versa. The classic use is extending the built-in User with a Profile:

Extend the User with a one-to-one Profile
from django.contrib.auth.models import User

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    bio = models.TextField(blank=True)

# each user has one profile, each profile one user
user.profile.bio        # forwards
profile.user.username   # backwards

Watch out: After adding or changing any relationship field you must run python manage.py makemigrations then python manage.py migrate — a relationship changes the database tables.

Tip: Remember to migrate before you query, and always set a related_name — it makes the "backwards" code read like plain English (author.posts.all()).

Q. You want each blog post to belong to exactly one author, while an author can write many posts. Which field, and on which model?

Answer: One-to-many uses a ForeignKey on the "many" side. A post is the many, so Post gets a ForeignKey pointing to Author.

✍️ Practice

  1. Add an Author model and link Post to it with a ForeignKey (set related_name and on_delete).
  2. Add a Tag model and a ManyToManyField, then attach two tags to a post in the shell.

🏠 Homework

  1. Model a small library: Author, Book (ForeignKey to Author) and Genre (ManyToMany to Book). Migrate and create a few linked records.
Want to learn this with a mentor?

CodingClave runs guided, project-based training (28-day, 45-day & 6-month batches).

Explore Training →