F() and Q() Expressions

Do arithmetic in the database with F(), and build dynamic OR/AND/NOT logic with Q().

Two small classes unlock most of the ORM's real power. F() refers to a database column by name so the work happens in SQL, and Q() lets you build boolean logic (OR, AND, NOT) as composable objects.

F() — avoid the read-modify-write race

The naive way to increment a counter reads the value into Python, adds one, and saves it back. Between the read and the write, another request can do the same thing and you lose an update. F() pushes the arithmetic into a single atomic UPDATE ... SET views = views + 1.

from django.db.models import F

# WRONG: race condition under concurrency
post.views = post.views + 1
post.save()

# RIGHT: one atomic statement in the database
Post.objects.filter(pk=post.pk).update(views=F('views') + 1)

Q() — logic you can build up

Keyword filters are always AND-ed together. When you need OR, NOT, or a filter you assemble at runtime, use Q(). The operators are | (OR), & (AND) and ~ (NOT).

F() also compares two columns to each other — something a plain filter cannot do — e.g. .filter(spent__gt=F('budget')) to find over-budget rows.

Example

Example · python
from django.db.models import F, Q, DecimalField
from functools import reduce
import operator


# 1) F(): give every product in a category a 10% raise, atomically,
#    and compare two columns to find loss-making lines.
Product.objects.filter(category='books').update(
    price=F('price') * 1.10
)
loss_making = Product.objects.filter(cost__gt=F('price'))


# 2) Q(): build a search filter dynamically from user-supplied terms.
def search_products(terms, in_stock_only=False):
    query = Q()
    for term in terms:
        query |= Q(name__icontains=term) | Q(sku__iexact=term)

    qs = Product.objects.filter(query)
    if in_stock_only:
        qs = qs.filter(~Q(stock=0))   # NOT out-of-stock
    return qs


# 3) Combining OR-groups programmatically with reduce():
wanted = ['django', 'python', 'orm']
or_group = reduce(operator.or_, (Q(tags__name=t) for t in wanted))
articles = Article.objects.filter(or_group).distinct()


# 4) F() inside an annotation to compute a derived margin column:
from django.db.models import ExpressionWrapper
Product.objects.annotate(
    margin=ExpressionWrapper(
        F('price') - F('cost'),
        output_field=DecimalField(max_digits=10, decimal_places=2),
    )
).filter(margin__lt=0)

When to use it

  • A developer uses F('views') + 1 in an update() call to atomically increment a view counter without a race condition.
  • A team uses Q objects to build a search query that matches title OR body containing a keyword.
  • A developer combines F and annotate to compute a discount price column directly in the database query.

More examples

Atomic increment with F()

Uses F() to reference the database column value so the increment happens atomically in SQL.

Example · python
from django.db.models import F

# Atomically increment without race condition
Post.objects.filter(pk=post_id).update(view_count=F("view_count") + 1)

OR query with Q objects

Combines two icontains lookups with | to match posts where either the title or body contains the query.

Example · python
from django.db.models import Q

def search_posts(query):
    return Post.objects.filter(
        Q(title__icontains=query) | Q(body__icontains=query)
    ).distinct()

Annotate with F arithmetic

Annotates a queryset with a computed sale_price column calculated as 90% of the original price.

Example · python
from django.db.models import F, ExpressionWrapper, DecimalField

products = Product.objects.annotate(
    sale_price=ExpressionWrapper(
        F("price") * 0.9,
        output_field=DecimalField(max_digits=8, decimal_places=2),
    )
)

Discussion

  • Be the first to comment on this lesson.