Relationships (FK, M2M)
In Django, relationships connect one model to another so your data can mirror the real world — a ForeignKey models a many-to-one link (many posts to one author), a ManyToManyField models a many-to-many link (posts to tags), and a OneToOneField models a one-to-one link (a user to a profile).
Learn Relationships (FK, M2M) in our free Django course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick…
Part of the free Django course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
In this lesson you'll wire models together with each of the three relationship fields, follow a link forward to the related object, walk it backward with reverse managers, and filter across relationships using the double-underscore syntax.
A ForeignKey creates a many-to-one relationship. Many rows on the model that holds the field point to one row on the target model. The classic example: many Post rows belong to one Author .
Every ForeignKey needs an on_delete argument that tells Django what to do with the dependent rows when the target is deleted. The most common options are:
Here is the real Django model code. Notice related_name , which names the reverse accessor used to go from an author back to their posts:
Forward access goes from the post to its author: post.author.name . Reverse access goes from the author back to the posts: author.posts.all() (or author.post_set.all() if you never set related_name ).
A ManyToManyField models a many-to-many relationship: each row can link to many rows on the other side, and vice versa. A Post can have many Tag rows, and each Tag can apply to many posts. Django manages a hidden join table for you, so no column appears on either model.
You manage the links with the relationship manager. The key methods are .add() , .remove() , .set() , and .clear() :
Important: the post must be saved (have a primary key) before you can call .add() , because the join table stores its id. You query across the relation the same way in both directions: tag.posts.all() returns every post that uses that tag.
A OneToOneField links exactly one row to exactly one row. It behaves like a ForeignKey with unique=True . The textbook use is a Profile that extends Django's built-in User : each user has one profile, each profile belongs to one user.
Once your models are connected, you can filter across relationships in a single query using the double-underscore ( __ ) syntax. It lets you reach into a related model's fields without writing a join by hand:
Fill in the blanks so the helper prints a correct ForeignKey definition. The field type should be ForeignKey and the delete rule should be on_delete=models.CASCADE .
❌ TypeError: __init__() missing 1 required argument: 'on_delete'
You declared a ForeignKey (or OneToOneField) without telling Django what to do on delete.
✅ Fix: always pass on_delete , e.g. models.ForeignKey(Author, on_delete=models.CASCADE) .
❌ ValueError: needs to have a value for field "id" before this many-to-many relationship can be used
You called post.tags.add(...) before the post was saved, so it has no primary key.
✅ Fix: call post.save() first, then post.tags.add(tag) .
❌ AttributeError: 'Author' object has no attribute 'post'
You confused forward and reverse access. The reverse side is a manager, not a single object.
✅ Fix: use the reverse manager: author.posts.all() (or author.post_set.all() without related_name).
Build a tiny blog graph with posts and tags, then count how many posts use each tag — mirroring a reverse many-to-many query like tag.posts.count() .
Lesson 9 complete — your models are connected!
You can now model a many-to-one link with ForeignKey and on_delete, a many-to-many link with ManyToManyField using add/remove/set, and a one-to-one link with OneToOneField — and you can follow relationships forward, backward with related_name, and across with double-underscore lookups.
🚀 Up next: Forms — collect and validate user input the Django way.
Practice quiz
Which field models a many-to-one relationship?
- ForeignKey
- ManyToManyField
- OneToOneField
- CharField
Answer: ForeignKey. A ForeignKey models many-to-one: many rows point to one related row.
Which field models a many-to-many relationship?
- ForeignKey
- ManyToManyField
- OneToOneField
- IntegerField
Answer: ManyToManyField. A ManyToManyField links many rows on each side via a hidden join table.
Which field is used for a one-to-one relationship like a user profile?
- ForeignKey
- ManyToManyField
- OneToOneField
- SlugField
Answer: OneToOneField. A OneToOneField links exactly one row to one row, behaving like a unique ForeignKey.
Which argument is required on every ForeignKey?
- related_name
- null
- default
- on_delete
Answer: on_delete. on_delete is required so Django knows what to do with dependent rows on delete.
What does on_delete=models.CASCADE do?
- Blocks the deletion
- Deletes the rows that point to the deleted record too
- Sets the link to null
- Sets a default value
Answer: Deletes the rows that point to the deleted record too. CASCADE deletes the dependent rows along with the record they point to.
What does related_name control?
- The attribute used for reverse access from the related side
- The database table name
- The field's default value
- The on_delete behaviour
Answer: The attribute used for reverse access from the related side. related_name names the reverse accessor, e.g. author.posts instead of author.post_set.
Without related_name, how do you access posts from an author?
- author.posts.all()
- author.posts_all()
- author.post_set.all()
- author.get_posts()
Answer: author.post_set.all(). The default reverse manager is <modelname>_set, e.g. author.post_set.all().
Which ManyToManyField method replaces the entire set of links at once?
- add()
- remove()
- clear()
- set()
Answer: set(). post.tags.set([t1, t2]) replaces all current links with the given list.
How do you filter posts written by an author named 'Alice'?
- Post.objects.filter(author__name='Alice')
- Post.objects.filter(author.name='Alice')
- Post.objects.filter(author='Alice')
- Post.objects.get(name='Alice')
Answer: Post.objects.filter(author__name='Alice'). Double-underscore syntax spans the relation: author__name reaches the related field.
On which table does Django add the column for a ForeignKey?
- The target model's table
- A new join table
- The model holding the ForeignKey
- Both tables
Answer: The model holding the ForeignKey. The FK column (e.g. author_id) lives on the model that declares the ForeignKey.