Challenge 2: Write Five QuerySet Queries — Possible Solution ==================================================================== from catalog.models import Book # 1. All in-stock books, ordered by price ascending in_stock_by_price = Book.objects.filter(in_stock=True).order_by("price") # 2. Books under $15 cheap_books = Book.objects.filter(price__lt=15) # 3. The single book with pk=3 book_three = Book.objects.get(pk=3) # 4. All books excluding out-of-stock ones available_books = Book.objects.exclude(in_stock=False) # 5. The count of all books total_books = Book.objects.count() WHY THIS WORKS -------------- - Book.objects.filter(in_stock=True).order_by("price") chains two QuerySet methods — because QuerySets are lazy, this doesn't run two separate queries; Django combines both conditions into ONE SQL query (WHERE in_stock = TRUE ORDER BY price ASC) at the moment it's finally evaluated (e.g. iterated over or printed). - price__lt=15 uses Django's field lookup syntax — the double underscore before `lt` ("less than") is how QuerySet filters express comparison operators beyond simple equality (other common ones: __gt, __gte, __lte, __contains, __icontains). - .get(pk=3) is appropriate here specifically because a primary key lookup is guaranteed to match at most one row — this is exactly the use case the chapter's gotcha says .get() is safe for, versus filtering on a field that could plausibly match zero or several rows. - .exclude(in_stock=False) produces the logical opposite of .filter(in_stock=False) — functionally equivalent to .filter(in_stock=True) in this specific case, but demonstrates .exclude() as its own distinct QuerySet method for "give me everything that does NOT match." - .count() is itself a QuerySet-evaluating call — it runs a SQL `SELECT COUNT(*)` directly rather than fetching every row into Python and calling len() on the result, which matters for performance on a large table.