Nobody has started this yet — be first.
Business impact
POST /api/auth/login/ is the only way an existing user gets back into the app once their original registration token is gone (expired client storage, a new device, a cleared browser). Right now, every single login attempt with completely correct credentials fails with "Unable to log in with the provided credentials." That is a full authentication outage for the returning-user flow -- new signups still work (registration hands back a token directly), but anyone who already has an account and needs to log back in cannot get past this endpoint. This is the kind of bug that generates a support ticket flood and an on-call page within minutes of shipping, because it doesn't fail for one edge case -- it fails for 100% of real login attempts.
Problem
LoginSerializer.validate() calls Django's authenticate() with the username and password keyword arguments swapped -- authenticate(username=attrs["password"], password=attrs["username"]). authenticate() checks the value passed as username= against the user table's username field and the value passed as password= against the stored password hash, so with the arguments swapped it only ever matches by coincidence (a user whose username literally equals their password).
Current behavior
POST /api/auth/login/ with a real user's exactly correct username and password still returns 400 with "Unable to log in with the provided credentials." -- for every account, not one edge case.
Expected behavior
A POST /api/auth/login/ with a real user's correct username and password returns 200 with a token. A request with an incorrect username or password still correctly returns 400.
Steps to reproduce
python manage.py shell -c "
from django.contrib.auth import get_user_model
get_user_model().objects.create_user(username='dana', password='a-strong-password-123')
"
python manage.py runserver &
curl -s -X POST http://127.0.0.1:8000/api/auth/login/
-H 'Content-Type: application/json'
-d '{"username":"dana","password":"a-strong-password-123"}'
Why this matters
This isn't a validation-logic bug (the "if user is None: raise ..." check right below it is correct) -- it's a call-site argument-order mistake. It's easy to miss in review because the line still reads like a normal authenticate(username=..., password=...) call; you have to actually check which attrs[...] key lands in which keyword slot.
Suggested approach
Look at LoginSerializer.validate() in accounts/serializers.py line by line against what django.contrib.auth.authenticate() expects for its username= and password= keyword arguments, and compare to how RegisterSerializer (same file) and accounts/tests.py construct their payloads.
Acceptance criteria
Verification
python manage.py test practicetickets.ticket01_login_credential_swap -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 fix/login-credential-swapFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/login-credential-swapSubmit 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 in.envhttp://127.0.0.1:8000/api/http://127.0.0.1:8000/admin/To run against Postgres via Docker instead:
docker compose up --build # postgres + web, migrations run automatically on startup
Work the tickets in practicetickets/ (TICKET_01 through TICKET_07); each names one dedicated test, addressed by its explicit dotted label since these modules are deliberately named so Django's default test*.py discovery never picks them up:
python manage.py test practicetickets.ticket01_login_credential_swap -v 2 # a single ticket
./practicetickets/run_tickets.sh # all 7, clean pass/fail summary
Two of the seven bugs (tickets 01 and 06) are real enough to also break the project's own pre-existing suite (python manage.py test) -- fixing them correctly makes those pass again too, with zero changes needed inside accounts/tests.py or applications/tests.py.
Level 1
Fix a bug
Read existing behaviour, correct it.