Large database migration
All checks were successful
Build Formula10 Docker Image / build-docker (push) Successful in 15s

This commit is contained in:
2024-03-03 15:38:35 +01:00
parent 96cb8ca891
commit d3097038a5
34 changed files with 307 additions and 593 deletions

View File

@ -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
1 name abbr team_name country_code
2 None None None NO
3 Alexander Albon ALB Williams TH
4 Fernando Alonso ALO Aston Martin ES
5 Valteri Bottas BOT Sauber FL
6 Pierre Gasly GAS Alpine FR
7 Lewis Hamilton HAM Mercedes UK
8 Nico Hulkenberg HUL Haas DE
9 Charles Leclerc LEC Ferrari MC
10 Kevin Magnussen MAG Haas DK
11 Lando Norris NOR McLaren UK
12 Esteban Ocon OCO Alpine FR
13 Sergio Perez PER Red Bull MX
14 Oscar Piastri PIA McLaren AU
15 Daniel Ricciardo RIC VCARB AU
16 George Russel RUS Mercedes UK
17 Carlos Sainz SAI Ferrari ES
18 Logan Sargeant SAR Williams US
19 Lance Stroll STR Aston Martin CA
20 Yuki Tsunoda TSU VCARB JP
21 Max Verstappen VER Red Bull NL
22 Zhou Guanyu ZHO Sauber CN

View File

@ -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
1 name number date pxx
2 Bahrain 1 2024-03-02-16-00 10
3 Saudi Arabia 2 2024-03-09-18-00 6
4 Australia 3 2024-03-24-05-00 15
5 Japan 4 2024-04-07-07-00 9
6 China 5 2024-04-21-09-00 7
7 Miami 6 2024-05-05-22-00 13
8 Emilia-Romagna 7 2024-05-21-15-00 17
9 Monaco 8 2024-05-26-15-00 5
10 Canada 9 2024-06-09-20-00 12
11 Spain 10 2024-06-23-15-00 8
12 Austria 11 2024-06-30-15-00 11
13 Great Britain 12 2024-07-07-16-00 4
14 Hungary 12 2024-07-23-15-00 17
15 Belgium 13 2024-07-28-15-00 13
16 Netherlands 14 2024-08-25-15-00 7
17 Monza 15 2024-09-01-15-00 16
18 Azerbaijan 16 2024-09-15-13-00 8
19 Singapore 17 2024-09-22-14-00 11
20 Austin 18 2024-10-20-21-00 5
21 Mexico 19 2024-10-27-21-00 14
22 Brazil 20 2024-11-03-18-00 4
23 Las Vegas 21 2024-11-23-07-00 12
24 Qatar 22 2024-12-01-18-00 6
25 Abu Dhabi 23 2024-12-08-14-00 10

View File

@ -1 +0,0 @@
user_name,hot_take_correct,overtakes_correct
1 user_name hot_take_correct overtakes_correct

View File

@ -1,12 +0,0 @@
name
None
Alpine
Aston Martin
Ferrari
Haas
McLaren
Mercedes
Red Bull
Sauber
VCARB
Williams
1 name
2 None
3 Alpine
4 Aston Martin
5 Ferrari
6 Haas
7 McLaren
8 Mercedes
9 Red Bull
10 Sauber
11 VCARB
12 Williams

View File

@ -37,6 +37,9 @@
nodePackages.sass nodePackages.sass
nodePackages.postcss-cli nodePackages.postcss-cli
nodePackages.autoprefixer nodePackages.autoprefixer
sqlitebrowser # To modify tables
dbeaver # To import/export data + diagrams
]; ];
# Use $1 for positional args # Use $1 for positional args

View File

@ -4,12 +4,9 @@ from flask_sqlalchemy import SQLAlchemy
# Load local ENV variables (can be set when calling the executable) # Load local ENV variables (can be set when calling the executable)
ENABLE_TIMING: bool = False if os.getenv("DISABLE_TIMING") == "True" else True 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:") print("Running Formula10 with:")
if not ENABLE_TIMING: if not ENABLE_TIMING:
print("- Disabled timing constraints") print("- Disabled timing constraints")
if ENABLE_DEBUG_ENDPOINTS:
print("- Enabled debug endpoints")
app: Flask = Flask(__name__) app: Flask = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:///formula10.db" app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:///formula10.db"
@ -36,8 +33,9 @@ import formula10.controller.error_controller
# TODO # TODO
# Large DB Update # 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) # - 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) # - Mask to allow changing usernames (easy if name is not used as ID)
# - Maybe even masks for races + drivers + teams? # - Maybe even masks for races + drivers + teams?
# - DB fields for links to F1 site # - 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?) # - Auto calculate season points (display season points in table + season guess card title?)
# General # 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)? # - 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) # - Unit testing (as much as possible, but especially points calculation)

View File

@ -2,49 +2,11 @@ from typing import List
from urllib.parse import unquote from urllib.parse import unquote
from flask import redirect, render_template, request from flask import redirect, render_template, request
from werkzeug import Response 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.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.domain.template_model import TemplateModel
from formula10 import ENABLE_DEBUG_ENDPOINTS, app from formula10 import 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("/")
@app.route("/result") @app.route("/result")
@ -69,7 +31,9 @@ def result_enter_post(race_name: str) -> Response:
dnfs: List[str] = request.form.getlist("dnf-drivers") dnfs: List[str] = request.form.getlist("dnf-drivers")
excluded: List[str] = request.form.getlist("excluded-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") @app.route("/user")

View File

@ -3,6 +3,7 @@ from flask import redirect, render_template, request
from werkzeug import Response from werkzeug import Response
from formula10.database.update_queries import delete_race_guess, update_race_guess 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.points_model import PointsModel
from formula10.domain.template_model import TemplateModel from formula10.domain.template_model import TemplateModel
from formula10 import app 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") pxx: str | None = request.form.get("pxxselect")
dnf: str | None = request.form.get("dnfselect") 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/<race_name>/<user_name>", methods=["POST"]) @app.route("/race-guess-delete/<race_name>/<user_name>", methods=["POST"])
@ -44,4 +49,6 @@ def race_guess_delete_post(race_name: str, user_name: str) -> Response:
race_name = unquote(race_name) race_name = unquote(race_name)
user_name = unquote(user_name) user_name = unquote(user_name)
return delete_race_guess(race_name, user_name) 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)

View File

