diff --git a/app/main.py b/app/main.py index f1ac15d..7488cfe 100644 --- a/app/main.py +++ b/app/main.py @@ -3,7 +3,7 @@ from flask import Blueprint, render_template, flash, redirect, url_for, abort, send_from_directory, request, jsonify, current_app from sqlalchemy import desc, func, text, extract, and_ -from sqlalchemy.orm import joinedload, aliased +from sqlalchemy.orm import joinedload, aliased, contains_eager from collections import OrderedDict from datetime import datetime @@ -637,28 +637,41 @@ def search_tags(): """ Search existing tags for autocomplete. Query params: - - q: search query (matches tag name, case-insensitive) + - q: search query (matches against display_name, case-insensitive) - limit: max results (default 10) - - exclude_kind: comma-separated list of tag kinds to exclude (e.g., 'archive') - - kind: filter to only include tags of this kind (e.g., 'series') + - exclude_kind: comma-separated list of tag kinds to exclude + - kind: filter to only include tags of this kind """ - query = request.args.get('q', '').strip().lower() - limit = request.args.get('limit', 10, type=int) - exclude_kind = request.args.get('exclude_kind', '').strip() - exclude_kinds = [k.strip() for k in exclude_kind.split(',') if k.strip()] - include_kind = request.args.get('kind', '').strip() + from sqlalchemy import case + + query = request.args.get("q", "").strip().lower() + limit = request.args.get("limit", 10, type=int) + exclude_kind = request.args.get("exclude_kind", "").strip() + exclude_kinds = [k.strip() for k in exclude_kind.split(",") if k.strip()] + include_kind = request.args.get("kind", "").strip() + + f = aliased(Tag) + display_expr = case( + ( + and_(Tag.kind == "character", Tag.fandom_id.isnot(None)), + func.concat(Tag.name, " (", f.name, ")"), + ), + else_=Tag.name, + ) + + base_query = ( + db.session.query(Tag) + .outerjoin(f, f.id == Tag.fandom_id) + .options(contains_eager(Tag.fandom, alias=f)) + ) + if exclude_kinds: + base_query = base_query.filter(~Tag.kind.in_(exclude_kinds)) + if include_kind: + base_query = base_query.filter(Tag.kind == include_kind) if not query: - # Return most-used tags when no query - base_query = ( - Tag.query - .join(image_tags) - .group_by(Tag.id) - ) - if exclude_kinds: - base_query = base_query.filter(~Tag.kind.in_(exclude_kinds)) - if include_kind: - base_query = base_query.filter(Tag.kind == include_kind) + # No query: most-used tags. + base_query = base_query.join(image_tags, image_tags.c.tag_id == Tag.id).group_by(Tag.id, f.id) tags = ( base_query .order_by(func.count(image_tags.c.image_id).desc()) @@ -666,14 +679,10 @@ def search_tags(): .all() ) else: - base_query = Tag.query.filter(Tag.name.ilike(f'%{query}%')) - if exclude_kinds: - base_query = base_query.filter(~Tag.kind.in_(exclude_kinds)) - if include_kind: - base_query = base_query.filter(Tag.kind == include_kind) + base_query = base_query.filter(display_expr.ilike(f"%{query}%")) tags = ( base_query - .order_by(Tag.name) + .order_by(display_expr) .limit(limit) .all() ) @@ -682,9 +691,10 @@ def search_tags(): { "id": t.id, "name": t.name, + "display_name": t.display_name, "kind": t.kind, "fandom_id": t.fandom_id if t.kind == "character" else None, - "fandom_name": (t.fandom.name.split(":", 1)[1] if t.fandom else None) if t.kind == "character" else None + "fandom_name": (t.fandom.name if t.fandom else None) if t.kind == "character" else None, } for t in tags ])