Finish restructuring files
All checks were successful
Build Formula10 Docker Image / build-docker (push) Successful in 14s
All checks were successful
Build Formula10 Docker Image / build-docker (push) Successful in 14s
This commit is contained in:
@ -1,490 +0,0 @@
|
|||||||
import json
|
|
||||||
from datetime import datetime
|
|
||||||
from typing import Any, List, Dict
|
|
||||||
from urllib.parse import quote
|
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
|
||||||
from sqlalchemy import Integer, String, DateTime, ForeignKey
|
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
||||||
|
|
||||||
db: SQLAlchemy = SQLAlchemy()
|
|
||||||
|
|
||||||
####################################
|
|
||||||
# Static Data (Defined in Backend) #
|
|
||||||
####################################
|
|
||||||
|
|
||||||
|
|
||||||
class Race(db.Model):
|
|
||||||
"""
|
|
||||||
A single race at a certain date and GrandPrix in the calendar.
|
|
||||||
It stores the place to guess for this race.
|
|
||||||
"""
|
|
||||||
__tablename__ = "race"
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_csv(row: List[str]):
|
|
||||||
race: Race = Race()
|
|
||||||
race.name = str(row[0])
|
|
||||||
race.number = int(row[1])
|
|
||||||
race.date = datetime.strptime(row[2], "%Y-%m-%d")
|
|
||||||
race.pxx = int(row[3])
|
|
||||||
return race
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name_sanitized(self) -> str:
|
|
||||||
return quote(self.name)
|
|
||||||
|
|
||||||
name: Mapped[str] = mapped_column(String(64), primary_key=True)
|
|
||||||
number: Mapped[int] = mapped_column(Integer)
|
|
||||||
date: Mapped[datetime] = mapped_column(DateTime)
|
|
||||||
pxx: Mapped[int] = mapped_column(Integer) # This is the place to guess
|
|
||||||
|
|
||||||
|
|
||||||
class Team(db.Model):
|
|
||||||
"""
|
|
||||||
A constructor/team (name only).
|
|
||||||
"""
|
|
||||||
__tablename__ = "team"
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_csv(row: List[str]):
|
|
||||||
team: Team = Team()
|
|
||||||
team.name = str(row[0])
|
|
||||||
return team
|
|
||||||
|
|
||||||
name: Mapped[str] = mapped_column(String(32), primary_key=True)
|
|
||||||
|
|
||||||
|
|
||||||
class Driver(db.Model):
|
|
||||||
"""
|
|
||||||
A F1 driver.
|
|
||||||
It stores the corresponding team + name abbreviation.
|
|
||||||
"""
|
|
||||||
__tablename__ = "driver"
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_csv(row: List[str]):
|
|
||||||
driver: Driver = Driver()
|
|
||||||
driver.name = str(row[0])
|
|
||||||
driver.abbr = str(row[1])
|
|
||||||
driver.team_name = str(row[2])
|
|
||||||
driver.country_code = str(row[3])
|
|
||||||
return driver
|
|
||||||
|
|
||||||
name: Mapped[str] = mapped_column(String(32), primary_key=True)
|
|
||||||
abbr: Mapped[str] = mapped_column(String(4))
|
|
||||||
team_name: Mapped[str] = mapped_column(ForeignKey("team.name"))
|
|
||||||
country_code: Mapped[str] = mapped_column(String(2)) # alpha-2 code
|
|
||||||
|
|
||||||
# Relationships
|
|
||||||
team: Mapped["Team"] = relationship("Team", foreign_keys=[team_name])
|
|
||||||
|
|
||||||
|
|
||||||
######################################
|
|
||||||
# Dynamic Data (Defined in Frontend) #
|
|
||||||
######################################
|
|
||||||
|
|
||||||
|
|
||||||
class User(db.Model):
|
|
||||||
"""
|
|
||||||
A user that can guess races (name only).
|
|
||||||
"""
|
|
||||||
__tablename__ = "user"
|
|
||||||
__csv_header__ = ["name"]
|
|
||||||
|
|
||||||
def __init__(self, name: str):
|
|
||||||
self.name = name # Primary key
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_csv(row: List[str]):
|
|
||||||
user: User = User(str(row[0]))
|
|
||||||
return user
|
|
||||||
|
|
||||||
def to_csv(self) -> List[Any]:
|
|
||||||
return [
|
|
||||||
self.name
|
|
||||||
]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name_sanitized(self) -> str:
|
|
||||||
return quote(self.name)
|
|
||||||
|
|
||||||
name: Mapped[str] = mapped_column(String(32), primary_key=True)
|
|
||||||
|
|
||||||
|
|
||||||
class RaceResult(db.Model):
|
|
||||||
"""
|
|
||||||
The result of a past race.
|
|
||||||
It stores the corresponding race and dictionaries of place-/dnf-order and a list of drivers that are excluded from the standings for this race.
|
|
||||||
"""
|
|
||||||
__tablename__ = "raceresult"
|
|
||||||
__allow_unmapped__ = True # TODO: Used for json conversion, move this to some other class instead
|
|
||||||
__csv_header__ = ["race_name", "pxx_driver_names_json", "first_dnf_driver_names_json", "dnf_driver_names_json", "excluded_driver_names_json"]
|
|
||||||
|
|
||||||
def __init__(self, race_name: str):
|
|
||||||
self.race_name = race_name # Primary key
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_csv(row: List[str]):
|
|
||||||
race_result: RaceResult = RaceResult(str(row[0]))
|
|
||||||
race_result.pxx_driver_names_json = str(row[1])
|
|
||||||
race_result.first_dnf_driver_names_json = str(row[2])
|
|
||||||
race_result.dnf_driver_names_json = str(row[3])
|
|
||||||
race_result.excluded_driver_names_json = str(row[4])
|
|
||||||
return race_result
|
|
||||||
|
|
||||||
def to_csv(self) -> List[Any]:
|
|
||||||
return [
|
|
||||||
self.race_name,
|
|
||||||
self.pxx_driver_names_json,
|
|
||||||
self.first_dnf_driver_names_json,
|
|
||||||
self.dnf_driver_names_json,
|
|
||||||
self.excluded_driver_names_json
|
|
||||||
]
|
|
||||||
|
|
||||||
race_name: Mapped[str] = mapped_column(ForeignKey("race.name"), primary_key=True)
|
|
||||||
pxx_driver_names_json: Mapped[str] = mapped_column(String(1024), nullable=True)
|
|
||||||
first_dnf_driver_names_json: Mapped[str] = mapped_column(String(1024), nullable=True)
|
|
||||||
dnf_driver_names_json: Mapped[str] = mapped_column(String(1024), nullable=True)
|
|
||||||
excluded_driver_names_json: Mapped[str] = mapped_column(String(1024), nullable=True)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def pxx_driver_names(self) -> Dict[str, str]:
|
|
||||||
return json.loads(self.pxx_driver_names_json)
|
|
||||||
|
|
||||||
@pxx_driver_names.setter
|
|
||||||
def pxx_driver_names(self, new_pxx_driver_names: Dict[str, str]):
|
|
||||||
self.pxx_driver_names_json = json.dumps(new_pxx_driver_names)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def first_dnf_driver_names(self) -> List[str]:
|
|
||||||
return json.loads(self.first_dnf_driver_names_json)
|
|
||||||
|
|
||||||
@first_dnf_driver_names.setter
|
|
||||||
def first_dnf_driver_names(self, new_first_dnf_driver_names: List[str]):
|
|
||||||
self.first_dnf_driver_names_json = json.dumps(new_first_dnf_driver_names)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def dnf_driver_names(self) -> List[str]:
|
|
||||||
return json.loads(self.dnf_driver_names_json)
|
|
||||||
|
|
||||||
@dnf_driver_names.setter
|
|
||||||
def dnf_driver_names(self, new_dnf_driver_names: List[str]):
|
|
||||||
self.dnf_driver_names_json = json.dumps(new_dnf_driver_names)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def excluded_driver_names(self) -> List[str]:
|
|
||||||
return json.loads(self.excluded_driver_names_json)
|
|
||||||
|
|
||||||
@excluded_driver_names.setter
|
|
||||||
def excluded_driver_names(self, new_excluded_driver_names: List[str]):
|
|
||||||
self.excluded_driver_names_json = json.dumps(new_excluded_driver_names)
|
|
||||||
|
|
||||||
# Relationships
|
|
||||||
race: Mapped["Race"] = relationship("Race", foreign_keys=[race_name])
|
|
||||||
_pxx_drivers: Dict[str, Driver] | None = None
|
|
||||||
_first_dnf_drivers: List[Driver] | None = None
|
|
||||||
_dnf_drivers: List[Driver] | None = None
|
|
||||||
_excluded_drivers: List[Driver] | None = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def pxx_drivers(self) -> Dict[str, Driver]:
|
|
||||||
if self._pxx_drivers is None:
|
|
||||||
self._pxx_drivers = dict()
|
|
||||||
for position, driver_name in self.pxx_driver_names.items():
|
|
||||||
driver: Driver | None = db.session.query(Driver).filter_by(name=driver_name).first()
|
|
||||||
if driver is None:
|
|
||||||
raise Exception(f"Error: Couldn't find driver with id {driver_name}")
|
|
||||||
|
|
||||||
self._pxx_drivers[position] = driver
|
|
||||||
|
|
||||||
return self._pxx_drivers
|
|
||||||
|
|
||||||
def pxx_driver(self, offset: int = 0) -> Driver | None:
|
|
||||||
pxx_num: str = str(self.race.pxx + offset)
|
|
||||||
|
|
||||||
if pxx_num not in self.pxx_drivers:
|
|
||||||
raise Exception(f"Position {pxx_num} not found in RaceResult.pxx_drivers")
|
|
||||||
|
|
||||||
if self.pxx_drivers[pxx_num].name in self.excluded_driver_names:
|
|
||||||
none_driver: Driver | None = db.session.query(Driver).filter_by(name="None").first()
|
|
||||||
if none_driver is None:
|
|
||||||
raise Exception(f"NONE-driver not found in database")
|
|
||||||
|
|
||||||
return none_driver
|
|
||||||
|
|
||||||
|
|
||||||
return self.pxx_drivers[pxx_num]
|
|
||||||
|
|
||||||
def pxx_driver_position_string(self, driver_name: str) -> str:
|
|
||||||
for position, driver in self.pxx_driver_names.items():
|
|
||||||
if driver == driver_name and driver not in self.excluded_driver_names:
|
|
||||||
return f"P{position}"
|
|
||||||
|
|
||||||
return "NC"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def all_positions(self) -> List[Driver]:
|
|
||||||
return [
|
|
||||||
self.pxx_drivers[str(position)] for position in range(1, 21)
|
|
||||||
]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def first_dnf_drivers(self) -> List[Driver]:
|
|
||||||
if self._first_dnf_drivers is None:
|
|
||||||
self._first_dnf_drivers = list()
|
|
||||||
for driver_name in self.first_dnf_driver_names:
|
|
||||||
driver: Driver | None = db.session.query(Driver).filter_by(name=driver_name).first()
|
|
||||||
if driver is None:
|
|
||||||
raise Exception(f"Error: Couldn't find driver with id {driver_name}")
|
|
||||||
|
|
||||||
self._first_dnf_drivers.append(driver)
|
|
||||||
|
|
||||||
if len(self._first_dnf_drivers) == 0:
|
|
||||||
none_driver: Driver | None = db.session.query(Driver).filter_by(name="None").first()
|
|
||||||
if none_driver is None:
|
|
||||||
raise Exception("NONE-driver not found in database")
|
|
||||||
|
|
||||||
self._first_dnf_drivers.append(none_driver)
|
|
||||||
|
|
||||||
return self._first_dnf_drivers
|
|
||||||
|
|
||||||
@property
|
|
||||||
def dnf_drivers(self) -> List[Driver]:
|
|
||||||
if self._dnf_drivers is None:
|
|
||||||
self._dnf_drivers = list()
|
|
||||||
for driver_name in self.dnf_driver_names:
|
|
||||||
driver: Driver | None = db.session.query(Driver).filter_by(name=driver_name).first()
|
|
||||||
if driver is None:
|
|
||||||
raise Exception(f"Error: Couldn't find driver with id {driver_name}")
|
|
||||||
|
|
||||||
self._dnf_drivers.append(driver)
|
|
||||||
|
|
||||||
return self._dnf_drivers
|
|
||||||
|
|
||||||
@property
|
|
||||||
def excluded_drivers(self) -> List[Driver]:
|
|
||||||
if self._excluded_drivers is None:
|
|
||||||
self._excluded_drivers = list()
|
|
||||||
for driver_name in self.excluded_driver_names:
|
|
||||||
driver: Driver | None = db.session.query(Driver).filter_by(name=driver_name).first()
|
|
||||||
if driver is None:
|
|
||||||
raise Exception(f"Error: Couldn't find driver with id {driver_name}")
|
|
||||||
|
|
||||||
self._excluded_drivers.append(driver)
|
|
||||||
|
|
||||||
return self._excluded_drivers
|
|
||||||
|
|
||||||
|
|
||||||
class RaceGuess(db.Model):
|
|
||||||
"""
|
|
||||||
A guess a user made for a race.
|
|
||||||
It stores the corresponding race and the guessed drivers for PXX and DNF.
|
|
||||||
"""
|
|
||||||
__tablename__ = "raceguess"
|
|
||||||
__csv_header__ = ["user_name", "race_name", "pxx_driver_name", "dnf_driver_name"]
|
|
||||||
|
|
||||||
def __init__(self, user_name: str, race_name: str):
|
|
||||||
self.user_name = user_name # Primary key
|
|
||||||
self.race_name = race_name # Primery key
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_csv(row: List[str]):
|
|
||||||
race_guess: RaceGuess = RaceGuess(str(row[0]), str(row[1]))
|
|
||||||
race_guess.pxx_driver_name = str(row[2])
|
|
||||||
race_guess.dnf_driver_name = str(row[3])
|
|
||||||
return race_guess
|
|
||||||
|
|
||||||
def to_csv(self) -> List[Any]:
|
|
||||||
return [
|
|
||||||
self.user_name,
|
|
||||||
self.race_name,
|
|
||||||
self.pxx_driver_name,
|
|
||||||
self.dnf_driver_name
|
|
||||||
]
|
|
||||||
|
|
||||||
user_name: Mapped[str] = mapped_column(ForeignKey("user.name"), primary_key=True)
|
|
||||||
race_name: Mapped[str] = mapped_column(ForeignKey("race.name"), primary_key=True)
|
|
||||||
pxx_driver_name: Mapped[str] = mapped_column(ForeignKey("driver.name"), nullable=True)
|
|
||||||
dnf_driver_name: Mapped[str] = mapped_column(ForeignKey("driver.name"), nullable=True)
|
|
||||||
|
|
||||||
# Relationships
|
|
||||||
user: Mapped["User"] = relationship("User", foreign_keys=[user_name])
|
|
||||||
race: Mapped["Race"] = relationship("Race", foreign_keys=[race_name])
|
|
||||||
pxx: Mapped["Driver"] = relationship("Driver", foreign_keys=[pxx_driver_name])
|
|
||||||
dnf: Mapped["Driver"] = relationship("Driver", foreign_keys=[dnf_driver_name])
|
|
||||||
|
|
||||||
|
|
||||||
class TeamWinners(db.Model):
|
|
||||||
"""
|
|
||||||
A guessed list of each best driver per team.
|
|
||||||
"""
|
|
||||||
__tablename__ = "teamwinners"
|
|
||||||
__allow_unmapped__ = True
|
|
||||||
__csv_header__ = ["user_name", "teamwinner_driver_names_json"]
|
|
||||||
|
|
||||||
def __init__(self, user_name: str):
|
|
||||||
self.user_name = user_name # Primary key
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_csv(row: List[str]):
|
|
||||||
team_winners: TeamWinners = TeamWinners(str(row[0]))
|
|
||||||
team_winners.teamwinner_driver_names_json = str(row[1])
|
|
||||||
return team_winners
|
|
||||||
|
|
||||||
def to_csv(self) -> List[Any]:
|
|
||||||
return [
|
|
||||||
self.user_name,
|
|
||||||
self.teamwinner_driver_names_json
|
|
||||||
]
|
|
||||||
|
|
||||||
user_name: Mapped[str] = mapped_column(ForeignKey("user.name"), primary_key=True)
|
|
||||||
teamwinner_driver_names_json: Mapped[str] = mapped_column(String(1024), nullable=True)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def teamwinner_driver_names(self) -> List[str]:
|
|
||||||
return json.loads(self.teamwinner_driver_names_json)
|
|
||||||
|
|
||||||
@teamwinner_driver_names.setter
|
|
||||||
def teamwinner_driver_names(self, new_teamwinner_driver_names: List[str]):
|
|
||||||
self.teamwinner_driver_names_json = json.dumps(new_teamwinner_driver_names)
|
|
||||||
|
|
||||||
# Relationships
|
|
||||||
user: Mapped["User"] = relationship("User", foreign_keys=[user_name])
|
|
||||||
_teamwinner_drivers: List[Driver] | None = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def teamwinners(self) -> List[Driver]:
|
|
||||||
if self._teamwinner_drivers is None:
|
|
||||||
self._teamwinner_drivers = list()
|
|
||||||
for driver_name in self.teamwinner_driver_names:
|
|
||||||
driver: Driver | None = db.session.query(Driver).filter_by(name=driver_name).first()
|
|
||||||
if driver is None:
|
|
||||||
raise Exception(f"Error: Couldn't find driver with id {driver_name}")
|
|
||||||
|
|
||||||
self._teamwinner_drivers.append(driver)
|
|
||||||
|
|
||||||
return self._teamwinner_drivers
|
|
||||||
|
|
||||||
|
|
||||||
class PodiumDrivers(db.Model):
|
|
||||||
"""
|
|
||||||
A guessed list of each driver that will reach at least a single podium.
|
|
||||||
"""
|
|
||||||
__tablename__ = "podiumdrivers"
|
|
||||||
__allow_unmapped__ = True
|
|
||||||
__csv_header__ = ["user_name", "podium_driver_names_json"]
|
|
||||||
|
|
||||||
def __init__(self, user_name: str):
|
|
||||||
self.user_name = user_name
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_csv(row: List[str]):
|
|
||||||
podium_drivers: PodiumDrivers = PodiumDrivers(str(row[0]))
|
|
||||||
podium_drivers.podium_driver_names_json = str(row[1])
|
|
||||||
return podium_drivers
|
|
||||||
|
|
||||||
def to_csv(self) -> List[Any]:
|
|
||||||
return [
|
|
||||||
self.user_name,
|
|
||||||
self.podium_driver_names_json
|
|
||||||
]
|
|
||||||
|
|
||||||
user_name: Mapped[str] = mapped_column(ForeignKey("user.name"), primary_key=True)
|
|
||||||
podium_driver_names_json: Mapped[str] = mapped_column(String(1024), nullable=True)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def podium_driver_names(self) -> List[str]:
|
|
||||||
return json.loads(self.podium_driver_names_json)
|
|
||||||
|
|
||||||
@podium_driver_names.setter
|
|
||||||
def podium_driver_names(self, new_podium_driver_names: List[str]):
|
|
||||||
self.podium_driver_names_json = json.dumps(new_podium_driver_names)
|
|
||||||
|
|
||||||
# Relationships
|
|
||||||
user: Mapped["User"] = relationship("User", foreign_keys=[user_name])
|
|
||||||
_podium_drivers: List[Driver] | None = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def podium_drivers(self) -> List[Driver]:
|
|
||||||
if self._podium_drivers is None:
|
|
||||||
self._podium_drivers = list()
|
|
||||||
for driver_name in self.podium_driver_names:
|
|
||||||
driver: Driver | None = db.session.query(Driver).filter_by(name=driver_name).first()
|
|
||||||
if driver is None:
|
|
||||||
raise Exception(f"Error: Couldn't find driver with id {driver_name}")
|
|
||||||
|
|
||||||
self._podium_drivers.append(driver)
|
|
||||||
|
|
||||||
return self._podium_drivers
|
|
||||||
|
|
||||||
|
|
||||||
class SeasonGuess(db.Model):
|
|
||||||
"""
|
|
||||||
A collection of bonus guesses for the entire season.
|
|
||||||
"""
|
|
||||||
__tablename__ = "seasonguess"
|
|
||||||
__csv_header__ = ["user_name", "hot_take", "p2_team_name",
|
|
||||||
"overtake_driver_name", "dnf_driver_name", "gained_driver_name", "lost_driver_name",
|
|
||||||
"team_winners_id", "podium_drivers_id"]
|
|
||||||
|
|
||||||
def __init__(self, user_name: str, team_winners_user_name: str | None = None, podium_drivers_user_name: str | None = None):
|
|
||||||
self.user_name = user_name # Primary key
|
|
||||||
|
|
||||||
# Although this is the same username, handle separately, in case they don't exist in the database yet
|
|
||||||
if team_winners_user_name is not None:
|
|
||||||
if user_name != team_winners_user_name:
|
|
||||||
raise Exception(f"SeasonGuess for {user_name} was supplied TeamWinners for {team_winners_user_name}")
|
|
||||||
|
|
||||||
self.team_winners_id = team_winners_user_name
|
|
||||||
|
|
||||||
if podium_drivers_user_name is not None:
|
|
||||||
if user_name != podium_drivers_user_name:
|
|
||||||
raise Exception(f"SeasonGuess for {user_name} was supplied PodiumDrivers for {podium_drivers_user_name}")
|
|
||||||
|
|
||||||
self.podium_drivers_id = podium_drivers_user_name
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_csv(row: List[str]):
|
|
||||||
season_guess: SeasonGuess = SeasonGuess(str(row[0]), team_winners_user_name=str(row[7]), podium_drivers_user_name=str(row[8]))
|
|
||||||
season_guess.hot_take = str(row[1])
|
|
||||||
season_guess.p2_team_name = str(row[2])
|
|
||||||
season_guess.overtake_driver_name = str(row[3])
|
|
||||||
season_guess.dnf_driver_name = str(row[4])
|
|
||||||
season_guess.gained_driver_name = str(row[5])
|
|
||||||
season_guess.lost_driver_name = str(row[6])
|
|
||||||
return season_guess
|
|
||||||
|
|
||||||
def to_csv(self) -> List[Any]:
|
|
||||||
return [
|
|
||||||
self.user_name,
|
|
||||||
self.hot_take,
|
|
||||||
self.p2_team_name,
|
|
||||||
self.overtake_driver_name,
|
|
||||||
self.dnf_driver_name,
|
|
||||||
self.gained_driver_name,
|
|
||||||
self.lost_driver_name,
|
|
||||||
self.team_winners_id,
|
|
||||||
self.podium_drivers_id
|
|
||||||
]
|
|
||||||
|
|
||||||
user_name: Mapped[str] = mapped_column(ForeignKey("user.name"), primary_key=True)
|
|
||||||
hot_take: Mapped[str] = mapped_column(String(512), nullable=True)
|
|
||||||
p2_team_name: Mapped[str] = mapped_column(ForeignKey("team.name"), nullable=True)
|
|
||||||
overtake_driver_name: Mapped[str] = mapped_column(ForeignKey("driver.name"), nullable=True)
|
|
||||||
dnf_driver_name: Mapped[str] = mapped_column(ForeignKey("driver.name"), nullable=True)
|
|
||||||
gained_driver_name: Mapped[str] = mapped_column(ForeignKey("driver.name"), nullable=True)
|
|
||||||
lost_driver_name: Mapped[str] = mapped_column(ForeignKey("driver.name"), nullable=True)
|
|
||||||
|
|
||||||
team_winners_id: Mapped[str] = mapped_column(ForeignKey("teamwinners.user_name"))
|
|
||||||
podium_drivers_id: Mapped[str] = mapped_column(ForeignKey("podiumdrivers.user_name"))
|
|
||||||
|
|
||||||
# Relationships
|
|
||||||
user: Mapped["User"] = relationship("User", foreign_keys=[user_name])
|
|
||||||
p2_team: Mapped["Team"] = relationship("Team", foreign_keys=[p2_team_name])
|
|
||||||
overtake_driver: Mapped["Driver"] = relationship("Driver", foreign_keys=[overtake_driver_name])
|
|
||||||
dnf_driver: Mapped["Driver"] = relationship("Driver", foreign_keys=[dnf_driver_name])
|
|
||||||
gained_driver: Mapped["Driver"] = relationship("Driver", foreign_keys=[gained_driver_name])
|
|
||||||
lost_driver: Mapped["Driver"] = relationship("Driver", foreign_keys=[lost_driver_name])
|
|
||||||
|
|
||||||
team_winners: Mapped["TeamWinners"] = relationship("TeamWinners", foreign_keys=[team_winners_id])
|
|
||||||
podium_drivers: Mapped["PodiumDrivers"] = relationship("PodiumDrivers", foreign_keys=[podium_drivers_id])
|
|
@ -1,174 +0,0 @@
|
|||||||
from typing import List
|
|
||||||
from urllib.parse import unquote
|
|
||||||
from flask import Flask, render_template, request, redirect
|
|
||||||
from werkzeug import Response
|
|
||||||
from app.database.model import Team, db
|
|
||||||
from app.database.file_utils import reload_static_data, reload_dynamic_data, export_dynamic_data
|
|
||||||
from app.frontend.template_model import TemplateModel
|
|
||||||
from app.database.backend_model import delete_race_guess, update_race_guess, update_race_result, update_season_guess, update_user
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
|
|
||||||
app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:///formula10.db"
|
|
||||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
|
||||||
app.url_map.strict_slashes = False
|
|
||||||
|
|
||||||
db.init_app(app)
|
|
||||||
|
|
||||||
|
|
||||||
# TODO
|
|
||||||
# General
|
|
||||||
|
|
||||||
# - Choose "place to guess" late before the race? Make a page for this
|
|
||||||
# - Rules page
|
|
||||||
|
|
||||||
# - Make user order changeable using drag'n'drop?
|
|
||||||
# - Show place when entering race result (would require updating the drag'n'drop code...)
|
|
||||||
# - Show cards of previous race results, like with season guesses?
|
|
||||||
# - Make the season card grid left-aligned? So e.g. 2 cards are not spread over the whole screen with large gaps?
|
|
||||||
|
|
||||||
# Statistics
|
|
||||||
# - Auto calculate points
|
|
||||||
# - Order user table by points + display points somewhere
|
|
||||||
# - Show current values for some season guesses (e.g. current most dnfs)
|
|
||||||
# - Generate static diagram using chart.js + templating the js (funny yikes)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
def root() -> Response:
|
|
||||||
return redirect("/race/Everyone")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/save/all")
|
|
||||||
def save() -> Response:
|
|
||||||
export_dynamic_data()
|
|
||||||
return redirect("/")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/load/all")
|
|
||||||
def load() -> Response:
|
|
||||||
reload_static_data()
|
|
||||||
reload_dynamic_data()
|
|
||||||
return redirect("/")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/load/static")
|
|
||||||
def load_static() -> Response:
|
|
||||||
reload_static_data()
|
|
||||||
return redirect("/")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/load/dynamic")
|
|
||||||
def load_dynamic() -> Response:
|
|
||||||
reload_dynamic_data()
|
|
||||||
return redirect("/")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/race")
|
|
||||||
def race_root() -> Response:
|
|
||||||
return redirect("/race/Everyone")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/race/<user_name>")
|
|
||||||
def race_active_user(user_name: str) -> str:
|
|
||||||
user_name = unquote(user_name)
|
|
||||||
model = TemplateModel()
|
|
||||||
return render_template("race.jinja",
|
|
||||||
active_user=model.user_by(user_name=user_name, ignore=["Everyone"]),
|
|
||||||
model=model)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/race-guess/<race_name>/<user_name>", methods=["POST"])
|
|
||||||
def race_guess_post(race_name: str, user_name: str) -> Response:
|
|
||||||
race_name = unquote(race_name)
|
|
||||||
user_name = unquote(user_name)
|
|
||||||
|
|
||||||
pxx: str | None = request.form.get("pxxselect")
|
|
||||||
dnf: str | None = request.form.get("dnfselect")
|
|
||||||
|
|
||||||
return update_race_guess(race_name, user_name, pxx, dnf)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/race-guess-delete/<race_name>/<user_name>", methods=["POST"])
|
|
||||||
def race_guess_delete_post(race_name: str, user_name: str) -> Response:
|
|
||||||
race_name = unquote(race_name)
|
|
||||||
user_name = unquote(user_name)
|
|
||||||
|
|
||||||
return delete_race_guess(race_name, user_name)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/season")
|
|
||||||
def season_root() -> Response:
|
|
||||||
return redirect("/season/Everyone")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/season/<user_name>")
|
|
||||||
def season_active_user(user_name: str) -> str:
|
|
||||||
user_name = unquote(user_name)
|
|
||||||
model = TemplateModel()
|
|
||||||
return render_template("season.jinja",
|
|
||||||
active_user=model.user_by(user_name=user_name, ignore=["Everyone"]),
|
|
||||||
model=model)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/season-guess/<user_name>", methods=["POST"])
|
|
||||||
def season_guess_post(user_name: str) -> Response:
|
|
||||||
user_name = unquote(user_name)
|
|
||||||
guesses: List[str | None] = [
|
|
||||||
request.form.get("hottakeselect"),
|
|
||||||
request.form.get("p2select"),
|
|
||||||
request.form.get("overtakeselect"),
|
|
||||||
request.form.get("dnfselect"),
|
|
||||||
request.form.get("gainedselect"),
|
|
||||||
request.form.get("lostselect")
|
|
||||||
]
|
|
||||||
team_winner_guesses: List[str | None] = [
|
|
||||||
request.form.get(f"teamwinner-{team.name}") for team in db.session.query(Team).all()
|
|
||||||
]
|
|
||||||
podium_driver_guesses: List[str] = request.form.getlist("podiumdrivers")
|
|
||||||
|
|
||||||
return update_season_guess(user_name, guesses, team_winner_guesses, podium_driver_guesses)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/result")
|
|
||||||
def result_root() -> Response:
|
|
||||||
return redirect("/result/Current")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/result/<race_name>")
|
|
||||||
def result_active_race(race_name: str) -> str:
|
|
||||||
race_name = unquote(race_name)
|
|
||||||
model = TemplateModel()
|
|
||||||
return render_template("enter.jinja",
|
|
||||||
active_result=model.race_result_by(race_name=race_name),
|
|
||||||
model=model)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/result-enter/<race_name>", methods=["POST"])
|
|
||||||
def result_enter_post(race_name: str) -> Response:
|
|
||||||
race_name = unquote(race_name)
|
|
||||||
pxxs: List[str] = request.form.getlist("pxx-drivers")
|
|
||||||
first_dnfs: List[str] = request.form.getlist("first-dnf-drivers")
|
|
||||||
dnfs: List[str] = request.form.getlist("dnf-drivers")
|
|
||||||
excluded: List[str] = request.form.getlist("excluded-drivers")
|
|
||||||
|
|
||||||
return update_race_result(race_name, pxxs, first_dnfs, dnfs, excluded)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/user")
|
|
||||||
def user_root() -> str:
|
|
||||||
model = TemplateModel()
|
|
||||||
return render_template("users.jinja",
|
|
||||||
model=model)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/user-add", methods=["POST"])
|
|
||||||
def user_add_post() -> Response:
|
|
||||||
username: str | None = request.form.get("select-add-user")
|
|
||||||
return update_user(username, add=True)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/user-delete", methods=["POST"])
|
|
||||||
def user_delete_post() -> Response:
|
|
||||||
username: str | None = request.form.get("select-delete-user")
|
|
||||||
return update_user(username, delete=True)
|
|
8
data/dynamic_export/users.csv
Normal file
8
data/dynamic_export/users.csv
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
name
|
||||||
|
Christoph
|
||||||
|
Angela Merkel
|
||||||
|
Xi Jinping
|
||||||
|
Donald Trump
|
||||||
|
Joe Biden
|
||||||
|
Henri
|
||||||
|
Vinzent
|
|
@ -1,4 +0,0 @@
|
|||||||
from app.frontend.controller import app
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
app.run(debug=True, host="0.0.0.0")
|
|
41
formula10/__init__.py
Normal file
41
formula10/__init__.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
from flask import Flask
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
|
||||||
|
app: Flask = Flask(__name__)
|
||||||
|
app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:///formula10.db"
|
||||||
|
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||||
|
app.url_map.strict_slashes = False
|
||||||
|
|
||||||
|
db: SQLAlchemy = SQLAlchemy()
|
||||||
|
db.init_app(app)
|
||||||
|
|
||||||
|
# NOTE: These imports are required to register the routes. They need to be after "app" is declared
|
||||||
|
import formula10.controller.race_controller # type: ignore
|
||||||
|
import formula10.controller.season_controller # type: ignore
|
||||||
|
import formula10.controller.admin_controller # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
# General
|
||||||
|
|
||||||
|
# Split the database model from the frontend-model/template-model/domain-model
|
||||||
|
# - Move most of the template logic into this
|
||||||
|
# - Allow exclusion of e.g. most-gained driver and other stuff
|
||||||
|
|
||||||
|
# - Choose "place to guess" late before the race? Make a page for this
|
||||||
|
# - Rules page
|
||||||
|
|
||||||
|
# - Make user order changeable using drag'n'drop?
|
||||||
|
# - Show place when entering race result (would require updating the drag'n'drop code...)
|
||||||
|
# - Show cards of previous race results, like with season guesses?
|
||||||
|
# - Make the season card grid left-aligned? So e.g. 2 cards are not spread over the whole screen with large gaps?
|
||||||
|
|
||||||
|
# Statistics
|
||||||
|
# - Auto calculate points
|
||||||
|
# - Order user table by points + display points somewhere
|
||||||
|
# - Show current values for some season guesses (e.g. current most dnfs)
|
||||||
|
# - Generate static diagram using chart.js + templating the js (funny yikes)
|
||||||
|
|
||||||
|
|
||||||
|
# if __name__ == "__main__":
|
||||||
|
# app.run(debug=True, host="0.0.0.0")
|
78
formula10/controller/admin_controller.py
Normal file
78
formula10/controller/admin_controller.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
from typing import List
|
||||||
|
from urllib.parse import unquote
|
||||||
|
from flask import redirect, render_template, request
|
||||||
|
from werkzeug import Response
|
||||||
|
|
||||||
|
from formula10.database.update_query_util import update_race_result, update_user
|
||||||
|
from formula10.database.import_export_util import export_dynamic_data, reload_dynamic_data, reload_static_data
|
||||||
|
from formula10.frontend.template_model import TemplateModel
|
||||||
|
from formula10 import app
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/save/all")
|
||||||
|
def save() -> Response:
|
||||||
|
export_dynamic_data()
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/load/all")
|
||||||
|
def load() -> Response:
|
||||||
|
reload_static_data()
|
||||||
|
reload_dynamic_data()
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/load/static")
|
||||||
|
def load_static() -> Response:
|
||||||
|
reload_static_data()
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/load/dynamic")
|
||||||
|
def load_dynamic() -> Response:
|
||||||
|
reload_dynamic_data()
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/result")
|
||||||
|
def result_root() -> Response:
|
||||||
|
return redirect("/result/Current")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/result/<race_name>")
|
||||||
|
def result_active_race(race_name: str) -> str:
|
||||||
|
race_name = unquote(race_name)
|
||||||
|
model = TemplateModel()
|
||||||
|
return render_template("enter.jinja",
|
||||||
|
active_result=model.race_result_by(race_name=race_name),
|
||||||
|
model=model)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/result-enter/<race_name>", methods=["POST"])
|
||||||
|
def result_enter_post(race_name: str) -> Response:
|
||||||
|
race_name = unquote(race_name)
|
||||||
|
pxxs: List[str] = request.form.getlist("pxx-drivers")
|
||||||
|
first_dnfs: List[str] = request.form.getlist("first-dnf-drivers")
|
||||||
|
dnfs: List[str] = request.form.getlist("dnf-drivers")
|
||||||
|
excluded: List[str] = request.form.getlist("excluded-drivers")
|
||||||
|
|
||||||
|
return update_race_result(race_name, pxxs, first_dnfs, dnfs, excluded)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/user")
|
||||||
|
def user_root() -> str:
|
||||||
|
model = TemplateModel()
|
||||||
|
return render_template("users.jinja",
|
||||||
|
model=model)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/user-add", methods=["POST"])
|
||||||
|
def user_add_post() -> Response:
|
||||||
|
username: str | None = request.form.get("select-add-user")
|
||||||
|
return update_user(username, add=True)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/user-delete", methods=["POST"])
|
||||||
|
def user_delete_post() -> Response:
|
||||||
|
username: str | None = request.form.get("select-delete-user")
|
||||||
|
return update_user(username, delete=True)
|
45
formula10/controller/race_controller.py
Normal file
45
formula10/controller/race_controller.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
from urllib.parse import unquote
|
||||||
|
from flask import redirect, render_template, request
|
||||||
|
from werkzeug import Response
|
||||||
|
|
||||||
|
from formula10.database.update_query_util import delete_race_guess, update_race_guess
|
||||||
|
from formula10.frontend.template_model import TemplateModel
|
||||||
|
from formula10 import app
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def root() -> Response:
|
||||||
|
return redirect("/race/Everyone")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/race")
|
||||||
|
def race_root() -> Response:
|
||||||
|
return redirect("/race/Everyone")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/race/<user_name>")
|
||||||
|
def race_active_user(user_name: str) -> str:
|
||||||
|
user_name = unquote(user_name)
|
||||||
|
model = TemplateModel()
|
||||||
|
return render_template("race.jinja",
|
||||||
|
active_user=model.user_by(user_name=user_name, ignore=["Everyone"]),
|
||||||
|
model=model)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/race-guess/<race_name>/<user_name>", methods=["POST"])
|
||||||
|
def race_guess_post(race_name: str, user_name: str) -> Response:
|
||||||
|
race_name = unquote(race_name)
|
||||||
|
user_name = unquote(user_name)
|
||||||
|
|
||||||
|
pxx: str | None = request.form.get("pxxselect")
|
||||||
|
dnf: str | None = request.form.get("dnfselect")
|
||||||
|
|
||||||
|
return update_race_guess(race_name, user_name, pxx, dnf)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/race-guess-delete/<race_name>/<user_name>", methods=["POST"])
|
||||||
|
def race_guess_delete_post(race_name: str, user_name: str) -> Response:
|
||||||
|
race_name = unquote(race_name)
|
||||||
|
user_name = unquote(user_name)
|
||||||
|
|
||||||
|
return delete_race_guess(race_name, user_name)
|
43
formula10/controller/season_controller.py
Normal file
43
formula10/controller/season_controller.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
from typing import List
|
||||||
|
from urllib.parse import unquote
|
||||||
|
from flask import redirect, render_template, request
|
||||||
|
from werkzeug import Response
|
||||||
|
|
||||||
|
from formula10.database.model.team import Team
|
||||||
|
from formula10.database.update_query_util import update_season_guess
|
||||||
|
from formula10.frontend.template_model import TemplateModel
|
||||||
|
from formula10 import app, db
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/season")
|
||||||
|
def season_root() -> Response:
|
||||||
|
return redirect("/season/Everyone")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/season/<user_name>")
|
||||||
|
def season_active_user(user_name: str) -> str:
|
||||||
|
user_name = unquote(user_name)
|
||||||
|
model = TemplateModel()
|
||||||
|
return render_template("season.jinja",
|
||||||
|
active_user=model.user_by(user_name=user_name, ignore=["Everyone"]),
|
||||||
|
model=model)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/season-guess/<user_name>", methods=["POST"])
|
||||||
|
def season_guess_post(user_name: str) -> Response:
|
||||||
|
user_name = unquote(user_name)
|
||||||
|
guesses: List[str | None] = [
|
||||||
|
request.form.get("hottakeselect"),
|
||||||
|
request.form.get("p2select"),
|
||||||
|
request.form.get("overtakeselect"),
|
||||||
|
request.form.get("dnfselect"),
|
||||||
|
request.form.get("gainedselect"),
|
||||||
|
request.form.get("lostselect")
|
||||||
|
]
|
||||||
|
# TODO: This is pretty ugly, to do queries in the controller
|
||||||
|
team_winner_guesses: List[str | None] = [
|
||||||
|
request.form.get(f"teamwinner-{team.name}") for team in db.session.query(Team).all()
|
||||||
|
]
|
||||||
|
podium_driver_guesses: List[str] = request.form.getlist("podiumdrivers")
|
||||||
|
|
||||||
|
return update_season_guess(user_name, guesses, team_winner_guesses, podium_driver_guesses)
|
@ -1,5 +1,6 @@
|
|||||||
from app.database.model import User, db, RaceResult
|
from formula10.database.model.race_result import RaceResult
|
||||||
|
from formula10.database.model.user import User
|
||||||
|
from formula10 import db
|
||||||
|
|
||||||
def race_has_result(race_name: str) -> bool:
|
def race_has_result(race_name: str) -> bool:
|
||||||
return db.session.query(RaceResult).filter_by(race_name=race_name).first() is not None
|
return db.session.query(RaceResult).filter_by(race_name=race_name).first() is not None
|
@ -1,7 +1,17 @@
|
|||||||
import csv
|
import csv
|
||||||
import os.path
|
import os.path
|
||||||
from typing import List, Any
|
from typing import List, Any
|
||||||
from app.database.model import Team, Driver, Race, User, RaceResult, RaceGuess, TeamWinners, PodiumDrivers, SeasonGuess, db
|
|
||||||
|
from formula10.database.model.driver import Driver
|
||||||
|
from formula10.database.model.podium_drivers import PodiumDrivers
|
||||||
|
from formula10.database.model.race import Race
|
||||||
|
from formula10.database.model.race_guess import RaceGuess
|
||||||
|
from formula10.database.model.race_result import RaceResult
|
||||||
|
from formula10.database.model.season_guess import SeasonGuess
|
||||||
|
from formula10.database.model.team import Team
|
||||||
|
from formula10.database.model.team_winners import TeamWinners
|
||||||
|
from formula10.database.model.user import User
|
||||||
|
from formula10 import db
|
||||||
|
|
||||||
|
|
||||||
def load_csv(filename: str) -> List[List[str]]:
|
def load_csv(filename: str) -> List[List[str]]:
|
||||||
@ -39,11 +49,11 @@ def reload_static_data():
|
|||||||
db.session.query(Race).delete()
|
db.session.query(Race).delete()
|
||||||
|
|
||||||
# Reload static data
|
# Reload static data
|
||||||
for row in load_csv("static_data/teams.csv"):
|
for row in load_csv("../../data/static_import/teams.csv"):
|
||||||
db.session.add(Team.from_csv(row))
|
db.session.add(Team.from_csv(row))
|
||||||
for row in load_csv("static_data/drivers.csv"):
|
for row in load_csv("../../data/static_import/drivers.csv"):
|
||||||
db.session.add(Driver.from_csv(row))
|
db.session.add(Driver.from_csv(row))
|
||||||
for row in load_csv("static_data/races.csv"):
|
for row in load_csv("../../data/static_import/races.csv"):
|
||||||
db.session.add(Race.from_csv(row))
|
db.session.add(Race.from_csv(row))
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@ -63,17 +73,17 @@ def reload_dynamic_data():
|
|||||||
db.session.query(SeasonGuess).delete()
|
db.session.query(SeasonGuess).delete()
|
||||||
|
|
||||||
# Reload dynamic data
|
# Reload dynamic data
|
||||||
for row in load_csv("dynamic_data/users.csv"):
|
for row in load_csv("../../data/dynamic_export/users.csv"):
|
||||||
db.session.add(User.from_csv(row))
|
db.session.add(User.from_csv(row))
|
||||||
for row in load_csv("dynamic_data/raceresults.csv"):
|
for row in load_csv("../../data/dynamic_export/raceresults.csv"):
|
||||||
db.session.add(RaceResult.from_csv(row))
|
db.session.add(RaceResult.from_csv(row))
|
||||||
for row in load_csv("dynamic_data/raceguesses.csv"):
|
for row in load_csv("../../data/dynamic_export/raceguesses.csv"):
|
||||||
db.session.add(RaceGuess.from_csv(row))
|
db.session.add(RaceGuess.from_csv(row))
|
||||||
for row in load_csv("dynamic_data/teamwinners.csv"):
|
for row in load_csv("../../data/dynamic_export/teamwinners.csv"):
|
||||||
db.session.add(TeamWinners.from_csv(row))
|
db.session.add(TeamWinners.from_csv(row))
|
||||||
for row in load_csv("dynamic_data/podiumdrivers.csv"):
|
for row in load_csv("../../data/dynamic_export/podiumdrivers.csv"):
|
||||||
db.session.add(PodiumDrivers.from_csv(row))
|
db.session.add(PodiumDrivers.from_csv(row))
|
||||||
for row in load_csv("dynamic_data/seasonguesses.csv"):
|
for row in load_csv("../../data/dynamic_export/seasonguesses.csv"):
|
||||||
db.session.add(SeasonGuess.from_csv(row))
|
db.session.add(SeasonGuess.from_csv(row))
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@ -89,9 +99,9 @@ def export_dynamic_data():
|
|||||||
podiumdrivers: List[PodiumDrivers] = db.session.query(PodiumDrivers).all()
|
podiumdrivers: List[PodiumDrivers] = db.session.query(PodiumDrivers).all()
|
||||||
seasonguesses: List[SeasonGuess] = db.session.query(SeasonGuess).all()
|
seasonguesses: List[SeasonGuess] = db.session.query(SeasonGuess).all()
|
||||||
|
|
||||||
write_csv("dynamic_data/users.csv", users)
|
write_csv("../../data/dynamic_export/users.csv", users)
|
||||||
write_csv("dynamic_data/raceresults.csv", raceresults)
|
write_csv("../../data/dynamic_export/raceresults.csv", raceresults)
|
||||||
write_csv("dynamic_data/raceguesses.csv", raceguesses)
|
write_csv("../../data/dynamic_export/raceguesses.csv", raceguesses)
|
||||||
write_csv("dynamic_data/teamwinners.csv", teamwinners)
|
write_csv("../../data/dynamic_export/teamwinners.csv", teamwinners)
|
||||||
write_csv("dynamic_data/podiumdrivers.csv", podiumdrivers)
|
write_csv("../../data/dynamic_export/podiumdrivers.csv", podiumdrivers)
|
||||||
write_csv("dynamic_data/seasonguesses.csv", seasonguesses)
|
write_csv("../../data/dynamic_export/seasonguesses.csv", seasonguesses)
|
31
formula10/database/model/driver.py
Normal file
31
formula10/database/model/driver.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
from typing import List
|
||||||
|
from sqlalchemy import String, ForeignKey
|
||||||
|
from sqlalchemy.orm import mapped_column, Mapped, relationship
|
||||||
|
|
||||||
|
from formula10.database.model.team import Team
|
||||||
|
from formula10 import db
|
||||||
|
|
||||||
|
|
||||||
|
class Driver(db.Model):
|
||||||
|
"""
|
||||||
|
A F1 driver.
|
||||||
|
It stores the corresponding team + name abbreviation.
|
||||||
|
"""
|
||||||
|
__tablename__ = "driver"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_csv(row: List[str]):
|
||||||
|
driver: Driver = Driver()
|
||||||
|
driver.name = str(row[0])
|
||||||
|
driver.abbr = str(row[1])
|
||||||
|
driver.team_name = str(row[2])
|
||||||
|
driver.country_code = str(row[3])
|
||||||
|
return driver
|
||||||
|
|
||||||
|
name: Mapped[str] = mapped_column(String(32), primary_key=True)
|
||||||
|
abbr: Mapped[str] = mapped_column(String(4))
|
||||||
|
team_name: Mapped[str] = mapped_column(ForeignKey("team.name"))
|
||||||
|
country_code: Mapped[str] = mapped_column(String(2)) # alpha-2 code
|
||||||
|
|
||||||
|
# Relationships
|
||||||
|
team: Mapped["Team"] = relationship("Team", foreign_keys=[team_name])
|
60
formula10/database/model/podium_drivers.py
Normal file
60
formula10/database/model/podium_drivers.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import json
|
||||||
|
from typing import Any, List
|
||||||
|
from sqlalchemy import ForeignKey, String
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
|
from formula10.database.model.user import User
|
||||||
|
from formula10.database.model.driver import Driver
|
||||||
|
from formula10 import db
|
||||||
|
|
||||||
|
|
||||||
|
class PodiumDrivers(db.Model):
|
||||||
|
"""
|
||||||
|
A guessed list of each driver that will reach at least a single podium.
|
||||||
|
"""
|
||||||
|
__tablename__ = "podiumdrivers"
|
||||||
|
__allow_unmapped__ = True
|
||||||
|
__csv_header__ = ["user_name", "podium_driver_names_json"]
|
||||||
|
|
||||||
|
def __init__(self, user_name: str):
|
||||||
|
self.user_name = user_name
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_csv(row: List[str]):
|
||||||
|
podium_drivers: PodiumDrivers = PodiumDrivers(str(row[0]))
|
||||||
|
podium_drivers.podium_driver_names_json = str(row[1])
|
||||||
|
return podium_drivers
|
||||||
|
|
||||||
|
def to_csv(self) -> List[Any]:
|
||||||
|
return [
|
||||||
|
self.user_name,
|
||||||
|
self.podium_driver_names_json
|
||||||
|
]
|
||||||
|
|
||||||
|
user_name: Mapped[str] = mapped_column(ForeignKey("user.name"), primary_key=True)
|
||||||
|
podium_driver_names_json: Mapped[str] = mapped_column(String(1024), nullable=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def podium_driver_names(self) -> List[str]:
|
||||||
|
return json.loads(self.podium_driver_names_json)
|
||||||
|
|
||||||
|
@podium_driver_names.setter
|
||||||
|
def podium_driver_names(self, new_podium_driver_names: List[str]):
|
||||||
|
self.podium_driver_names_json = json.dumps(new_podium_driver_names)
|
||||||
|
|
||||||
|
# Relationships
|
||||||
|
user: Mapped["User"] = relationship("User", foreign_keys=[user_name])
|
||||||
|
_podium_drivers: List[Driver] | None = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def podium_drivers(self) -> List[Driver]:
|
||||||
|
if self._podium_drivers is None:
|
||||||
|
self._podium_drivers = list()
|
||||||
|
for driver_name in self.podium_driver_names:
|
||||||
|
driver: Driver | None = db.session.query(Driver).filter_by(name=driver_name).first()
|
||||||
|
if driver is None:
|
||||||
|
raise Exception(f"Error: Couldn't find driver with id {driver_name}")
|
||||||
|
|
||||||
|
self._podium_drivers.append(driver)
|
||||||
|
|
||||||
|
return self._podium_drivers
|
33
formula10/database/model/race.py
Normal file
33
formula10/database/model/race.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from typing import List
|
||||||
|
from urllib.parse import quote
|
||||||
|
from sqlalchemy import DateTime, Integer, String
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
|
from formula10 import db
|
||||||
|
|
||||||
|
|
||||||
|
class Race(db.Model):
|
||||||
|
"""
|
||||||
|
A single race at a certain date and GrandPrix in the calendar.
|
||||||
|
It stores the place to guess for this race.
|
||||||
|
"""
|
||||||
|
__tablename__ = "race"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_csv(row: List[str]):
|
||||||
|
race: Race = Race()
|
||||||
|
race.name = str(row[0])
|
||||||
|
race.number = int(row[1])
|
||||||
|
race.date = datetime.strptime(row[2], "%Y-%m-%d")
|
||||||
|
race.pxx = int(row[3])
|
||||||
|
return race
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name_sanitized(self) -> str:
|
||||||
|
return quote(self.name)
|
||||||
|
|
||||||
|
name: Mapped[str] = mapped_column(String(64), primary_key=True)
|
||||||
|
number: Mapped[int] = mapped_column(Integer)
|
||||||
|
date: Mapped[datetime] = mapped_column(DateTime)
|
||||||
|
pxx: Mapped[int] = mapped_column(Integer) # This is the place to guess
|
46
formula10/database/model/race_guess.py
Normal file
46
formula10/database/model/race_guess.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
from typing import Any, List
|
||||||
|
from sqlalchemy import ForeignKey
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
|
from formula10.database.model.user import User
|
||||||
|
from formula10.database.model.race import Race
|
||||||
|
from formula10.database.model.driver import Driver
|
||||||
|
from formula10 import db
|
||||||
|
|
||||||
|
class RaceGuess(db.Model):
|
||||||
|
"""
|
||||||
|
A guess a user made for a race.
|
||||||
|
It stores the corresponding race and the guessed drivers for PXX and DNF.
|
||||||
|
"""
|
||||||
|
__tablename__ = "raceguess"
|
||||||
|
__csv_header__ = ["user_name", "race_name", "pxx_driver_name", "dnf_driver_name"]
|
||||||
|
|
||||||
|
def __init__(self, user_name: str, race_name: str):
|
||||||
|
self.user_name = user_name # Primary key
|
||||||
|
self.race_name = race_name # Primery key
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_csv(row: List[str]):
|
||||||
|
race_guess: RaceGuess = RaceGuess(str(row[0]), str(row[1]))
|
||||||
|
race_guess.pxx_driver_name = str(row[2])
|
||||||
|
race_guess.dnf_driver_name = str(row[3])
|
||||||
|
return race_guess
|
||||||
|
|
||||||
|
def to_csv(self) -> List[Any]:
|
||||||
|
return [
|
||||||
|
self.user_name,
|
||||||
|
self.race_name,
|
||||||
|
self.pxx_driver_name,
|
||||||
|
self.dnf_driver_name
|
||||||
|
]
|
||||||
|
|
||||||
|
user_name: Mapped[str] = mapped_column(ForeignKey("user.name"), primary_key=True)
|
||||||
|
race_name: Mapped[str] = mapped_column(ForeignKey("race.name"), primary_key=True)
|
||||||
|
pxx_driver_name: Mapped[str] = mapped_column(ForeignKey("driver.name"), nullable=True)
|
||||||
|
dnf_driver_name: Mapped[str] = mapped_column(ForeignKey("driver.name"), nullable=True)
|
||||||
|
|
||||||
|
# Relationships
|
||||||
|
user: Mapped["User"] = relationship("User", foreign_keys=[user_name])
|
||||||
|
race: Mapped["Race"] = relationship("Race", foreign_keys=[race_name])
|
||||||
|
pxx: Mapped["Driver"] = relationship("Driver", foreign_keys=[pxx_driver_name])
|
||||||
|
dnf: Mapped["Driver"] = relationship("Driver", foreign_keys=[dnf_driver_name])
|
171
formula10/database/model/race_result.py
Normal file
171
formula10/database/model/race_result.py
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
import json
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
from sqlalchemy import ForeignKey, String
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
|
from formula10.database.model.driver import Driver
|
||||||
|
from formula10.database.model.race import Race
|
||||||
|
from formula10 import db
|
||||||
|
|
||||||
|
class RaceResult(db.Model):
|
||||||
|
"""
|
||||||
|
The result of a past race.
|
||||||
|
It stores the corresponding race and dictionaries of place-/dnf-order and a list of drivers that are excluded from the standings for this race.
|
||||||
|
"""
|
||||||
|
__tablename__ = "raceresult"
|
||||||
|
__allow_unmapped__ = True # TODO: Used for json conversion, move this to some other class instead
|
||||||
|
__csv_header__ = ["race_name", "pxx_driver_names_json", "first_dnf_driver_names_json", "dnf_driver_names_json", "excluded_driver_names_json"]
|
||||||
|
|
||||||
|
def __init__(self, race_name: str):
|
||||||
|
self.race_name = race_name # Primary key
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_csv(row: List[str]):
|
||||||
|
race_result: RaceResult = RaceResult(str(row[0]))
|
||||||
|
race_result.pxx_driver_names_json = str(row[1])
|
||||||
|
race_result.first_dnf_driver_names_json = str(row[2])
|
||||||
|
race_result.dnf_driver_names_json = str(row[3])
|
||||||
|
race_result.excluded_driver_names_json = str(row[4])
|
||||||
|
return race_result
|
||||||
|
|
||||||
|
def to_csv(self) -> List[Any]:
|
||||||
|
return [
|
||||||
|
self.race_name,
|
||||||
|
self.pxx_driver_names_json,
|
||||||
|
self.first_dnf_driver_names_json,
|
||||||
|
self.dnf_driver_names_json,
|
||||||
|
self.excluded_driver_names_json
|
||||||
|
]
|
||||||
|
|
||||||
|
race_name: Mapped[str] = mapped_column(ForeignKey("race.name"), primary_key=True)
|
||||||
|
pxx_driver_names_json: Mapped[str] = mapped_column(String(1024), nullable=True)
|
||||||
|
first_dnf_driver_names_json: Mapped[str] = mapped_column(String(1024), nullable=True)
|
||||||
|
dnf_driver_names_json: Mapped[str] = mapped_column(String(1024), nullable=True)
|
||||||
|
excluded_driver_names_json: Mapped[str] = mapped_column(String(1024), nullable=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pxx_driver_names(self) -> Dict[str, str]:
|
||||||
|
return json.loads(self.pxx_driver_names_json)
|
||||||
|
|
||||||
|
@pxx_driver_names.setter
|
||||||
|
def pxx_driver_names(self, new_pxx_driver_names: Dict[str, str]):
|
||||||
|
self.pxx_driver_names_json = json.dumps(new_pxx_driver_names)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def first_dnf_driver_names(self) -> List[str]:
|
||||||
|
return json.loads(self.first_dnf_driver_names_json)
|
||||||
|
|
||||||
|
@first_dnf_driver_names.setter
|
||||||
|
def first_dnf_driver_names(self, new_first_dnf_driver_names: List[str]):
|
||||||
|
self.first_dnf_driver_names_json = json.dumps(new_first_dnf_driver_names)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dnf_driver_names(self) -> List[str]:
|
||||||
|
return json.loads(self.dnf_driver_names_json)
|
||||||
|
|
||||||
|
@dnf_driver_names.setter
|
||||||
|
def dnf_driver_names(self, new_dnf_driver_names: List[str]):
|
||||||
|
self.dnf_driver_names_json = json.dumps(new_dnf_driver_names)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def excluded_driver_names(self) -> List[str]:
|
||||||
|
return json.loads(self.excluded_driver_names_json)
|
||||||
|
|
||||||
|
@excluded_driver_names.setter
|
||||||
|
def excluded_driver_names(self, new_excluded_driver_names: List[str]):
|
||||||
|
self.excluded_driver_names_json = json.dumps(new_excluded_driver_names)
|
||||||
|
|
||||||
|
# Relationships
|
||||||
|
race: Mapped["Race"] = relationship("Race", foreign_keys=[race_name])
|
||||||
|
_pxx_drivers: Dict[str, Driver] | None = None
|
||||||
|
_first_dnf_drivers: List[Driver] | None = None
|
||||||
|
_dnf_drivers: List[Driver] | None = None
|
||||||
|
_excluded_drivers: List[Driver] | None = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pxx_drivers(self) -> Dict[str, Driver]:
|
||||||
|
if self._pxx_drivers is None:
|
||||||
|
self._pxx_drivers = dict()
|
||||||
|
for position, driver_name in self.pxx_driver_names.items():
|
||||||
|
driver: Driver | None = db.session.query(Driver).filter_by(name=driver_name).first()
|
||||||
|
if driver is None:
|
||||||
|
raise Exception(f"Error: Couldn't find driver with id {driver_name}")
|
||||||
|
|
||||||
|
self._pxx_drivers[position] = driver
|
||||||
|
|
||||||
|
return self._pxx_drivers
|
||||||
|
|
||||||
|
def pxx_driver(self, offset: int = 0) -> Driver | None:
|
||||||
|
pxx_num: str = str(self.race.pxx + offset)
|
||||||
|
|
||||||
|
if pxx_num not in self.pxx_drivers:
|
||||||
|
raise Exception(f"Position {pxx_num} not found in RaceResult.pxx_drivers")
|
||||||
|
|
||||||
|
if self.pxx_drivers[pxx_num].name in self.excluded_driver_names:
|
||||||
|
none_driver: Driver | None = db.session.query(Driver).filter_by(name="None").first()
|
||||||
|
if none_driver is None:
|
||||||
|
raise Exception(f"NONE-driver not found in database")
|
||||||
|
|
||||||
|
return none_driver
|
||||||
|
|
||||||
|
|
||||||
|
return self.pxx_drivers[pxx_num]
|
||||||
|
|
||||||
|
def pxx_driver_position_string(self, driver_name: str) -> str:
|
||||||
|
for position, driver in self.pxx_driver_names.items():
|
||||||
|
if driver == driver_name and driver not in self.excluded_driver_names:
|
||||||
|
return f"P{position}"
|
||||||
|
|
||||||
|
return "NC"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def all_positions(self) -> List[Driver]:
|
||||||
|
return [
|
||||||
|
self.pxx_drivers[str(position)] for position in range(1, 21)
|
||||||
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def first_dnf_drivers(self) -> List[Driver]:
|
||||||
|
if self._first_dnf_drivers is None:
|
||||||
|
self._first_dnf_drivers = list()
|
||||||
|
for driver_name in self.first_dnf_driver_names:
|
||||||
|
driver: Driver | None = db.session.query(Driver).filter_by(name=driver_name).first()
|
||||||
|
if driver is None:
|
||||||
|
raise Exception(f"Error: Couldn't find driver with id {driver_name}")
|
||||||
|
|
||||||
|
self._first_dnf_drivers.append(driver)
|
||||||
|
|
||||||
|
if len(self._first_dnf_drivers) == 0:
|
||||||
|
none_driver: Driver | None = db.session.query(Driver).filter_by(name="None").first()
|
||||||
|
if none_driver is None:
|
||||||
|
raise Exception("NONE-driver not found in database")
|
||||||
|
|
||||||
|
self._first_dnf_drivers.append(none_driver)
|
||||||
|
|
||||||
|
return self._first_dnf_drivers
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dnf_drivers(self) -> List[Driver]:
|
||||||
|
if self._dnf_drivers is None:
|
||||||
|
self._dnf_drivers = list()
|
||||||
|
for driver_name in self.dnf_driver_names:
|
||||||
|
driver: Driver | None = db.session.query(Driver).filter_by(name=driver_name).first()
|
||||||
|
if driver is None:
|
||||||
|
raise Exception(f"Error: Couldn't find driver with id {driver_name}")
|
||||||
|
|
||||||
|
self._dnf_drivers.append(driver)
|
||||||
|
|
||||||
|
return self._dnf_drivers
|
||||||
|
|
||||||
|
@property
|
||||||
|
def excluded_drivers(self) -> List[Driver]:
|
||||||
|
if self._excluded_drivers is None:
|
||||||
|
self._excluded_drivers = list()
|
||||||
|
for driver_name in self.excluded_driver_names:
|
||||||
|
driver: Driver | None = db.session.query(Driver).filter_by(name=driver_name).first()
|
||||||
|
if driver is None:
|
||||||
|
raise Exception(f"Error: Couldn't find driver with id {driver_name}")
|
||||||
|
|
||||||
|
self._excluded_drivers.append(driver)
|
||||||
|
|
||||||
|
return self._excluded_drivers
|
81
formula10/database/model/season_guess.py
Normal file
81
formula10/database/model/season_guess.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
from typing import Any, List
|
||||||
|
from sqlalchemy import ForeignKey, String
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
|
from formula10.database.model.driver import Driver
|
||||||
|
from formula10.database.model.podium_drivers import PodiumDrivers
|
||||||
|
from formula10.database.model.team import Team
|
||||||
|
from formula10.database.model.team_winners import TeamWinners
|
||||||
|
from formula10.database.model.user import User
|
||||||
|
from formula10 import db
|
||||||
|
|
||||||
|
class SeasonGuess(db.Model):
|
||||||
|
"""
|
||||||
|
A collection of bonus guesses for the entire season.
|
||||||
|
"""
|
||||||
|
__tablename__ = "seasonguess"
|
||||||
|
__csv_header__ = ["user_name", "hot_take", "p2_team_name",
|
||||||
|
"overtake_driver_name", "dnf_driver_name", "gained_driver_name", "lost_driver_name",
|
||||||
|
"team_winners_id", "podium_drivers_id"]
|
||||||
|
|
||||||
|
def __init__(self, user_name: str, team_winners_user_name: str | None = None, podium_drivers_user_name: str | None = None):
|
||||||
|
self.user_name = user_name # Primary key
|
||||||
|
|
||||||
|
# Although this is the same username, handle separately, in case they don't exist in the database yet
|
||||||
|
if team_winners_user_name is not None:
|
||||||
|
if user_name != team_winners_user_name:
|
||||||
|
raise Exception(f"SeasonGuess for {user_name} was supplied TeamWinners for {team_winners_user_name}")
|
||||||
|
|
||||||
|
self.team_winners_id = team_winners_user_name
|
||||||
|
|
||||||
|
if podium_drivers_user_name is not None:
|
||||||
|
if user_name != podium_drivers_user_name:
|
||||||
|
raise Exception(f"SeasonGuess for {user_name} was supplied PodiumDrivers for {podium_drivers_user_name}")
|
||||||
|
|
||||||
|
self.podium_drivers_id = podium_drivers_user_name
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_csv(row: List[str]):
|
||||||
|
season_guess: SeasonGuess = SeasonGuess(str(row[0]), team_winners_user_name=str(row[7]), podium_drivers_user_name=str(row[8]))
|
||||||
|
season_guess.hot_take = str(row[1])
|
||||||
|
season_guess.p2_team_name = str(row[2])
|
||||||
|
season_guess.overtake_driver_name = str(row[3])
|
||||||
|
season_guess.dnf_driver_name = str(row[4])
|
||||||
|
season_guess.gained_driver_name = str(row[5])
|
||||||
|
season_guess.lost_driver_name = str(row[6])
|
||||||
|
return season_guess
|
||||||
|
|
||||||
|
def to_csv(self) -> List[Any]:
|
||||||
|
return [
|
||||||
|
self.user_name,
|
||||||
|
self.hot_take,
|
||||||
|
self.p2_team_name,
|
||||||
|
self.overtake_driver_name,
|
||||||
|
self.dnf_driver_name,
|
||||||
|
self.gained_driver_name,
|
||||||
|
self.lost_driver_name,
|
||||||
|
self.team_winners_id,
|
||||||
|
self.podium_drivers_id
|
||||||
|
]
|
||||||
|
|
||||||
|
user_name: Mapped[str] = mapped_column(ForeignKey("user.name"), primary_key=True)
|
||||||
|
hot_take: Mapped[str] = mapped_column(String(512), nullable=True)
|
||||||
|
p2_team_name: Mapped[str] = mapped_column(ForeignKey("team.name"), nullable=True)
|
||||||
|
overtake_driver_name: Mapped[str] = mapped_column(ForeignKey("driver.name"), nullable=True)
|
||||||
|
dnf_driver_name: Mapped[str] = mapped_column(ForeignKey("driver.name"), nullable=True)
|
||||||
|
gained_driver_name: Mapped[str] = mapped_column(ForeignKey("driver.name"), nullable=True)
|
||||||
|
lost_driver_name: Mapped[str] = mapped_column(ForeignKey("driver.name"), nullable=True)
|
||||||
|
|
||||||
|
team_winners_id: Mapped[str] = mapped_column(ForeignKey("teamwinners.user_name"))
|
||||||
|
podium_drivers_id: Mapped[str] = mapped_column(ForeignKey("podiumdrivers.user_name"))
|
||||||
|
|
||||||
|
# Relationships
|
||||||
|
user: Mapped["User"] = relationship("User", foreign_keys=[user_name])
|
||||||
|
p2_team: Mapped["Team"] = relationship("Team", foreign_keys=[p2_team_name])
|
||||||
|
overtake_driver: Mapped["Driver"] = relationship("Driver", foreign_keys=[overtake_driver_name])
|
||||||
|
dnf_driver: Mapped["Driver"] = relationship("Driver", foreign_keys=[dnf_driver_name])
|
||||||
|
gained_driver: Mapped["Driver"] = relationship("Driver", foreign_keys=[gained_driver_name])
|
||||||
|
lost_driver: Mapped["Driver"] = relationship("Driver", foreign_keys=[lost_driver_name])
|
||||||
|
|
||||||
|
team_winners: Mapped["TeamWinners"] = relationship("TeamWinners", foreign_keys=[team_winners_id])
|
||||||
|
podium_drivers: Mapped["PodiumDrivers"] = relationship("PodiumDrivers", foreign_keys=[podium_drivers_id])
|
19
formula10/database/model/team.py
Normal file
19
formula10/database/model/team.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
from typing import List
|
||||||
|
from sqlalchemy import String
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
|
from formula10 import db
|
||||||
|
|
||||||
|
class Team(db.Model):
|
||||||
|
"""
|
||||||
|
A constructor/team (name only).
|
||||||
|
"""
|
||||||
|
__tablename__ = "team"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_csv(row: List[str]):
|
||||||
|
team: Team = Team()
|
||||||
|
team.name = str(row[0])
|
||||||
|
return team
|
||||||
|
|
||||||
|
name: Mapped[str] = mapped_column(String(32), primary_key=True)
|
59
formula10/database/model/team_winners.py
Normal file
59
formula10/database/model/team_winners.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import json
|
||||||
|
from typing import Any, List
|
||||||
|
from sqlalchemy import ForeignKey, String
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
|
from formula10.database.model.driver import Driver
|
||||||
|
from formula10.database.model.user import User
|
||||||
|
from formula10 import db
|
||||||
|
|
||||||
|
class TeamWinners(db.Model):
|
||||||
|
"""
|
||||||
|
A guessed list of each best driver per team.
|
||||||
|
"""
|
||||||
|
__tablename__ = "teamwinners"
|
||||||
|
__allow_unmapped__ = True
|
||||||
|
__csv_header__ = ["user_name", "teamwinner_driver_names_json"]
|
||||||
|
|
||||||
|
def __init__(self, user_name: str):
|
||||||
|
self.user_name = user_name # Primary key
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_csv(row: List[str]):
|
||||||
|
team_winners: TeamWinners = TeamWinners(str(row[0]))
|
||||||
|
team_winners.teamwinner_driver_names_json = str(row[1])
|
||||||
|
return team_winners
|
||||||
|
|
||||||
|
def to_csv(self) -> List[Any]:
|
||||||
|
return [
|
||||||
|
self.user_name,
|
||||||
|
self.teamwinner_driver_names_json
|
||||||
|
]
|
||||||
|
|
||||||
|
user_name: Mapped[str] = mapped_column(ForeignKey("user.name"), primary_key=True)
|
||||||
|
teamwinner_driver_names_json: Mapped[str] = mapped_column(String(1024), nullable=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def teamwinner_driver_names(self) -> List[str]:
|
||||||
|
return json.loads(self.teamwinner_driver_names_json)
|
||||||
|
|
||||||
|
@teamwinner_driver_names.setter
|
||||||
|
def teamwinner_driver_names(self, new_teamwinner_driver_names: List[str]):
|
||||||
|
self.teamwinner_driver_names_json = json.dumps(new_teamwinner_driver_names)
|
||||||
|
|
||||||
|
# Relationships
|
||||||
|
user: Mapped["User"] = relationship("User", foreign_keys=[user_name])
|
||||||
|
_teamwinner_drivers: List[Driver] | None = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def teamwinners(self) -> List[Driver]:
|
||||||
|
if self._teamwinner_drivers is None:
|
||||||
|
self._teamwinner_drivers = list()
|
||||||
|
for driver_name in self.teamwinner_driver_names:
|
||||||
|
driver: Driver | None = db.session.query(Driver).filter_by(name=driver_name).first()
|
||||||
|
if driver is None:
|
||||||
|
raise Exception(f"Error: Couldn't find driver with id {driver_name}")
|
||||||
|
|
||||||
|
self._teamwinner_drivers.append(driver)
|
||||||
|
|
||||||
|
return self._teamwinner_drivers
|
32
formula10/database/model/user.py
Normal file
32
formula10/database/model/user.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
from typing import Any, List
|
||||||
|
from urllib.parse import quote
|
||||||
|
from sqlalchemy import String
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
|
from formula10 import db
|
||||||
|
|
||||||
|
class User(db.Model):
|
||||||
|
"""
|
||||||
|
A user that can guess races (name only).
|
||||||
|
"""
|
||||||
|
__tablename__ = "user"
|
||||||
|
__csv_header__ = ["name"]
|
||||||
|
|
||||||
|
def __init__(self, name: str):
|
||||||
|
self.name = name # Primary key
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_csv(row: List[str]):
|
||||||
|
user: User = User(str(row[0]))
|
||||||
|
return user
|
||||||
|
|
||||||
|
def to_csv(self) -> List[Any]:
|
||||||
|
return [
|
||||||
|
self.name
|
||||||
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name_sanitized(self) -> str:
|
||||||
|
return quote(self.name)
|
||||||
|
|
||||||
|
name: Mapped[str] = mapped_column(String(32), primary_key=True)
|
@ -2,9 +2,16 @@ from typing import Dict, List, cast
|
|||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
from flask import redirect
|
from flask import redirect
|
||||||
from werkzeug import Response
|
from werkzeug import Response
|
||||||
from app.database.database_utils import race_has_result, user_exists
|
|
||||||
from app.database.model import PodiumDrivers, RaceResult, SeasonGuess, TeamWinners, User, db, RaceGuess
|
from formula10.database.common_query_util import race_has_result, user_exists
|
||||||
from app.database.validation_utils import any_is_none, positions_are_contiguous
|
from formula10.database.model.podium_drivers import PodiumDrivers
|
||||||
|
from formula10.database.model.race_guess import RaceGuess
|
||||||
|
from formula10.database.model.race_result import RaceResult
|
||||||
|
from formula10.database.model.season_guess import SeasonGuess
|
||||||
|
from formula10.database.model.team_winners import TeamWinners
|
||||||
|
from formula10.database.model.user import User
|
||||||
|
from formula10.database.validation_util import any_is_none, positions_are_contiguous
|
||||||
|
from formula10 import db
|
||||||
|
|
||||||
|
|
||||||
def find_or_create_race_guess(user_name: str, race_name: str) -> RaceGuess:
|
def find_or_create_race_guess(user_name: str, race_name: str) -> RaceGuess:
|
@ -1,7 +1,15 @@
|
|||||||
from typing import List, Callable, Dict, overload
|
from typing import List, Callable, Dict, overload
|
||||||
from sqlalchemy import desc
|
from sqlalchemy import desc
|
||||||
from app.database.model import User, RaceResult, RaceGuess, Race, Driver, Team, SeasonGuess, db
|
|
||||||
from app.database.validation_utils import find_first_or_none, find_multiple, find_single, find_single_or_none
|
from formula10.database.model.driver import Driver
|
||||||
|
from formula10.database.model.race import Race
|
||||||
|
from formula10.database.model.race_guess import RaceGuess
|
||||||
|
from formula10.database.model.race_result import RaceResult
|
||||||
|
from formula10.database.model.season_guess import SeasonGuess
|
||||||
|
from formula10.database.model.team import Team
|
||||||
|
from formula10.database.model.user import User
|
||||||
|
from formula10.database.validation_util import find_first_or_none, find_multiple, find_single, find_single_or_none
|
||||||
|
from formula10 import db
|
||||||
|
|
||||||
|
|
||||||
# This could also be moved to database_utils (at least partially), but I though the template should cache the database responses
|
# This could also be moved to database_utils (at least partially), but I though the template should cache the database responses
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
@ -6,8 +6,8 @@
|
|||||||
{% set active_user = none %}
|
{% set active_user = none %}
|
||||||
|
|
||||||
{% block head_extra %}
|
{% block head_extra %}
|
||||||
<link href="../static/style/draggable.css" rel="stylesheet">
|
<link href="../style/draggable.css" rel="stylesheet">
|
||||||
<script src="../static/script/draggable.js" defer></script>
|
<script src="../script/draggable.js" defer></script>
|
||||||
{% endblock head_extra %}
|
{% endblock head_extra %}
|
||||||
|
|
||||||
{% set current_race = model.first_race_without_result() %}
|
{% set current_race = model.first_race_without_result() %}
|
Reference in New Issue
Block a user