Chapter 3: Django QuerySet – Filter
QuerySet – Filter: .filter() — the heart of almost every real QuerySet you will ever write.
Most beginners do one or two simple .filter(is_active=True) and think “okay, I know how to filter”. But .filter() is actually a very rich, very expressive mini-language with 20–30 lookup types, chaining rules, Q objects, related-field traversal, negation, and performance tricks.
Today we’re going to learn it properly, step-by-step, like pair-programming in the shell — I’ll show you exactly what SQL Django generates, when it executes, what each lookup does, common beginner traps, and the 25 most useful real-world .filter() patterns you’ll copy-paste every single day.
We’ll do almost everything live in the Django shell so you can see the results immediately.
Step 1: Open the Shell & Prepare
|
0 1 2 3 4 5 6 |
python manage.py shell |
|
0 1 2 3 4 5 6 7 8 9 |
from polls.models import Question, Choice from django.utils import timezone from datetime import timedelta from django.db.models import Q, Sum |
(If your database is empty, create 5–10 questions with different categories, pub_dates, vote counts via admin or shell first — it will make examples more fun.)
Step 2: The Absolute Basic .filter() (What Everyone Starts With)
|
0 1 2 3 4 5 6 7 8 |
qs = Question.objects.filter(is_active=True) print(qs) # shows matching questions print(qs.count()) # how many |
Generated SQL (approximate):
|
0 1 2 3 4 5 6 |
SELECT * FROM polls_question WHERE is_active = TRUE; |
Step 3: Multiple Conditions = AND (Default Behavior)
|
0 1 2 3 4 5 6 7 8 9 10 |
qs = Question.objects.filter( is_active=True, category="fun", pub_date__gte=timezone.now() - timedelta(days=30) ) |
→ All three conditions must be true (implicit AND)
SQL:
|
0 1 2 3 4 5 6 7 8 |
WHERE is_active = TRUE AND category = 'fun' AND pub_date >= '2026-01-01' |
Step 4: The 15 Most Important Lookup Types (Memorize These)
Django calls them field lookups — the __something part.
| Lookup suffix | Meaning | Example | SQL equivalent |
|---|---|---|---|
| __exact | exact match (case-sensitive) | category__exact=”fun” | category = ‘fun’ |
| __iexact | case-insensitive exact | question_text__iexact=”best biryani?” | LOWER(category) = ‘fun’ |
| __contains | contains substring (case-sensitive) | question_text__contains=”Hyderabad” | question_text LIKE ‘%Hyderabad%’ |
| __icontains | contains (case-insensitive) | question_text__icontains=”biryani” | most used string search |
| __startswith | starts with | slug__startswith=”best-“ | slug LIKE ‘best-%’ |
| __endswith | ends with | slug__endswith=”-2026″ | slug LIKE ‘%-2026’ |
| __gt / __gte | greater / greater-or-equal | vote_count__gt=10 | vote_count > 10 |
| __lt / __lte | less / less-or-equal | pub_date__lte=timezone.now() | pub_date <= NOW() |
| __in | in list/tuple | category__in=[“fun”, “sports”] | category IN (‘fun’, ‘sports’) |
| __range | between two values | pub_date__range=(start, end) | pub_date BETWEEN start AND end |
| __year / __month | date part | pub_date__year=2026, __month=1 | EXTRACT(YEAR FROM pub_date) = 2026 |
| __isnull | IS NULL / IS NOT NULL | deleted_at__isnull=True | deleted_at IS NULL |
| __regex / __iregex | regular expression | slug__regex=r’^best-.*-2026$’ | slug ~ ‘^best-.*-2026$’ |
Step 5: Combining Lookups with AND/OR/NOT
AND (multiple kwargs)
|
0 1 2 3 4 5 6 |
Question.objects.filter(is_active=True, category="fun") |
OR (using Q objects)
|
0 1 2 3 4 5 6 7 8 9 10 |
from django.db.models import Q qs = Question.objects.filter( Q(category="fun") | Q(category="sports") ) |
NOT / exclude
|
0 1 2 3 4 5 6 7 8 9 10 |
# Exclude politics Question.objects.exclude(category="politics") # OR using Q Question.objects.filter(~Q(category="politics")) |
Complex example (very common in real filters)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
# Active polls that are either recent OR popular OR fun category qs = Question.objects.filter( is_active=True, Q(pub_date__gte=timezone.now() - timedelta(days=7)) | Q(vote_count__gt=20) | Q(category="fun") ) |
Step 6: Related Field Lookups (Traversing ForeignKey / ManyToMany)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
# Questions that have at least one choice with > 5 votes Question.objects.filter(choices__votes__gt=5) # Choices belonging to active questions Choice.objects.filter(question__is_active=True) # Questions in "fun" category with choices containing "red" Question.objects.filter(category="fun", choices__choice_text__icontains="red") |
Double-underscore traversal is one of Django ORM’s most beautiful features.
Step 7: Your Quick Practice Session (Do This in Shell Right Now)
-
Get all active questions from 2026:
Python0123456Question.objects.filter(is_active=True, pub_date__year=2026) -
Get fun or sports category:
Python0123456Question.objects.filter(Q(category="fun") | Q(category="sports")) -
Get questions with “Hyderabad” in text (case-insensitive):
Python0123456Question.objects.filter(question_text__icontains="hyderabad") -
Get questions published in last 7 days:
Python0123456Question.objects.filter(pub_date__gte=timezone.now() - timedelta(days=7)) -
Get questions that have choices with votes:
Python0123456Question.objects.filter(choices__votes__gt=0).distinct()
Tell me what feels next:
- Which lookup or pattern is still confusing? (__icontains vs __contains? Q objects? related lookups?)
- Want 20 more real .filter() examples from different apps/scenarios?
- Ready to learn .annotate() + .aggregate() in detail?
- Or finally ready for Django Forms + Voting + POST + F()?
You’re now writing real-world filters like a Django developer — this skill alone makes your views fast and expressive.
Keep experimenting in shell — you’re doing fantastic! 🚀🇮🇳
