diff --git a/data/static_import/drivers.csv b/data/static_import/drivers.csv deleted file mode 100644 index ae111a5..0000000 --- a/data/static_import/drivers.csv +++ /dev/null @@ -1,22 +0,0 @@ -name,abbr,team_name,country_code -None,None,None,NO -Alexander Albon,ALB,Williams,TH -Fernando Alonso,ALO,Aston Martin,ES -Valteri Bottas,BOT,Sauber,FL -Pierre Gasly,GAS,Alpine,FR -Lewis Hamilton,HAM,Mercedes,UK -Nico Hulkenberg,HUL,Haas,DE -Charles Leclerc,LEC,Ferrari,MC -Kevin Magnussen,MAG,Haas,DK -Lando Norris,NOR,McLaren,UK -Esteban Ocon,OCO,Alpine,FR -Sergio Perez,PER,Red Bull,MX -Oscar Piastri,PIA,McLaren,AU -Daniel Ricciardo,RIC,VCARB,AU -George Russel,RUS,Mercedes,UK -Carlos Sainz,SAI,Ferrari,ES -Logan Sargeant,SAR,Williams,US -Lance Stroll,STR,Aston Martin,CA -Yuki Tsunoda,TSU,VCARB,JP -Max Verstappen,VER,Red Bull,NL -Zhou Guanyu,ZHO,Sauber,CN \ No newline at end of file diff --git a/data/static_import/races.csv b/data/static_import/races.csv deleted file mode 100644 index 09b3cf2..0000000 --- a/data/static_import/races.csv +++ /dev/null @@ -1,25 +0,0 @@ -name,number,date,pxx -Bahrain,1,2024-03-02-16-00,10 -Saudi Arabia,2,2024-03-09-18-00,6 -Australia,3,2024-03-24-05-00,15 -Japan,4,2024-04-07-07-00,9 -China,5,2024-04-21-09-00,7 -Miami,6,2024-05-05-22-00,13 -Emilia-Romagna,7,2024-05-21-15-00,17 -Monaco,8,2024-05-26-15-00,5 -Canada,9,2024-06-09-20-00,12 -Spain,10,2024-06-23-15-00,8 -Austria,11,2024-06-30-15-00,11 -Great Britain,12,2024-07-07-16-00,4 -Hungary,12,2024-07-23-15-00,17 -Belgium,13,2024-07-28-15-00,13 -Netherlands,14,2024-08-25-15-00,7 -Monza,15,2024-09-01-15-00,16 -Azerbaijan,16,2024-09-15-13-00,8 -Singapore,17,2024-09-22-14-00,11 -Austin,18,2024-10-20-21-00,5 -Mexico,19,2024-10-27-21-00,14 -Brazil,20,2024-11-03-18-00,4 -Las Vegas,21,2024-11-23-07-00,12 -Qatar,22,2024-12-01-18-00,6 -Abu Dhabi,23,2024-12-08-14-00,10 \ No newline at end of file diff --git a/data/static_import/season_guess_results.csv b/data/static_import/season_guess_results.csv deleted file mode 100644 index 4c6fcad..0000000 --- a/data/static_import/season_guess_results.csv +++ /dev/null @@ -1 +0,0 @@ -user_name,hot_take_correct,overtakes_correct \ No newline at end of file diff --git a/data/static_import/teams.csv b/data/static_import/teams.csv deleted file mode 100644 index c6d4ea9..0000000 --- a/data/static_import/teams.csv +++ /dev/null @@ -1,12 +0,0 @@ -name -None -Alpine -Aston Martin -Ferrari -Haas -McLaren -Mercedes -Red Bull -Sauber -VCARB -Williams \ No newline at end of file diff --git a/flake.nix b/flake.nix index cd7077c..96a6698 100644 --- a/flake.nix +++ b/flake.nix @@ -37,6 +37,9 @@ nodePackages.sass nodePackages.postcss-cli nodePackages.autoprefixer + + sqlitebrowser # To modify tables + dbeaver # To import/export data + diagrams ]; # Use $1 for positional args diff --git a/formula10/__init__.py b/formula10/__init__.py index 728bf5c..631c940 100644 --- a/formula10/__init__.py +++ b/formula10/__init__.py @@ -4,12 +4,9 @@ from flask_sqlalchemy import SQLAlchemy # Load local ENV variables (can be set when calling the executable) ENABLE_TIMING: bool = False if os.getenv("DISABLE_TIMING") == "True" else True -ENABLE_DEBUG_ENDPOINTS: bool = True if os.getenv("ENABLE_DEBUG_ENDPOINTS") == "True" else False print("Running Formula10 with:") if not ENABLE_TIMING: print("- Disabled timing constraints") -if ENABLE_DEBUG_ENDPOINTS: - print("- Enabled debug endpoints") app: Flask = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:///formula10.db" @@ -36,8 +33,9 @@ import formula10.controller.error_controller # TODO # Large DB Update +# - Don't use names for frontend post requests, either use IDs or post the whole object (if its possible)... # - For season guess calc there is missing: Fastest laps + sprint points + sprint DNFs (in race result) -# - Decouple names from IDs in each object + Fix Valtteri/Russel spelling errors +# - Fix Valtteri/Russel spelling errors # - Mask to allow changing usernames (easy if name is not used as ID) # - Maybe even masks for races + drivers + teams? # - DB fields for links to F1 site @@ -47,6 +45,5 @@ import formula10.controller.error_controller # - Auto calculate season points (display season points in table + season guess card title?) # General -# - Export all data to CSV (already have that), but downloadable via browser # - Add links to the official F1 stats page (for quali/result), probably best to store entire link in DB (because they are not entirely regular)? # - Unit testing (as much as possible, but especially points calculation) \ No newline at end of file diff --git a/formula10/controller/admin_controller.py b/formula10/controller/admin_controller.py index 5153bdd..27f0d1e 100644 --- a/formula10/controller/admin_controller.py +++ b/formula10/controller/admin_controller.py @@ -2,49 +2,11 @@ from typing import List from urllib.parse import unquote from flask import redirect, render_template, request from werkzeug import Response -from formula10.controller.error_controller import error_redirect from formula10.database.update_queries import update_race_result, update_user -from formula10.database.import_export import export_dynamic_data, reload_dynamic_data, reload_season_guess_result_data, reload_static_data +from formula10.domain.domain_model import Model from formula10.domain.template_model import TemplateModel -from formula10 import ENABLE_DEBUG_ENDPOINTS, app - - -@app.route("/save/all") -def save() -> Response: - export_dynamic_data() - return redirect("/") - - -@app.route("/load/all") -def load() -> Response: - if not ENABLE_DEBUG_ENDPOINTS: - return error_redirect("Debug endpoints are disabled!") - - reload_static_data() - reload_dynamic_data() - return redirect("/") - - -@app.route("/load/static") -def load_static() -> Response: - reload_static_data() - return redirect("/") - - -@app.route("/load/seasonresults") -def load_season_results() -> Response: - reload_season_guess_result_data() - return redirect("/") - - -@app.route("/load/dynamic") -def load_dynamic() -> Response: - if not ENABLE_DEBUG_ENDPOINTS: - return error_redirect("Debug endpoints are disabled!") - - reload_dynamic_data() - return redirect("/") +from formula10 import app @app.route("/result") @@ -69,7 +31,9 @@ def result_enter_post(race_name: str) -> Response: 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) + # @todo Ugly + race_id: int = Model().race_by(race_name=race_name).id + return update_race_result(race_id, pxxs, first_dnfs, dnfs, excluded) @app.route("/user") diff --git a/formula10/controller/race_controller.py b/formula10/controller/race_controller.py index aa0eff2..cf776dd 100644 --- a/formula10/controller/race_controller.py +++ b/formula10/controller/race_controller.py @@ -3,6 +3,7 @@ from flask import redirect, render_template, request from werkzeug import Response from formula10.database.update_queries import delete_race_guess, update_race_guess +from formula10.domain.domain_model import Model from formula10.domain.points_model import PointsModel from formula10.domain.template_model import TemplateModel from formula10 import app @@ -36,7 +37,11 @@ def race_guess_post(race_name: str, user_name: str) -> Response: pxx: str | None = request.form.get("pxxselect") dnf: str | None = request.form.get("dnfselect") - return update_race_guess(race_name, user_name, pxx, dnf) + race_id: int = Model().race_by(race_name=race_name).id + user_id: int = Model().user_by(user_name=user_name).id + return update_race_guess(race_id, user_id, + int(pxx) if pxx is not None else None, + int(dnf) if dnf is not None else None) @app.route("/race-guess-delete//", methods=["POST"]) @@ -44,4 +49,6 @@ 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) \ No newline at end of file + race_id: int = Model().race_by(race_name=race_name).id + user_id: int = Model().user_by(user_name=user_name).id + return delete_race_guess(race_id, user_id) \ No newline at end of file diff --git a/formula10/controller/season_controller.py b/formula10/controller/season_controller.py index eed6983..ee86e90 100644 --- a/formula10/controller/season_controller.py +++ b/formula10/controller/season_controller.py @@ -5,6 +5,7 @@ from werkzeug import Response from formula10.database.model.db_team import DbTeam from formula10.database.update_queries import update_season_guess +from formula10.domain.domain_model import Model from formula10.domain.model.team import NONE_TEAM from formula10.domain.points_model import PointsModel from formula10.domain.template_model import TemplateModel @@ -39,8 +40,9 @@ def season_guess_post(user_name: str) -> Response: ] # 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(DbTeam).all() if team.name != NONE_TEAM.name + request.form.get(f"teamwinner-{team.id}") for team in db.session.query(DbTeam).all() if team.id != NONE_TEAM.id ] podium_driver_guesses: List[str] = request.form.getlist("podiumdrivers") - return update_season_guess(user_name, guesses, team_winner_guesses, podium_driver_guesses) \ No newline at end of file + user_id: int = Model().user_by(user_name=user_name).id + return update_season_guess(user_id, guesses, team_winner_guesses, podium_driver_guesses) \ No newline at end of file diff --git a/formula10/database/common_queries.py b/formula10/database/common_queries.py index e5f3db2..068fbce 100644 --- a/formula10/database/common_queries.py +++ b/formula10/database/common_queries.py @@ -3,8 +3,8 @@ from formula10.database.model.db_race_result import DbRaceResult from formula10.database.model.db_user import DbUser from formula10 import db -def race_has_result(race_name: str) -> bool: - return db.session.query(DbRaceResult).filter_by(race_name=race_name).first() is not None +def race_has_result(race_id: int) -> bool: + return db.session.query(DbRaceResult).filter_by(race_id=race_id).first() is not None def user_exists_and_enabled(user_name: str) -> bool: @@ -15,9 +15,9 @@ def user_exists_and_disabled(user_name: str) -> bool: return db.session.query(DbUser).filter_by(name=user_name, enabled=False).first() is not None -def find_single_driver_strict(driver_name: str) -> DbDriver: - db_driver: DbDriver | None = db.session.query(DbDriver).filter_by(name=driver_name).first() +def find_single_driver_strict(driver_id: int) -> DbDriver: + db_driver: DbDriver | None = db.session.query(DbDriver).filter_by(id=driver_id).first() if db_driver is None: - raise Exception(f"Could not find driver with name {driver_name} in database") + raise Exception(f"Could not find driver with id {driver_id} in database") return db_driver \ No newline at end of file diff --git a/formula10/database/import_export.py b/formula10/database/import_export.py deleted file mode 100644 index a0d08bb..0000000 --- a/formula10/database/import_export.py +++ /dev/null @@ -1,111 +0,0 @@ -import csv -import os.path -from typing import List, Any - -from formula10 import db -from formula10.database.model.db_driver import DbDriver -from formula10.database.model.db_race import DbRace -from formula10.database.model.db_race_guess import DbRaceGuess -from formula10.database.model.db_race_result import DbRaceResult -from formula10.database.model.db_season_guess import DbSeasonGuess -from formula10.database.model.db_season_guess_result import DbSeasonGuessResult -from formula10.database.model.db_team import DbTeam -from formula10.database.model.db_user import DbUser - - -def load_csv(filename: str) -> List[List[str]]: - if not os.path.exists(filename): - print(f"Could not load data from file {filename}, as it doesn't exist!") - return [] - - with open(filename, "r", newline="") as file: - reader = csv.reader(file, delimiter=",") - next(reader, None) # skip header - return list(reader) - - -def write_csv(filename: str, objects: List[Any]): - if len(objects) == 0: - print(f"Could not write objects to file {filename}, as no objects were given!") - return - - with open(filename, "w", newline="") as file: - writer = csv.writer(file, delimiter=",") - writer.writerow(objects[0].__csv_header__) - for obj in objects: - writer.writerow(obj.to_csv()) - - -# Reload static database data, this has to be called from the app context -def reload_static_data(): - print("Initializing database with static values...") - # Create it/update tables (if it/they doesn't exist!) - db.create_all() - - # Clear static data - db.session.query(DbTeam).delete() - db.session.query(DbDriver).delete() - db.session.query(DbRace).delete() - - # Reload static data - for row in load_csv("data/static_import/teams.csv"): - db.session.add(DbTeam.from_csv(row)) - for row in load_csv("data/static_import/drivers.csv"): - db.session.add(DbDriver.from_csv(row)) - for row in load_csv("data/static_import/races.csv"): - db.session.add(DbRace.from_csv(row)) - - db.session.commit() - - -def reload_dynamic_data(): - print("Initializing database with dynamic values...") - # Create it/update tables (if it/they doesn't exist!) - db.create_all() - - # Clear dynamic data - db.session.query(DbUser).delete() - db.session.query(DbRaceResult).delete() - db.session.query(DbRaceGuess).delete() - db.session.query(DbSeasonGuess).delete() - - # Reload dynamic data - for row in load_csv("data/dynamic_export/users.csv"): - db.session.add(DbUser.from_csv(row)) - for row in load_csv("data/dynamic_export/raceresults.csv"): - db.session.add(DbRaceResult.from_csv(row)) - for row in load_csv("data/dynamic_export/raceguesses.csv"): - db.session.add(DbRaceGuess.from_csv(row)) - for row in load_csv("data/dynamic_export/seasonguesses.csv"): - db.session.add(DbSeasonGuess.from_csv(row)) - - db.session.commit() - - -def reload_season_guess_result_data(): - print("Loading season guess results...") - # Create it/update tables (if it/they doesn't exist!) - db.create_all() - - # Clear result data - db.session.query(DbSeasonGuessResult).delete() - - # Reload result data - for row in load_csv("data/static_import/season_guess_results.csv"): - db.session.add(DbSeasonGuessResult.from_csv(row)) - - db.session.commit() - - -def export_dynamic_data(): - print("Exporting Userdata...") - - users: List[DbUser] = db.session.query(DbUser).all() - raceresults: List[DbRaceResult] = db.session.query(DbRaceResult).all() - raceguesses: List[DbRaceGuess] = db.session.query(DbRaceGuess).all() - seasonguesses: List[DbSeasonGuess] = db.session.query(DbSeasonGuess).all() - - write_csv("data/dynamic_export/users.csv", users) - write_csv("data/dynamic_export/raceresults.csv", raceresults) - write_csv("data/dynamic_export/raceguesses.csv", raceguesses) - write_csv("data/dynamic_export/seasonguesses.csv", seasonguesses) diff --git a/formula10/database/model/db_driver.py b/formula10/database/model/db_driver.py index 21cb85d..a2b50d9 100644 --- a/formula10/database/model/db_driver.py +++ b/formula10/database/model/db_driver.py @@ -1,5 +1,4 @@ -from typing import List -from sqlalchemy import String, ForeignKey +from sqlalchemy import Integer, String, ForeignKey from sqlalchemy.orm import mapped_column, Mapped, relationship from formula10.database.model.db_team import DbTeam @@ -13,21 +12,14 @@ class DbDriver(db.Model): """ __tablename__ = "driver" - def __init__(self, *, name: str): - self.name = name # Primary key + def __init__(self, *, id: int): + self.id = id # Primary key - @classmethod - def from_csv(cls, row: List[str]): - db_driver: DbDriver = cls(name=str(row[0])) - db_driver.abbr = str(row[1]) - db_driver.team_name = str(row[2]) - db_driver.country_code = str(row[3]) - return db_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 + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=False) + name: Mapped[str] = mapped_column(String(32), nullable=False, unique=True) + abbr: Mapped[str] = mapped_column(String(4), nullable=False, unique=True) + team_id: Mapped[str] = mapped_column(ForeignKey("team.id"), nullable=False) + country_code: Mapped[str] = mapped_column(String(2), nullable=False) # alpha-2 code # Relationships - team: Mapped[DbTeam] = relationship("DbTeam", foreign_keys=[team_name]) \ No newline at end of file + team: Mapped[DbTeam] = relationship("DbTeam", foreign_keys=[team_id]) \ No newline at end of file diff --git a/formula10/database/model/db_race.py b/formula10/database/model/db_race.py index f075e79..cd0160c 100644 --- a/formula10/database/model/db_race.py +++ b/formula10/database/model/db_race.py @@ -1,5 +1,4 @@ from datetime import datetime -from typing import List from sqlalchemy import DateTime, Integer, String from sqlalchemy.orm import Mapped, mapped_column @@ -13,22 +12,11 @@ class DbRace(db.Model): """ __tablename__ = "race" - def __init__(self, *, name: str, number: int, date: datetime, pxx: int): - self.name = name # Primary key + def __init__(self, *, id: int): + self.id = id # Primary key - self.number = number - self.date = date - self.pxx = pxx - - @classmethod - def from_csv(cls, row: List[str]): - db_race: DbRace = cls(name=str(row[0]), - number=int(row[1]), - date=datetime.strptime(str(row[2]), "%Y-%m-%d-%H-%M"), - pxx=int(row[3])) - return db_race - - 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 \ No newline at end of file + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=False) + name: Mapped[str] = mapped_column(String(64), nullable=False, unique=True) + number: Mapped[int] = mapped_column(Integer, nullable=False, unique=True) + date: Mapped[datetime] = mapped_column(DateTime, nullable=False, unique=True) + pxx: Mapped[int] = mapped_column(Integer, nullable=False) # This is the place to guess \ No newline at end of file diff --git a/formula10/database/model/db_race_guess.py b/formula10/database/model/db_race_guess.py index 3aad55f..200f8aa 100644 --- a/formula10/database/model/db_race_guess.py +++ b/formula10/database/model/db_race_guess.py @@ -1,4 +1,3 @@ -from typing import Any, List from sqlalchemy import ForeignKey from sqlalchemy.orm import Mapped, mapped_column, relationship @@ -14,38 +13,18 @@ class DbRaceGuess(db.Model): 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, pxx_driver_name: str, dnf_driver_name: str): - self.user_name = user_name # Primary key - self.race_name = race_name # Primary key + def __init__(self, *, user_id: int, race_id: int): + self.user_id = user_id # Primary key + self.race_id = race_id # Primary key - self.dnf_driver_name = dnf_driver_name - self.pxx_driver_name = pxx_driver_name - - @classmethod - def from_csv(cls, row: List[str]): - db_race_guess: DbRaceGuess = cls(user_name=str(row[0]), - race_name=str(row[1]), - pxx_driver_name=str(row[2]), - dnf_driver_name=str(row[3])) - return db_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")) - dnf_driver_name: Mapped[str] = mapped_column(ForeignKey("driver.name")) + user_id: Mapped[int] = mapped_column(ForeignKey("user.id"), primary_key=True) + race_id: Mapped[int] = mapped_column(ForeignKey("race.id"), primary_key=True) + pxx_driver_id: Mapped[int] = mapped_column(ForeignKey("driver.id"), nullable=False) + dnf_driver_id: Mapped[int] = mapped_column(ForeignKey("driver.id"), nullable=False) # Relationships - user: Mapped[DbUser] = relationship("DbUser", foreign_keys=[user_name]) - race: Mapped[DbRace] = relationship("DbRace", foreign_keys=[race_name]) - pxx: Mapped[DbDriver] = relationship("DbDriver", foreign_keys=[pxx_driver_name]) - dnf: Mapped[DbDriver] = relationship("DbDriver", foreign_keys=[dnf_driver_name]) \ No newline at end of file + user: Mapped[DbUser] = relationship("DbUser", foreign_keys=[user_id]) + race: Mapped[DbRace] = relationship("DbRace", foreign_keys=[race_id]) + pxx: Mapped[DbDriver] = relationship("DbDriver", foreign_keys=[pxx_driver_id]) + dnf: Mapped[DbDriver] = relationship("DbDriver", foreign_keys=[dnf_driver_id]) \ No newline at end of file diff --git a/formula10/database/model/db_race_result.py b/formula10/database/model/db_race_result.py index ba14237..39bcd64 100644 --- a/formula10/database/model/db_race_result.py +++ b/formula10/database/model/db_race_result.py @@ -1,4 +1,3 @@ -from typing import Any, List from sqlalchemy import ForeignKey, String from sqlalchemy.orm import Mapped, mapped_column, relationship @@ -11,39 +10,15 @@ class DbRaceResult(db.Model): 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" - __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, pxx_driver_names_json: str, first_dnf_driver_names_json: str, dnf_driver_names_json: str, excluded_driver_names_json: str): - self.race_name = race_name # Primary key + def __init__(self, *, race_id: int): + self.race_id = race_id # Primary key - self.pxx_driver_names_json = pxx_driver_names_json - self.first_dnf_driver_names_json = first_dnf_driver_names_json - self.dnf_driver_names_json = dnf_driver_names_json - self.excluded_driver_names_json = excluded_driver_names_json - - @classmethod - def from_csv(cls, row: List[str]): - db_race_result: DbRaceResult = cls(race_name=str(row[0]), - pxx_driver_names_json=str(row[1]), - first_dnf_driver_names_json=str(row[2]), - dnf_driver_names_json=str(row[3]), - excluded_driver_names_json=str(row[4])) - return db_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)) - first_dnf_driver_names_json: Mapped[str] = mapped_column(String(1024)) - dnf_driver_names_json: Mapped[str] = mapped_column(String(1024)) - excluded_driver_names_json: Mapped[str] = mapped_column(String(1024)) + race_id: Mapped[int] = mapped_column(ForeignKey("race.id"), primary_key=True) + pxx_driver_ids_json: Mapped[str] = mapped_column(String(1024), nullable=False) + first_dnf_driver_ids_json: Mapped[str] = mapped_column(String(1024), nullable=False) + dnf_driver_ids_json: Mapped[str] = mapped_column(String(1024), nullable=False) + excluded_driver_ids_json: Mapped[str] = mapped_column(String(1024), nullable=False) # Relationships - race: Mapped[DbRace] = relationship("DbRace", foreign_keys=[race_name]) \ No newline at end of file + race: Mapped[DbRace] = relationship("DbRace", foreign_keys=[race_id]) \ No newline at end of file diff --git a/formula10/database/model/db_season_guess.py b/formula10/database/model/db_season_guess.py index 519c7d7..66c646a 100644 --- a/formula10/database/model/db_season_guess.py +++ b/formula10/database/model/db_season_guess.py @@ -1,4 +1,3 @@ -from typing import Any, List from sqlalchemy import ForeignKey, String from sqlalchemy.orm import Mapped, mapped_column, relationship @@ -12,56 +11,24 @@ class DbSeasonGuess(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_driver_names_json", "podium_drivers_driver_names_json"] - def __init__(self, *, user_name: str, team_winners_driver_names_json: str, podium_drivers_driver_names_json: str): - self.user_name = user_name # Primary key + def __init__(self, *, user_id: int): + self.user_id = user_id # Primary key - self.team_winners_driver_names_json = team_winners_driver_names_json - self.podium_drivers_driver_names_json = podium_drivers_driver_names_json - - @classmethod - def from_csv(cls, row: List[str]): - db_season_guess: DbSeasonGuess = cls(user_name=str(row[0]), - team_winners_driver_names_json=str(row[7]), - podium_drivers_driver_names_json=str(row[8])) - db_season_guess.hot_take = str(row[1]) - db_season_guess.p2_team_name = str(row[2]) - db_season_guess.overtake_driver_name = str(row[3]) - db_season_guess.dnf_driver_name = str(row[4]) - db_season_guess.gained_driver_name = str(row[5]) - db_season_guess.lost_driver_name = str(row[6]) - return db_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_driver_names_json, - self.podium_drivers_driver_names_json - ] - - user_name: Mapped[str] = mapped_column(ForeignKey("user.name"), primary_key=True) + user_id: Mapped[int] = mapped_column(ForeignKey("user.id"), primary_key=True) hot_take: Mapped[str | None] = mapped_column(String(512), nullable=True) - p2_team_name: Mapped[str | None] = mapped_column(ForeignKey("team.name"), nullable=True) - overtake_driver_name: Mapped[str | None] = mapped_column(ForeignKey("driver.name"), nullable=True) - dnf_driver_name: Mapped[str | None] = mapped_column(ForeignKey("driver.name"), nullable=True) - gained_driver_name: Mapped[str | None] = mapped_column(ForeignKey("driver.name"), nullable=True) - lost_driver_name: Mapped[str | None] = mapped_column(ForeignKey("driver.name"), nullable=True) - team_winners_driver_names_json: Mapped[str] = mapped_column(String(1024)) - podium_drivers_driver_names_json: Mapped[str] = mapped_column(String(1024)) + p2_team_id: Mapped[int | None] = mapped_column(ForeignKey("team.id"), nullable=True) + overtake_driver_id: Mapped[int | None] = mapped_column(ForeignKey("driver.id"), nullable=True) + dnf_driver_id: Mapped[int | None] = mapped_column(ForeignKey("driver.id"), nullable=True) + gained_driver_id: Mapped[int | None] = mapped_column(ForeignKey("driver.id"), nullable=True) + lost_driver_id: Mapped[int | None] = mapped_column(ForeignKey("driver.id"), nullable=True) + team_winners_driver_ids_json: Mapped[str] = mapped_column(String(1024)) + podium_drivers_driver_ids_json: Mapped[str] = mapped_column(String(1024)) # Relationships - user: Mapped[DbUser] = relationship("DbUser", foreign_keys=[user_name]) - p2_team: Mapped[DbTeam | None] = relationship("DbTeam", foreign_keys=[p2_team_name]) - overtake_driver: Mapped[DbDriver | None] = relationship("DbDriver", foreign_keys=[overtake_driver_name]) - dnf_driver: Mapped[DbDriver | None] = relationship("DbDriver", foreign_keys=[dnf_driver_name]) - gained_driver: Mapped[DbDriver | None] = relationship("DbDriver", foreign_keys=[gained_driver_name]) - lost_driver: Mapped[DbDriver | None] = relationship("DbDriver", foreign_keys=[lost_driver_name]) \ No newline at end of file + user: Mapped[DbUser] = relationship("DbUser", foreign_keys=[user_id]) + p2_team: Mapped[DbTeam | None] = relationship("DbTeam", foreign_keys=[p2_team_id]) + overtake_driver: Mapped[DbDriver | None] = relationship("DbDriver", foreign_keys=[overtake_driver_id]) + dnf_driver: Mapped[DbDriver | None] = relationship("DbDriver", foreign_keys=[dnf_driver_id]) + gained_driver: Mapped[DbDriver | None] = relationship("DbDriver", foreign_keys=[gained_driver_id]) + lost_driver: Mapped[DbDriver | None] = relationship("DbDriver", foreign_keys=[lost_driver_id]) \ No newline at end of file diff --git a/formula10/database/model/db_season_guess_result.py b/formula10/database/model/db_season_guess_result.py index bbb838b..1f72206 100644 --- a/formula10/database/model/db_season_guess_result.py +++ b/formula10/database/model/db_season_guess_result.py @@ -1,5 +1,3 @@ -from typing import List - from sqlalchemy import Boolean, ForeignKey from sqlalchemy.orm import Mapped, mapped_column, relationship @@ -12,33 +10,13 @@ class DbSeasonGuessResult(db.Model): """ __tablename__ = "seasonguessresult" - __csv_header__ = ["user_name", "hot_take_correct", "overtakes_correct"] - def __init__(self, *, user_name: str, hot_take_correct: bool, overtakes_correct: bool): - self.user_name = user_name # Primary key + def __init__(self, *, user_id: int): + self.user_id = user_id # Primary key - self.hot_take_correct = hot_take_correct - self.overtakes_correct = overtakes_correct - - @classmethod - def from_csv(cls, row: List[str]): - db_season_guess_result: DbSeasonGuessResult = cls(user_name=str(row[0]), - hot_take_correct=True if str(row[1])=="True" else False, - overtakes_correct=True if str(row[2])=="True" else False) - - return db_season_guess_result - - # This object can't be edited from the page context - # def to_csv(self) -> List[Any]: - # return [ - # self.user_name, - # self.hot_take_correct, - # self.overtakes_correct - # ] - - user_name: Mapped[str] = mapped_column(ForeignKey("user.name"), primary_key=True) - hot_take_correct: Mapped[bool] = mapped_column(Boolean) - overtakes_correct: Mapped[bool] = mapped_column(Boolean) + user_id: Mapped[int] = mapped_column(ForeignKey("user.id"), primary_key=True) + hot_take_correct: Mapped[bool] = mapped_column(Boolean, nullable=False) + overtakes_correct: Mapped[bool] = mapped_column(Boolean, nullable=False) # Relationships - user: Mapped[DbUser] = relationship("DbUser", foreign_keys=[user_name]) + user: Mapped[DbUser] = relationship("DbUser", foreign_keys=[user_id]) diff --git a/formula10/database/model/db_team.py b/formula10/database/model/db_team.py index 528413c..4f1370b 100644 --- a/formula10/database/model/db_team.py +++ b/formula10/database/model/db_team.py @@ -1,5 +1,4 @@ -from typing import List -from sqlalchemy import String +from sqlalchemy import Integer, String from sqlalchemy.orm import Mapped, mapped_column from formula10 import db @@ -11,12 +10,8 @@ class DbTeam(db.Model): """ __tablename__ = "team" - def __init__(self, *, name: str): - self.name = name # Primary key + def __init__(self, *, id: int): + self.id = id # Primary key - @classmethod - def from_csv(cls, row: List[str]): - db_team: DbTeam = cls(name=str(row[0])) - return db_team - - name: Mapped[str] = mapped_column(String(32), primary_key=True) \ No newline at end of file + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=False) + name: Mapped[str] = mapped_column(String(32), nullable=False, unique=True) \ No newline at end of file diff --git a/formula10/database/model/db_user.py b/formula10/database/model/db_user.py index 01d824f..abb6d79 100644 --- a/formula10/database/model/db_user.py +++ b/formula10/database/model/db_user.py @@ -1,5 +1,4 @@ -from typing import Any, List -from sqlalchemy import Boolean, String +from sqlalchemy import Boolean, Integer, String from sqlalchemy.orm import Mapped, mapped_column from formula10 import db @@ -10,24 +9,11 @@ class DbUser(db.Model): A user that can guess races (name only). """ __tablename__ = "user" - __csv_header__ = ["name", "enabled"] - def __init__(self, *, name: str, enabled: bool): - self.name = name # Primary key + def __init__(self, *, id: int | None): + if id is not None: + self.id = id # Primary key - self.enabled = enabled - - @classmethod - def from_csv(cls, row: List[str]): - db_user: DbUser = cls(name=str(row[0]), - enabled=True if str(row[1])=="True" else False) - return db_user - - def to_csv(self) -> List[Any]: - return [ - self.name, - self.enabled - ] - - name: Mapped[str] = mapped_column(String(32), primary_key=True) - enabled: Mapped[bool] = mapped_column(Boolean) \ No newline at end of file + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + name: Mapped[str] = mapped_column(String(32), nullable=False, unique=True) + enabled: Mapped[bool] = mapped_column(Boolean, nullable=False) \ No newline at end of file diff --git a/formula10/database/update_queries.py b/formula10/database/update_queries.py index 9ccb85e..2dbfb13 100644 --- a/formula10/database/update_queries.py +++ b/formula10/database/update_queries.py @@ -6,6 +6,7 @@ from werkzeug import Response from formula10.controller.error_controller import error_redirect from formula10.database.common_queries import race_has_result, user_exists_and_disabled, user_exists_and_enabled +from formula10.database.model.db_race import DbRace from formula10.database.model.db_race_guess import DbRaceGuess from formula10.database.model.db_race_result import DbRaceResult from formula10.database.model.db_season_guess import DbSeasonGuess @@ -14,159 +15,167 @@ from formula10.database.validation import any_is_none, positions_are_contiguous, from formula10 import ENABLE_TIMING, db -def find_or_create_race_guess(user_name: str, race_name: str) -> DbRaceGuess: +def find_or_create_race_guess(user_id: int, race_id: int) -> DbRaceGuess: # There can be a single RaceGuess at most, since (user_name, race_name) is the composite primary key - race_guess: DbRaceGuess | None = db.session.query(DbRaceGuess).filter_by(user_name=user_name, race_name=race_name).first() + race_guess: DbRaceGuess | None = db.session.query(DbRaceGuess).filter_by(user_id=user_id, race_id=race_id).first() if race_guess is not None: return race_guess # Insert a new RaceGuess - race_guess = DbRaceGuess(user_name=user_name, race_name=race_name, pxx_driver_name="TEMP", dnf_driver_name="TEMP") + race_guess = DbRaceGuess(user_id=user_id, race_id=race_id) + race_guess.pxx_driver_id = 9999 + race_guess.dnf_driver_id = 9999 db.session.add(race_guess) db.session.commit() # Double check if database insertion worked and obtain any values set by the database - race_guess = db.session.query(DbRaceGuess).filter_by(user_name=user_name, race_name=race_name).first() + race_guess = db.session.query(DbRaceGuess).filter_by(user_id=user_id, race_id=race_id).first() if race_guess is None: raise Exception("Failed adding RaceGuess to the database") return race_guess -def update_race_guess(race_name: str, user_name: str, pxx_select: str | None, dnf_select: str | None) -> Response: - if any_is_none(pxx_select, dnf_select): - return error_redirect(f"Picks for race \"{race_name}\" were not saved, because you did not fill all the fields.") +def update_race_guess(race_id: int, user_id: int, pxx_select_id: int | None, dnf_select_id: int | None) -> Response: + if any_is_none(pxx_select_id, dnf_select_id): + return error_redirect(f"Picks for race \"{race_id}\" were not saved, because you did not fill all the fields.") - if ENABLE_TIMING and race_has_started(race_name=race_name): - return error_redirect(f"No picks for race \"{race_name}\" can be entered, as this race has already started.") + if ENABLE_TIMING and race_has_started(race_id=race_id): + return error_redirect(f"No picks for race \"{race_id}\" can be entered, as this race has already started.") - if race_has_result(race_name): - return error_redirect(f"No picks for race \"{race_name}\" can be entered, as this race has already finished.") + if race_has_result(race_id): + return error_redirect(f"No picks for race \"{race_id}\" can be entered, as this race has already finished.") - pxx_driver_name: str = cast(str, pxx_select) - dnf_driver_name: str = cast(str, dnf_select) + pxx_driver_id: int = cast(int, pxx_select_id) + dnf_driver_id: int = cast(int, dnf_select_id) - race_guess: DbRaceGuess = find_or_create_race_guess(user_name, race_name) - race_guess.pxx_driver_name = pxx_driver_name - race_guess.dnf_driver_name = dnf_driver_name + race_guess: DbRaceGuess = find_or_create_race_guess(user_id, race_id) + race_guess.pxx_driver_id = pxx_driver_id + race_guess.dnf_driver_id = dnf_driver_id db.session.commit() return redirect("/race/Everyone") -def delete_race_guess(race_name: str, user_name: str) -> Response: +def delete_race_guess(race_id: int, user_id: int) -> Response: # Don't change guesses that are already over - if ENABLE_TIMING and race_has_started(race_name=race_name): - return error_redirect(f"No picks for race \"{race_name}\" can be deleted, as this race has already started.") + if ENABLE_TIMING and race_has_started(race_id=race_id): + return error_redirect(f"No picks for race with id \"{race_id}\" can be deleted, as this race has already started.") - if race_has_result(race_name): - return error_redirect(f"No picks for race \"{race_name}\" can be deleted, as this race has already finished.") + if race_has_result(race_id): + return error_redirect(f"No picks for race \"{race_id}\" can be deleted, as this race has already finished.") # Does not throw if row doesn't exist - db.session.query(DbRaceGuess).filter_by(race_name=race_name, user_name=user_name).delete() + db.session.query(DbRaceGuess).filter_by(race_id=race_id, user_id=user_id).delete() db.session.commit() return redirect("/race/Everyone") -def find_or_create_season_guess(user_name: str) -> DbSeasonGuess: +def find_or_create_season_guess(user_id: int) -> DbSeasonGuess: # There can be a single SeasonGuess at most, since user_name is the primary key - season_guess: DbSeasonGuess | None = db.session.query(DbSeasonGuess).filter_by(user_name=user_name).first() + season_guess: DbSeasonGuess | None = db.session.query(DbSeasonGuess).filter_by(user_id=user_id).first() if season_guess is not None: return season_guess # Insert a new SeasonGuess - season_guess = DbSeasonGuess(user_name=user_name, team_winners_driver_names_json=json.dumps(["TEMP"]), podium_drivers_driver_names_json=json.dumps(["TEMP"])) + season_guess = DbSeasonGuess(user_id=user_id) + season_guess.team_winners_driver_ids_json=json.dumps(["9999"]) + season_guess.podium_drivers_driver_ids_json=json.dumps(["9999"]) db.session.add(season_guess) db.session.commit() # Double check if database insertion worked and obtain any values set by the database - season_guess = db.session.query(DbSeasonGuess).filter_by(user_name=user_name).first() + season_guess = db.session.query(DbSeasonGuess).filter_by(user_id=user_id).first() if season_guess is None: raise Exception("Failed adding SeasonGuess to the database") return season_guess -def update_season_guess(user_name: str, guesses: List[str | None], team_winner_guesses: List[str | None], podium_driver_guesses: List[str]) -> Response: +def update_season_guess(user_id: int, guesses: List[str | None], team_winner_guesses: List[str | None], podium_driver_guesses: List[str]) -> Response: # Pylance marks type errors here, but those are intended. Columns are marked nullable. - if ENABLE_TIMING and race_has_started(race_name="Bahrain"): + if ENABLE_TIMING and race_has_started(race_id=1): return error_redirect("No season picks can be entered, as the season has already begun!") - season_guess: DbSeasonGuess = find_or_create_season_guess(user_name) + season_guess: DbSeasonGuess = find_or_create_season_guess(user_id) season_guess.hot_take = guesses[0] # type: ignore - season_guess.p2_team_name = guesses[1] # type: ignore - season_guess.overtake_driver_name = guesses[2] # type: ignore - season_guess.dnf_driver_name = guesses[3] # type: ignore - season_guess.gained_driver_name = guesses[4] # type: ignore - season_guess.lost_driver_name = guesses[5] # type: ignore - season_guess.team_winners_driver_names_json = json.dumps(team_winner_guesses) - season_guess.podium_drivers_driver_names_json = json.dumps(podium_driver_guesses) + season_guess.p2_team_id = guesses[1] # type: ignore + season_guess.overtake_driver_id = guesses[2] # type: ignore + season_guess.dnf_driver_id = guesses[3] # type: ignore + season_guess.gained_driver_id = guesses[4] # type: ignore + season_guess.lost_driver_id = guesses[5] # type: ignore + season_guess.team_winners_driver_ids_json = json.dumps(team_winner_guesses) + season_guess.podium_drivers_driver_ids_json = json.dumps(podium_driver_guesses) db.session.commit() return redirect(f"/season/Everyone") -def find_or_create_race_result(race_name: str) -> DbRaceResult: +def find_or_create_race_result(race_id: int) -> DbRaceResult: # There can be a single RaceResult at most, since race_name is the primary key - race_result: DbRaceResult | None = db.session.query(DbRaceResult).filter_by(race_name=race_name).first() + race_result: DbRaceResult | None = db.session.query(DbRaceResult).filter_by(race_id=race_id).first() if race_result is not None: return race_result - race_result = DbRaceResult(race_name=race_name, - pxx_driver_names_json=json.dumps(["TEMP"]), - first_dnf_driver_names_json=json.dumps(["TEMP"]), - dnf_driver_names_json=json.dumps(["TEMP"]), - excluded_driver_names_json=json.dumps(["TEMP"])) + race_result = DbRaceResult(race_id=race_id) + race_result.pxx_driver_ids_json = json.dumps(["9999"]) + race_result.first_dnf_driver_ids_json = json.dumps(["9999"]) + race_result.dnf_driver_ids_json = json.dumps(["9999"]) + race_result.excluded_driver_ids_json = json.dumps(["9999"]) db.session.add(race_result) db.session.commit() # Double check if database insertion worked and obtain any values set by the database - race_result = db.session.query(DbRaceResult).filter_by(race_name=race_name).first() + race_result = db.session.query(DbRaceResult).filter_by(race_id=race_id).first() if race_result is None: raise Exception("Failed adding RaceResult to the database") return race_result -def update_race_result(race_name: str, pxx_driver_names_list: List[str], first_dnf_driver_names_list: List[str], dnf_driver_names_list: List[str], excluded_driver_names_list: List[str]) -> Response: - if ENABLE_TIMING and not race_has_started(race_name=race_name): +def update_race_result(race_id: int, pxx_driver_ids_list: List[str], first_dnf_driver_ids_list: List[str], dnf_driver_ids_list: List[str], excluded_driver_ids_list: List[str]) -> Response: + if ENABLE_TIMING and not race_has_started(race_id=race_id): return error_redirect("No race result can be entered, as the race has not begun!") # Use strings as keys, as these dicts will be serialized to json pxx_driver_names: Dict[str, str] = { - str(position + 1): driver for position, driver in enumerate(pxx_driver_names_list) + str(position + 1): driver_id for position, driver_id in enumerate(pxx_driver_ids_list) } # Not counted drivers have to be at the end excluded_driver_names: Dict[str, str] = { - str(position + 1): driver for position, driver in enumerate(pxx_driver_names_list) - if driver in excluded_driver_names_list + str(position + 1): driver_id for position, driver_id in enumerate(pxx_driver_ids_list) + if driver_id in excluded_driver_ids_list } if len(excluded_driver_names) > 0 and (not "20" in excluded_driver_names or not positions_are_contiguous(list(excluded_driver_names.keys()))): return error_redirect("Race result was not saved, as excluded drivers must be contiguous and at the end of the field!") # First DNF drivers have to be contained in DNF drivers - for driver_name in first_dnf_driver_names_list: - if driver_name not in dnf_driver_names_list: - dnf_driver_names_list.append(driver_name) + for driver_id in first_dnf_driver_ids_list: + if driver_id not in dnf_driver_ids_list: + dnf_driver_ids_list.append(driver_id) # There can't be dnfs but no initial dnfs - if len(dnf_driver_names_list) > 0 and len(first_dnf_driver_names_list) == 0: + if len(dnf_driver_ids_list) > 0 and len(first_dnf_driver_ids_list) == 0: return error_redirect("Race result was not saved, as there cannot be DNFs without (an) initial DNF(s)!") - race_result: DbRaceResult = find_or_create_race_result(race_name) - race_result.pxx_driver_names_json = json.dumps(pxx_driver_names) - race_result.first_dnf_driver_names_json = json.dumps(first_dnf_driver_names_list) - race_result.dnf_driver_names_json = json.dumps(dnf_driver_names_list) - race_result.excluded_driver_names_json = json.dumps(excluded_driver_names_list) + race_result: DbRaceResult = find_or_create_race_result(race_id) + race_result.pxx_driver_ids_json = json.dumps(pxx_driver_names) + race_result.first_dnf_driver_ids_json = json.dumps(first_dnf_driver_ids_list) + race_result.dnf_driver_ids_json = json.dumps(dnf_driver_ids_list) + race_result.excluded_driver_ids_json = json.dumps(excluded_driver_ids_list) db.session.commit() - return redirect(f"/result/{quote(race_name)}") + race: DbRace | None = db.session.query(DbRace).filter_by(id=race_id).first() + if race is None: + raise Exception(f"Could not find DbRace with id {race_id}") + + return redirect(f"/result/{quote(race.name)}") def update_user(user_name: str | None, add: bool = False, delete: bool = False) -> Response: @@ -194,7 +203,9 @@ def update_user(user_name: str | None, add: bool = False, delete: bool = False) disabled_user.enabled = True else: - user: DbUser = DbUser(name=user_name, enabled=True) + user: DbUser = DbUser(id=None) + user.name = user_name + user.enabled = True db.session.add(user) db.session.commit() diff --git a/formula10/database/validation.py b/formula10/database/validation.py index dffaafa..186b908 100644 --- a/formula10/database/validation.py +++ b/formula10/database/validation.py @@ -31,19 +31,19 @@ def race_has_started(*, race: Race) -> bool: return race_has_started(race=race) @overload -def race_has_started(*, race_name: str) -> bool: - return race_has_started(race_name=race_name) +def race_has_started(*, race_id: int) -> bool: + return race_has_started(race_id=race_id) -def race_has_started(*, race: Race | None = None, race_name: str | None = None) -> bool: - if race is None and race_name is not None: - _race: DbRace | None = db.session.query(DbRace).filter_by(name=race_name).first() +def race_has_started(*, race: Race | None = None, race_id: int | None = None) -> bool: + if race is None and race_id is not None: + _race: DbRace | None = db.session.query(DbRace).filter_by(id=race_id).first() if _race is None: - raise Exception(f"Couldn't obtain race {race_name} to check date") + raise Exception(f"Couldn't obtain race with id {race_id} to check date") return datetime.now() > _race.date - if race is not None and race_name is None: + if race is not None and race_id is None: return datetime.now() > race.date raise Exception("race_has_started received illegal arguments") diff --git a/formula10/domain/model/driver.py b/formula10/domain/model/driver.py index 0b951f4..71caa13 100644 --- a/formula10/domain/model/driver.py +++ b/formula10/domain/model/driver.py @@ -8,6 +8,7 @@ class Driver(): @classmethod def from_db_driver(cls, db_driver: DbDriver): driver: Driver = cls() + driver.id = db_driver.id driver.name = db_driver.name driver.abbr = db_driver.abbr driver.country = db_driver.country_code @@ -15,18 +16,23 @@ class Driver(): return driver def to_db_driver(self) -> DbDriver: - db_driver: DbDriver = DbDriver(name=self.name) + db_driver: DbDriver = DbDriver(id=self.id) + db_driver.name = self.name db_driver.abbr = self.abbr db_driver.country_code = self.country - db_driver.team_name = self.team.name + db_driver.team_id = self.team.name return db_driver def __eq__(self, __value: object) -> bool: if isinstance(__value, Driver): - return self.name == __value.name + return self.id == __value.id return NotImplemented + def __hash__(self) -> int: + return hash(self.id) + + id: int name: str abbr: str country: str @@ -38,6 +44,7 @@ class Driver(): NONE_DRIVER: Driver = Driver() +NONE_DRIVER.id = 0 NONE_DRIVER.name = "None" NONE_DRIVER.abbr = "None" NONE_DRIVER.country = "NO" diff --git a/formula10/domain/model/race.py b/formula10/domain/model/race.py index 1e36cc6..a74e20e 100644 --- a/formula10/domain/model/race.py +++ b/formula10/domain/model/race.py @@ -8,6 +8,7 @@ class Race(): @classmethod def from_db_race(cls, db_race: DbRace): race: Race = cls() + race.id = db_race.id race.name = db_race.name race.number = db_race.number race.date = db_race.date @@ -15,18 +16,23 @@ class Race(): return race def to_db_race(self) -> DbRace: - db_race: DbRace = DbRace(name=self.name, - number=self.number, - date=self.date, - pxx=self.place_to_guess) + db_race: DbRace = DbRace(id=self.id) + db_race.name = self.name + db_race.number = self.number + db_race.date = self.date + db_race.pxx = self.place_to_guess return db_race def __eq__(self, __value: object) -> bool: if isinstance(__value, Race): - return self.name == __value.name + return self.id == __value.id return NotImplemented + def __hash__(self) -> int: + return hash(self.id) + + id: int name: str number: int date: datetime diff --git a/formula10/domain/model/race_guess.py b/formula10/domain/model/race_guess.py index 4e67cd7..fe46c5c 100644 --- a/formula10/domain/model/race_guess.py +++ b/formula10/domain/model/race_guess.py @@ -15,10 +15,9 @@ class RaceGuess(): return race_guess def to_db_race_guess(self) -> DbRaceGuess: - db_race_guess: DbRaceGuess = DbRaceGuess(user_name=self.user.name, - race_name=self.race.name, - pxx_driver_name=self.pxx_guess.name, - dnf_driver_name=self.dnf_guess.name) + db_race_guess: DbRaceGuess = DbRaceGuess(user_id=self.user.id, race_id=self.race.id) + db_race_guess.pxx_driver_id = self.pxx_guess.id + db_race_guess.dnf_driver_id = self.dnf_guess.id return db_race_guess def __eq__(self, __value: object) -> bool: @@ -27,6 +26,9 @@ class RaceGuess(): return NotImplemented + def __hash__(self) -> int: + return hash((self.user, self.race)) + user: User race: Race pxx_guess: Driver diff --git a/formula10/domain/model/race_result.py b/formula10/domain/model/race_result.py index f02d1cf..94f5ace 100644 --- a/formula10/domain/model/race_result.py +++ b/formula10/domain/model/race_result.py @@ -14,27 +14,27 @@ class RaceResult: race_result.race = Race.from_db_race(db_race_result.race) # Deserialize from json - standing: Dict[str, str] = json.loads(db_race_result.pxx_driver_names_json) - initial_dnf: List[str] = json.loads(db_race_result.first_dnf_driver_names_json) - all_dnfs: List[str] = json.loads(db_race_result.dnf_driver_names_json) - standing_exclusions: List[str] = json.loads(db_race_result.excluded_driver_names_json) + standing: Dict[str, str] = json.loads(db_race_result.pxx_driver_ids_json) + initial_dnf: List[str] = json.loads(db_race_result.first_dnf_driver_ids_json) + all_dnfs: List[str] = json.loads(db_race_result.dnf_driver_ids_json) + standing_exclusions: List[str] = json.loads(db_race_result.excluded_driver_ids_json) # Populate relationships race_result.standing = { - position: Driver.from_db_driver(find_single_driver_strict(driver_name)) - for position, driver_name in standing.items() + position: Driver.from_db_driver(find_single_driver_strict(int(driver_id))) + for position, driver_id in standing.items() } race_result.initial_dnf = [ - Driver.from_db_driver(find_single_driver_strict(driver_name)) - for driver_name in initial_dnf + Driver.from_db_driver(find_single_driver_strict(int(driver_id))) + for driver_id in initial_dnf ] race_result.all_dnfs = [ - Driver.from_db_driver(find_single_driver_strict(driver_name)) - for driver_name in all_dnfs + Driver.from_db_driver(find_single_driver_strict(int(driver_id))) + for driver_id in all_dnfs ] race_result.standing_exclusions = [ - Driver.from_db_driver(find_single_driver_strict(driver_name)) - for driver_name in standing_exclusions + Driver.from_db_driver(find_single_driver_strict(int(driver_id))) + for driver_id in standing_exclusions ] return race_result @@ -45,21 +45,21 @@ class RaceResult: position: driver.name for position, driver in self.standing.items() } initial_dnf: List[str] = [ - driver.name for driver in self.initial_dnf if driver + str(driver.id) for driver in self.initial_dnf if driver ] all_dnfs: List[str] = [ - driver.name for driver in self.all_dnfs if driver + str(driver.id) for driver in self.all_dnfs if driver ] standing_exclusions: List[str] = [ - driver.name for driver in self.standing_exclusions if driver + str(driver.id) for driver in self.standing_exclusions if driver ] # Serialize to json - db_race_result: DbRaceResult = DbRaceResult(race_name=self.race.name, - pxx_driver_names_json=json.dumps(standing), - first_dnf_driver_names_json=json.dumps(initial_dnf), - dnf_driver_names_json=json.dumps(all_dnfs), - excluded_driver_names_json=json.dumps(standing_exclusions)) + db_race_result: DbRaceResult = DbRaceResult(race_id=self.race.id) + db_race_result.pxx_driver_ids_json = json.dumps(standing) + db_race_result.first_dnf_driver_ids_json = json.dumps(initial_dnf) + db_race_result.dnf_driver_ids_json = json.dumps(all_dnfs) + db_race_result.excluded_driver_ids_json = json.dumps(standing_exclusions) return db_race_result @@ -69,6 +69,9 @@ class RaceResult: return NotImplemented + def __hash__(self) -> int: + return hash(self.race) + race: Race standing: Dict[str, Driver] # Always contains all 20 drivers, even if DNF'ed or excluded initial_dnf: List[Driver] # initial_dnf is empty if no-one DNF'ed diff --git a/formula10/domain/model/season_guess.py b/formula10/domain/model/season_guess.py index 98e2956..900f83c 100644 --- a/formula10/domain/model/season_guess.py +++ b/formula10/domain/model/season_guess.py @@ -20,17 +20,17 @@ class SeasonGuess(): season_guess.most_wdc_lost = Driver.from_db_driver(db_season_guess.lost_driver) if db_season_guess.lost_driver is not None else None # Deserialize from json - team_winners: List[str | None] = json.loads(db_season_guess.team_winners_driver_names_json) - podiums: List[str] = json.loads(db_season_guess.podium_drivers_driver_names_json) + team_winners: List[str | None] = json.loads(db_season_guess.team_winners_driver_ids_json) + podiums: List[str] = json.loads(db_season_guess.podium_drivers_driver_ids_json) # Populate relationships season_guess.team_winners = [ - Driver.from_db_driver(find_single_driver_strict(driver_name)) if driver_name is not None else None - for driver_name in team_winners + Driver.from_db_driver(find_single_driver_strict(int(driver_id))) if driver_id is not None else None + for driver_id in team_winners ] season_guess.podiums = [ - Driver.from_db_driver(find_single_driver_strict(driver_name)) - for driver_name in podiums + Driver.from_db_driver(find_single_driver_strict(int(driver_id))) + for driver_id in podiums ] return season_guess @@ -46,16 +46,15 @@ class SeasonGuess(): ] # Serialize to json - db_season_guess: DbSeasonGuess = DbSeasonGuess(user_name=self.user.name, - team_winners_driver_names_json=json.dumps(team_winners), - podium_drivers_driver_names_json=json.dumps(podiums)) - db_season_guess.user_name = self.user.name + db_season_guess: DbSeasonGuess = DbSeasonGuess(user_id=self.user.id) db_season_guess.hot_take = self.hot_take - db_season_guess.p2_team_name = self.p2_wcc.name if self.p2_wcc is not None else None - db_season_guess.overtake_driver_name = self.most_overtakes.name if self.most_overtakes is not None else None - db_season_guess.dnf_driver_name = self.most_dnfs.name if self.most_dnfs is not None else None - db_season_guess.gained_driver_name = self.most_wdc_gained.name if self.most_wdc_gained is not None else None - db_season_guess.lost_driver_name = self.most_wdc_lost.name if self.most_wdc_lost is not None else None + db_season_guess.p2_team_id = self.p2_wcc.id if self.p2_wcc is not None else None + db_season_guess.overtake_driver_id = self.most_overtakes.id if self.most_overtakes is not None else None + db_season_guess.dnf_driver_id = self.most_dnfs.id if self.most_dnfs is not None else None + db_season_guess.gained_driver_id = self.most_wdc_gained.id if self.most_wdc_gained is not None else None + db_season_guess.lost_driver_id = self.most_wdc_lost.id if self.most_wdc_lost is not None else None + db_season_guess.team_winners_driver_ids_json=json.dumps(team_winners) + db_season_guess.podium_drivers_driver_ids_json=json.dumps(podiums) return db_season_guess diff --git a/formula10/domain/model/season_guess_result.py b/formula10/domain/model/season_guess_result.py index 4794918..b08c3da 100644 --- a/formula10/domain/model/season_guess_result.py +++ b/formula10/domain/model/season_guess_result.py @@ -12,6 +12,12 @@ class SeasonGuessResult(): return season_guess_result + def to_db_season_guess_result(self) -> DbSeasonGuessResult: + db_season_guess_result: DbSeasonGuessResult = DbSeasonGuessResult(user_id=self.user.id) + db_season_guess_result.hot_take_correct = self.hot_take_correct + db_season_guess_result.overtakes_correct = self.overtakes_correct + return db_season_guess_result + user: User hot_take_correct: bool overtakes_correct: bool \ No newline at end of file diff --git a/formula10/domain/model/team.py b/formula10/domain/model/team.py index 51925fc..f412775 100644 --- a/formula10/domain/model/team.py +++ b/formula10/domain/model/team.py @@ -7,19 +7,25 @@ class Team(): @classmethod def from_db_team(cls, db_team: DbTeam): team: Team = cls() + team.id = db_team.id team.name = db_team.name return team def to_db_team(self) -> DbTeam: - db_team: DbTeam = DbTeam(name=self.name) + db_team: DbTeam = DbTeam(id=self.id) + db_team.name = self.name return db_team def __eq__(self, __value: object) -> bool: if isinstance(__value, Team): - return self.name == __value.name + return self.id == __value.id return NotImplemented + def __hash__(self) -> int: + return hash(self.id) + + id: int name: str @property @@ -27,4 +33,5 @@ class Team(): return quote(self.name) NONE_TEAM: Team = Team() +NONE_TEAM.id = 0 NONE_TEAM.name = "None" \ No newline at end of file diff --git a/formula10/domain/model/user.py b/formula10/domain/model/user.py index 7dc18b6..4373448 100644 --- a/formula10/domain/model/user.py +++ b/formula10/domain/model/user.py @@ -7,20 +7,27 @@ class User(): @classmethod def from_db_user(cls, db_user: DbUser): user: User = cls() + user.id = db_user.id user.name = db_user.name user.enabled = db_user.enabled return user def to_db_user(self) -> DbUser: - db_user: DbUser = DbUser(name=self.name, enabled=self.enabled) + db_user: DbUser = DbUser(id=self.id) + db_user.name = self.name + db_user.enabled = self.enabled return db_user def __eq__(self, __value: object) -> bool: if isinstance(__value, User): - return self.name == __value.name + return self.id == __value.id return NotImplemented + def __hash__(self) -> int: + return hash(self.id) + + id: int name: str enabled: bool diff --git a/formula10/domain/points_model.py b/formula10/domain/points_model.py index 30ca67e..9bd42b7 100644 --- a/formula10/domain/points_model.py +++ b/formula10/domain/points_model.py @@ -51,14 +51,14 @@ WDC_STANDING_2023: Dict[str, int] = { "Charles Leclerc": 5, "Lando Norris": 6, "Carlos Sainz": 7, - "George Russel": 8, # @todo typo + "George Russell": 8, "Oscar Piastri": 9, "Lance Stroll": 10, "Pierre Gasly": 11, "Esteban Ocon": 12, "Alexander Albon": 13, "Yuki Tsunoda": 14, - "Valteri Bottas": 15, # @todo typo + "Valtteri Bottas": 15, "Nico Hulkenberg": 16, "Daniel Ricciardo": 17, "Zhou Guanyu": 18, diff --git a/formula10/domain/template_model.py b/formula10/domain/template_model.py index 17f8c07..669775f 100644 --- a/formula10/domain/template_model.py +++ b/formula10/domain/template_model.py @@ -6,7 +6,7 @@ from formula10.domain.model.driver import Driver from formula10.domain.model.race import Race from formula10.domain.model.race_result import RaceResult from formula10.domain.model.user import User -from formula10.database.validation import find_first_else_none, find_multiple_strict, race_has_started +from formula10.database.validation import find_first_else_none, find_multiple_strict, find_single_strict, race_has_started class TemplateModel(Model): @@ -33,10 +33,12 @@ class TemplateModel(Model): return not race_has_started(race=race) if ENABLE_TIMING else True def season_guess_open(self) -> bool: - return not race_has_started(race_name="Bahrain") if ENABLE_TIMING else True + return not race_has_started(race_id=1) if ENABLE_TIMING else True def race_result_open(self, race_name: str) -> bool: - return race_has_started(race_name=race_name) if ENABLE_TIMING else True + predicate: Callable[[Race], bool] = lambda race: race.name == race_name + race: Race = find_single_strict(predicate, self.all_races()) + return race_has_started(race_id=race.id) if ENABLE_TIMING else True def active_user_name_or_everyone(self) -> str: return self.active_user.name if self.active_user is not None else "Everyone" diff --git a/formula10/templates/base.jinja b/formula10/templates/base.jinja index f56f1c6..3941954 100644 --- a/formula10/templates/base.jinja +++ b/formula10/templates/base.jinja @@ -34,7 +34,7 @@ {% endif %} {% for driver in drivers %} - + {% endfor %} @@ -55,13 +55,13 @@ {% for driver in drivers %} {% if driver_match == driver %} {% set user_has_chosen.driverpre = true %} - + {% else %} - + {% endif %} {% if (include_none == true) and (driver == model.none_driver()) %} - + {% endif %} {% endfor %} @@ -85,7 +85,7 @@ {% endif %} {% for team in teams %} - + {% endfor %} @@ -106,13 +106,13 @@ {% for team in teams %} {% if team_match == team %} {% set user_has_chosen.teampre = true %} - + {% else %} - + {% endif %} {% if (include_none == true) and (team == model.none_team()) %} - + {% endif %} {% endfor %} diff --git a/formula10/templates/result.jinja b/formula10/templates/result.jinja index 25fbf34..2d1cd61 100644 --- a/formula10/templates/result.jinja +++ b/formula10/templates/result.jinja @@ -60,6 +60,7 @@ {% endif %}
+ {# Place numbers #}
    {% for driver in model.all_drivers_or_active_result_standing_drivers() %}
  • + {# Drag and drop #}
      {% for driver in model.all_drivers_or_active_result_standing_drivers() %} @@ -79,40 +81,40 @@ {# Driver DNFed at first #}
      -
      {# Driver DNFed #}
      -
      {# Driver Excluded #}
      -
      {# Standing order #} - + {% endfor %}
    diff --git a/formula10/templates/season.jinja b/formula10/templates/season.jinja index bf5e746..1af2adb 100644 --- a/formula10/templates/season.jinja +++ b/formula10/templates/season.jinja @@ -102,26 +102,26 @@
    + for="teamwinner-{{ team.id }}-1-{{ user.id }}">{{ driver_a.name }}
    + for="teamwinner-{{ team.id }}-2-{{ user.id }}">{{ driver_b.name }}
    {% endfor %} @@ -139,12 +139,12 @@
    + for="podium-{{ driver_a.id }}-{{ user.id }}">{{ driver_a.name }}
    @@ -152,12 +152,12 @@
    + for="podium-{{ driver_b.id }}-{{ user.id }}">{{ driver_b.name }}
    {% endfor %}