feat: init trails

This commit is contained in:
swve 2023-11-18 12:22:00 +01:00
parent b04fd64c92
commit eca819b896
5 changed files with 495 additions and 253 deletions

View file

@ -0,0 +1,53 @@
from typing import Optional
from pydantic import BaseModel
from sqlalchemy import JSON, Column
from sqlmodel import Field, SQLModel
from enum import Enum
from src.db.trail_steps import TrailStep
class TrailRunEnum(str, Enum):
RUN_TYPE_COURSE = "RUN_TYPE_COURSE"
class StatusEnum(str, Enum):
STATUS_IN_PROGRESS = "STATUS_IN_PROGRESS"
STATUS_COMPLETED = "STATUS_COMPLETED"
STATUS_PAUSED = "STATUS_PAUSED"
STATUS_CANCELLED = "STATUS_CANCELLED"
class TrailRun(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
data: dict = Field(default={}, sa_column=Column(JSON))
status: StatusEnum = StatusEnum.STATUS_IN_PROGRESS
# foreign keys
trail_id: int = Field(default=None, foreign_key="trail.id")
course_id: int = Field(default=None, foreign_key="course.id")
org_id: int = Field(default=None, foreign_key="organization.id")
user_id: int = Field(default=None, foreign_key="user.id")
# timestamps
creation_date: str
update_date: str
class TrailRunCreate(TrailRun):
pass
# trick because Lists are not supported in SQLModel (runs: list[TrailStep] )
class TrailRunRead(BaseModel):
id: Optional[int] = Field(default=None, primary_key=True)
data: dict = Field(default={}, sa_column=Column(JSON))
status: StatusEnum = StatusEnum.STATUS_IN_PROGRESS
# foreign keys
trail_id: int = Field(default=None, foreign_key="trail.id")
course_id: int = Field(default=None, foreign_key="course.id")
org_id: int = Field(default=None, foreign_key="organization.id")
user_id: int = Field(default=None, foreign_key="user.id")
# timestamps
creation_date: str
update_date: str
steps: list[TrailStep]
pass

View file

@ -0,0 +1,32 @@
from enum import Enum
from typing import Optional
from sqlalchemy import JSON, Column
from sqlmodel import Field, SQLModel
class TrailStepTypeEnum(str, Enum):
STEP_TYPE_READABLE_ACTIVITY = "STEP_TYPE_READABLE_ACTIVITY"
STEP_TYPE_ASSIGNMENT_ACTIVITY = "STEP_TYPE_ASSIGNMENT_ACTIVITY"
STEP_TYPE_CUSTOM_ACTIVITY = "STEP_TYPE_CUSTOM_ACTIVITY"
class TrailStep(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
complete: bool
teacher_verified: bool
grade: str
data: dict = Field(default={}, sa_column=Column(JSON))
# foreign keys
trailrun_id: int = Field(default=None, foreign_key="trailrun.id")
trail_id: int = Field(default=None, foreign_key="trail.id")
activity_id: int = Field(default=None, foreign_key="activity.id")
course_id: int = Field(default=None, foreign_key="course.id")
org_id: int = Field(default=None, foreign_key="organization.id")
user_id: int = Field(default=None, foreign_key="user.id")
# timestamps
creation_date: str
update_date: str
# note : prepare assignments support
# an assignment object will be linked to a trail step object in the future

34
apps/api/src/db/trails.py Normal file
View file

@ -0,0 +1,34 @@
from typing import Optional
from pydantic import BaseModel
from sqlmodel import Field, SQLModel
from enum import Enum
from src.db.trail_runs import TrailRun, TrailRunRead
from src.db.trail_steps import TrailStep
class TrailBase(SQLModel):
org_id: int = Field(default=None, foreign_key="organization.id")
user_id: int = Field(default=None, foreign_key="user.id")
class Trail(TrailBase, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
trail_uuid: str = ""
creation_date: str = ""
update_date: str = ""
class TrailCreate(TrailBase):
pass
# trick because Lists are not supported in SQLModel (runs: list[TrailRun] )
class TrailRead(BaseModel):
id: Optional[int] = Field(default=None, primary_key=True)
trail_uuid: str
org_id: int = Field(default=None, foreign_key="organization.id")
user_id: int = Field(default=None, foreign_key="user.id")
creation_date: str
update_date: str
runs: list[TrailRunRead]

View file

@ -1,56 +1,103 @@
from fastapi import APIRouter, Depends, Request from fastapi import APIRouter, Depends, Request
from src.core.events.database import get_db_session
from src.db.trails import TrailCreate, TrailRead
from src.security.auth import get_current_user from src.security.auth import get_current_user
from src.services.trail.trail import Trail, add_activity_to_trail, add_course_to_trail, create_trail, get_user_trail_with_orgslug, get_user_trail, remove_course_from_trail from src.services.trail.trail import (
Trail,
add_activity_to_trail,
add_course_to_trail,
create_user_trail,
get_user_trails,
get_user_trail_with_orgid,
remove_course_from_trail,
)
router = APIRouter() router = APIRouter()
@router.post("/start") @router.post("/start")
async def api_start_trail(request: Request, trail_object: Trail, org_id: str, user=Depends(get_current_user)) -> Trail: async def api_start_trail(
request: Request,
trail_object: TrailCreate,
user=Depends(get_current_user),
db_session=Depends(get_db_session),
) -> Trail:
""" """
Start trail Start trail
""" """
return await create_trail(request, user, org_id, trail_object) return await create_user_trail(request, user, trail_object, db_session)
@router.get("/org_id/{org_id}/trail") @router.get("/")
async def api_get_trail_by_orgid(request: Request, org_slug: str, user=Depends(get_current_user)): async def api_get_user_trail(
request: Request,
user=Depends(get_current_user),
db_session=Depends(get_db_session),
) -> TrailRead:
""" """
Get a user trails Get a user trails
""" """
return await get_user_trail(request, user=user, org_slug=org_slug) return await get_user_trails(
request, user=user, db_session=db_session
)
@router.get("/org_slug/{org_slug}/trail") @router.get("/org_slug/{org_id}/trail")
async def api_get_trail_by_orgslug(request: Request, org_slug: str, user=Depends(get_current_user)): async def api_get_trail_by_org_id(
request: Request,
org_id: int,
user=Depends(get_current_user),
db_session=Depends(get_db_session),
) -> TrailRead:
""" """
Get a user trails using org slug Get a user trails using org slug
""" """
return await get_user_trail_with_orgslug(request, user, org_slug=org_slug) return await get_user_trail_with_orgid(
request, user, org_id=org_id, db_session=db_session
)
# Courses in trail # Courses in trail
@router.post("/org_slug/{org_slug}/add_course/{course_id}") @router.post("/add_course/{course_id}")
async def api_add_course_to_trail(request: Request, course_id: str, org_slug: str, user=Depends(get_current_user)): async def api_add_course_to_trail(
request: Request,
course_id: str,
user=Depends(get_current_user),
db_session=Depends(get_db_session),
) -> TrailRead:
""" """
Add Course to trail Add Course to trail
""" """
return await add_course_to_trail(request, user, org_slug, course_id) return await add_course_to_trail(request, user, course_id, db_session)
@router.post("/org_slug/{org_slug}/remove_course/{course_id}") @router.post("/remove_course/{course_id}")
async def api_remove_course_to_trail(request: Request, course_id: str, org_slug: str, user=Depends(get_current_user)): async def api_remove_course_to_trail(
request: Request,
course_id: str,
user=Depends(get_current_user),
db_session=Depends(get_db_session),
) -> TrailRead:
""" """
Remove Course from trail Remove Course from trail
""" """
return await remove_course_from_trail(request, user, org_slug, course_id) return await remove_course_from_trail(request, user, course_id, db_session)
@router.post("/org_slug/{org_slug}/add_activity/course_id/{course_id}/activity_id/{activity_id}") @router.post("/add_activity/course_id/{course_id}/activity_id/{activity_id}")
async def api_add_activity_to_trail(request: Request, activity_id: str, course_id: str, org_slug: str, user=Depends(get_current_user)): async def api_add_activity_to_trail(
request: Request,
activity_id: int,
course_id: int,
user=Depends(get_current_user),
db_session=Depends(get_db_session),
) -> TrailRead:
""" """
Add Course to trail Add Course to trail
""" """
return await add_activity_to_trail(request, user, course_id, org_slug, activity_id) return await add_activity_to_trail(
request, user, course_id, activity_id, db_session
)

View file

@ -1,286 +1,362 @@
from datetime import datetime from datetime import datetime
import stat
from typing import List, Literal, Optional from typing import List, Literal, Optional
from uuid import uuid4 from uuid import uuid4
from fastapi import HTTPException, Request, status from fastapi import HTTPException, Request, status
from pydantic import BaseModel from pydantic import BaseModel
from sqlmodel import Session, select
from src.db.courses import Course
from src.db.trail_runs import TrailRun, TrailRunCreate, TrailRunRead
from src.db.trail_steps import TrailStep
from src.db.trails import Trail, TrailCreate, TrailRead
from src.db.users import PublicUser
from src.services.orgs.schemas.orgs import PublicOrganization from src.services.orgs.schemas.orgs import PublicOrganization
from src.services.courses.chapters import get_coursechapters_meta from src.services.courses.chapters import get_coursechapters_meta
from src.services.users.users import PublicUser
#### Classes #################################################### async def create_user_trail(
request: Request,
user: PublicUser,
class ActivityData(BaseModel): trail_object: TrailCreate,
activity_id: str db_session: Session,
activity_type: str
data: Optional[dict]
class TrailCourse(BaseModel):
course_id: str
elements_type: Optional[Literal["course"]] = "course"
status: Optional[Literal["ongoing", "done", "closed"]] = "ongoing"
course_object: dict
masked: Optional[bool] = False
activities_marked_complete: Optional[List[str]]
activities_data: Optional[List[ActivityData]]
progress: Optional[int]
class Trail(BaseModel):
status: Optional[Literal["ongoing", "done", "closed"]] = "ongoing"
masked: Optional[bool] = False
courses: Optional[List[TrailCourse]]
class TrailInDB(Trail):
trail_id: str
org_id: str
user_id: str
creationDate: str = datetime.now().isoformat()
updateDate: str = datetime.now().isoformat()
#### Classes ####################################################
async def create_trail(
request: Request, user: PublicUser, org_id: str, trail_object: Trail
) -> Trail: ) -> Trail:
trails = request.app.db["trails"] statement = select(Trail).where(Trail.org_id == trail_object.org_id)
trail = db_session.exec(statement).first()
# get list of courses if trail:
if trail_object.courses: raise HTTPException(
courses = trail_object.courses status_code=status.HTTP_400_BAD_REQUEST,
# get course ids detail="Trail already exists",
course_ids = [course.course_id for course in courses]
# find if the user has already started the course
existing_trail = await trails.find_one(
{"user_id": user.user_id, "courses.course_id": {"$in": course_ids}}
) )
if existing_trail:
# update the status of the element with the matching course_id to "ongoing"
for element in existing_trail["courses"]:
if element["course_id"] in course_ids:
element["status"] = "ongoing"
# update the existing trail in the database
await trails.replace_one(
{"trail_id": existing_trail["trail_id"]}, existing_trail
)
# create trail id trail = Trail.from_orm(trail_object)
trail_id = f"trail_{uuid4()}"
trail.creation_date = str(datetime.now())
trail.update_date = str(datetime.now())
trail.org_id = trail_object.org_id
trail.trail_uuid = str(f"trail_{uuid4()}")
# create trail # create trail
trail = TrailInDB( db_session.add(trail)
**trail_object.dict(), trail_id=trail_id, user_id=user.user_id, org_id=org_id db_session.commit()
) db_session.refresh(trail)
await trails.insert_one(trail.dict())
return trail return trail
async def get_user_trail(request: Request, org_slug: str, user: PublicUser) -> Trail: async def get_user_trails(
trails = request.app.db["trails"] request: Request,
trail = await trails.find_one({"user_id": user.user_id}) user: PublicUser,
db_session: Session,
) -> TrailRead:
statement = select(Trail).where(Trail.user_id == user.id)
trail = db_session.exec(statement).first()
if not trail: if not trail:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Trail not found" status_code=status.HTTP_404_NOT_FOUND, detail="Trail not found"
) )
for element in trail["courses"]:
course_id = element["course_id"]
chapters_meta = await get_coursechapters_meta(request, course_id, user)
activities = chapters_meta["activities"]
num_activities = len(activities)
num_completed_activities = len(element.get("activities_marked_complete", [])) statement = select(TrailRun).where(TrailRun.trail_id == trail.id)
element["progress"] = ( trail_runs = db_session.exec(statement).all()
round((num_completed_activities / num_activities) * 100, 2)
if num_activities > 0
else 0
)
return Trail(**trail) trail_runs = [
TrailRunRead(**trail_run.__dict__, steps=[]) for trail_run in trail_runs
]
for trail_run in trail_runs:
statement = select(TrailStep).where(TrailStep.trailrun_id == trail_run.id)
trail_steps = db_session.exec(statement).all()
async def get_user_trail_with_orgslug( trail_steps = [TrailStep(**trail_step.__dict__) for trail_step in trail_steps]
request: Request, user: PublicUser, org_slug: str trail_run.steps = trail_steps
) -> Trail:
trails = request.app.db["trails"]
orgs = request.app.db["organizations"]
courses_mongo = request.app.db["courses"]
# get org_id from orgslug for trail_step in trail_steps:
org = await orgs.find_one({"slug": org_slug}) statement = select(Course).where(Course.id == trail_step.course_id)
course = db_session.exec(statement).first()
trail_step.data = dict(course=course)
trail = await trails.find_one({"user_id": user.user_id, "org_id": org["org_id"]}) trail_read = TrailRead(
**trail.dict(),
if not trail: runs=trail_runs,
return Trail(masked=False, courses=[])
course_ids = [course["course_id"] for course in trail["courses"]]
live_courses = await courses_mongo.find({"course_id": {"$in": course_ids}}).to_list(
length=None
) )
for course in trail["courses"]: return trail_read
course_id = course["course_id"]
if course_id not in [course["course_id"] for course in live_courses]:
course["masked"] = True
continue
chapters_meta = await get_coursechapters_meta(request, course_id, user) async def get_user_trail_with_orgid(
activities = chapters_meta["activities"] request: Request, user: PublicUser, org_id: int, db_session: Session
) -> TrailRead:
statement = select(Trail).where(Trail.org_id == org_id, Trail.user_id == user.id)
trail = db_session.exec(statement).first()
# get course object without _id if not trail:
course_object = await courses_mongo.find_one( raise HTTPException(
{"course_id": course_id}, {"_id": 0} status_code=status.HTTP_404_NOT_FOUND, detail="Trail not found"
) )
course["course_object"] = course_object statement = select(TrailRun).where(TrailRun.trail_id == trail.id)
num_activities = len(activities) trail_runs = db_session.exec(statement).all()
num_completed_activities = len(course.get("activities_marked_complete", [])) trail_runs = [
course["progress"] = ( TrailRunRead(**trail_run.__dict__, steps=[]) for trail_run in trail_runs
round((num_completed_activities / num_activities) * 100, 2) ]
if num_activities > 0
else 0
)
return Trail(**trail) for trail_run in trail_runs:
statement = select(TrailStep).where(TrailStep.trailrun_id == trail_run.id)
trail_steps = db_session.exec(statement).all()
trail_steps = [TrailStep(**trail_step.__dict__) for trail_step in trail_steps]
trail_run.steps = trail_steps
for trail_step in trail_steps:
statement = select(Course).where(Course.id == trail_step.course_id)
course = db_session.exec(statement).first()
trail_step.data = dict(course=course)
trail_read = TrailRead(
**trail.dict(),
runs=trail_runs,
)
return trail_read
async def add_activity_to_trail( async def add_activity_to_trail(
request: Request, user: PublicUser, course_id: str, org_slug: str, activity_id: str request: Request,
) -> Trail: user: PublicUser,
trails = request.app.db["trails"] course_id: int,
orgs = request.app.db["organizations"] activity_id: int,
courseid = "course_" + course_id db_session: Session,
activityid = "activity_" + activity_id ) -> TrailRead:
# get org_id from orgslug # check if run already exists
org = await orgs.find_one({"slug": org_slug}) statement = select(TrailRun).where(TrailRun.course_id == course_id)
org_id = org["org_id"] trailrun = db_session.exec(statement).first()
# find a trail with the user_id and course_id in the courses array if trailrun:
trail = await trails.find_one( raise HTTPException(
{"user_id": user.user_id, "courses.course_id": courseid, "org_id": org_id} status_code=status.HTTP_400_BAD_REQUEST, detail="TrailRun already exists"
)
statement = select(Course).where(Course.id == course_id)
course = db_session.exec(statement).first()
if not course:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Course not found"
)
statement = select(Trail).where(
Trail.org_id == course.org_id, Trail.user_id == user.id
) )
trail = db_session.exec(statement).first()
if user.user_id == "anonymous":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Anonymous users cannot add activity to trail",
)
if not trail:
return Trail(masked=False, courses=[])
# if a trail has course_id in the courses array, then add the activity_id to the activities_marked_complete array
for element in trail["courses"]:
if element["course_id"] == courseid:
if "activities_marked_complete" in element:
# check if activity_id is already in the array
if activityid not in element["activities_marked_complete"]:
element["activities_marked_complete"].append(activityid)
else:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Activity already marked complete",
)
else:
element["activities_marked_complete"] = [activity_id]
# modify trail object
await trails.replace_one({"trail_id": trail["trail_id"]}, trail)
return Trail(**trail)
async def add_course_to_trail(
request: Request, user: PublicUser, orgslug: str, course_id: str
) -> Trail:
trails = request.app.db["trails"]
orgs = request.app.db["organizations"]
if user.user_id == "anonymous":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Anonymous users cannot add activity to trail",
)
org = await orgs.find_one({"slug": orgslug})
org = PublicOrganization(**org)
trail = await trails.find_one({"user_id": user.user_id, "org_id": org["org_id"]})
if not trail:
trail_to_insert = TrailInDB(
trail_id=f"trail_{uuid4()}",
user_id=user.user_id,
org_id=org["org_id"],
courses=[],
)
trail_to_insert = await trails.insert_one(trail_to_insert.dict())
trail = await trails.find_one({"_id": trail_to_insert.inserted_id})
# check if course is already present in the trail
for element in trail["courses"]:
if element["course_id"] == course_id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Course already present in the trail",
)
updated_trail = TrailCourse(
course_id=course_id,
activities_data=[],
activities_marked_complete=[],
progress=0,
course_object={},
status="ongoing",
masked=False,
)
trail["courses"].append(updated_trail.dict())
await trails.replace_one({"trail_id": trail["trail_id"]}, trail)
return Trail(**trail)
async def remove_course_from_trail(
request: Request, user: PublicUser, orgslug: str, course_id: str
) -> Trail:
trails = request.app.db["trails"]
orgs = request.app.db["organizations"]
if user.user_id == "anonymous":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Anonymous users cannot add activity to trail",
)
org = await orgs.find_one({"slug": orgslug})
org = PublicOrganization(**org)
trail = await trails.find_one({"user_id": user.user_id, "org_id": org["org_id"]})
if not trail: if not trail:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Trail not found" status_code=status.HTTP_404_NOT_FOUND, detail="Trail not found"
) )
# check if course is already present in the trail statement = select(TrailRun).where(
TrailRun.trail_id == trail.id, TrailRun.course_id == course.id
)
trailrun = db_session.exec(statement).first()
for element in trail["courses"]: if not trailrun:
if element["course_id"] == course_id: trailrun = TrailRun(
trail["courses"].remove(element) trail_id=trail.id is not None,
break course_id=course.id is not None,
org_id=course.org_id,
user_id=user.id,
creation_date=str(datetime.now()),
update_date=str(datetime.now()),
)
db_session.add(trailrun)
db_session.commit()
db_session.refresh(trailrun)
await trails.replace_one({"trail_id": trail["trail_id"]}, trail) statement = select(TrailStep).where(
return Trail(**trail) TrailStep.trailrun_id == trailrun.id, TrailStep.activity_id == activity_id
)
trailstep = db_session.exec(statement).first()
if not trailstep:
trailstep = TrailStep(
trailrun_id=trailrun.id is not None,
activity_id=activity_id,
course_id=course.id is not None,
org_id=course.org_id,
complete=False,
teacher_verified=False,
grade="",
user_id=user.id,
creation_date=str(datetime.now()),
update_date=str(datetime.now()),
)
db_session.add(trailstep)
db_session.commit()
db_session.refresh(trailstep)
statement = select(TrailRun).where(TrailRun.trail_id == trail.id)
trail_runs = db_session.exec(statement).all()
trail_runs = [
TrailRunRead(**trail_run.__dict__, steps=[]) for trail_run in trail_runs
]
for trail_run in trail_runs:
statement = select(TrailStep).where(TrailStep.trailrun_id == trail_run.id)
trail_steps = db_session.exec(statement).all()
trail_steps = [TrailStep(**trail_step.__dict__) for trail_step in trail_steps]
trail_run.steps = trail_steps
for trail_step in trail_steps:
statement = select(Course).where(Course.id == trail_step.course_id)
course = db_session.exec(statement).first()
trail_step.data = dict(course=course)
trail_read = TrailRead(
**trail.dict(),
runs=trail_runs,
)
return trail_read
async def add_course_to_trail(
request: Request,
user: PublicUser,
course_id: str,
db_session: Session,
) -> TrailRead:
# check if run already exists
statement = select(TrailRun).where(TrailRun.course_id == course_id)
trailrun = db_session.exec(statement).first()
if trailrun:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail="TrailRun already exists"
)
statement = select(Course).where(Course.id == course_id)
course = db_session.exec(statement).first()
if not course:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Course not found"
)
statement = select(Trail).where(
Trail.org_id == course.org_id, Trail.user_id == user.id
)
trail = db_session.exec(statement).first()
if not trail:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Trail not found"
)
statement = select(TrailRun).where(
TrailRun.trail_id == trail.id, TrailRun.course_id == course.id
)
trail_run = db_session.exec(statement).first()
if not trail_run:
trail_run = TrailRun(
trail_id=trail.id is not None,
course_id=course.id is not None,
org_id=course.org_id,
user_id=user.id,
creation_date=str(datetime.now()),
update_date=str(datetime.now()),
)
db_session.add(trail_run)
db_session.commit()
db_session.refresh(trail_run)
statement = select(TrailRun).where(TrailRun.trail_id == trail.id)
trail_runs = db_session.exec(statement).all()
trail_runs = [
TrailRunRead(**trail_run.__dict__, steps=[]) for trail_run in trail_runs
]
for trail_run in trail_runs:
statement = select(TrailStep).where(TrailStep.trailrun_id == trail_run.id)
trail_steps = db_session.exec(statement).all()
trail_steps = [TrailStep(**trail_step.__dict__) for trail_step in trail_steps]
trail_run.steps = trail_steps
for trail_step in trail_steps:
statement = select(Course).where(Course.id == trail_step.course_id)
course = db_session.exec(statement).first()
trail_step.data = dict(course=course)
trail_read = TrailRead(
**trail.dict(),
runs=trail_runs,
)
return trail_read
async def remove_course_from_trail(
request: Request,
user: PublicUser,
course_id: str,
db_session: Session,
) -> TrailRead:
statement = select(Course).where(Course.id == course_id)
course = db_session.exec(statement).first()
if not course:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Course not found"
)
statement = select(Trail).where(
Trail.org_id == course.org_id, Trail.user_id == user.id
)
trail = db_session.exec(statement).first()
if not trail:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Trail not found"
)
statement = select(TrailRun).where(
TrailRun.trail_id == trail.id, TrailRun.course_id == course.id
)
trail_run = db_session.exec(statement).first()
if trail_run:
db_session.delete(trail_run)
db_session.commit()
statement = select(TrailRun).where(TrailRun.trail_id == trail.id)
trail_runs = db_session.exec(statement).all()
trail_runs = [
TrailRunRead(**trail_run.__dict__, steps=[]) for trail_run in trail_runs
]
for trail_run in trail_runs:
statement = select(TrailStep).where(TrailStep.trailrun_id == trail_run.id)
trail_steps = db_session.exec(statement).all()
trail_steps = [TrailStep(**trail_step.__dict__) for trail_step in trail_steps]
trail_run.steps = trail_steps
for trail_step in trail_steps:
statement = select(Course).where(Course.id == trail_step.course_id)
course = db_session.exec(statement).first()
trail_step.data = dict(course=course)
trail_read = TrailRead(
**trail.dict(),
runs=trail_runs,
)
return trail_read