Nobody has started this yet — be first.
Business impact
This app enforces "a book marked read must have a finish date" in three independent places on purpose: the DRF serializer (what the API checks), Book.clean() (what ModelForm-based paths like the Django admin check), and a database-level CheckConstraint (what protects the data even if some future code path -- a bulk import script, a Celery task, a data migration, a different API version -- writes to the table without going through either of the first two). That third layer exists specifically because "some future code forgets to call the serializer" is not a hypothetical, it's the default outcome of a codebase growing past its first admin panel and its first API client. Right now that DB-level guarantee is silently gone: nothing enforces it below the serializer, and because the serializer still catches the normal path, nobody testing the app through the API would ever notice. The first sign of trouble would be a "read" book quietly missing its finish date, showing up however far downstream someone eventually looks for it.
Problem
Book.objects.create(status="read", date_finished=None) -- i.e. writing directly through the ORM, bypassing BookSerializer and Book.full_clean() entirely -- succeeds and commits a row that violates the app's own core invariant.
Current behavior
A read book with no finish date can be written directly through the ORM with no error at all, even though the README documents a database-level constraint that should reject it.
Expected behavior
That same call must raise django.db.utils.IntegrityError: the database's CheckConstraint should reject the row regardless of which code path tried to write it, exactly as documented in this project's README ("A database-level CheckConstraint enforces that a book with status="read" must have date_finished set").
Steps to reproduce
cd django/library_tracker source ../library_tracker_venv/bin/activate python manage.py shell -c " from books.models import Book b = Book.objects.create(title='Bypassed', author='Someone', status='read', date_finished=None) print('created id', b.id, '-- status=read with no date_finished, and no error was raised') "
Why this matters
This is a two-artifact bug, not just a one-line typo you can spot by reading models.py alone: Django CheckConstraints are compiled into the actual database schema by a migration, not evaluated live from models.py at request time. So even once you find and fix the string in models.py, the real SQLite table won't enforce the corrected condition until a new migration (makemigrations + migrate) actually rebuilds the constraint -- the model file and the database schema are two separately-maintained representations of "the same" rule, and this project's actual bug is that they were allowed to say different things without anything erroring at makemigrations --check time. The wrongness itself: the constraint's condition checks ~Q(status="finished"), but Book.STATUS_READ is "read" -- "finished" is not, and has never been, one of Book.STATUS_CHOICES (to_read / reading / read). Since no row can ever actually have status="finished", ~Q(status="finished") evaluates True for every row that will ever exist, which makes the whole OR always True -- the constraint accepts everything, unconditionally.
Suggested approach
Compare the string literal inside Book.Meta.constraints's CheckConstraint (books/models.py) against Book.STATUS_READ and STATUS_CHOICES a few lines above it in the same file. Once you fix the model, you still need to regenerate the migration (python manage.py makemigrations books) so the real database schema picks up the corrected condition -- a model-only fix will pass a read of the file but not this ticket's test.
Acceptance criteria
Verification
python manage.py test practicetickets.test_ticket06_check_constraint_typo -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/check-constraint-typoFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/check-constraint-typoSubmit 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.