Backend: Produktliste gebuendelt laden (N+1 weg, deutlich schneller)

list_products rief product_to_out je Artikel auf - pro Artikel mehrere Abfragen
(Bestand, abgelaufene Chargen, Codes, Bild, Lazy-Relationen). Bei vielen
Artikeln = hunderte Queries und mehrere Sekunden. Neu: products_to_out_bulk holt
Bestand/Abgelaufen/Codes/Bild je einmal fuer alle und laedt Relationen eager
(joinedload/selectinload). Ergebnis identisch (Test), aber nur noch wenige Queries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-29 12:23:12 +02:00
parent 4034b97cf3
commit 4728a839ea
3 changed files with 119 additions and 6 deletions

View File

@@ -1,8 +1,8 @@
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status
from fastapi.responses import Response
from sqlalchemy.orm import Session
from sqlalchemy.orm import Session, joinedload, selectinload
from ..crud import product_to_out, product_tracking
from ..crud import product_to_out, product_tracking, products_to_out_bulk
from ..database import get_db
from ..deps import get_current_user, require_admin
from ..models import (
@@ -56,7 +56,15 @@ def list_products(
Kategorie zählen die Unterkategorien mit: Wer auf "Süßwaren" filtert, will
auch "Schokolade" sehen.
"""
query = db.query(Product)
query = db.query(Product).options(
# Relationen vorab laden, damit products_to_out_bulk kein N+1 auslöst.
joinedload(Product.category),
joinedload(Product.shop),
joinedload(Product.display_unit),
joinedload(Product.min_stock_unit),
selectinload(Product.field_values),
selectinload(Product.location_min_stocks).joinedload(ProductLocationMinStock.location),
)
if q:
like = f"%{q}%"
query = query.filter(Product.name.ilike(like))
@@ -65,7 +73,7 @@ def list_products(
elif category_id is not None:
query = query.filter(Product.category_id.in_(descendant_ids(db, category_id)))
products = query.order_by(Product.name).all()
return [product_to_out(db, p) for p in products]
return products_to_out_bulk(db, products)
@router.get("/lookup", response_model=LookupResult)