diff options
| author | Tyler Hoang <tyler@tylerhoang.xyz> | 2026-05-06 17:12:46 -0700 |
|---|---|---|
| committer | Tyler Hoang <tyler@tylerhoang.xyz> | 2026-05-06 17:12:46 -0700 |
| commit | 4bbafdd460945eb506ddb07b9068731245708812 (patch) | |
| tree | 3a3ac9151fb9db3d57af10d781658c13b1a2cfdd /routers | |
| parent | 21f0cb70ca8f6c1731bf0e514e2668f42ea00718 (diff) | |
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 <noreply@anthropic.com>
Diffstat (limited to 'routers')
| -rw-r--r-- | routers/films.py | 30 |
1 files changed, 27 insertions, 3 deletions
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) |
