Relations¶
ForeignKey (many-to-one)¶
class Author(Model):
name = fields.CharField(max_length=100)
class Book(Model):
title = fields.CharField(max_length=200)
author = fields.ForeignKey(Author, related_name="books", on_delete="CASCADE")
Stored as a author_id column. Assign an instance or an id:
book = Book.objects.create(title="X", author=some_author) # or author_id=1
book.author # lazily loads the Author (cached)
book.author_id # the raw FK value
Cross-table lookups produce JOINs:
Book.objects.filter(author__name="Ada")
Book.objects.filter(author__country__code="US") # multi-level
Book.objects.order_by("author__name")
Reverse accessor (related_name, default <model>_set):
author.books.all() # QuerySet of this author's books
author.books.filter(title__icontains="py")
author.books.count()
on_delete options: CASCADE, SET NULL (needs null=True), RESTRICT,
PROTECT (mapped to RESTRICT at the DB), NO ACTION/DO_NOTHING.
OneToOneField¶
A unique FK — one row on each side:
class Profile(Model):
user = fields.OneToOneField(User, related_name="profile")
bio = fields.TextField(default="")
user.profile # the single related Profile, or None
ManyToManyField¶
An auto-created through table with a relation manager:
class Tag(Model):
name = fields.CharField(max_length=30)
class Post(Model):
title = fields.CharField(max_length=200)
tags = fields.ManyToManyField(Tag)
post.tags.add(tag1, tag2) # idempotent (duplicates ignored)
post.tags.remove(tag1)
post.tags.set([tag2, tag3]) # replace the set
post.tags.clear()
post.tags.all() # QuerySet of related Tags
post.tags.count()
create_all(...) builds through tables in a second pass, so relation order
doesn't matter.
add() stays idempotent under concurrency too: if two callers race adding
the same pair, the loser's insert hits the through table's composite primary
key and is treated as "already related", not raised.
Avoiding N+1: select_related & prefetch_related¶
select_related — for forward FK / OneToOne — fetches related rows in the
same query via a JOIN:
for book in Book.objects.select_related("author"):
book.author.name # no extra query
Book.objects.select_related("author__country") # multi-level
prefetch_related batch-loads a relation for a whole page of results
instead of one query per row — but check which relation kinds it actually
supports before reaching for it, since the two supported kinds cost
different numbers of queries:
for post in Post.objects.prefetch_related("tags"):
post.tags.all() # served from cache, no per-row query
- A
ManyToManyFieldname (like"tags"above) — two extra queries total: one against the through table (grouping target ids by source id), then one against the target model (pk__in=[...]) to fetch the actual rows. Still a huge win over N+1, just not literally "one" query. - A forward
ForeignKeyfield's own name (not itsrelated_name) — one extra query (pk__in=[...]on the target model). Equivalent toselect_relatedfor that same field, except it stays a separate SELECT instead of a JOIN — usually you'd reach forselect_relatedon a forward FK instead, butprefetch_relatedworks too if you'd rather avoid the JOIN's duplicated base-table columns. - A reverse relation name (
related_name, or<model>_setif none was set) — one extra query (fk__in=[...]on the source model, grouped by fk id in Python):
for author in Author.objects.prefetch_related("books"):
author.books.all() # served from cache — no per-author query
list(author.books) # plain iteration is cached too
author.books.filter(title__startswith="A") # chaining still re-queries
Only a bare .all() (or iterating the relation directly, with no other
clause) reads from the cache — anything chained afterwards
(.filter(), .exclude(), .order_by(), ...) is a genuinely different
query and always hits the database, exactly like an ordinary QuerySet.
.count() on the reverse relation also always queries fresh (it doesn't
consult the prefetch cache, the same as any other QuerySet's .count())
— if per-row counting is the actual bottleneck, annotate(n=Count("books"))
avoids that instead (see Queries).
Referencing models by name¶
Forward references (a model defined later, or self-references) can use a string once the class exists at call time — but the plain class reference is preferred: