Chapter 4: Django QuerySet – Order By
QuerySet – Order By: .order_by() — sorting your data.
Beginners often think “oh, just add -pub_date and done”, but .order_by() has many subtle but very powerful behaviors, especially when you combine it with annotations, related fields, multiple columns, random ordering, case-insensitive sorting, database-specific tricks, and performance considerations.
Today we’re going to learn .order_by() properly — like a senior sitting next to you in the shell, explaining every pattern, every trap, every real-world use-case you’ll actually need.
We’ll do almost everything live in the Django shell so you see the results instantly.
Step 1: Open Shell & Prepare Some Data
|
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 Sum, F |
(If your database is empty, quickly create 5–10 questions via admin or shell with different pub_date, category, vote_count (annotate or manual).)
Step 2: The Absolute Basic .order_by()
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
# Oldest first oldest_first = Question.objects.order_by("pub_date") print(oldest_first) # Newest first (most common for blogs, news, polls) newest_first = Question.objects.order_by("-pub_date") print(newest_first) |
The – prefix means DESC (descending).
No prefix = ASC (ascending).
Step 3: Multiple Fields (Sort by A then by B)
|
0 1 2 3 4 5 6 7 8 9 10 |
# First by category (A→Z), then newest inside each category qs = Question.objects.order_by("category", "-pub_date") # First newest, then alphabetical inside same date qs = Question.objects.order_by("-pub_date", "question_text") |
Rule: fields are applied left to right — first field is primary sort, next is tie-breaker.
Step 4: Sorting by Related Fields (ForeignKey / ManyToMany)
|
0 1 2 3 4 5 6 7 8 9 |
# Sort questions by total votes (descending) qs = Question.objects.annotate( total_votes=Sum("choices__votes") ).order_by("-total_votes") |
|
0 1 2 3 4 5 6 7 |
# Sort choices by question’s pub_date choices_sorted_by_question_date = Choice.objects.order_by("question__pub_date") |
|
0 1 2 3 4 5 6 7 |
# Newest question first, then highest voted choice inside it choices = Choice.objects.order_by("-question__pub_date", "-votes") |
Double-underscore traversal works perfectly in .order_by().
Step 5: Sorting by Computed / Annotated Values
|
0 1 2 3 4 5 6 7 8 9 |
# Questions sorted by vote count descending (highest votes first) qs = Question.objects.annotate( vote_count=Sum("choices__votes") ).order_by("-vote_count") |
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# Questions sorted by "hotness" = votes + recency bonus from django.db.models import ExpressionWrapper, FloatField from django.db.models.functions import Now qs = Question.objects.annotate( recency_bonus=ExpressionWrapper( F("vote_count") / (Now() - F("pub_date")) * 86400, # votes per day output_field=FloatField() ) ).order_by("-recency_bonus") |
Step 6: Random Ordering (Very Common for “Surprise me”)
|
0 1 2 3 4 5 6 7 |
# Random 5 questions random_qs = Question.objects.order_by("?")[:5] |
Warning: ? is slow on large tables (full table scan). Better alternatives:
- PostgreSQL: order_by(“random()”) or use tablesample system (10)
- MySQL: order_by(“RAND()”) (also slow)
- For big tables: pre-compute random field or use external randomization
Step 7: Case-Insensitive Sorting (Very Useful)
|
0 1 2 3 4 5 6 7 |
# Alphabetical category, ignoring case Question.objects.order_by("category__lower") |
|
0 1 2 3 4 5 6 7 |
# Question text A→Z case-insensitive Question.objects.order_by("question_text__lower") |
(Only PostgreSQL & some databases support __lower — for others use annotation)
Step 8: Combining .filter() + .order_by() (Daily Bread)
|
0 1 2 3 4 5 6 7 8 9 10 |
# Active, fun category, newest first, annotated vote count qs = Question.objects \ .filter(is_active=True, category="fun") \ .annotate(vote_count=Sum("choices__votes")) \ .order_by("-vote_count", "-pub_date")[:10] |
Step 9: Real View Example (Copy-Paste Ready)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
def index(request): latest = Question.objects \ .filter(is_active=True) \ .annotate(vote_count=Sum("choices__votes")) \ .order_by("-vote_count", "-pub_date") \ .select_related() \ [:10] return render(request, "polls/index.html", { "latest_questions": latest }) |
Step 10: Common Traps & Fixes
| Trap / Mistake | What happens | Correct / Better way |
|---|---|---|
| .order_by(“-vote_count”) but always 0 | Forgot to annotate first | .annotate(vote_count=Sum(“choices__votes”)) first |
| Sorting by related field slow | No index on related field | Add db_index=True on ForeignKey or index in Meta |
| order_by(“?”) very slow on 100k+ rows | Full table scan | Pre-compute random field or paginate + random offset |
| Multiple .order_by() calls | Last one wins | Chain only once — last .order_by() overrides |
| Case-sensitive sort unwanted | “Zebra” before “apple” | Use __lower or annotate Lower(“field”) |
Your Quick Practice Session (Do This in Shell Right Now)
-
from polls.models import Question
-
Latest 5 active:
Python0123456Question.objects.filter(is_active=True).order_by("-pub_date")[:5] -
By vote count (annotate first):
Python0123456Question.objects.annotate(vote_count=Sum("choices__votes")).order_by("-vote_count")[:5] -
Fun category, newest:
Python0123456Question.objects.filter(category="fun").order_by("-pub_date") -
Random 3:
Python0123456Question.objects.order_by("?")[:3]
Tell me what feels next:
- Which ordering pattern is still confusing? (related fields? annotation? random?)
- Want examples of ordering by multiple annotated fields?
- Ready to learn .distinct() + ordering?
- Or finally ready for Django Forms + Voting + POST + F()?
You’re now writing very clean, very efficient QuerySets — this .order_by() mastery makes your list views look professional and fast.
Keep playing in shell — you’re doing fantastic! 🚀🇮🇳