@ -5,6 +5,7 @@ from werkzeug import Response
from formula10.database.model.db_team import DbTeam from formula10.database.model.db_team import DbTeam
from formula10.database.update_queries import update_season_guess 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.model.team import NONE_TEAM
from formula10.domain.points_model import PointsModel from formula10.domain.points_model import PointsModel
from formula10.domain.template_model import TemplateModel 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 # TODO: This is pretty ugly, to do queries in the controller
team_winner_guesses: List[str | None] = [ 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") podium_driver_guesses: List[str] = request.form.getlist("podiumdrivers")
return update_season_guess(user_name, guesses, team_winner_guesses, podium_driver_guesses) user_id: int = Model().user_by(user_name=user_name).id
return update_season_guess(user_id, guesses, team_winner_guesses, podium_driver_guesses)

View File

@ -3,8 +3,8 @@ from formula10.database.model.db_race_result import DbRaceResult
from formula10.database.model.db_user import DbUser from formula10.database.model.db_user import DbUser
from formula10 import db from formula10 import db
def race_has_result(race_name: str) -> bool: def race_has_result(race_id: int) -> bool:
return db.session.query(DbRaceResult).filter_by(race_name=race_name).first() is not None return db.session.query(DbRaceResult).filter_by(race_id=race_id).first() is not None
def user_exists_and_enabled(user_name: str) -> bool: 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 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: def find_single_driver_strict(driver_id: int) -> DbDriver:
db_driver: DbDriver | None = db.session.query(DbDriver).filter_by(name=driver_name).first() db_driver: DbDriver | None = db.session.query(DbDriver).filter_by(id=driver_id).first()
if db_driver is None: 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 return db_driver

View File

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

View File

@ -1,5 +1,4 @@
from typing import List from sqlalchemy import Integer, String, ForeignKey
from sqlalchemy import String, ForeignKey
from sqlalchemy.orm import mapped_column, Mapped, relationship from sqlalchemy.orm import mapped_column, Mapped, relationship
from formula10.database.model.db_team import DbTeam from formula10.database.model.db_team import DbTeam
@ -13,21 +12,14 @@ class DbDriver(db.Model):
""" """
__tablename__ = "driver" __tablename__ = "driver"
def __init__(self, *, name: str): def __init__(self, *, id: int):
self.name = name # Primary key self.id = id # Primary key
@classmethod id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=False)
def from_csv(cls, row: List[str]): name: Mapped[str] = mapped_column(String(32), nullable=False, unique=True)
db_driver: DbDriver = cls(name=str(row[0])) abbr: Mapped[str] = mapped_column(String(4), nullable=False, unique=True)
db_driver.abbr = str(row[1]) team_id: Mapped[str] = mapped_column(ForeignKey("team.id"), nullable=False)
db_driver.team_name = str(row[2]) country_code: Mapped[str] = mapped_column(String(2), nullable=False) # alpha-2 code
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
# Relationships # Relationships
team: Mapped[DbTeam] = relationship("DbTeam", foreign_keys=[team_name]) team: Mapped[DbTeam] = relationship("DbTeam", foreign_keys=[team_id])

View File

@ -1,5 +1,4 @@
from datetime import datetime from datetime import datetime
from typing import List
from sqlalchemy import DateTime, Integer, String from sqlalchemy import DateTime, Integer, String
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
@ -13,22 +12,11 @@ class DbRace(db.Model):
""" """
__tablename__ = "race" __tablename__ = "race"
def __init__(self, *, name: str, number: int, date: datetime, pxx: int): def __init__(self, *, id: int):
self.name = name # Primary key self.id = id # Primary key
self.number = number id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=False)
self.date = date name: Mapped[str] = mapped_column(String(64), nullable=False, unique=True)
self.pxx = pxx number: Mapped[int] = mapped_column(Integer, nullable=False, unique=True)
date: Mapped[datetime] = mapped_column(DateTime, nullable=False, unique=True)
@classmethod pxx: Mapped[int] = mapped_column(Integer, nullable=False) # This is the place to guess
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

View File

@ -1,4 +1,3 @@
from typing import Any, List
from sqlalchemy import ForeignKey from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship 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. It stores the corresponding race and the guessed drivers for PXX and DNF.
""" """
__tablename__ = "raceguess" __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): def __init__(self, *, user_id: int, race_id: int):
self.user_name = user_name # Primary key self.user_id = user_id # Primary key
self.race_name = race_name # Primary key self.race_id = race_id # Primary key
self.dnf_driver_name = dnf_driver_name user_id: Mapped[int] = mapped_column(ForeignKey("user.id"), primary_key=True)
self.pxx_driver_name = pxx_driver_name race_id: Mapped[int] = mapped_column(ForeignKey("race.id"), primary_key=True)
pxx_driver_id: Mapped[int] = mapped_column(ForeignKey("driver.id"), nullable=False)
@classmethod dnf_driver_id: Mapped[int] = mapped_column(ForeignKey("driver.id"), nullable=False)
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"))
# Relationships # Relationships
user: Mapped[DbUser] = relationship("DbUser", foreign_keys=[user_name]) user: Mapped[DbUser] = relationship("DbUser", foreign_keys=[user_id])
race: Mapped[DbRace] = relationship("DbRace", foreign_keys=[race_name]) race: Mapped[DbRace] = relationship("DbRace", foreign_keys=[race_id])
pxx: Mapped[DbDriver] = relationship("DbDriver", foreign_keys=[pxx_driver_name]) pxx: Mapped[DbDriver] = relationship("DbDriver", foreign_keys=[pxx_driver_id])
dnf: Mapped[DbDriver] = relationship("DbDriver", foreign_keys=[dnf_driver_name]) dnf: Mapped[DbDriver] = relationship("DbDriver", foreign_keys=[dnf_driver_id])

View File

@ -1,4 +1,3 @@
from typing import Any, List
from sqlalchemy import ForeignKey, String from sqlalchemy import ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column, relationship 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. 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" __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): def __init__(self, *, race_id: int):
self.race_name = race_name # Primary key self.race_id = race_id # Primary key
self.pxx_driver_names_json = pxx_driver_names_json race_id: Mapped[int] = mapped_column(ForeignKey("race.id"), primary_key=True)
self.first_dnf_driver_names_json = first_dnf_driver_names_json pxx_driver_ids_json: Mapped[str] = mapped_column(String(1024), nullable=False)
self.dnf_driver_names_json = dnf_driver_names_json first_dnf_driver_ids_json: Mapped[str] = mapped_column(String(1024), nullable=False)
self.excluded_driver_names_json = excluded_driver_names_json dnf_driver_ids_json: Mapped[str] = mapped_column(String(1024), nullable=False)
excluded_driver_ids_json: Mapped[str] = mapped_column(String(1024), nullable=False)
@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))
# Relationships # Relationships
race: Mapped[DbRace] = relationship("DbRace", foreign_keys=[race_name]) race: Mapped[DbRace] = relationship("DbRace", foreign_keys=[race_id])

View File

@ -1,4 +1,3 @@
from typing import Any, List
from sqlalchemy import ForeignKey, String from sqlalchemy import ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column, relationship 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. A collection of bonus guesses for the entire season.
""" """
__tablename__ = "seasonguess" __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): def __init__(self, *, user_id: int):
self.user_name = user_name # Primary key self.user_id = user_id # Primary key
self.team_winners_driver_names_json = team_winners_driver_names_json user_id: Mapped[int] = mapped_column(ForeignKey("user.id"), primary_key=True)
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)
hot_take: Mapped[str | None] = mapped_column(String(512), nullable=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) p2_team_id: Mapped[int | None] = mapped_column(ForeignKey("team.id"), nullable=True)
overtake_driver_name: Mapped[str | None] = mapped_column(ForeignKey("driver.name"), nullable=True) overtake_driver_id: Mapped[int | None] = mapped_column(ForeignKey("driver.id"), nullable=True)
dnf_driver_name: Mapped[str | None] = mapped_column(ForeignKey("driver.name"), nullable=True) dnf_driver_id: Mapped[int | None] = mapped_column(ForeignKey("driver.id"), nullable=True)
gained_driver_name: Mapped[str | None] = mapped_column(ForeignKey("driver.name"), nullable=True) gained_driver_id: Mapped[int | None] = mapped_column(ForeignKey("driver.id"), nullable=True)
lost_driver_name: Mapped[str | None] = mapped_column(ForeignKey("driver.name"), nullable=True) lost_driver_id: Mapped[int | None] = mapped_column(ForeignKey("driver.id"), nullable=True)
team_winners_driver_names_json: Mapped[str] = mapped_column(String(1024)) team_winners_driver_ids_json: Mapped[str] = mapped_column(String(1024))
podium_drivers_driver_names_json: Mapped[str] = mapped_column(String(1024)) podium_drivers_driver_ids_json: Mapped[str] = mapped_column(String(1024))
# Relationships # Relationships
user: Mapped[DbUser] = relationship("DbUser", foreign_keys=[user_name]) user: Mapped[DbUser] = relationship("DbUser", foreign_keys=[user_id])
p2_team: Mapped[DbTeam | None] = relationship("DbTeam", foreign_keys=[p2_team_name]) p2_team: Mapped[DbTeam | None] = relationship("DbTeam", foreign_keys=[p2_team_id])
overtake_driver: Mapped[DbDriver | None] = relationship("DbDriver", foreign_keys=[overtake_driver_name]) overtake_driver: Mapped[DbDriver | None] = relationship("DbDriver", foreign_keys=[overtake_driver_id])
dnf_driver: Mapped[DbDriver | None] = relationship("DbDriver", foreign_keys=[dnf_driver_name]) dnf_driver: Mapped[DbDriver | None] = relationship("DbDriver", foreign_keys=[dnf_driver_id])
gained_driver: Mapped[DbDriver | None] = relationship("DbDriver", foreign_keys=[gained_driver_name]) gained_driver: Mapped[DbDriver | None] = relationship("DbDriver", foreign_keys=[gained_driver_id])
lost_driver: Mapped[DbDriver | None] = relationship("DbDriver", foreign_keys=[lost_driver_name]) lost_driver: Mapped[DbDriver | None] = relationship("DbDriver", foreign_keys=[lost_driver_id])

View File

@ -1,5 +1,3 @@
from typing import List
from sqlalchemy import Boolean, ForeignKey from sqlalchemy import Boolean, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
@ -12,33 +10,13 @@ class DbSeasonGuessResult(db.Model):
""" """
__tablename__ = "seasonguessresult" __tablename__ = "seasonguessresult"
__csv_header__ = ["user_name", "hot_take_correct", "overtakes_correct"]
def __init__(self, *, user_name: str, hot_take_correct: bool, overtakes_correct: bool): def __init__(self, *, user_id: int):
self.user_name = user_name # Primary key self.user_id = user_id # Primary key
self.hot_take_correct = hot_take_correct user_id: Mapped[int] = mapped_column(ForeignKey("user.id"), primary_key=True)
self.overtakes_correct = overtakes_correct hot_take_correct: Mapped[bool] = mapped_column(Boolean, nullable=False)
overtakes_correct: Mapped[bool] = mapped_column(Boolean, nullable=False)
@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)
# Relationships # Relationships
user: Mapped[DbUser] = relationship("DbUser", foreign_keys=[user_name]) user: Mapped[DbUser] = relationship("DbUser", foreign_keys=[user_id])

View File

@ -1,5 +1,4 @@
from typing import List from sqlalchemy import Integer, String
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from formula10 import db from formula10 import db
@ -11,12 +10,8 @@ class DbTeam(db.Model):
""" """
__tablename__ = "team" __tablename__ = "team"
def __init__(self, *, name: str): def __init__(self, *, id: int):
self.name = name # Primary key self.id = id # Primary key
@classmethod id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=False)
def from_csv(cls, row: List[str]): name: Mapped[str] = mapped_column(String(32), nullable=False, unique=True)
db_team: DbTeam = cls(name=str(row[0]))
return db_team
name: Mapped[str] = mapped_column(String(32), primary_key=True)

View File

@ -1,5 +1,4 @@
from typing import Any, List from sqlalchemy import Boolean, Integer, String
from sqlalchemy import Boolean, String
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from formula10 import db from formula10 import db
@ -10,24 +9,11 @@ class DbUser(db.Model):
A user that can guess races (name only). A user that can guess races (name only).
""" """
__tablename__ = "user" __tablename__ = "user"
__csv_header__ = ["name", "enabled"]
def __init__(self, *, name: str, enabled: bool): def __init__(self, *, id: int | None):
self.name = name # Primary key if id is not None:
self.id = id # Primary key
self.enabled = enabled id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(32), nullable=False, unique=True)
@classmethod enabled: Mapped[bool] = mapped_column(Boolean, nullable=False)
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)

View File

@ -6,6 +6,7 @@ from werkzeug import Response
from formula10.controller.error_controller import error_redirect 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.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_guess import DbRaceGuess
from formula10.database.model.db_race_result import DbRaceResult 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 import DbSeasonGuess
@ -14,159 +15,167 @@ from formula10.database.validation import any_is_none, positions_are_contiguous,
from formula10 import ENABLE_TIMING, db 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 # 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: if race_guess is not None:
return race_guess return race_guess
# Insert a new RaceGuess # 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.add(race_guess)
db.session.commit() db.session.commit()
# Double check if database insertion worked and obtain any values set by the database # 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: if race_guess is None:
raise Exception("Failed adding RaceGuess to the database") raise Exception("Failed adding RaceGuess to the database")
return race_guess return race_guess
def update_race_guess(race_name: str, user_name: str, pxx_select: str | None, dnf_select: str | None) -> Response: 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, dnf_select): if any_is_none(pxx_select_id, dnf_select_id):
return error_redirect(f"Picks for race \"{race_name}\" were not saved, because you did not fill all the fields.") 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): if ENABLE_TIMING and race_has_started(race_id=race_id):
return error_redirect(f"No picks for race \"{race_name}\" can be entered, as this race has already started.") 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): if race_has_result(race_id):
return error_redirect(f"No picks for race \"{race_name}\" can be entered, as this race has already finished.") 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) pxx_driver_id: int = cast(int, pxx_select_id)
dnf_driver_name: str = cast(str, dnf_select) dnf_driver_id: int = cast(int, dnf_select_id)
race_guess: DbRaceGuess = find_or_create_race_guess(user_name, race_name) race_guess: DbRaceGuess = find_or_create_race_guess(user_id, race_id)
race_guess.pxx_driver_name = pxx_driver_name race_guess.pxx_driver_id = pxx_driver_id
race_guess.dnf_driver_name = dnf_driver_name race_guess.dnf_driver_id = dnf_driver_id
db.session.commit() db.session.commit()
return redirect("/race/Everyone") 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 # Don't change guesses that are already over
if ENABLE_TIMING and race_has_started(race_name=race_name): if ENABLE_TIMING and race_has_started(race_id=race_id):
return error_redirect(f"No picks for race \"{race_name}\" can be deleted, as this race has already started.") 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): if race_has_result(race_id):
return error_redirect(f"No picks for race \"{race_name}\" can be deleted, as this race has already finished.") 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 # 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() db.session.commit()
return redirect("/race/Everyone") 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 # 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: if season_guess is not None:
return season_guess return season_guess
# Insert a new SeasonGuess # 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.add(season_guess)
db.session.commit() db.session.commit()
# Double check if database insertion worked and obtain any values set by the database # 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: if season_guess is None:
raise Exception("Failed adding SeasonGuess to the database") raise Exception("Failed adding SeasonGuess to the database")
return season_guess 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. # 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!") 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.hot_take = guesses[0] # type: ignore
season_guess.p2_team_name = guesses[1] # type: ignore season_guess.p2_team_id = guesses[1] # type: ignore
season_guess.overtake_driver_name = guesses[2] # type: ignore season_guess.overtake_driver_id = guesses[2] # type: ignore
season_guess.dnf_driver_name = guesses[3] # type: ignore season_guess.dnf_driver_id = guesses[3] # type: ignore
season_guess.gained_driver_name = guesses[4] # type: ignore season_guess.gained_driver_id = guesses[4] # type: ignore
season_guess.lost_driver_name = guesses[5] # type: ignore season_guess.lost_driver_id = guesses[5] # type: ignore
season_guess.team_winners_driver_names_json = json.dumps(team_winner_guesses) season_guess.team_winners_driver_ids_json = json.dumps(team_winner_guesses)
season_guess.podium_drivers_driver_names_json = json.dumps(podium_driver_guesses) season_guess.podium_drivers_driver_ids_json = json.dumps(podium_driver_guesses)
db.session.commit() db.session.commit()
return redirect(f"/season/Everyone") 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 # 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: if race_result is not None:
return race_result return race_result
race_result = DbRaceResult(race_name=race_name, race_result = DbRaceResult(race_id=race_id)
pxx_driver_names_json=json.dumps(["TEMP"]), race_result.pxx_driver_ids_json = json.dumps(["9999"])
first_dnf_driver_names_json=json.dumps(["TEMP"]), race_result.first_dnf_driver_ids_json = json.dumps(["9999"])
dnf_driver_names_json=json.dumps(["TEMP"]), race_result.dnf_driver_ids_json = json.dumps(["9999"])
excluded_driver_names_json=json.dumps(["TEMP"])) race_result.excluded_driver_ids_json = json.dumps(["9999"])
db.session.add(race_result) db.session.add(race_result)
db.session.commit() db.session.commit()
# Double check if database insertion worked and obtain any values set by the database # 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: if race_result is None:
raise Exception("Failed adding RaceResult to the database") raise Exception("Failed adding RaceResult to the database")
return race_result 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: 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_name=race_name): 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!") 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 # Use strings as keys, as these dicts will be serialized to json
pxx_driver_names: Dict[str, str] = { 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 # Not counted drivers have to be at the end
excluded_driver_names: Dict[str, str] = { excluded_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)
if driver in excluded_driver_names_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()))): 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!") 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 # First DNF drivers have to be contained in DNF drivers
for driver_name in first_dnf_driver_names_list: for driver_id in first_dnf_driver_ids_list:
if driver_name not in dnf_driver_names_list: if driver_id not in dnf_driver_ids_list:
dnf_driver_names_list.append(driver_name) dnf_driver_ids_list.append(driver_id)
# There can't be dnfs but no initial dnfs # 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)!") 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: DbRaceResult = find_or_create_race_result(race_id)
race_result.pxx_driver_names_json = json.dumps(pxx_driver_names) race_result.pxx_driver_ids_json = json.dumps(pxx_driver_names)
race_result.first_dnf_driver_names_json = json.dumps(first_dnf_driver_names_list) race_result.first_dnf_driver_ids_json = json.dumps(first_dnf_driver_ids_list)
race_result.dnf_driver_names_json = json.dumps(dnf_driver_names_list) race_result.dnf_driver_ids_json = json.dumps(dnf_driver_ids_list)
race_result.excluded_driver_names_json = json.dumps(excluded_driver_names_list) race_result.excluded_driver_ids_json = json.dumps(excluded_driver_ids_list)
db.session.commit() 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: 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 disabled_user.enabled = True
else: 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.add(user)
db.session.commit() db.session.commit()

View File

@ -31,19 +31,19 @@ def race_has_started(*, race: Race) -> bool:
return race_has_started(race=race) return race_has_started(race=race)
@overload @overload
def race_has_started(*, race_name: str) -> bool: def race_has_started(*, race_id: int) -> bool:
return race_has_started(race_name=race_name) return race_has_started(race_id=race_id)
def race_has_started(*, race: Race | None = None, race_name: str | None = None) -> bool: def race_has_started(*, race: Race | None = None, race_id: int | None = None) -> bool:
if race is None and race_name is not None: if race is None and race_id is not None:
_race: DbRace | None = db.session.query(DbRace).filter_by(name=race_name).first() _race: DbRace | None = db.session.query(DbRace).filter_by(id=race_id).first()
if _race is None: 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 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 return datetime.now() > race.date
raise Exception("race_has_started received illegal arguments") raise Exception("race_has_started received illegal arguments")

View File

@ -8,6 +8,7 @@ class Driver():
@classmethod @classmethod
def from_db_driver(cls, db_driver: DbDriver): def from_db_driver(cls, db_driver: DbDriver):
driver: Driver = cls() driver: Driver = cls()
driver.id = db_driver.id
driver.name = db_driver.name driver.name = db_driver.name
driver.abbr = db_driver.abbr driver.abbr = db_driver.abbr
driver.country = db_driver.country_code driver.country = db_driver.country_code
@ -15,18 +16,23 @@ class Driver():
return driver return driver
def to_db_driver(self) -> DbDriver: 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.abbr = self.abbr
db_driver.country_code = self.country db_driver.country_code = self.country
db_driver.team_name = self.team.name db_driver.team_id = self.team.name
return db_driver return db_driver
def __eq__(self, __value: object) -> bool: def __eq__(self, __value: object) -> bool:
if isinstance(__value, Driver): if isinstance(__value, Driver):
return self.name == __value.name return self.id == __value.id
return NotImplemented return NotImplemented
def __hash__(self) -> int:
return hash(self.id)
id: int
name: str name: str
abbr: str abbr: str
country: str country: str
@ -38,6 +44,7 @@ class Driver():
NONE_DRIVER: Driver = Driver() NONE_DRIVER: Driver = Driver()
NONE_DRIVER.id = 0
NONE_DRIVER.name = "None" NONE_DRIVER.name = "None"
NONE_DRIVER.abbr = "None" NONE_DRIVER.abbr = "None"
NONE_DRIVER.country = "NO" NONE_DRIVER.country = "NO"

View File

@ -8,6 +8,7 @@ class Race():
@classmethod @classmethod
def from_db_race(cls, db_race: DbRace): def from_db_race(cls, db_race: DbRace):
race: Race = cls() race: Race = cls()
race.id = db_race.id
race.name = db_race.name race.name = db_race.name
race.number = db_race.number race.number = db_race.number
race.date = db_race.date race.date = db_race.date
@ -15,18 +16,23 @@ class Race():
return race return race
def to_db_race(self) -> DbRace: def to_db_race(self) -> DbRace:
db_race: DbRace = DbRace(name=self.name, db_race: DbRace = DbRace(id=self.id)
number=self.number, db_race.name = self.name
date=self.date, db_race.number = self.number
pxx=self.place_to_guess) db_race.date = self.date
db_race.pxx = self.place_to_guess
return db_race return db_race
def __eq__(self, __value: object) -> bool: def __eq__(self, __value: object) -> bool:
if isinstance(__value, Race): if isinstance(__value, Race):
return self.name == __value.name return self.id == __value.id
return NotImplemented return NotImplemented
def __hash__(self) -> int:
return hash(self.id)
id: int
name: str name: str
number: int number: int
date: datetime date: datetime

View File

@ -15,10 +15,9 @@ class RaceGuess():
return race_guess return race_guess
def to_db_race_guess(self) -> DbRaceGuess: def to_db_race_guess(self) -> DbRaceGuess:
db_race_guess: DbRaceGuess = DbRaceGuess(user_name=self.user.name, db_race_guess: DbRaceGuess = DbRaceGuess(user_id=self.user.id, race_id=self.race.id)
race_name=self.race.name, db_race_guess.pxx_driver_id = self.pxx_guess.id
pxx_driver_name=self.pxx_guess.name, db_race_guess.dnf_driver_id = self.dnf_guess.id
dnf_driver_name=self.dnf_guess.name)
return db_race_guess return db_race_guess
def __eq__(self, __value: object) -> bool: def __eq__(self, __value: object) -> bool:
@ -27,6 +26,9 @@ class RaceGuess():
return NotImplemented return NotImplemented
def __hash__(self) -> int:
return hash((self.user, self.race))
user: User user: User
race: Race race: Race
pxx_guess: Driver pxx_guess: Driver

View File

