F() and Q() expressions
coreintermediateF("field_name") refers to a field's value inside the database itself, without pulling it into Python — used for race-safe increments (F("count") + 1) and for comparing two fields on the same row. Q(...) wraps a lookup so multiple lookups can be combined with & (AND), | (OR), and ~ (NOT) — needed the moment a filter needs OR logic or NOT logic, which filter()'s own keyword arguments cannot express.
Think of it as
F() moves a computation from Python into SQL — instead of reading a value, computing in Python, then writing it back (two round trips, with a race-condition window between them), F() tells the database "set this column to itself plus one," entirely inside one UPDATE statement, atomically. Q() exists because filter(a=1, b=2) always means AND — there is no keyword-argument syntax for OR or NOT, so Q objects give filter() something to combine with the boolean operators Python already has (&, |, ~), each Q wrapping one lookup.
What we're doing: Increment a view counter without a race condition between two concurrent requests, and query for orders in either of two statuses.
- 2
- F("view_count") + 1 tells the database to compute the new value itself — two concurrent requests both calling this at once each correctly add 1, with no possibility of one overwriting the other's increment.
- 6
- Q(status="PENDING") | Q(status="PROCESSING") is OR logic — combined with customer=customer as a normal kwarg, which still ANDs with the whole Q expression.
Why this works: article.view_count += 1; article.save() has a real race condition: two requests can both read the same starting value, both compute +1 from it, and the second save() silently overwrites the first's increment, losing a view — F() avoids this by never reading the value into Python at all, letting the database perform the read-and-increment as one atomic operation.
Reading an F()-updated attribute immediately after save() without refreshing
Wrong
Better
What you see: Printing or using article.view_count right after save() shows a CombinedExpression object (or raises when used in arithmetic), not the actual new integer — code that expects a number breaks immediately.
Why: F() never resolves to an actual number in Python — it is a reference the database evaluates when the SQL runs. save() sends that expression to the database and does not read the result back into the instance automatically; refresh_from_db() (or re-fetching the object) is required to load the real, current value into Python.
- F("count") + 1
- Computed inside the database, atomically
- Race-safe — no read-then-write window
- refresh_from_db() needed to see the real value
- Q(a=1) | Q(b=2)
- filter() kwargs are always AND
- Q wraps a lookup for &, |, ~
- The only way to express OR/NOT
F() vs Q() — what each solves
Together
Remember: F("field") defers a computation to the database (race-safe increments, cross-field comparisons) — refresh_from_db() is required to see the real value in Python afterward. Q(...) wraps a lookup so filter() can express OR/NOT, which plain keyword arguments never can.
See also: conditional expressions · annotate vs aggregate · write methods

