From 4bbafdd460945eb506ddb07b9068731245708812 Mon Sep 17 00:00:00 2001 From: Tyler Hoang Date: Wed, 6 May 2026 17:12:46 -0700 Subject: Add search, filter, and sort functionality to film shelves - Add _SORT_COLUMNS dict to routers/films.py with 8 sort options - Extend _get_shelf_query to accept q (search) and sort parameters - Update /films/partial endpoint to accept q/sort query params and pass search_active to template to suppress month grouping when searching - Add filter bar (search input + sort select) to templates/index.html - Add data-shelf attribute to #film-feed for JS to read current shelf - Rewrite infinite scroll JS to support debounced search (300ms), feed reset on filter/sort change, and pass params on all fetches Filters text search by title OR director (case-insensitive ilike). Sort options: date watched (newest/oldest), title (A-Z/Z-A), year, stars. Month grouping disabled when search is active. Co-Authored-By: Claude Haiku 4.5 --- routers/films.py | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) (limited to 'routers/films.py') diff --git a/routers/films.py b/routers/films.py index afcf0ae..ec56c0a 100644 --- a/routers/films.py +++ b/routers/films.py @@ -176,9 +176,29 @@ def _notice_context( } -def _get_shelf_query(db: Session, shelf: str): +_SORT_COLUMNS = { + "date_watched_desc": [Film.date_watched.desc().nullslast(), Film.id.desc()], + "date_watched_asc": [Film.date_watched.asc().nullslast(), Film.id.asc()], + "title_asc": [Film.title.asc(), Film.id.asc()], + "title_desc": [Film.title.desc(), Film.id.desc()], + "year_desc": [Film.year.desc().nullslast(), Film.id.desc()], + "year_asc": [Film.year.asc().nullslast(), Film.id.asc()], + "stars_desc": [Film.stars.desc(), Film.id.desc()], + "stars_asc": [Film.stars.asc(), Film.id.asc()], +} + + +def _get_shelf_query(db: Session, shelf: str, q: str | None = None, sort: str | None = None): """Get a query for films on a shelf, ordered appropriately.""" query = db.query(Film).filter(Film.shelf == shelf) + + if q: + term = f"%{q}%" + query = query.filter(Film.title.ilike(term) | Film.director.ilike(term)) + + if sort and sort in _SORT_COLUMNS: + return query.order_by(*_SORT_COLUMNS[sort]) + if shelf == "diary": return query.order_by(Film.date_watched.desc(), Film.created_at.desc(), Film.id.desc()) elif shelf == "queue": @@ -379,18 +399,21 @@ def get_films_partial( shelf: str = Query("diary"), offset: int = Query(0), limit: int = Query(20), + q: str | None = Query(None), + sort: str | None = Query(None), db: Session = Depends(get_db), ): """Returns partial HTML for pagination. Used by infinite scroll.""" if shelf not in ALLOWED_SHELVES: raise HTTPException(status_code=400, detail="Invalid shelf.") - query = _get_shelf_query(db, shelf) + search_active = bool(q and q.strip()) + query = _get_shelf_query(db, shelf, q=q or None, sort=sort or None) total = query.count() films = query.offset(offset).limit(limit).all() has_more = (offset + limit) < total - if shelf == "diary": + if shelf == "diary" and not search_active: grouped_films = _group_films_by_month(films) else: grouped_films = None @@ -401,6 +424,7 @@ def get_films_partial( films=films, grouped_films=grouped_films, active_shelf=shelf, + search_active=search_active, ) response = HTMLResponse(html) -- cgit v1.3-2-g0d8e