QuerySets: filtering, ordering, and fetching data
Every model gets a .objects manager for free — it's the entry point to every query this part covers. With real posts in the database from part 7's admin work, this is where they finally get read back out.
The shell, for experimenting
python manage.py shell>>> from blog.models import Post
>>> Post.objects.all()
<QuerySet [<Post: My First Post>, <Post: Another Post>]>manage.py shell is a regular Python REPL with the project's settings already loaded — the fastest way to try a query without writing a view and refreshing a browser to see the result.
Filtering
Post.objects.filter(published=True)
Post.objects.filter(author=some_user)
Post.objects.filter(title__icontains="django")
Post.objects.exclude(published=False)filter() returns a QuerySet matching every keyword condition, ANDed together. The double-underscore in title__icontains is a lookup — icontains is a case-insensitive substring match; other common ones are __gte/__lte (greater/less than or equal), __startswith, and __year for date fields. exclude() is filter() inverted — every row that doesn't match.
Getting exactly one object
from django.shortcuts import get_object_or_404
post = Post.objects.get(slug="my-first-post")
post = get_object_or_404(Post, slug="my-first-post").get() raises Post.DoesNotExist if nothing matches, and Post.MultipleObjectsReturned if more than one row does — it's for when exactly one result is guaranteed, like a unique slug. get_object_or_404, imported from django.shortcuts, is what real views use instead: same lookup, but a missing post becomes a proper 404 page rather than an unhandled exception.
Ordering and chaining
Post.objects.filter(published=True).order_by("-created_at")
Post.objects.filter(published=True).order_by("-created_at")[:5]QuerySet methods chain because each one returns another QuerySet rather than a final list — order_by() after filter() narrows and sorts in one expression. Since Meta.ordering from part 5 already sorts by -created_at, this specific order_by() is redundant, but it's worth knowing how to override the default when a view needs a different order than the model's default.
None of the code above touches the database until the QuerySet is actually evaluated — iterated over, sliced, or passed to list(). Post.objects.filter(published=True) on its own does nothing yet; that's what lets Django combine several chained .filter() calls into a single efficient SQL query instead of one query per line.
Next: putting these queries into an actual view, with class-based views replacing the function-based ones written so far.