refactor(main): search_tags JOINs fandom; matches on display CONCAT
Post-refactor tag.name is bare. Autocomplete needs to match the
display form (name + ' (' + fandom.name + ')') so users can still
find 'Ruby Rose (RWBY)' by typing 'rwby'. Switched to
contains_eager(Tag.fandom, alias=f) to eliminate the duplicate
join that caused a Postgres GROUP BY error in the no-query path.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+36
-26
@@ -3,7 +3,7 @@
|
|||||||
from flask import Blueprint, render_template, flash, redirect, url_for, abort, send_from_directory, request, jsonify, current_app
|
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 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 collections import OrderedDict
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
@@ -637,28 +637,41 @@ def search_tags():
|
|||||||
"""
|
"""
|
||||||
Search existing tags for autocomplete.
|
Search existing tags for autocomplete.
|
||||||
Query params:
|
Query params:
|
||||||
- q: search query (matches tag name, case-insensitive)
|
- q: search query (matches against display_name, case-insensitive)
|
||||||
- limit: max results (default 10)
|
- limit: max results (default 10)
|
||||||
- exclude_kind: comma-separated list of tag kinds to exclude (e.g., 'archive')
|
- exclude_kind: comma-separated list of tag kinds to exclude
|
||||||
- kind: filter to only include tags of this kind (e.g., 'series')
|
- kind: filter to only include tags of this kind
|
||||||
"""
|
"""
|
||||||
query = request.args.get('q', '').strip().lower()
|
from sqlalchemy import case
|
||||||
limit = request.args.get('limit', 10, type=int)
|
|
||||||
exclude_kind = request.args.get('exclude_kind', '').strip()
|
query = request.args.get("q", "").strip().lower()
|
||||||
exclude_kinds = [k.strip() for k in exclude_kind.split(',') if k.strip()]
|
limit = request.args.get("limit", 10, type=int)
|
||||||
include_kind = request.args.get('kind', '').strip()
|
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:
|
if not query:
|
||||||
# Return most-used tags when no query
|
# No query: most-used tags.
|
||||||
base_query = (
|
base_query = base_query.join(image_tags, image_tags.c.tag_id == Tag.id).group_by(Tag.id, f.id)
|
||||||
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)
|
|
||||||
tags = (
|
tags = (
|
||||||
base_query
|
base_query
|
||||||
.order_by(func.count(image_tags.c.image_id).desc())
|
.order_by(func.count(image_tags.c.image_id).desc())
|
||||||
@@ -666,14 +679,10 @@ def search_tags():
|
|||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
base_query = Tag.query.filter(Tag.name.ilike(f'%{query}%'))
|
base_query = base_query.filter(display_expr.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)
|
|
||||||
tags = (
|
tags = (
|
||||||
base_query
|
base_query
|
||||||
.order_by(Tag.name)
|
.order_by(display_expr)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
@@ -682,9 +691,10 @@ def search_tags():
|
|||||||
{
|
{
|
||||||
"id": t.id,
|
"id": t.id,
|
||||||
"name": t.name,
|
"name": t.name,
|
||||||
|
"display_name": t.display_name,
|
||||||
"kind": t.kind,
|
"kind": t.kind,
|
||||||
"fandom_id": t.fandom_id if t.kind == "character" else None,
|
"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
|
for t in tags
|
||||||
])
|
])
|
||||||
|
|||||||
Reference in New Issue
Block a user