Nobody has started this yet — be first.
Business impact
The entire point of a personal watchlist is "did I already add this?" and "where's the one I'm thinking of?" -- but once a user has more than one page of movies, there is no way to answer either question except by paging through everything by hand. This is the single most obviously missing piece of core watchlist functionality in the API as it stands today, and it's the most common thing a user of an app like this would reach for.
Problem
GET /api/movies can currently be narrowed only by watched (true/false) and paged with page/per_page. There is no way to search by title. MovieListQuerySchema does already declare a title field -- it was added as scaffolding for this ticket -- but list_movies() never reads it, so passing ?title=... is silently accepted and silently ignored.
Current behavior
Passing ?title=... to GET /api/movies is silently accepted (MovieListQuerySchema already declares the field) but silently ignored -- list_movies() never reads query_args["title"], so the response is the full unfiltered list regardless.
Expected behavior
GET /api/movies?title=<text> returns only the current user's movies whose title contains <text>, case-insensitively, as a substring match (not an exact match). Omitting title entirely must behave exactly as it does today (no filtering).
Steps to reproduce
curl -s "localhost:5000/api/movies?title=Reloaded" -H "Authorization: Bearer $TOKEN"
Why this matters
list_movies() already has the pattern to copy: it conditionally calls .filter_by(...) on base_query only when the watched query param was actually supplied (if query_args["watched"] is not None:). MovieListQuerySchema already declares and validates a title field as scaffolding for this ticket, so query_args["title"] is already available and None when the caller didn't pass it -- the view function just never reads it.
Suggested approach
Look at how the existing watched filter is applied in list_movies(): it conditionally calls .filter_by(...) on base_query only when the query param was actually supplied (if query_args["watched"] is not None:). The title field on MovieListQuerySchema already gives you query_args["title"] the same way -- it's None when the caller didn't pass it. You'll want a substring, case-insensitive match against MovieEntry.title, not filter_by's exact-match semantics (SQLAlchemy has a column method built for exactly this).
Acceptance criteria
Verification
cd flask/movie_watchlist && .venv/bin/python -m pytest practicetickets/test_ticket05_title_filter.py -v
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/title-filterFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin feat/title-filterSubmit 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 inRun the existing test suite (uses an in-memory SQLite DB, no setup needed):
.venv/bin/python -m pytest -v # 33 passed, 0 failed on a clean checkout
cp .env.example .env
# edit .env: set SECRET_KEY, JWT_SECRET_KEY, POSTGRES_PASSWORD to real values
docker compose up --build
This builds the web image, starts Postgres (db), waits for its healthcheck, then runs flask db upgrade and starts gunicorn -- all with one command, no manual migration step. The API is then available at http://localhost:5000.
Work the tickets in practicetickets/ (TICKET_01 through TICKET_07); each names one pytest test file in the same directory. This directory is outside pytest.ini's testpaths = tests, so a bare pytest run from the project root never picks these up -- they only run when pointed at directly:
cd flask/movie_watchlist
.venv/bin/python -m pytest practicetickets/test_ticket01_movie_list_ordering.py -v # a single ticket
./practicetickets/run_tickets.sh # all 7, clean pass/fail summary
run_tickets.sh also unsets TEST_DATABASE_URL/DATABASE_URL for its own run, so a leftover Postgres URL from a different project in your shell doesn't get picked up instead of the in-memory SQLite DB these tests are written against.
Level 2
Implement a feature
Extend the system within its own patterns.