DjangoCheatsheet

Django ORM Cheatsheet📋

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.

Quick Reference

NameSyntaxDescription
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') + 1References 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.

QuerySet Basics

Creating and Retrieving Objects

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.

python

Tips

  • Always wrap .get() in a try/except or use .filter().first() when the object may not exist
  • get_or_create and update_or_create use SELECT ... FOR UPDATE to prevent race conditions
  • Use .only() or .defer() after .get() when you only need a subset of fields on large models

QuerySet Chaining and Evaluation

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.

python

Tips

  • QuerySets are immutable — each .filter() returns a new QuerySet, not a mutated one
  • Use print(qs.query) during development to see the generated SQL
  • Slicing with [:N] adds LIMIT to the SQL — always prefer it over Python slicing of list(qs)

values() and values_list()

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.

python

Tips

  • Use flat=True with values_list for a single field to get a flat list instead of a list of 1-tuples
  • values() with no arguments returns all fields as dicts — specify fields explicitly for performance
  • Combine values() with annotate() for GROUP BY behavior

Filtering & Lookups

Field Lookups

field__lookup=value

Django 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.

python

Tips

  • The default lookup is __exact which you can omit: filter(status='published') equals filter(status__exact='published')
  • Use __in with a subquery QuerySet for efficient EXISTS-style filtering
  • String lookups like __contains use LIKE which cannot use standard indexes — consider full-text search for large datasets

Q Objects — Complex Conditions

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.

python

Tips

  • Start with an empty Q() when building filters dynamically — it acts as an identity element for & and |
  • Keyword arguments in .filter() always combine with AND — you need Q objects for OR
  • Q objects can be nested: Q(Q(a=1) | Q(b=2)) & Q(c=3)

F Expressions — Database-Level References

F('field_name') + value

F 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.

python

Tips

  • Always use F() for counter increments — Python-level read-modify-write is a race condition under concurrency
  • F expressions support arithmetic (+, -, *, /), bitwise operations, and date math with timedelta
  • Combine F() with database functions like Coalesce, Lower, and Length for powerful SQL expressions

Subqueries and Exists

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.

python

Tips

  • Always slice Subquery results with [:1] — subqueries in SELECT must return a single value
  • Exists() is more efficient than Count() > 0 for checking presence of related objects
  • OuterRef('pk') refers to the primary key of the outer query — use any field name the outer model has

Aggregation & Annotation

Aggregate Functions

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.

python

Tips

  • aggregate() returns a plain dict, not a QuerySet — you cannot chain further QuerySet methods after it
  • Use the filter= argument inside Count/Sum/Avg to compute conditional aggregates in a single query
  • AnyValue (Django 6) is ideal for GROUP BY queries where you need a representative value without caring about which specific row it comes from

Annotation — Per-Object Aggregates

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.

python

Tips

  • values() before annotate() determines the GROUP BY columns — the combination acts like SQL GROUP BY
  • Use Coalesce(expression, Value(0)) to replace NULL aggregation results with a default
  • Annotated fields can be used in subsequent filter(), order_by(), and values() calls

StringAgg and ArrayAgg

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).

python

Tips

  • Django 6 made StringAgg backend-agnostic — import from django.db.models.aggregates instead of django.contrib.postgres
  • Use distinct=True to avoid duplicate values and ordering= to control the concatenation order
  • ArrayAgg with filter= lets you build filtered lists in a single query without Python post-processing

Window Functions

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.

python

Tips

  • Window functions do not reduce rows like GROUP BY — every original row is preserved with the computed value
  • You cannot filter on window annotations directly — wrap in a subquery or use raw SQL
  • partition_by acts like GROUP BY within the window; order_by determines the row ordering for ranking

Query Optimization

select_related — JOIN Fetching

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.

python

Tips

  • Only works with ForeignKey and OneToOneField — use prefetch_related for ManyToMany and reverse FKs
  • Selecting too many relations can produce a very wide SQL result set — profile with .explain()
  • Define a custom Manager with default select_related for models that are almost always accessed with relations

prefetch_related and Prefetch Objects

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.

python

Tips

  • to_attr makes the prefetched data a Python list — use attribute access, not .all()
  • Prefetch with a filtered queryset avoids the invalidation problem where .filter() on a prefetched set hits the DB again
  • Combine select_related inside a Prefetch queryset for multi-level optimization

only(), defer(), and AsyncPaginator

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.

python

Tips

  • Accessing a deferred field triggers a full query for that single object — only defer fields you truly will not access
  • Django 6's AsyncPaginator works with async views and ASGI servers — it calls aget_page instead of get_page
  • Use .explain(analyze=True) to compare query plans before and after adding only()/defer()

Transactions & Signals

Atomic Transactions

with transaction.atomic(): / @transaction.atomic

transaction.atomic() guarantees that a block of database operations either all succeed or all roll back. Nested atomic blocks create savepoints for partial rollback.

python

Tips

  • Never catch exceptions inside atomic() if you want the transaction to roll back — let the exception propagate
  • Nested atomic() blocks create savepoints — only the inner block rolls back on failure, not the outer
  • Always use update_fields in .save() inside transactions to avoid overwriting concurrent changes to other fields

on_commit Hooks

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.

python

Tips

  • Always use on_commit for Celery tasks — sending a task before commit means the worker might read stale data
  • on_commit callbacks execute in the order they were registered
  • If the transaction rolls back, all on_commit callbacks are discarded — no cleanup needed

Signals — Model Lifecycle Hooks

pre_save / post_save / pre_delete / post_delete

Django 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.

python

Tips

  • Signal handlers run inside the same transaction as the save() — use on_commit for external side effects
  • Connect signals in AppConfig.ready() to avoid import ordering issues
  • Prefer overriding save() for single-model logic — signals are best for cross-app decoupling

Raw SQL & Database Functions

Raw Queries

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.

python

Tips

  • NEVER use string formatting or f-strings for SQL parameters — always use %s placeholders or named %(name)s params
  • raw() must include the primary key in the SELECT — Django needs it to construct model instances
  • Use connection.cursor() as a context manager to ensure the cursor is closed properly

Database Functions

from django.db.models.functions import Func

Django 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.

python

Tips

  • Coalesce is essential for handling NULL in annotations — without it, Sum/Avg return None for empty sets
  • TruncMonth, TruncDay, and ExtractYear are the standard way to do time-series GROUP BY in the ORM
  • GeneratedField with db_persist=True in Django 6 auto-refreshes when source fields change — no triggers needed

JSONField Queries

jsonfield__key__lookup=value

JSONField 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.

python

Tips

  • Django 6 allows negative array indexes on SQLite JSONField — tags__-1 accesses the last element
  • PostgreSQL GIN indexes dramatically speed up __contains and __has_key lookups on JSONField
  • JSONField lookups traverse nested structures with __ — metadata__dimensions__width follows the JSON path

Common Patterns

Complex query: multi-filter search with pagination

python

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.

Bulk operations: efficient batch create and update

python

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 with reusable QuerySet methods

python

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.

Watch Out For

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).

Master Django with Stanza

Go beyond the cheatsheet with hands-on lessons and challenges.

Dive Deeper