Django ORM Patterns
The Django ORM is powerful but easy to misuse. These patterns keep queries efficient and code readable.
QuerySet Chaining
QuerySets are lazy — they don't hit the database until evaluated:
# No query yet — just building the filter
posts = Post.objects.filter(status=Post.Status.PUBLISHED)
posts = posts.filter(categories__slug="python")
posts = posts.order_by("-published_date")
# NOW the query runs (iteration triggers evaluation)
for post in posts:
print(post.title)
Avoiding N+1 Queries
The most common performance problem. Use select_related for ForeignKey and prefetch_related for reverse/M2M:
# BAD: N+1 — one query per doc to get its subject
docs = Doc.objects.all()
for doc in docs:
print(doc.subject.name) # Extra query each time
# GOOD: single JOIN
docs = Doc.objects.select_related("subject").all()
for doc in docs:
print(doc.subject.name) # No extra query
# For reverse relations or M2M
subjects = Subject.objects.prefetch_related("docs").all()
for subject in subjects:
print(subject.docs.count()) # Already cached
update_or_create for Idempotent Seeding
Perfect for seed scripts that might run multiple times:
subject, created = Subject.objects.update_or_create(
slug="ai-agents", # Lookup key
defaults={ # Fields to set/update
"name": "AI Agents",
"description": "...",
"updated_at": int(time.time()),
},
)
action = "Created" if created else "Updated"
Aggregation
Use the database for math, not Python:
from django.db.models import Count, Avg, Q
# Count published docs per subject
subjects = Subject.objects.annotate(
doc_count=Count("docs", filter=Q(docs__is_published=True))
)
for s in subjects:
print(f"{s.name}: {s.doc_count} docs")
Manager Methods
Encapsulate common queries in custom managers:
class PostManager(models.Manager):
def published(self):
return self.filter(status=Post.Status.PUBLISHED)
def by_category(self, slug):
return self.published().filter(categories__slug=slug)
class Post(models.Model):
objects = PostManager()
# Usage
posts = Post.objects.published()
python_posts = Post.objects.by_category("python")