Nobody has started this yet — be first.
Business impact
Aggregate numbers are the entire reason someone keeps a tracker running for months instead of abandoning it after a week -- "how many books this year," "what's my average rating" are the kind of small satisfying answers that make the habit stick. Forcing every client to fetch every row and compute this itself means: it gets recomputed slightly differently by every client that bothers to build it, it doesn't scale as someone's library grows, and most clients will just... not bother, so the feature effectively doesn't exist even though "the data is all there."
Problem
There's no way to answer "how many books have I read, and what's my average rating?" without fetching every single book and computing it client-side. A stub action already exists at GET /api/books/stats/ (books/views.py), but calling it unconditionally raises NotImplementedError.
Current behavior
GET /api/books/stats/ returns a 500 with a NotImplementedError traceback instead of the status counts and average rating.
Expected behavior
GET /api/books/stats/ returns a JSON object with to_read/reading/read counts and an average_rating -- the mean rating across read books that have a rating set (unrated or non-read books must not pull the average down or count as 0), and null (not 0, not a 500) when there are zero rated read books.
Steps to reproduce
curl -s http://127.0.0.1:8000/api/books/stats/
Why this matters
This is a detail=False DRF @action -- it operates on the whole collection, not one book, so unlike the "start reading" action there's no get_object() involved. The interesting part is the aggregation: Django's Avg() aggregate over an empty/all-null queryset returns None, not 0 -- the implementation needs to pass that None straight through rather than coercing it, and needs to make sure the Avg() is computed only over read books with a non-null rating, not over every book.
Suggested approach
Look at BookViewSet.stats in books/views.py, and at django.db.models.Count/Avg used with Book.objects -- .filter() + .aggregate(), or .values("status").annotate(...), are both reasonable starting points depending on how you want to shape the counts.
Acceptance criteria
Verification
python manage.py test practicetickets.test_ticket07_stats_endpoint -v 2
Hints (0/2)
Try it without hints first — the reading is the exercise.
Working on this ticket
Work on a branch named for the ticket — that's what you'll submit.
Branch off your fork
$git checkout -b feat/stats-endpointFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin feat/stats-endpointSubmit it below
Paste your fork URL and the branch name, with a short write-up of the root cause.
Questions
Ask about anything unclear in the ticket — the maintainer and anyone who has solved it can answer. Please don't post full solutions.
Sign in to ask a question or reply.
Sign inhttp://127.0.0.1:8000/api/books/http://127.0.0.1:8000/admin/SECRET_KEYDEBUGALLOWED_HOSTSOr with Docker (migrations run automatically on container startup):
docker compose up --build
docker compose exec web python manage.py createsuperuser # optional, to use /admin/
Work the tickets in practicetickets/ (TICKET_01 through TICKET_07); each names one Django test:
python manage.py test practicetickets.test_ticket01_rating_boundary -v 2 # a single ticket
./practicetickets/run_tickets.sh # all 7, clean pass/fail summary
Tickets are independent and deliberately isolated from each other's bugs -- fix them in any order. python manage.py test books runs the project's own 4-test suite (separate from practicetickets/, which is never added to INSTALLED_APPS) and should report OK both before and after every ticket is fixed.
Level 2
Implement a feature
Extend the system within its own patterns.