@ -14,27 +14,27 @@ class RaceResult:
race_result.race = Race.from_db_race(db_race_result.race) race_result.race = Race.from_db_race(db_race_result.race)
# Deserialize from json # Deserialize from json
standing: Dict[str, str] = json.loads(db_race_result.pxx_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_names_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_names_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_names_json) standing_exclusions: List[str] = json.loads(db_race_result.excluded_driver_ids_json)
# Populate relationships # Populate relationships
race_result.standing = { race_result.standing = {
position: Driver.from_db_driver(find_single_driver_strict(driver_name)) position: Driver.from_db_driver(find_single_driver_strict(int(driver_id)))
for position, driver_name in standing.items() for position, driver_id in standing.items()
} }
race_result.initial_dnf = [ race_result.initial_dnf = [
Driver.from_db_driver(find_single_driver_strict(driver_name)) Driver.from_db_driver(find_single_driver_strict(int(driver_id)))
for driver_name in initial_dnf for driver_id in initial_dnf
] ]
race_result.all_dnfs = [ race_result.all_dnfs = [
Driver.from_db_driver(find_single_driver_strict(driver_name)) Driver.from_db_driver(find_single_driver_strict(int(driver_id)))
for driver_name in all_dnfs for driver_id in all_dnfs
] ]
race_result.standing_exclusions = [ race_result.standing_exclusions = [
Driver.from_db_driver(find_single_driver_strict(driver_name)) Driver.from_db_driver(find_single_driver_strict(int(driver_id)))
for driver_name in standing_exclusions for driver_id in standing_exclusions
] ]
return race_result return race_result
@ -45,21 +45,21 @@ class RaceResult:
position: driver.name for position, driver in self.standing.items() position: driver.name for position, driver in self.standing.items()
} }
initial_dnf: List[str] = [ 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] = [ 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] = [ 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 # Serialize to json
db_race_result: DbRaceResult = DbRaceResult(race_name=self.race.name, db_race_result: DbRaceResult = DbRaceResult(race_id=self.race.id)
pxx_driver_names_json=json.dumps(standing), db_race_result.pxx_driver_ids_json = json.dumps(standing)
first_dnf_driver_names_json=json.dumps(initial_dnf), db_race_result.first_dnf_driver_ids_json = json.dumps(initial_dnf)
dnf_driver_names_json=json.dumps(all_dnfs), db_race_result.dnf_driver_ids_json = json.dumps(all_dnfs)
excluded_driver_names_json=json.dumps(standing_exclusions)) db_race_result.excluded_driver_ids_json = json.dumps(standing_exclusions)
return db_race_result return db_race_result
@ -69,6 +69,9 @@ class RaceResult:
return NotImplemented return NotImplemented
def __hash__(self) -> int:
return hash(self.race)
race: Race race: Race
standing: Dict[str, Driver] # Always contains all 20 drivers, even if DNF'ed or excluded 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 initial_dnf: List[Driver] # initial_dnf is empty if no-one DNF'ed

View File

@ -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 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 # Deserialize from json
team_winners: List[str | None] = json.loads(db_season_guess.team_winners_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_names_json) podiums: List[str] = json.loads(db_season_guess.podium_drivers_driver_ids_json)
# Populate relationships # Populate relationships
season_guess.team_winners = [ season_guess.team_winners = [
Driver.from_db_driver(find_single_driver_strict(driver_name)) if driver_name is not None else None Driver.from_db_driver(find_single_driver_strict(int(driver_id))) if driver_id is not None else None
for driver_name in team_winners for driver_id in team_winners
] ]
season_guess.podiums = [ season_guess.podiums = [
Driver.from_db_driver(find_single_driver_strict(driver_name)) Driver.from_db_driver(find_single_driver_strict(int(driver_id)))
for driver_name in podiums for driver_id in podiums
] ]
return season_guess return season_guess
@ -46,16 +46,15 @@ class SeasonGuess():
] ]
# Serialize to json # Serialize to json
db_season_guess: DbSeasonGuess = DbSeasonGuess(user_name=self.user.name, db_season_guess: DbSeasonGuess = DbSeasonGuess(user_id=self.user.id)
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.hot_take = self.hot_take 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.p2_team_id = self.p2_wcc.id 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.overtake_driver_id = self.most_overtakes.id 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.dnf_driver_id = self.most_dnfs.id 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.gained_driver_id = self.most_wdc_gained.id 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.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 return db_season_guess

View File

@ -12,6 +12,12 @@ class SeasonGuessResult():
return season_guess_result 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 user: User
hot_take_correct: bool hot_take_correct: bool
overtakes_correct: bool overtakes_correct: bool

View File

@ -7,19 +7,25 @@ class Team():
@classmethod @classmethod
def from_db_team(cls, db_team: DbTeam): def from_db_team(cls, db_team: DbTeam):
team: Team = cls() team: Team = cls()
team.id = db_team.id
team.name = db_team.name team.name = db_team.name
return team return team
def to_db_team(self) -> DbTeam: 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 return db_team
def __eq__(self, __value: object) -> bool: def __eq__(self, __value: object) -> bool:
if isinstance(__value, Team): if isinstance(__value, Team):
return self.name == __value.name return self.id == __value.id
return NotImplemented return NotImplemented
def __hash__(self) -> int:
return hash(self.id)
id: int
name: str name: str
@property @property
@ -27,4 +33,5 @@ class Team():
return quote(self.name) return quote(self.name)
NONE_TEAM: Team = Team() NONE_TEAM: Team = Team()
NONE_TEAM.id = 0
NONE_TEAM.name = "None" NONE_TEAM.name = "None"

View File

@ -7,20 +7,27 @@ class User():
@classmethod @classmethod
def from_db_user(cls, db_user: DbUser): def from_db_user(cls, db_user: DbUser):
user: User = cls() user: User = cls()
user.id = db_user.id
user.name = db_user.name user.name = db_user.name
user.enabled = db_user.enabled user.enabled = db_user.enabled
return user return user
def to_db_user(self) -> DbUser: 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 return db_user
def __eq__(self, __value: object) -> bool: def __eq__(self, __value: object) -> bool:
if isinstance(__value, User): if isinstance(__value, User):
return self.name == __value.name return self.id == __value.id
return NotImplemented return NotImplemented
def __hash__(self) -> int:
return hash(self.id)
id: int
name: str name: str
enabled: bool enabled: bool

View File

@ -51,14 +51,14 @@ WDC_STANDING_2023: Dict[str, int] = {
"Charles Leclerc": 5, "Charles Leclerc": 5,
"Lando Norris": 6, "Lando Norris": 6,
"Carlos Sainz": 7, "Carlos Sainz": 7,
"George Russel": 8, # @todo typo "George Russell": 8,
"Oscar Piastri": 9, "Oscar Piastri": 9,
"Lance Stroll": 10, "Lance Stroll": 10,
"Pierre Gasly": 11, "Pierre Gasly": 11,
"Esteban Ocon": 12, "Esteban Ocon": 12,
"Alexander Albon": 13, "Alexander Albon": 13,
"Yuki Tsunoda": 14, "Yuki Tsunoda": 14,
"Valteri Bottas": 15, # @todo typo "Valtteri Bottas": 15,
"Nico Hulkenberg": 16, "Nico Hulkenberg": 16,
"Daniel Ricciardo": 17, "Daniel Ricciardo": 17,
"Zhou Guanyu": 18, "Zhou Guanyu": 18,

View File

@ -6,7 +6,7 @@ from formula10.domain.model.driver import Driver
from formula10.domain.model.race import Race from formula10.domain.model.race import Race
from formula10.domain.model.race_result import RaceResult from formula10.domain.model.race_result import RaceResult
from formula10.domain.model.user import User 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): class TemplateModel(Model):
@ -33,10 +33,12 @@ class TemplateModel(Model):
return not race_has_started(race=race) if ENABLE_TIMING else True return not race_has_started(race=race) if ENABLE_TIMING else True
def season_guess_open(self) -> bool: 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: 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: def active_user_name_or_everyone(self) -> str:
return self.active_user.name if self.active_user is not None else "Everyone" return self.active_user.name if self.active_user is not None else "Everyone"

View File

@ -34,7 +34,7 @@
{% endif %} {% endif %}
{% for driver in drivers %} {% for driver in drivers %}
<option value="{{ driver.name }}">{{ driver.abbr }}</option> <option value="{{ driver.id }}">{{ driver.abbr }}</option>
{% endfor %} {% endfor %}
</select> </select>
<label for="{{ name }}" class="text-primary">{{ label }}</label> <label for="{{ name }}" class="text-primary">{{ label }}</label>
@ -55,13 +55,13 @@
{% for driver in drivers %} {% for driver in drivers %}
{% if driver_match == driver %} {% if driver_match == driver %}
{% set user_has_chosen.driverpre = true %} {% set user_has_chosen.driverpre = true %}
<option selected="selected" value="{{ driver.name }}">{{ driver.abbr }}</option> <option selected="selected" value="{{ driver.id }}">{{ driver.abbr }}</option>
{% else %} {% else %}
<option value="{{ driver.name }}">{{ driver.abbr }}</option> <option value="{{ driver.id }}">{{ driver.abbr }}</option>
{% endif %} {% endif %}
{% if (include_none == true) and (driver == model.none_driver()) %} {% if (include_none == true) and (driver == model.none_driver()) %}
<option disabled>──────────</option> <option disabled="disabled">──────────</option>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
@ -85,7 +85,7 @@
{% endif %} {% endif %}
{% for team in teams %} {% for team in teams %}
<option value="{{ team.name }}">{{ team.name }}</option> <option value="{{ team.id }}">{{ team.name }}</option>
{% endfor %} {% endfor %}
</select> </select>
<label for="{{ name }}" class="text-primary">{{ label }}</label> <label for="{{ name }}" class="text-primary">{{ label }}</label>
@ -106,13 +106,13 @@
{% for team in teams %} {% for team in teams %}
{% if team_match == team %} {% if team_match == team %}
{% set user_has_chosen.teampre = true %} {% set user_has_chosen.teampre = true %}
<option selected="selected" value="{{ team.name }}">{{ team.name }}</option> <option selected="selected" value="{{ team.id }}">{{ team.name }}</option>
{% else %} {% else %}
<option value="{{ team.name }}">{{ team.name }}</option> <option value="{{ team.id }}">{{ team.name }}</option>
{% endif %} {% endif %}
{% if (include_none == true) and (team == model.none_team()) %} {% if (include_none == true) and (team == model.none_team()) %}
<option disabled>──────────</option> <option disabled="disabled">──────────</option>
{% endif %} {% endif %}
{% endfor %} {% endfor %}

View File

@ -60,6 +60,7 @@
{% endif %} {% endif %}
<form action="{{ action_save_href }}" method="post"> <form action="{{ action_save_href }}" method="post">
{# Place numbers #}
<ul class="list-group list-group-flush d-inline-block"> <ul class="list-group list-group-flush d-inline-block">
{% for driver in model.all_drivers_or_active_result_standing_drivers() %} {% for driver in model.all_drivers_or_active_result_standing_drivers() %}
<li class="list-group-item p-1"><span id="place_number" <li class="list-group-item p-1"><span id="place_number"
@ -68,6 +69,7 @@
{% endfor %} {% endfor %}
</ul> </ul>
{# Drag and drop #}
<ul id="columns" class="list-group list-group-flush d-inline-block float-end"> <ul id="columns" class="list-group list-group-flush d-inline-block float-end">
{% for driver in model.all_drivers_or_active_result_standing_drivers() %} {% for driver in model.all_drivers_or_active_result_standing_drivers() %}
@ -79,40 +81,40 @@
{# Driver DNFed at first #} {# Driver DNFed at first #}
<div class="form-check form-check-reverse d-inline-block"> <div class="form-check form-check-reverse d-inline-block">
<input type="checkbox" class="form-check-input" <input type="checkbox" class="form-check-input"
value="{{ driver.name }}" value="{{ driver.id }}"
id="first-dnf-{{ driver.name }}" name="first-dnf-drivers" id="first-dnf-{{ driver.id }}" name="first-dnf-drivers"
{% if (model.active_result is not none) and (driver in model.active_result.initial_dnf) %}checked{% endif %} {% if (model.active_result is not none) and (driver in model.active_result.initial_dnf) %}checked{% endif %}
{% if race_result_open == false %}readonly="readonly"{% endif %}> {% if race_result_open == false %}disabled="disabled"{% endif %}>
<label for="first-dnf-{{ driver.name }}" <label for="first-dnf-{{ driver.id }}"
class="form-check-label text-muted">1. DNF</label> class="form-check-label text-muted">1. DNF</label>
</div> </div>
{# Driver DNFed #} {# Driver DNFed #}
<div class="form-check form-check-reverse d-inline-block mx-2"> <div class="form-check form-check-reverse d-inline-block mx-2">
<input type="checkbox" class="form-check-input" <input type="checkbox" class="form-check-input"
value="{{ driver.name }}" value="{{ driver.id }}"
id="dnf-{{ driver.name }}" name="dnf-drivers" id="dnf-{{ driver.id }}" name="dnf-drivers"
{% if (model.active_result is not none) and (driver in model.active_result.all_dnfs) %}checked{% endif %} {% if (model.active_result is not none) and (driver in model.active_result.all_dnfs) %}checked{% endif %}
{% if race_result_open == false %}readonly="readonly"{% endif %}> {% if race_result_open == false %}disabled="disabled"{% endif %}>
<label for="dnf-{{ driver.name }}" <label for="dnf-{{ driver.id }}"
class="form-check-label text-muted">DNF</label> class="form-check-label text-muted">DNF</label>
</div> </div>
{# Driver Excluded #} {# Driver Excluded #}
<div class="form-check form-check-reverse d-inline-block"> <div class="form-check form-check-reverse d-inline-block">
<input type="checkbox" class="form-check-input" <input type="checkbox" class="form-check-input"
value="{{ driver.name }}" value="{{ driver.id }}"
id="exclude-{{ driver.name }}" name="excluded-drivers" id="exclude-{{ driver.id }}" name="excluded-drivers"
{% if (model.active_result is not none) and (driver in model.active_result.standing_exclusions) %}checked{% endif %} {% if (model.active_result is not none) and (driver in model.active_result.standing_exclusions) %}checked{% endif %}
{% if race_result_open == false %}readonly="readonly"{% endif %}> {% if race_result_open == false %}disabled="disabled"{% endif %}>
<label for="exclude-{{ driver.name }}" <label for="exclude-{{ driver.id }}"
class="form-check-label text-muted" data-bs-toggle="tooltip" class="form-check-label text-muted" data-bs-toggle="tooltip"
title="Driver is not counted for standing">NC</label> title="Driver is not counted for standing">NC</label>
</div> </div>
</div> </div>
{# Standing order #} {# Standing order #}
<input type="hidden" name="pxx-drivers" value="{{ driver.name }}"> <input type="hidden" name="pxx-drivers" value="{{ driver.id }}">
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>

View File

@ -102,26 +102,26 @@
<div class="g-col-6"> <div class="g-col-6">
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="radio" <input class="form-check-input" type="radio"
name="teamwinner-{{ team.name }}" name="teamwinner-{{ team.id }}"
id="teamwinner-{{ team.name }}-1-{{ user.name }}" id="teamwinner-{{ team.id }}-1-{{ user.id }}"
value="{{ driver_a.name }}" value="{{ driver_a.id }}"
{% if (user_guess is not none) and (driver_a in user_guess.team_winners) %}checked="checked"{% endif %} {% if (user_guess is not none) and (driver_a in user_guess.team_winners) %}checked="checked"{% endif %}
{% if season_guess_open == false %}disabled="disabled"{% endif %}> {% if season_guess_open == false %}disabled="disabled"{% endif %}>
<label class="form-check-label {% if (user_guess is not none) and (driver_a in user_guess.team_winners) and points.is_team_winner(driver_a) %}text-success{% endif %}" <label class="form-check-label {% if (user_guess is not none) and (driver_a in user_guess.team_winners) and points.is_team_winner(driver_a) %}text-success{% endif %}"
for="teamwinner-{{ team.name }}-1-{{ user.name }}">{{ driver_a.name }}</label> for="teamwinner-{{ team.id }}-1-{{ user.id }}">{{ driver_a.name }}</label>
</div> </div>
</div> </div>
<div class="g-col-6"> <div class="g-col-6">
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="radio" <input class="form-check-input" type="radio"
name="teamwinner-{{ team.name }}" name="teamwinner-{{ team.id }}"
id="teamwinner-{{ team.name }}-2-{{ user.name }}" id="teamwinner-{{ team.id }}-2-{{ user.id }}"
value="{{ driver_b.name }}" value="{{ driver_b.id }}"
{% if (user_guess is not none) and (driver_b in user_guess.team_winners) %}checked="checked"{% endif %} {% if (user_guess is not none) and (driver_b in user_guess.team_winners) %}checked="checked"{% endif %}
{% if season_guess_open == false %}disabled="disabled"{% endif %}> {% if season_guess_open == false %}disabled="disabled"{% endif %}>
<label class="form-check-label {% if (user_guess is not none) and (driver_b in user_guess.team_winners) and points.is_team_winner(driver_b) %}text-success{% endif %}" <label class="form-check-label {% if (user_guess is not none) and (driver_b in user_guess.team_winners) and points.is_team_winner(driver_b) %}text-success{% endif %}"
for="teamwinner-{{ team.name }}-2-{{ user.name }}">{{ driver_b.name }}</label> for="teamwinner-{{ team.id }}-2-{{ user.id }}">{{ driver_b.name }}</label>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
@ -139,12 +139,12 @@
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" <input class="form-check-input" type="checkbox"
name="podiumdrivers" name="podiumdrivers"
id="podium-{{ driver_a.name }}-{{ user.name }}" id="podium-{{ driver_a.id }}-{{ user.id }}"
value="{{ driver_a.name }}" value="{{ driver_a.id }}"
{% if (user_guess is not none) and (driver_a in user_guess.podiums) %}checked="checked"{% endif %} {% if (user_guess is not none) and (driver_a in user_guess.podiums) %}checked="checked"{% endif %}
{% if season_guess_open == false %}disabled="disabled"{% endif %}> {% if season_guess_open == false %}disabled="disabled"{% endif %}>
<label class="form-check-label {% if (user_guess is not none) and (driver_a in user_guess.podiums) and points.has_podium(driver_a) %}text-success{% endif %}" <label class="form-check-label {% if (user_guess is not none) and (driver_a in user_guess.podiums) and points.has_podium(driver_a) %}text-success{% endif %}"
for="podium-{{ driver_a.name }}-{{ user.name }}">{{ driver_a.name }}</label> for="podium-{{ driver_a.id }}-{{ user.id }}">{{ driver_a.name }}</label>
</div> </div>
</div> </div>
@ -152,12 +152,12 @@
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" <input class="form-check-input" type="checkbox"
name="podiumdrivers" name="podiumdrivers"
id="podium-{{ driver_b.name }}-{{ user.name }}" id="podium-{{ driver_b.id }}-{{ user.id }}"
value="{{ driver_b.name }}" value="{{ driver_b.id }}"
{% if (user_guess is not none) and (driver_b in user_guess.podiums) %}checked="checked"{% endif %} {% if (user_guess is not none) and (driver_b in user_guess.podiums) %}checked="checked"{% endif %}
{% if season_guess_open == false %}disabled="disabled"{% endif %}> {% if season_guess_open == false %}disabled="disabled"{% endif %}>
<label class="form-check-label {% if (user_guess is not none) and (driver_b in user_guess.podiums) and points.has_podium(driver_b) %}text-success{% endif %}" <label class="form-check-label {% if (user_guess is not none) and (driver_b in user_guess.podiums) and points.has_podium(driver_b) %}text-success{% endif %}"
for="podium-{{ driver_b.name }}-{{ user.name }}">{{ driver_b.name }}</label> for="podium-{{ driver_b.id }}-{{ user.id }}">{{ driver_b.name }}</label>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}