from typing import TYPE_CHECKING from sqlmodel import ( Session, Relationship, Field, ) from . import mixin from .base import ( BaseSQLModel, DocumentedStrEnum, DocumentedIntFlag, DocumentedStrFlagType, auto_enum, RowId, ) if TYPE_CHECKING: from .route import Route from .team import Team # region # Hike ################################################################ class HikeTeamPage(DocumentedIntFlag): ROUTE = auto_enum() # Route steps info QUESTIONS = auto_enum() # Visible questions VISITED_PLACES = auto_enum() # Places the team has been CURRENT_PLACE = auto_enum() # Place where the team currently is UNVISITED_PLACES = auto_enum() # Places the team still neat to visit PLACES = VISITED_PLACES | CURRENT_PLACE | UNVISITED_PLACES # All the places TRAVEL_TIME = auto_enum() # Total travel time TRAVEL_FREE_TIME = auto_enum() # Total travel time PLACE_TIME = auto_enum() # Total place time CALCULATE_TIME = auto_enum() # Total time calculated based on hike settings # TODO: Think about time between oter teams PLACE_POINTS = auto_enum() # Points scored on a place TOTAL_PLACE_POINTS = auto_enum() # All points got on all places ASSIST_POINTS = auto_enum() # Minus points for assistants # TODO: Think about place in classement ASSIST_LOG = auto_enum() # Assisted items ASSIST_LATTER = auto_enum() # ??? # ############################################################################## class HikeTimeCalculation(DocumentedIntFlag): TRAVEL_TIME = auto_enum() # Time traveling # TODO: Think about time groups (in this model we have 2, free and non free) TRAVEL_FREE_TIME = auto_enum() # Time that is excluded from traveling but not at a place PLACE_TIME = auto_enum() # Time checked in at a place TOTAL_TIME = TRAVEL_TIME | TRAVEL_FREE_TIME | PLACE_TIME # ############################################################################## class HikeVisitLogType(DocumentedStrEnum): FIRST_VISIT = auto_enum() LAST_VISIT = auto_enum() LONGEST_VISIT = auto_enum() SHORTEST_VISIT = auto_enum() EACH_VISIT = auto_enum() DISABLE_VISIT_LOGING = auto_enum() # ############################################################################## class HikeBase( mixin.Name, mixin.Contact, BaseSQLModel, ): tracker_interval: int | None = Field( default=None, nullable=True, description="Is GPS button available, value will be the interval", ) is_multi_day: bool = Field( default=False, nullable=False, description="Show datetime in stead of time only", ) team_page: HikeTeamPage = Field( default=HikeTeamPage.PLACES | HikeTeamPage.ROUTE | HikeTeamPage.QUESTIONS, nullable=False, description="Show all the places of the route inside the teams page", sa_type=DocumentedStrFlagType(HikeTeamPage), ) time_calculation: HikeTimeCalculation = Field( default=HikeTimeCalculation.TRAVEL_TIME, nullable=False, description="Wath should we calculate inside the total time", sa_type=DocumentedStrFlagType(HikeTimeCalculation), ) visit_log_type: HikeVisitLogType = Field( default=HikeVisitLogType.FIRST_VISIT, nullable=False, ) min_time_points: int | None = Field( default=0, ge=0, # TODO: le: max_time_points description="Min points for time", ) max_time_points: int | None = Field( default=100, ge=0, # TODO: ge > min_time_points description="Max points for time", ) max_question_points: int | None = Field( default=None, description="None: Calculate from answers (no max), Positive: Set as max for dynamic range", ) # Properties to receive via API on creation class HikeCreate(HikeBase): pass # Properties to receive via API on update, all are optional class HikeUpdate(HikeBase): is_multi_day: bool | None = Field(default=None) # type: ignore team_page: HikeTeamPage | None = Field(default=None) # type: ignore time_calculation: HikeTimeCalculation | None = Field(default=None) # type: ignore visit_log_type: HikeVisitLogType | None = Field(default=None) # type: ignore class Hike(mixin.RowId, HikeBase, table=True): # --- database only items -------------------------------------------------- # --- read only items ------------------------------------------------------ # --- back_populates links ------------------------------------------------- routes: list["Route"] = Relationship(back_populates="hike", cascade_delete=True) teams: list["HikeTeamLink"] = Relationship(back_populates="hike", cascade_delete=True) # --- CRUD actions --------------------------------------------------------- @classmethod def create(cls, *, session: Session, create_obj: HikeCreate) -> "Hike": data_obj = create_obj.model_dump(exclude_unset=True) db_obj = cls.model_validate(data_obj) session.add(db_obj) session.commit() session.refresh(db_obj) return db_obj @classmethod def update( cls, *, session: Session, db_obj: "Hike", in_obj: HikeUpdate ) -> "Hike": data_obj = in_obj.model_dump(exclude_unset=True) db_obj.sqlmodel_update(data_obj) session.add(db_obj) session.commit() session.refresh(db_obj) return db_obj # Properties to return via API, id is always required class HikePublic(mixin.RowIdPublic, HikeBase): pass class HikesPublic(BaseSQLModel): data: list[HikePublic] count: int # endregion # region # Hike / Team ######################################################### class HikeTeamLinkBase( BaseSQLModel, ): hike_id: RowId = Field( nullable=False, foreign_key="hike.id", ) team_id: RowId = Field( nullable=False, foreign_key="team.id", ) route_id: RowId = Field( nullable=False, foreign_key="route.id", ) start_place_id: RowId | None = Field( default=None, nullable=True, foreign_key="place.id", ) # Properties to receive via API on creation class HikeTeamLinkCreate(HikeTeamLinkBase): pass # Properties to receive via API on update, all are optional class HikeTeamLinkUpdate(HikeTeamLinkBase): hike_id: RowId | None = Field(default=None, nullable=True) # type: ignore team_id: RowId | None = Field(default=None, nullable=True) # type: ignore route_id: RowId | None = Field(default=None, nullable=True) # type: ignore class HikeTeamLink(mixin.RowId, HikeTeamLinkBase, table=True): # --- database only items -------------------------------------------------- # --- read only items ------------------------------------------------------ # --- back_populates links ------------------------------------------------- team: "Team" = Relationship(back_populates="hike_links") hike: "Hike" = Relationship(back_populates="teams") route: "Route" = Relationship(back_populates="teams") # start_place: "Place" = Relationship(back_populates="teams") # --- CRUD actions --------------------------------------------------------- @classmethod def create(cls, *, session: Session, create_obj: HikeTeamLinkCreate) -> "HikeTeamLink": data_obj = create_obj.model_dump(exclude_unset=True) db_obj = cls.model_validate(data_obj) session.add(db_obj) session.commit() session.refresh(db_obj) return db_obj @classmethod def update( cls, *, session: Session, db_obj: "HikeTeamLink", in_obj: HikeTeamLinkUpdate ) -> "HikeTeamLink": data_obj = in_obj.model_dump(exclude_unset=True) db_obj.sqlmodel_update(data_obj) session.add(db_obj) session.commit() session.refresh(db_obj) return db_obj # Properties to return via API, id is always required class HikeTeamLinkPublic(mixin.RowIdPublic, HikeTeamLinkBase): pass class HikeTeamLinksPublic(BaseSQLModel): data: list[HikeTeamLinkPublic] count: int # endregion