Every essential Django ORM operation in one place, updated for Django 6 and Python 3.12+. Each entry has the signature, a working example, and performance considerations. Covers QuerySet basics through advanced aggregation, transactions, and raw SQL. Bookmark this and stop searching the docs every time you need a lookup.
| Name | Syntax | Description |
|---|---|---|
| Model.objects.all() | MyModel.objects.all() | Returns a QuerySet of all rows. Lazy — no SQL until evaluated. |
| .filter() | qs.filter(field=value, field__lookup=value) | Returns rows matching all conditions (AND). Chainable. |
| .exclude() | qs.exclude(field__lookup=value) | Returns rows that do NOT match the conditions. Inverse of filter. |
| .get() | MyModel.objects.get(pk=1) | Returns exactly one object. Raises DoesNotExist or MultipleObjectsReturned. |
| .create() | MyModel.objects.create(field=value) | Creates and saves an object in one step. Returns the instance. |
| .update() | qs.update(field=value) | Bulk UPDATE at the SQL level. Returns the count of affected rows. Does not call save() or signals. |
| .delete() | qs.delete() | Bulk DELETE at the SQL level. Returns (count, {model: count}) tuple. Respects on_delete cascades. |
| .values() / .values_list() | qs.values('field1', 'field2') | Returns dicts or tuples instead of model instances. Reduces memory and avoids full model hydration. |
| .annotate() | qs.annotate(alias=expression) | Adds a computed column to each row. Groups automatically when combined with values(). |
| .aggregate() | qs.aggregate(alias=Func('field')) | Returns a dict with aggregated values across the entire QuerySet. Terminal — evaluates immediately. |
| .select_related() | qs.select_related('fk_field', 'fk__nested') | Follows ForeignKey/OneToOne with a SQL JOIN. One query for related objects. |
| .prefetch_related() | qs.prefetch_related('m2m_field', 'reverse_set') | Separate query for ManyToMany/reverse FK. Joined in Python. Use Prefetch() for custom querysets. |
| Q() | Q(field__lookup=val) | Q(other=val) | Composable query expression. Supports | (OR), & (AND), ~ (NOT) operators. |
| F() | F('field') + 1 | References a model field in an expression. Evaluated at the database level — avoids race conditions. |
| .exists() | qs.filter(...).exists() | Returns True if at least one row matches. Stops at the first row — faster than .count() > 0. |
Model.objects.create() / .get() / .get_or_create() / .first()Core methods for creating and fetching individual objects. get() is strict and raises exceptions, while first() returns None gracefully. get_or_create() is atomic and avoids race conditions.
Tips
qs.filter().exclude().order_by().distinct()[:10]QuerySets are lazy and composable. No database query is executed until the QuerySet is evaluated by iteration, slicing, bool(), len(), or list(). Each chain returns a new QuerySet.
Tips
qs.values('f1', 'f2') / qs.values_list('f1', flat=True)Return lightweight dictionaries or tuples instead of full model instances. Significantly faster when you only need specific columns — avoids instantiating the full model.
Tips
field__lookup=valueDjango field lookups translate Python expressions to SQL WHERE clauses. The double-underscore syntax separates field names from lookup types. All lookups are composable with Q objects.
Tips
Q(condition) | Q(condition) & ~Q(condition)Q objects let you express OR, NOT, and complex boolean logic that keyword arguments cannot handle. They compose with | (OR), & (AND), and ~ (NOT) operators.
Tips
F('field_name') + valueF expressions reference model fields in queries, allowing field-to-field comparisons and atomic updates entirely at the database level. They avoid loading data into Python.
Tips
Subquery(qs.values('field')[:1]) / Exists(qs)Subquery and Exists let you embed one QuerySet inside another, producing correlated subqueries. OuterRef references a field from the outer query. Exists is the ORM equivalent of SQL EXISTS.
Tips
qs.aggregate(alias=Func('field'))aggregate() collapses an entire QuerySet into a single dict of computed values. It is a terminal operation that evaluates the query immediately. Django 6 adds AnyValue for picking an arbitrary group member.
Tips
qs.annotate(alias=expression)annotate() adds a computed column to each row in the QuerySet, like a SQL window function or GROUP BY. Combine with values() for GROUP BY behavior.
Tips
StringAgg('field', delimiter=', ') / ArrayAgg('field')StringAgg concatenates field values across rows with a delimiter. In Django 6, StringAgg moved from django.contrib.postgres to django.db.models.aggregates and works on all backends. ArrayAgg returns values as a Python list (PostgreSQL only).
Tips
Window(expression, partition_by=, order_by=)Window functions compute values across a set of rows related to the current row without collapsing the result. Supports RowNumber, Rank, DenseRank, Lead, Lag, and aggregates like Sum and Avg.
Tips
qs.select_related('fk_field', 'fk__nested_fk')select_related performs a SQL JOIN and includes related ForeignKey and OneToOneField objects in a single query. Use it when you know you will access the related object for every row.
Tips
qs.prefetch_related('m2m', Prefetch('m2m', queryset=...))prefetch_related executes a separate query per relation and joins results in Python. Use Prefetch objects for custom filtering, ordering, or limiting prefetched results. to_attr stores results as a plain list attribute.
Tips
qs.only('f1', 'f2') / qs.defer('big_field')only() and defer() control which fields are loaded. Deferred fields trigger a query on access. Django 6 introduces AsyncPaginator for fully async pagination in ASGI views.
Tips
with transaction.atomic(): / @transaction.atomictransaction.atomic() guarantees that a block of database operations either all succeed or all roll back. Nested atomic blocks create savepoints for partial rollback.
Tips
transaction.on_commit(callable, using='default')on_commit schedules a callback to run after the current transaction commits. Ideal for side effects like sending emails, queuing Celery tasks, or invalidating caches that should only happen if the database changes persist.
Tips
pre_save / post_save / pre_delete / post_deleteDjango signals fire at specific model lifecycle points. post_save receives a created boolean to distinguish inserts from updates. Always combine with on_commit for side effects.
Tips
Model.objects.raw(sql, params) / connection.cursor()raw() executes arbitrary SQL and maps results to model instances. Use connection.cursor() for queries that do not map to a model. Always use parameterized queries to prevent SQL injection.
Tips
from django.db.models.functions import FuncDjango provides Python wrappers for common SQL functions. They compose inside annotate(), filter(), and order_by(). Django 6 adds auto-refresh for GeneratedField, ensuring computed columns stay current.
Tips
jsonfield__key__lookup=valueJSONField supports deep key-path lookups, containment checks, and key existence queries. Django 6 adds negative array indexing support on SQLite, bringing it closer to PostgreSQL feature parity.
Tips
Production search pattern combining dynamic Q-object filtering, eager loading with select_related and prefetch_related, per-row annotations, and Django's built-in Paginator. Start with an empty Q() and compose conditionally to avoid messy if/else chains.
Three bulk patterns: bulk_create with a generator and batch_size for large imports, QuerySet.update() for SQL-level bulk updates without loading objects into Python, and F expressions for atomic counter increments. Always use batch_size to avoid hitting database parameter limits.
Custom Manager + QuerySet pattern encapsulates common filters and annotations as chainable methods. Define methods on the QuerySet class for chainability, then expose them through the Manager. This keeps view and service code clean and avoids scattering filter logic across the codebase.
QuerySets are lazy: no SQL executes until you iterate, slice, call len(), bool(), or list(). Assigning a QuerySet to a variable does not hit the database.
This is by design and enables chaining. Understand the evaluation triggers: for-loop iteration, [:N] slicing, list(qs), len(qs), bool(qs), repr(qs). Use print(qs.query) to inspect the SQL that will execute without actually running it.
N+1 queries: accessing a ForeignKey or ManyToMany field in a loop without select_related or prefetch_related fires one query per object — 50 articles with authors means 51 queries.
Always use select_related('fk_field') for ForeignKey/OneToOne and prefetch_related('m2m_field') for ManyToMany/reverse relations. Install Django Debug Toolbar in development to see query counts per request — the SQL panel makes N+1 problems immediately obvious.
.count() vs len(): calling qs.count() issues a SELECT COUNT(*) query. Calling len(qs) evaluates the entire QuerySet into memory first, then counts the Python list.
Use .count() when you only need the number and will not iterate the results. Use len(qs) only if you have already evaluated the QuerySet and want to avoid an extra query. For boolean checks, use .exists() instead of either — it stops at the first matching row.
QuerySet.update() and delete() bypass model.save() and signals entirely. post_save, pre_delete, and custom save() logic will not fire.
If you need signals or custom save() logic, iterate and call .save() or .delete() on each instance. If you need raw speed for bulk operations and your signals/save() are not required, update() and delete() are the right choice — just be aware of the trade-off.
Chaining .filter() with ManyToMany fields can produce unexpected results: Article.objects.filter(tags__name='python', tags__name='django') returns articles with a tag that is BOTH 'python' AND 'django' on the SAME join row (always empty).
Use separate .filter() calls for independent conditions on the same ManyToMany: Article.objects.filter(tags__name='python').filter(tags__name='django'). Each .filter() creates a separate JOIN, correctly finding articles that have both tags (possibly on different rows).
Go beyond the cheatsheet with hands-on lessons and challenges.