Django ORM
The Django ORM maps Python classes to database tables and Python method chains to SQL. You define models, and Django handles schema migrations, queries, relationships, and transactions across PostgreSQL, MySQL, SQLite, and Oracle. It's one of the most productive parts of Django: most applications never need handwritten SQL.
It's also where most Django performance problems originate. QuerySets are lazy, relationships load on attribute access, and an innocent-looking template loop can fire hundreds of queries. Learning to read the queries the ORM generates, and to shape them with select_related, prefetch_related, annotate, and only, is essential for production apps.
TL;DR
- A model is a class whose fields map to columns;
Model.objectsis a manager that returns QuerySets. - QuerySets are lazy: nothing hits the database until you iterate, slice,
len(),list(), or otherwise evaluate them. - Filter with field lookups (
price__gte=10,name__icontains="x"), combine conditions withQobjects, and reference columns withFexpressions. select_related(SQL JOIN) for ForeignKey/OneToOne, andprefetch_related(a second query) for reverse and many-to-many relations. Together they eliminate N+1 queries.annotate/aggregatepush computation into SQL;transaction.atomicgroups writes.- Inspect generated SQL with
str(qs.query),connection.queries, or the Django Debug Toolbar.
Quick Example
Core Concepts
Models and Fields
Each model subclass maps to a table, and each field to a column with a type and constraints: CharField, IntegerField, DecimalField, DateTimeField, JSONField, ForeignKey, ManyToManyField, and many more. Meta declares ordering, indexes, constraints (UniqueConstraint, CheckConstraint), and table options. Add business methods and properties to models, which is the "fat models" approach, but keep complex workflows in service functions.
QuerySets and Laziness
Order.objects.filter(...) builds a query without running it. Chaining (.filter().exclude().order_by()) refines it. Evaluation happens on iteration, slicing with a step, len(), list(), bool(), or methods like .get(), .first(), .count(), .exists(). A QuerySet caches its results after evaluation, so iterating it twice doesn't query twice, but calling .filter() again creates a new QuerySet and a new query.
Lookups, Q, and F
Double underscores traverse relationships (customer__email) and apply lookups (__gte, __in, __isnull, __date, __year).
Relationships
Always set on_delete deliberately: CASCADE, PROTECT, SET_NULL, or RESTRICT.
select_related vs prefetch_related
select_related("customer")does a SQL JOIN and fetches related rows in the same query. Use it for ForeignKey and OneToOne (single-valued) relations.prefetch_related("items")runs one extra query per relation (WHERE order_id IN (...)) and stitches results in Python. Use it for reverse ForeignKey and ManyToMany (multi-valued) relations.Prefetch("items", queryset=...)customizes the prefetch query with filters, ordering, and nestedselect_related.
This is the ORM equivalent of the batching DataLoader does in GraphQL.
Aggregation and Annotation
Beware of joining across multiple multi-valued relations in one annotate, since each join multiplies rows and inflates sums. Use subqueries (Subquery, OuterRef) instead.
Transactions
By default, each query autocommits. Group related writes:
select_for_update() locks rows for read-modify-write within a transaction. See database transactions.
Performance Toolkit
The Django Debug Toolbar (in development) and APM tools (in production) show query counts and slow queries per request. See query optimization.
Best Practices
Watch Query Counts
Use assertNumQueries in tests for important views and APIs, so N+1 regressions fail CI:
Push Work to the Database
Filtering, counting, summing, and updating in SQL beats loading objects into Python. update() with F() expressions also avoids race conditions in read-modify-write code.
Keep Queries Out of Templates
Templates that traverse relationships ({{ order.customer.name }} in a loop) trigger lazy loads. Prepare QuerySets with the right select_related/prefetch_related in the view.
Use Constraints for Data Integrity
UniqueConstraint, CheckConstraint, and non-null fields enforce rules in the database, where they can't be bypassed by bulk operations or concurrent requests, unlike validation in save().
Common Mistakes
The N+1 Loop
Read-Modify-Write Races
Evaluating QuerySets Unintentionally
if queryset: loads every row to check truthiness, and len(queryset) loads all rows to count them. Use .exists() and .count() when you don't need the objects.
FAQ
What's the difference between select_related and prefetch_related?
select_related follows single-valued relationships (ForeignKey, OneToOne) with a SQL JOIN in the same query. prefetch_related handles multi-valued relationships (reverse ForeignKey, ManyToMany) with a separate query per relation, joined in Python. Use both together as needed.
When should I use raw SQL?
Rarely: for complex reporting queries, database-specific features the ORM can't express, or performance-critical paths where you've measured a benefit. Prefer ORM features first (expressions, subqueries, window functions, RawSQL fragments), then Manager.raw() or connection.cursor() with parameterized queries.
Is the Django ORM slow?
The ORM adds modest overhead per query and per model instance; most "slow ORM" problems are really too many queries or too many rows. With select_related, prefetch_related, values(), proper indexes, and bulk operations, Django applications handle very high traffic.
Can I use the ORM asynchronously?
Django provides async QuerySet methods (aget(), acount(), async for obj in qs) that run safely in async views, though the underlying database drivers are largely still synchronous, so calls run in a thread pool. See async Django.
Related Topics
- Django — The framework overview
- Django Migrations — Evolving the schema your models define
- Object-Relational Mapping — ORM concepts and trade-offs
- Query Optimization — Reading plans and indexing
- PostgreSQL — The most common Django database
- GraphQL DataLoader — The same N+1 problem in GraphQL