Business impact
The library tracker's whole value proposition is "trust the numbers you put in." Every place a rating is shown -- the admin list view, the browsable API, any future stats/export feature -- assumes 1-5 is the full range, because that's the only range documented anywhere (README, admin panel, the API itself). Right now the API will happily accept and store a rating of 6. That's not just a cosmetic glitch: any future feature built on "ratings are 1-5" (a 5-star widget, a CSV export, an average-rating calculation) will either crash or silently produce wrong numbers the first time it touches one of these out-of-range rows, and by then the bad data is already in the database and mixed in with everything else.
Problem
POST /api/books/ accepts "rating": 6 for a book marked "read" and returns 201 Created, persisting the row.
Current behavior
A rating of 6 is written to the database and returned in the response body as if it were valid, with no error at all.
Expected behavior
A rating outside 1-5 inclusive must be rejected with 400 Bad Request and no row written, exactly the same way a rating of 0 or -1 already is.
Steps to reproduce
cd django/library_tracker source ../library_tracker_venv/bin/activate python manage.py shell -c " from rest_framework.test import APIClient c = APIClient() r = c.post('/api/books/', { 'title': 'Test Book', 'author': 'Test Author', 'status': 'read', 'rating': 6, 'date_started': '2026-01-01', 'date_finished': '2026-01-05', }) print(r.status_code, r.data) "
Why this matters
Django REST Framework's ModelSerializer doesn't hardcode field-level validation rules; it builds them by introspecting the model field itself, including whatever's in that field's validators=[...] list. So the single source of truth for "what rating values are legal" is exactly one line in books/models.py, and every layer above it (serializer, API, admin) inherits from it. That also means a one-character change to that line is invisible anywhere else in the codebase -- nothing else names the number 5 or 6 -- which is exactly why it's easy to ship and easy to miss in review.
Suggested approach
Look at Book.rating's validators= list in books/models.py. Compare the two bounds against what the rest of the app (README, admin, the existing test_crud_round_trip test which sets rating=5) treats as the valid range.
Acceptance criteria
Verification
python manage.py test practicetickets.test_ticket01_rating_boundary -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/rating-boundaryFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/rating-boundarySubmit 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 1
Fix a bug
Read existing behaviour, correct it.