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.
| Field | Means | Everyday example |
|---|---|---|
ForeignKey | one-to-many | one Author → many Posts |
ManyToManyField | many-to-many | many Posts ↔ many Tags |
OneToOneField | one-to-one | one 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
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.titleReading 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 value | When the author is deleted… |
|---|---|
CASCADE | delete this post too (most common) |
PROTECT | block the delete — refuse to remove an author who still has posts |
SET_NULL | keep the post, set its author to empty (needs null=True) |
SET_DEFAULT | keep 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:
# 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 hasNote: 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:
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 tagYou 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:
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 # backwardsWatch 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?
✍️ Practice
- Add an
Authormodel and linkPostto it with a ForeignKey (setrelated_nameandon_delete). - Add a
Tagmodel and a ManyToManyField, then attach two tags to a post in the shell.
🏠 Homework
- Model a small library:
Author,Book(ForeignKey to Author) andGenre(ManyToMany to Book). Migrate and create a few linked records.