Nobody has started this yet — be first.
Business impact
A voting UI almost always needs to render differently for "you haven't voted" (show the ballot) versus "you already voted for X" (show your choice, maybe disabled) -- right now the frontend has no way to know which state to render on page load without a wasted, side-effecting POST attempt that pollutes the flow with a 409 error response just to learn a piece of information that should be a normal, safe read.
Problem
/api/polls/{id}/vote/ currently only accepts POST (cast a vote). There is no way for a client to ask "did I already vote on this poll, and for which option?" -- the only way to find out today is to try to vote again and read the 409, which doesn't tell you what your existing choice was, and pollutes the flow with an error response for what should be a normal read.
Current behavior
GET /api/polls/{id}/vote/ returns 405 Method Not Allowed regardless of whether the requesting user has already voted, is unauthenticated, or hasn't voted at all.
Expected behavior
GET /api/polls/{id}/vote/, authenticated: if the requesting user has voted on this poll, 200 with that vote (same shape as VoteSerializer -- id, poll, option, voted_at). If they have not voted, 404 (there's genuinely nothing to return -- this is not an error state, just "no vote yet"). Anonymous request: 401, same as POST to the same URL today (this reveals a specific user's choice, so it needs the same authentication requirement as casting a vote).
Steps to reproduce
cd django/poll_app source .venv/bin/activate python manage.py runserver &
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8000/api/polls/1/vote/
-H "Authorization: Token $TOKEN"
Why this matters
PollViewSet.vote is currently declared with @action(detail=True, methods=["post"]). DRF supports binding more than one HTTP method to the same URL by declaring additional @action methods that share the same url_path (and url_name, so reverse("poll-vote") keeps resolving) -- this is a missing read endpoint, not a bug in an existing one; no stub is provided, since a test hitting GET against the current POST-only route already demonstrates the gap on its own.
Suggested approach
PollViewSet.vote is currently declared with @action(detail=True, methods=["post"]). DRF supports binding more than one HTTP method to the same URL by declaring additional @action methods that share the same url_path (and url_name, so reverse("poll-vote") keeps resolving) -- look at how the router combines those into one route. From there, think about which model you're querying for "the current user's vote on this poll" -- it already exists (Vote has a (user, poll) pair with a uniqueness guarantee), you just need to look it up and handle the "doesn't exist yet" case cleanly.
Acceptance criteria
Verification
.venv/bin/python manage.py test practicetickets.test_ticket05_my_vote_lookup -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/my-vote-lookupFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin feat/my-vote-lookupSubmit 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/http://127.0.0.1:8000/admin/.env.example.envSECRET_KEYDEBUGDB_*CELERY_BROKER_URLmanage.py testOr via Docker (Postgres, Redis, Django with hot reload, and a Celery worker, all wired together, migrations run automatically on startup):
docker compose up --build
Work the tickets in practicetickets/ (ticket01 through ticket07); each names one dedicated Django test module:
./practicetickets/run_tickets.sh # all 7, clean pass/fail summary
./practicetickets/run_tickets.sh -v # summary + each test's full output
.venv/bin/python manage.py test practicetickets.test_ticket01_permission_check_inverted -v 2 # a single ticket
Three of the seven tickets (01, 02, 03) also collaterally break pre-existing tests in polls/tests/; run .venv/bin/python manage.py test polls -v 2 to confirm the main suite is back to fully green once those are fixed. No Redis or Celery worker is required for any of this -- settings.py forces CELERY_TASK_ALWAYS_EAGER = True whenever "test" appears in sys.argv.
Level 2
Implement a feature
Extend the system within its own patterns.