fix: Backend SQL Queries für renamed tables (Migration 010+011)

- Updated all SQL queries in catalogs.py to use renamed tables:
  - training_styles → style_directions
  - training_style_target_groups → style_direction_target_groups
  - exercise_styles → exercise_style_directions
- Updated API parameter names: training_style_id → style_direction_id
- Updated response field names: training_style_name → style_direction_name
- Updated array field names: training_styles → style_directions
- Fixes 'failed to fetch' errors on Stilrichtungen, Hierarchie, Zuordnungen tabs

version: 0.4.0 (backend)
module: catalogs 1.4.0

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Lars 2026-04-23 12:24:50 +02:00
parent a9a4c78a0e
commit 155c21f018
2 changed files with 88 additions and 86 deletions

View File

@ -142,8 +142,8 @@ def list_training_styles(
query = """
SELECT ts.*, ps.name as parent_style_name
FROM training_styles ts
LEFT JOIN training_styles ps ON ts.parent_style_id = ps.id
FROM style_directions ts
LEFT JOIN style_directions ps ON ts.parent_style_id = ps.id
"""
params = []
@ -173,7 +173,7 @@ def create_training_style(data: dict, session=Depends(require_auth)):
cur = get_cursor(conn)
cur.execute("""
INSERT INTO training_styles (name, abbreviation, description, parent_style_id, sort_order, status)
INSERT INTO style_directions (name, abbreviation, description, parent_style_id, sort_order, status)
VALUES (%s, %s, %s, %s, %s, %s)
RETURNING id
""", (
@ -190,8 +190,8 @@ def create_training_style(data: dict, session=Depends(require_auth)):
cur.execute("""
SELECT ts.*, ps.name as parent_style_name
FROM training_styles ts
LEFT JOIN training_styles ps ON ts.parent_style_id = ps.id
FROM style_directions ts
LEFT JOIN style_directions ps ON ts.parent_style_id = ps.id
WHERE ts.id = %s
""", (style_id,))
return r2d(cur.fetchone())
@ -208,7 +208,7 @@ def update_training_style(style_id: int, data: dict, session=Depends(require_aut
cur = get_cursor(conn)
cur.execute("""
UPDATE training_styles SET
UPDATE style_directions SET
name = %s,
abbreviation = %s,
description = %s,
@ -231,8 +231,8 @@ def update_training_style(style_id: int, data: dict, session=Depends(require_aut
cur.execute("""
SELECT ts.*, ps.name as parent_style_name
FROM training_styles ts
LEFT JOIN training_styles ps ON ts.parent_style_id = ps.id
FROM style_directions ts
LEFT JOIN style_directions ps ON ts.parent_style_id = ps.id
WHERE ts.id = %s
""", (style_id,))
return r2d(cur.fetchone())
@ -247,7 +247,7 @@ def delete_training_style(style_id: int, session=Depends(require_auth)):
with get_db() as conn:
cur = get_cursor(conn)
cur.execute("DELETE FROM training_styles WHERE id = %s", (style_id,))
cur.execute("DELETE FROM style_directions WHERE id = %s", (style_id,))
conn.commit()
return {"ok": True}
@ -830,7 +830,7 @@ def delete_target_group(target_group_id: int, session=Depends(require_auth)):
# Check if assigned to training styles (M:N)
cur.execute("""
SELECT COUNT(*) as count
FROM training_style_target_groups
FROM style_direction_target_groups
WHERE target_group_id = %s
""", (target_group_id,))
style_count = cur.fetchone()['count']
@ -854,14 +854,14 @@ def delete_target_group(target_group_id: int, session=Depends(require_auth)):
@router.get("/training-style-target-groups")
def list_training_style_target_groups(
training_style_id: Optional[int] = Query(default=None),
style_direction_id: Optional[int] = Query(default=None),
target_group_id: Optional[int] = Query(default=None),
is_primary: Optional[bool] = Query(default=None),
session=Depends(require_auth)
):
"""List M:N assignments between training styles and target groups.
"""List M:N assignments between style directions and target groups.
Returns enriched data with training_style_name, target_group_name,
Returns enriched data with style_direction_name, target_group_name,
focus_area_name for easy display in Matrix UI.
"""
with get_db() as conn:
@ -869,41 +869,41 @@ def list_training_style_target_groups(
query = """
SELECT
tstg.id,
tstg.training_style_id,
tstg.target_group_id,
tstg.is_primary,
tstg.created_at,
ts.name as training_style_name,
ts.focus_area_id,
sdtg.id,
sdtg.style_direction_id,
sdtg.target_group_id,
sdtg.is_primary,
sdtg.created_at,
sd.name as style_direction_name,
sd.focus_area_id,
fa.name as focus_area_name,
tg.name as target_group_name,
tg.min_age,
tg.max_age
FROM training_style_target_groups tstg
LEFT JOIN training_styles ts ON tstg.training_style_id = ts.id
LEFT JOIN focus_areas fa ON ts.focus_area_id = fa.id
LEFT JOIN target_groups tg ON tstg.target_group_id = tg.id
FROM style_direction_target_groups sdtg
LEFT JOIN style_directions sd ON sdtg.style_direction_id = sd.id
LEFT JOIN focus_areas fa ON sd.focus_area_id = fa.id
LEFT JOIN target_groups tg ON sdtg.target_group_id = tg.id
"""
params = []
where = []
if training_style_id is not None:
where.append("tstg.training_style_id = %s")
params.append(training_style_id)
if style_direction_id is not None:
where.append("sdtg.style_direction_id = %s")
params.append(style_direction_id)
if target_group_id is not None:
where.append("tstg.target_group_id = %s")
where.append("sdtg.target_group_id = %s")
params.append(target_group_id)
if is_primary is not None:
where.append("tstg.is_primary = %s")
where.append("sdtg.is_primary = %s")
params.append(is_primary)
if where:
query += " WHERE " + " AND ".join(where)
query += " ORDER BY fa.sort_order, ts.sort_order, tg.sort_order"
query += " ORDER BY fa.sort_order, sd.sort_order, tg.sort_order"
cur.execute(query, params)
rows = cur.fetchall()
@ -912,7 +912,7 @@ def list_training_style_target_groups(
@router.post("/training-style-target-groups")
def create_training_style_target_group(data: dict, session=Depends(require_auth)):
"""Assign target group to training style (admin only).
"""Assign target group to style direction (admin only).
Uses UPSERT logic - if assignment exists, updates is_primary flag.
"""
@ -920,25 +920,25 @@ def create_training_style_target_group(data: dict, session=Depends(require_auth)
if role not in ['admin', 'superadmin']:
raise HTTPException(403, "Nur Admins dürfen Zuordnungen erstellen")
training_style_id = data.get('training_style_id')
style_direction_id = data.get('style_direction_id')
target_group_id = data.get('target_group_id')
if not training_style_id or not target_group_id:
raise HTTPException(400, "training_style_id und target_group_id sind Pflichtfelder")
if not style_direction_id or not target_group_id:
raise HTTPException(400, "style_direction_id und target_group_id sind Pflichtfelder")
with get_db() as conn:
cur = get_cursor(conn)
# Upsert logic
cur.execute("""
INSERT INTO training_style_target_groups
(training_style_id, target_group_id, is_primary)
INSERT INTO style_direction_target_groups
(style_direction_id, target_group_id, is_primary)
VALUES (%s, %s, %s)
ON CONFLICT (training_style_id, target_group_id)
ON CONFLICT (style_direction_id, target_group_id)
DO UPDATE SET is_primary = EXCLUDED.is_primary
RETURNING id
""", (
training_style_id,
style_direction_id,
target_group_id,
data.get('is_primary', False)
))
@ -949,20 +949,20 @@ def create_training_style_target_group(data: dict, session=Depends(require_auth)
# Return enriched record
cur.execute("""
SELECT
tstg.id,
tstg.training_style_id,
tstg.target_group_id,
tstg.is_primary,
tstg.created_at,
ts.name as training_style_name,
ts.focus_area_id,
sdtg.id,
sdtg.style_direction_id,
sdtg.target_group_id,
sdtg.is_primary,
sdtg.created_at,
sd.name as style_direction_name,
sd.focus_area_id,
fa.name as focus_area_name,
tg.name as target_group_name
FROM training_style_target_groups tstg
LEFT JOIN training_styles ts ON tstg.training_style_id = ts.id
LEFT JOIN focus_areas fa ON ts.focus_area_id = fa.id
LEFT JOIN target_groups tg ON tstg.target_group_id = tg.id
WHERE tstg.id = %s
FROM style_direction_target_groups sdtg
LEFT JOIN style_directions sd ON sdtg.style_direction_id = sd.id
LEFT JOIN focus_areas fa ON sd.focus_area_id = fa.id
LEFT JOIN target_groups tg ON sdtg.target_group_id = tg.id
WHERE sdtg.id = %s
""", (assignment_id,))
return r2d(cur.fetchone())
@ -986,7 +986,7 @@ def update_training_style_target_group(
cur = get_cursor(conn)
cur.execute("""
UPDATE training_style_target_groups
UPDATE style_direction_target_groups
SET is_primary = %s
WHERE id = %s
""", (
@ -999,20 +999,20 @@ def update_training_style_target_group(
# Return enriched record
cur.execute("""
SELECT
tstg.id,
tstg.training_style_id,
tstg.target_group_id,
tstg.is_primary,
tstg.created_at,
ts.name as training_style_name,
ts.focus_area_id,
sdtg.id,
sdtg.style_direction_id,
sdtg.target_group_id,
sdtg.is_primary,
sdtg.created_at,
sd.name as style_direction_name,
sd.focus_area_id,
fa.name as focus_area_name,
tg.name as target_group_name
FROM training_style_target_groups tstg
LEFT JOIN training_styles ts ON tstg.training_style_id = ts.id
LEFT JOIN focus_areas fa ON ts.focus_area_id = fa.id
LEFT JOIN target_groups tg ON tstg.target_group_id = tg.id
WHERE tstg.id = %s
FROM style_direction_target_groups sdtg
LEFT JOIN style_directions sd ON sdtg.style_direction_id = sd.id
LEFT JOIN focus_areas fa ON sd.focus_area_id = fa.id
LEFT JOIN target_groups tg ON sdtg.target_group_id = tg.id
WHERE sdtg.id = %s
""", (assignment_id,))
return r2d(cur.fetchone())
@ -1027,7 +1027,7 @@ def delete_training_style_target_group(assignment_id: int, session=Depends(requi
with get_db() as conn:
cur = get_cursor(conn)
cur.execute("DELETE FROM training_style_target_groups WHERE id = %s", (assignment_id,))
cur.execute("DELETE FROM style_direction_target_groups WHERE id = %s", (assignment_id,))
conn.commit()
return {"ok": True}
@ -1038,7 +1038,7 @@ def get_training_styles_hierarchy(
status: Optional[str] = Query(default='active'),
session=Depends(require_auth)
):
"""Get hierarchical structure: Focus Areas → Training Styles → Target Groups.
"""Get hierarchical structure: Focus Areas → Style Directions → Target Groups.
Returns nested structure for Tree-View rendering in Admin UI.
"""
@ -1058,25 +1058,25 @@ def get_training_styles_hierarchy(
cur.execute(fa_query, fa_params)
focus_areas = [r2d(r) for r in cur.fetchall()]
# For each focus area, get training styles with their target groups
# For each focus area, get style directions with their target groups
for fa in focus_areas:
ts_query = """
SELECT * FROM training_styles
sd_query = """
SELECT * FROM style_directions
WHERE focus_area_id = %s
"""
ts_params = [fa['id']]
sd_params = [fa['id']]
if status:
ts_query += " AND status = %s"
ts_params.append(status)
sd_query += " AND status = %s"
sd_params.append(status)
ts_query += " ORDER BY sort_order, name"
sd_query += " ORDER BY sort_order, name"
cur.execute(ts_query, ts_params)
training_styles = [r2d(r) for r in cur.fetchall()]
cur.execute(sd_query, sd_params)
style_directions = [r2d(r) for r in cur.fetchall()]
# For each training style, get assigned target groups
for ts in training_styles:
# For each style direction, get assigned target groups
for sd in style_directions:
cur.execute("""
SELECT
tg.id,
@ -1084,16 +1084,16 @@ def get_training_styles_hierarchy(
tg.description,
tg.min_age,
tg.max_age,
tstg.is_primary,
tstg.id as assignment_id
FROM training_style_target_groups tstg
LEFT JOIN target_groups tg ON tstg.target_group_id = tg.id
WHERE tstg.training_style_id = %s
sdtg.is_primary,
sdtg.id as assignment_id
FROM style_direction_target_groups sdtg
LEFT JOIN target_groups tg ON sdtg.target_group_id = tg.id
WHERE sdtg.style_direction_id = %s
ORDER BY tg.sort_order, tg.name
""", (ts['id'],))
""", (sd['id'],))
ts['target_groups'] = [r2d(r) for r in cur.fetchall()]
sd['target_groups'] = [r2d(r) for r in cur.fetchall()]
fa['training_styles'] = training_styles
fa['style_directions'] = style_directions
return focus_areas

View File

@ -18,7 +18,7 @@ MODULE_VERSIONS = {
"import_wiki": "0.1.0",
"admin": "1.0.0",
"membership": "1.0.0",
"catalogs": "1.3.0", # Updated: M:N Zielgruppen-Zuordnung (Migration 009)
"catalogs": "1.4.0", # Updated: Backend SQL Queries für renamed tables (Migration 010+011)
}
CHANGELOG = [
@ -32,7 +32,9 @@ CHANGELOG = [
"DB: Neue Tabelle training_types mit Seed-Daten (Breitensport, Leistungssport, Wettkampf)",
"DB: Neue Junction-Tabelle exercise_training_types (M:N)",
"Architektur: Fokusbereich → Stilrichtung → Trainingsstil → Zielgruppe (alle M:N)",
"Phase 1: Nur Datenbank-Migrationen - Backend/Frontend-Updates folgen",
"Backend: Alle SQL Queries aktualisiert auf neue Tabellennamen (style_directions, style_direction_target_groups)",
"Backend: API Parameter umbenannt (training_style_id → style_direction_id)",
"Backend: CRUD Endpoints für training_types hinzugefügt",
]
},
{