BudiBadu Logo
Samplebadu

Django by Example: Model Query Expressions

Django 5.0+

Django's F() and Q() objects allow for complex database queries. This sample code shows how F() objects reference model fields directly in the database, while Q() objects enable complex lookups with OR and AND logic.

Code

from django.db import models
from django.db.models import F, Q

class Product(models.Model):
    name = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    stock = models.IntegerField()
    views = models.IntegerField(default=0)

# 1. F() Expressions: Referencing fields in the database
# Increment views by 1 atomically without race conditions
# SQL: UPDATE product SET views = views + 1 WHERE id = 1
product = Product.objects.get(id=1)
product.views = F('views') + 1
product.save()

# Compare fields on the same model
# Find products where stock is less than views
popular_products = Product.objects.filter(stock__lt=F('views'))

# 2. Q() Objects: Complex lookups
# Standard filter() uses AND logic. Q() allows OR logic.

# Find products named "Pencil" OR "Pen"
# SQL: SELECT * FROM product WHERE name = 'Pencil' OR name = 'Pen'
writing_tools = Product.objects.filter(
    Q(name='Pencil') | Q(name='Pen')
)

# Complex logic: (Name starts with 'A' OR Price < 10) AND Stock > 0
# The ~ operator negates a Q object (NOT)
results = Product.objects.filter(
    (Q(name__startswith='A') | Q(price__lt=10)) & 
    Q(stock__gt=0) & 
    ~Q(name='Apple')
)

Explanation

Django's query expressions allow you to construct complex SQL queries using Python objects, pushing logic down to the database layer. This is essential for performance (doing work in the DB instead of Python) and for avoiding race conditions.

  • F() objects: Reference model fields directly in the database. This allows for atomic updates (e.g., incrementing a counter without fetching it first) and field-to-field comparisons within the same row.
  • Q() objects: Encapsulate SQL conditions, enabling the use of logical OR (|) and NOT (~) operators, which are impossible with standard keyword arguments.
  • ExpressionWrapper: Allows you to perform arithmetic on fields of different types (e.g., calculating a discounted price) by specifying the output_field.

Using F() expressions is a best practice for high-concurrency environments. For example, product.views = F('views') + 1 generates an SQL statement like UPDATE product SET views = views + 1. This ensures that even if two requests happen simultaneously, the database handles the locking and incrementing correctly, preventing the "lost update" problem that occurs with the fetch-modify-save pattern.

Q() objects are indispensable for complex filtering logic. While standard .filter() calls join arguments with AND, Q() objects allow you to construct nested OR and NOT conditions. They can be combined using bitwise operators (&, |, ~) to build sophisticated query predicates that map directly to SQL WHERE clauses.

Code Breakdown

14
product.views = F('views') + 1 is an atomic update. If two requests hit this line simultaneously, the database handles the locking/incrementing correctly, avoiding the "lost update" problem.
20
filter(stock__lt=F('views')) compares two columns in the same row. This is impossible with standard keyword arguments like filter(stock__lt=views).
27-29
Q(name='Pencil') | Q(name='Pen') constructs a SQL OR clause. Standard .filter() arguments are always joined by AND.
35
~Q(name='Apple'). The tilde (~) operator negates the Q object, effectively creating a NOT condition in the SQL query.