Fix: eagerload series.sets i admin og API (MissingGreenlet i async mode)

This commit is contained in:
2026-07-01 22:15:14 +02:00
parent c0952a3bd3
commit 8361066f2f
20 changed files with 1075 additions and 554 deletions
+116 -59
View File
@@ -6,7 +6,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from ..database import get_db
from ..models import Set, Card
from ..models import Serie, Set, Card
router = APIRouter()
templates = Jinja2Templates(directory="app/templates")
@@ -14,14 +14,40 @@ templates = Jinja2Templates(directory="app/templates")
@router.get("/", response_class=HTMLResponse)
async def index(request: Request, db: AsyncSession = Depends(get_db)):
result = await db.execute(select(Set).order_by(Set.code))
sets = result.scalars().all()
return templates.TemplateResponse("index.html", {"request": request, "sets": sets})
result = await db.execute(
select(Serie).options(selectinload(Serie.sets).selectinload(Set.cards)).order_by(Serie.name)
)
series = result.scalars().all()
return templates.TemplateResponse("index.html", {"request": request, "series": series})
@router.get("/series", response_class=HTMLResponse)
async def list_series(request: Request, db: AsyncSession = Depends(get_db)):
result = await db.execute(
select(Serie).options(selectinload(Serie.sets).selectinload(Set.cards)).order_by(Serie.name)
)
series = result.scalars().all()
return templates.TemplateResponse("series.html", {"request": request, "series": series})
@router.get("/series/{serie_name}", response_class=HTMLResponse)
async def serie_detail(request: Request, serie_name: str, db: AsyncSession = Depends(get_db)):
result = await db.execute(
select(Serie).where(Serie.name == serie_name).options(
selectinload(Serie.sets).selectinload(Set.cards)
)
)
serie = result.scalar_one_or_none()
if not serie:
return templates.TemplateResponse("404.html", {"request": request}, status_code=404)
return templates.TemplateResponse("serie_detail.html", {"request": request, "serie": serie})
@router.get("/sets", response_class=HTMLResponse)
async def list_sets(request: Request, db: AsyncSession = Depends(get_db)):
result = await db.execute(select(Set).order_by(Set.code))
result = await db.execute(
select(Set).options(selectinload(Set.serie), selectinload(Set.cards)).order_by(Set.code)
)
sets = result.scalars().all()
return templates.TemplateResponse("sets.html", {"request": request, "sets": sets})
@@ -29,7 +55,9 @@ async def list_sets(request: Request, db: AsyncSession = Depends(get_db)):
@router.get("/sets/{set_code}", response_class=HTMLResponse)
async def set_detail(request: Request, set_code: str, db: AsyncSession = Depends(get_db)):
result = await db.execute(
select(Set).where(Set.code == set_code.upper()).options(selectinload(Set.cards))
select(Set).where(Set.code == set_code.upper()).options(
selectinload(Set.cards), selectinload(Set.serie)
)
)
s = result.scalar_one_or_none()
if not s:
@@ -37,65 +65,61 @@ async def set_detail(request: Request, set_code: str, db: AsyncSession = Depends
return templates.TemplateResponse("set_detail.html", {"request": request, "set": s})
@router.get("/card/{set_code}/{number}", response_class=HTMLResponse)
async def card_detail(request: Request, set_code: str, number: str,
db: AsyncSession = Depends(get_db)):
# Prøv med og uden leading zero (robust overfor "50" vs "050")
candidates = set([number, number.zfill(3), number.lstrip("0")])
for num in candidates:
result = await db.execute(
select(Card)
.join(Card.set)
.where(Set.code == set_code.upper(), Card.number == num)
.options(selectinload(Card.set).selectinload(Set.serie))
)
card = result.scalar_one_or_none()
if card:
return templates.TemplateResponse("card_detail.html", {"request": request, "card": card})
return templates.TemplateResponse("404.html", {"request": request}, status_code=404)
@router.get("/search", response_class=HTMLResponse)
async def search_page(request: Request):
return templates.TemplateResponse("search.html", {"request": request})
@router.get("/api/search")
async def search_cards(
q: str = Query(""),
db: AsyncSession = Depends(get_db),
):
"""Search cards by full code (e.g. CRI-068) or by name."""
q = q.strip()
# ─── JSON API ───────────────────────────────────────────────────────
# Try full-code search: CODE-NUMBER
if "-" in q:
parts = q.split("-", 1)
code_part = parts[0].strip().upper()
num_part = parts[1].strip().lstrip("0") # remove leading zeros
stmt = (
select(Card)
.join(Card.set)
.where(Set.code == code_part, Card.number == num_part)
.options(selectinload(Card.set))
@router.get("/api/series")
async def api_list_series(db: AsyncSession = Depends(get_db)):
result = await db.execute(
select(Serie).options(selectinload(Serie.sets)).order_by(Serie.name)
)
return [{"id": s.id, "name": s.name, "description": s.description or "",
"set_count": len(s.sets)} for s in result.scalars().all()]
@router.get("/api/series/{serie_name}/sets")
async def api_series_sets(serie_name: str, db: AsyncSession = Depends(get_db)):
result = await db.execute(
select(Serie).where(Serie.name == serie_name).options(
selectinload(Serie.sets).selectinload(Set.cards)
)
else:
# Search by name
stmt = (
select(Card)
.join(Card.set)
.where(Card.name.ilike(f"%{q}%"))
.options(selectinload(Card.set))
.limit(50)
)
result = await db.execute(stmt)
cards = result.scalars().all()
return [
{
"id": c.id,
"full_code": c.full_code,
"name": c.name,
"type": c.type or "",
"rarity": c.rarity or "",
"set_name": c.set.name,
}
for c in cards
]
)
serie = result.scalar_one_or_none()
if not serie:
return {"error": "Serie ikke fundet"}
return [{"code": s.code, "name": s.name, "card_count": len(s.cards)} for s in serie.sets]
@router.get("/api/sets")
async def api_list_sets(db: AsyncSession = Depends(get_db)):
result = await db.execute(select(Set).order_by(Set.code))
sets = result.scalars().all()
return [
{"code": s.code, "name": s.name, "release_date": s.release_date or "",
"card_count": len(s.cards)}
for s in sets
]
result = await db.execute(
select(Set).options(selectinload(Set.serie), selectinload(Set.cards)).order_by(Set.code)
)
return [{"code": s.code, "name": s.name, "serie": s.serie.name,
"card_count": len(s.cards)} for s in result.scalars().all()]
@router.get("/api/sets/{set_code}/cards")
@@ -106,8 +130,41 @@ async def api_set_cards(set_code: str, db: AsyncSession = Depends(get_db)):
s = result.scalar_one_or_none()
if not s:
return []
return [
{"id": c.id, "number": c.number, "name": c.name, "type": c.type or "",
"rarity": c.rarity or "", "hp": c.hp, "full_code": c.full_code}
for c in s.cards
]
return [{"id": c.id, "number": c.number, "name": c.name,
"full_code": c.full_code, "image_url": c.image_url} for c in s.cards]
@router.get("/api/search")
async def search_cards(q: str = Query(""), db: AsyncSession = Depends(get_db)):
"""Search cards by full code (CRI-068) or by name."""
q = q.strip()
if not q:
return []
if "\u002D" in q or " " in q or "=" in q:
# Split på bindestreg, mellemrum eller =: "CRI-068" eller "CRI 068"
sep = "\u002D" if "\u002D" in q else (" " if " " in q else "=")
parts = q.split(sep, 1)
code_part = parts[0].strip().upper()
num_part = parts[1].strip()
candidates = set([num_part, num_part.zfill(3), num_part.lstrip("0")])
stmt = select(Card).join(Card.set).where(
Set.code == code_part, Card.number.in_(list(candidates))
).options(selectinload(Card.set).selectinload(Set.serie))
else:
stmt = (
select(Card)
.join(Card.set)
.where(Card.name.ilike(f"%{q}%"))
.options(selectinload(Card.set).selectinload(Set.serie))
.limit(50)
)
result = await db.execute(stmt)
cards = result.scalars().all()
return [{
"id": c.id, "full_code": c.full_code, "name": c.name,
"set_name": c.set.name, "set_code": c.set.code,
"serie_name": c.set.serie.name,
"image_url": c.image_url,
} for c in cards]