Real data is relational, and following relationships in both directions is everyday ORM work.
Three field types connect models:
- `ForeignKey` (many-to-one): many posts belong to one author.
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="posts") - `ManyToManyField`: posts have many tags and tags many posts. Django creates the join table for you; use
through=for a custom one with extra columns. - `OneToOneField`: one-to-one extension, such as a
Profilefor eachUser.
on_delete is required on ForeignKey and decides what happens when the referenced row is deleted:
CASCADE: delete dependent rows tooPROTECT/RESTRICT: refuse the deletion with an errorSET_NULL: set the column to NULL (requiresnull=True)SET_DEFAULT,DO_NOTHING
Navigating:
- forward:
post.author(one query the first time, then cached) - reverse:
author.posts.all(), named byrelated_name(without it,author.post_set) - many-to-many:
post.tags.add(t),.remove(t),.set([...]),.clear(), andpost.tags.all()
Query across relationships with double underscores, in both directions:
Post.objects.filter(author__name="Ann")
Author.objects.filter(posts__tags__name="django").distinct()Filters across multi-valued relationships can return duplicates, hence .distinct().
Always set related_name: it makes reverse access readable and avoids clashes when two foreign keys point at the same model.
Example
from django.db import models
class Tag(models.Model):
name = models.CharField(max_length=50, unique=True)
class Post(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey("Author", on_delete=models.CASCADE, related_name="posts")
tags = models.ManyToManyField(Tag, blank=True, related_name="posts")
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="comments")
text = models.TextField()
# usage
ann = Author.objects.create(name="Ann")
post = Post.objects.create(title="Hi", author=ann)
post.tags.add(Tag.objects.create(name="django"))
print(post.author.name, ann.posts.count()) # Ann 1
print(Post.objects.filter(author__name="Ann", tags__name="django").count()) # 1
print(Author.objects.filter(posts__comments__isnull=True).distinct().count()) # 1Remember
- ForeignKey = many-to-one, ManyToManyField = many-to-many, OneToOneField = one-to-one
- on_delete decides what deleting the parent does (CASCADE, PROTECT, SET_NULL...)
- related_name names the reverse accessor; __ spans relationships in queries
Watch out for
- SET_NULL without null=True on the field, which fails the system check.