- Introduced new endpoints for updating training category and type parameters in the backend. - Added corresponding update functions in the frontend API utility. - Enhanced the Admin Activity Attribute Profiles page to support editing and saving changes for category and type parameters. - Implemented state management for editing parameters and improved error handling during updates.
267 lines
9.1 KiB
Python
267 lines
9.1 KiB
Python
"""
|
|
Admin: training_category_parameter + training_type_parameter (attribute profiles).
|
|
|
|
Agent guide: .claude/docs/technical/ACTIVITY_SESSION_METRICS_EAV_AGENT_GUIDE.md
|
|
"""
|
|
from typing import Optional
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from pydantic import BaseModel, Field
|
|
|
|
from auth import require_admin
|
|
from db import get_db, get_cursor, r2d
|
|
|
|
router = APIRouter(prefix="/api/admin", tags=["admin", "activity-attribute-profiles"])
|
|
|
|
|
|
class CategoryParameterCreate(BaseModel):
|
|
training_category: str = Field(..., min_length=1, max_length=50)
|
|
training_parameter_id: int
|
|
sort_order: int = 0
|
|
required: bool = False
|
|
ui_group: Optional[str] = Field(None, max_length=50)
|
|
|
|
|
|
class TypeParameterCreate(BaseModel):
|
|
training_type_id: int
|
|
training_parameter_id: int
|
|
sort_order: Optional[int] = None
|
|
required: Optional[bool] = None
|
|
ui_group: Optional[str] = Field(None, max_length=50)
|
|
|
|
|
|
class CategoryParameterUpdate(BaseModel):
|
|
sort_order: Optional[int] = None
|
|
required: Optional[bool] = None
|
|
ui_group: Optional[str] = Field(None, max_length=50)
|
|
|
|
|
|
class TypeParameterUpdate(BaseModel):
|
|
sort_order: Optional[int] = None
|
|
required: Optional[bool] = None
|
|
ui_group: Optional[str] = Field(None, max_length=50)
|
|
|
|
|
|
@router.get("/training-category-parameters")
|
|
def admin_list_category_parameters(
|
|
category: Optional[str] = Query(None, description="Filter: training_types.category"),
|
|
session: dict = Depends(require_admin),
|
|
):
|
|
with get_db() as conn:
|
|
cur = get_cursor(conn)
|
|
if category:
|
|
cur.execute(
|
|
"""
|
|
SELECT tcp.*, tp.key AS parameter_key, tp.name_de AS parameter_name_de
|
|
FROM training_category_parameter tcp
|
|
JOIN training_parameters tp ON tp.id = tcp.training_parameter_id
|
|
WHERE tcp.training_category = %s
|
|
ORDER BY tcp.sort_order, tp.key
|
|
""",
|
|
(category.strip(),),
|
|
)
|
|
else:
|
|
cur.execute(
|
|
"""
|
|
SELECT tcp.*, tp.key AS parameter_key, tp.name_de AS parameter_name_de
|
|
FROM training_category_parameter tcp
|
|
JOIN training_parameters tp ON tp.id = tcp.training_parameter_id
|
|
ORDER BY tcp.training_category, tcp.sort_order, tp.key
|
|
"""
|
|
)
|
|
return [r2d(r) for r in cur.fetchall()]
|
|
|
|
|
|
@router.post("/training-category-parameters")
|
|
def admin_add_category_parameter(
|
|
body: CategoryParameterCreate,
|
|
session: dict = Depends(require_admin),
|
|
):
|
|
cat = body.training_category.strip()
|
|
with get_db() as conn:
|
|
cur = get_cursor(conn)
|
|
cur.execute("SELECT id FROM training_parameters WHERE id = %s", (body.training_parameter_id,))
|
|
if not cur.fetchone():
|
|
raise HTTPException(404, "training_parameter_id unbekannt")
|
|
try:
|
|
cur.execute(
|
|
"""
|
|
INSERT INTO training_category_parameter (
|
|
training_category, training_parameter_id, sort_order, required, ui_group
|
|
) VALUES (%s,%s,%s,%s,%s)
|
|
RETURNING id
|
|
""",
|
|
(cat, body.training_parameter_id, body.sort_order, body.required, body.ui_group),
|
|
)
|
|
new_id = cur.fetchone()["id"]
|
|
conn.commit()
|
|
except Exception as e:
|
|
conn.rollback()
|
|
if "uq_training_category_parameter" in str(e).lower() or "unique" in str(e).lower():
|
|
raise HTTPException(409, "Zuordnung existiert bereits") from e
|
|
raise HTTPException(400, str(e)) from e
|
|
return {"id": new_id}
|
|
|
|
|
|
@router.put("/training-category-parameters/{link_id}")
|
|
def admin_update_category_parameter(
|
|
link_id: int,
|
|
body: CategoryParameterUpdate,
|
|
session: dict = Depends(require_admin),
|
|
):
|
|
patch = body.model_dump(exclude_unset=True)
|
|
if not patch:
|
|
raise HTTPException(400, "Keine Felder zum Aktualisieren")
|
|
cols: list[str] = []
|
|
vals: list = []
|
|
if "sort_order" in patch:
|
|
cols.append("sort_order = %s")
|
|
vals.append(patch["sort_order"])
|
|
if "required" in patch:
|
|
cols.append("required = %s")
|
|
vals.append(patch["required"])
|
|
if "ui_group" in patch:
|
|
cols.append("ui_group = %s")
|
|
vals.append(patch["ui_group"].strip() if patch["ui_group"] else None)
|
|
if not cols:
|
|
raise HTTPException(400, "Keine Felder zum Aktualisieren")
|
|
vals.append(link_id)
|
|
with get_db() as conn:
|
|
cur = get_cursor(conn)
|
|
cur.execute(
|
|
f"UPDATE training_category_parameter SET {', '.join(cols)} WHERE id = %s RETURNING id",
|
|
vals,
|
|
)
|
|
if not cur.fetchone():
|
|
raise HTTPException(404, "Eintrag nicht gefunden")
|
|
conn.commit()
|
|
return {"ok": True, "id": link_id}
|
|
|
|
|
|
@router.delete("/training-category-parameters/{link_id}")
|
|
def admin_delete_category_parameter(
|
|
link_id: int,
|
|
session: dict = Depends(require_admin),
|
|
):
|
|
with get_db() as conn:
|
|
cur = get_cursor(conn)
|
|
cur.execute(
|
|
"DELETE FROM training_category_parameter WHERE id = %s RETURNING id",
|
|
(link_id,),
|
|
)
|
|
if not cur.fetchone():
|
|
raise HTTPException(404, "Eintrag nicht gefunden")
|
|
conn.commit()
|
|
return {"ok": True}
|
|
|
|
|
|
@router.get("/training-type-parameters")
|
|
def admin_list_type_parameters(
|
|
training_type_id: int = Query(..., ge=1),
|
|
session: dict = Depends(require_admin),
|
|
):
|
|
with get_db() as conn:
|
|
cur = get_cursor(conn)
|
|
cur.execute(
|
|
"""
|
|
SELECT ttp.*, tp.key AS parameter_key, tp.name_de AS parameter_name_de
|
|
FROM training_type_parameter ttp
|
|
JOIN training_parameters tp ON tp.id = ttp.training_parameter_id
|
|
WHERE ttp.training_type_id = %s
|
|
ORDER BY ttp.sort_order NULLS LAST, tp.key
|
|
""",
|
|
(training_type_id,),
|
|
)
|
|
return [r2d(r) for r in cur.fetchall()]
|
|
|
|
|
|
@router.post("/training-type-parameters")
|
|
def admin_add_type_parameter(
|
|
body: TypeParameterCreate,
|
|
session: dict = Depends(require_admin),
|
|
):
|
|
with get_db() as conn:
|
|
cur = get_cursor(conn)
|
|
cur.execute("SELECT id FROM training_types WHERE id = %s", (body.training_type_id,))
|
|
if not cur.fetchone():
|
|
raise HTTPException(404, "training_type_id unbekannt")
|
|
cur.execute("SELECT id FROM training_parameters WHERE id = %s", (body.training_parameter_id,))
|
|
if not cur.fetchone():
|
|
raise HTTPException(404, "training_parameter_id unbekannt")
|
|
try:
|
|
cur.execute(
|
|
"""
|
|
INSERT INTO training_type_parameter (
|
|
training_type_id, training_parameter_id, sort_order, required, ui_group
|
|
) VALUES (%s,%s,%s,%s,%s)
|
|
RETURNING id
|
|
""",
|
|
(
|
|
body.training_type_id,
|
|
body.training_parameter_id,
|
|
body.sort_order,
|
|
body.required,
|
|
body.ui_group,
|
|
),
|
|
)
|
|
new_id = cur.fetchone()["id"]
|
|
conn.commit()
|
|
except Exception as e:
|
|
conn.rollback()
|
|
if "uq_training_type_parameter" in str(e).lower() or "unique" in str(e).lower():
|
|
raise HTTPException(409, "Zuordnung existiert bereits") from e
|
|
raise HTTPException(400, str(e)) from e
|
|
return {"id": new_id}
|
|
|
|
|
|
@router.put("/training-type-parameters/{link_id}")
|
|
def admin_update_type_parameter(
|
|
link_id: int,
|
|
body: TypeParameterUpdate,
|
|
session: dict = Depends(require_admin),
|
|
):
|
|
patch = body.model_dump(exclude_unset=True)
|
|
if not patch:
|
|
raise HTTPException(400, "Keine Felder zum Aktualisieren")
|
|
cols: list[str] = []
|
|
vals: list = []
|
|
if "sort_order" in patch:
|
|
cols.append("sort_order = %s")
|
|
vals.append(patch["sort_order"])
|
|
if "required" in patch:
|
|
cols.append("required = %s")
|
|
vals.append(patch["required"])
|
|
if "ui_group" in patch:
|
|
cols.append("ui_group = %s")
|
|
vals.append(patch["ui_group"].strip() if patch["ui_group"] else None)
|
|
if not cols:
|
|
raise HTTPException(400, "Keine Felder zum Aktualisieren")
|
|
vals.append(link_id)
|
|
with get_db() as conn:
|
|
cur = get_cursor(conn)
|
|
cur.execute(
|
|
f"UPDATE training_type_parameter SET {', '.join(cols)} WHERE id = %s RETURNING id",
|
|
vals,
|
|
)
|
|
if not cur.fetchone():
|
|
raise HTTPException(404, "Eintrag nicht gefunden")
|
|
conn.commit()
|
|
return {"ok": True, "id": link_id}
|
|
|
|
|
|
@router.delete("/training-type-parameters/{link_id}")
|
|
def admin_delete_type_parameter(
|
|
link_id: int,
|
|
session: dict = Depends(require_admin),
|
|
):
|
|
with get_db() as conn:
|
|
cur = get_cursor(conn)
|
|
cur.execute(
|
|
"DELETE FROM training_type_parameter WHERE id = %s RETURNING id",
|
|
(link_id,),
|
|
)
|
|
if not cur.fetchone():
|
|
raise HTTPException(404, "Eintrag nicht gefunden")
|
|
conn.commit()
|
|
return {"ok": True}
|