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.