Finish restructuring files
All checks were successful
Build Formula10 Docker Image / build-docker (push) Successful in 14s

This commit is contained in:
2024-02-24 17:41:12 +01:00
parent fc8a890511
commit 3704192730
40 changed files with 798 additions and 693 deletions

View File

@ -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])

View File

@ -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)

View File

@ -0,0 +1,8 @@
name
Christoph
Angela Merkel
Xi Jinping
Donald Trump
Joe Biden
Henri
Vinzent
1 name
2 Christoph
3 Angela Merkel
4 Xi Jinping
5 Donald Trump
6 Joe Biden
7 Henri
8 Vinzent

View File

@ -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
View 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")

View 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)

View 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)

View 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)

View File

@ -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

View File

@ -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)

View 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])

View 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

View 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

View 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])

View 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

View 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])

View 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)

View 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

View 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)

View File

@ -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:

View File

@ -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

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -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() %}