Skip to main content

Filtering

The .filter() method is your primary tool for querying. It translates Python arguments into SQL WHERE clauses.

Basic Syntax

# Exact match (lookup name is implicit)
await Post.objects.filter(active=True)
await Post.objects.filter(author_id=5)

# With a lookup
await Post.objects.filter(views__gt=100)

The pattern is field_name__lookup=value. When no lookup is specified, exact is used.

Comparison Lookups

Post.objects.filter(views__gt=100)    # views > 100
Post.objects.filter(views__gte=100) # views >= 100
Post.objects.filter(views__lt=50) # views < 50
Post.objects.filter(views__lte=50) # views <= 50

String Lookups

Post.objects.filter(title__contains="Python")        # LIKE '%Python%'
Post.objects.filter(title__icontains="python") # LIKE '%python%' (case-insensitive)
Post.objects.filter(title__startswith="How") # LIKE 'How%'
Post.objects.filter(title__istartswith="how") # LIKE 'how%' (case-insensitive)
Post.objects.filter(title__endswith="guide") # LIKE '%guide'
Post.objects.filter(title__iendswith="Guide") # LIKE '%guide' (case-insensitive)

Null Checks

Post.objects.filter(body__isnull=True)    # body IS NULL
Post.objects.filter(body__isnull=False) # body IS NOT NULL

Membership

# IN clause
Post.objects.filter(status__in=["draft", "published"])
Post.objects.filter(id__in=[1, 2, 3, 4, 5])

Range

# BETWEEN clause
Post.objects.filter(views__range=(100, 1000))
Post.objects.filter(created_at__range=(start_date, end_date))

Multiple Filters

Multiple filters are AND-ed together:

# WHERE active = true AND views > 100
Post.objects.filter(active=True, views__gt=100)

Excluding

# WHERE NOT (status = 'draft')
Post.objects.exclude(status="draft")

# Chain filter + exclude
Post.objects.filter(active=True).exclude(title__startswith="Draft")

Built-in Lookups Reference

LookupSQLExample
exactcol = ?filter(title="Hello")
gtcol > ?filter(views__gt=100)
gtecol >= ?filter(views__gte=100)
ltcol < ?filter(views__lt=50)
ltecol <= ?filter(views__lte=1000)
containscol LIKE ?filter(title__contains="Py")
icontainsLOWER(col) LIKE LOWER(?)filter(title__icontains="py")
startswithcol LIKE ?filter(title__startswith="How")
istartswithLOWER(col) LIKE LOWER(?)filter(title__istartswith="how")
endswithcol LIKE ?filter(title__endswith="guide")
iendswithLOWER(col) LIKE LOWER(?)filter(title__iendswith="Guide")
isnullcol IS NULL / IS NOT NULLfilter(body__isnull=True)
incol IN (?, ?, ...)filter(id__in=[1,2,3])
rangecol BETWEEN ? AND ?filter(views__range=(100,1000))

Date & Time Transforms

These can be chained with other lookups (e.g. created_at__year__gte=2024).

TransformSQLExample
dateDATE(col) = ?filter(created_at__date=date(2024,1,1))
yearEXTRACT(YEAR FROM col) = ? (Postgres) / YEAR(col) = ? (MySQL) / CAST(strftime('%Y', col) AS INTEGER) = ? (SQLite)filter(created_at__year=2024)
monthEXTRACT(MONTH FROM col) = ? / MONTH(col) = ? / CAST(strftime('%m', col) AS INTEGER) = ?filter(created_at__month=5)
dayEXTRACT(DAY FROM col) = ? / DAYOFMONTH(col) = ? / CAST(strftime('%d', col) AS INTEGER) = ?filter(created_at__day=15)
hourEXTRACT(HOUR FROM col) = ? / HOUR(col) = ? / CAST(strftime('%H', col) AS INTEGER) = ?filter(created_at__hour=14)
minuteEXTRACT(MINUTE FROM col) = ? / MINUTE(col) = ? / CAST(strftime('%M', col) AS INTEGER) = ?filter(created_at__minute=30)
secondEXTRACT(SECOND FROM col) = ? / SECOND(col) = ? / CAST(strftime('%S', col) AS INTEGER) = ?filter(created_at__second=45)
weekEXTRACT(WEEK FROM col) = ? / WEEK(col) = ? / CAST(strftime('%W', col) AS INTEGER) = ?filter(created_at__week=20)
dowEXTRACT(DOW FROM col) = ? / DAYOFWEEK(col) = ? / CAST(strftime('%w', col) AS INTEGER) = ?filter(created_at__dow=1)
quarterEXTRACT(QUARTER FROM col) = ? / QUARTER(col) = ? / ((CAST(strftime('%m', col) AS INTEGER) + 2) / 3) = ?filter(created_at__quarter=2)
timeTIME(col) = ? (Postgres/MySQL) / time(col) = ? (SQLite)filter(created_at__time='12:34:56')
iso_weekEXTRACT(ISOWEEK FROM col) = ? / complex MySQL expression / CAST(strftime('%W', col) AS INTEGER) = ? (SQLite)filter(created_at__iso_week=15)
iso_dowEXTRACT(ISODOW FROM col) = ? / ((DAYOFWEEK(col) + 5) % 7) + 1 = ? (MySQL) / CAST(strftime('%w', col) AS INTEGER) = ? (SQLite)filter(created_at__iso_dow=3)

JSON Transforms & Lookups

These work on JSONB/JSON fields and can be chained (e.g. metadata__key__priority__exact="high").

Transform/LookupSQL (Postgres)Example
key(col->>'key')filter(metadata__key__priority__exact="high")
key_text(col->>'key')::textfilter(metadata__key_text__priority__icontains="urgent")
json(col::jsonb)filter(metadata__json__contains={'active': true})
has_keycol ? 'key'filter(metadata__has_key="priority")
has_keyscol ?& array['key1','key2']filter(metadata__has_keys=["priority","status"])
containscol @> ?filter(metadata__contains={"priority": "high"})
contained_bycol <@ ?filter(metadata__contained_by={"priority": "high"})

Custom Lookups

Register your own SQL lookups:

import ryx

# Postgres ILIKE
ryx.register_lookup("ilike", "{col} ILIKE ?")

# Usage
Post.objects.filter(title__ilike="%python%")

# Decorator style
@ryx.lookup("uuid_prefix")
def uuid_prefix_lookup(field, value):
"""{col}::text LIKE ?"""

Next Steps

Q Objects — OR and NOT expressions → Custom Lookups — Advanced lookup patterns