Nobody has started this yet — be first.
Business impact
"I started reading this" is one of the two or three things this app's users will do most often -- and right now it takes a full manual PATCH with a correctly-formatted date the client has to compute itself, instead of one click. Every client (web, mobile, a future CLI) has to reimplement "what does starting a book actually mean" (which status, is today's date right, what if it's already started) independently, and any of them can get it subtly wrong. A single server-side action is the only way to guarantee "starting a book" means the same thing everywhere.
Problem
The only way to move a book from to_read to reading today is a raw PATCH /api/books/{id}/ with a hand-built {"status": "reading", "date_started": "..."} body -- the client has to know today's date and build the whole payload itself just to flip one switch. A stub action already exists at POST /api/books/{id}/start_reading/ (books/views.py), but it's a placeholder: calling it unconditionally raises NotImplementedError.
Current behavior
POST /api/books/{id}/start_reading/ returns a 500 with a NotImplementedError traceback, no matter what book id is given.
Expected behavior
POST /api/books/{id}/start_reading/ with an empty body sets the book's status to "reading", sets date_started to today only if it isn't already set, returns 200 with the updated book, and returns 404 for a nonexistent book id.
Steps to reproduce
curl -s -X POST http://127.0.0.1:8000/api/books/1/start_reading/
Why this matters
This is a custom DRF @action (detail=True), which means it's responsible for its own object lookup, its own mutation, and its own serialization -- none of that comes for free the way it does for the built-in retrieve/update actions DRF generates automatically.
Suggested approach
Look at BookViewSet.start_reading in books/views.py, and at self.get_object() (used implicitly by every other action on this viewset) for the standard "look up by pk or 404" pattern. django.utils.timezone.localdate() gives you "today" as a date in Django's configured timezone.
Acceptance criteria
Verification
python manage.py test practicetickets.test_ticket05_start_reading_action -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/start-reading-actionFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin feat/start-reading-actionSubmit 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.