Compare commits
19 Commits
481492868b
...
openf1
| Author | SHA1 | Date | |
|---|---|---|---|
| 4b3f95764c | |||
| 4fc13471f5 | |||
| 8905673d6d | |||
| 8b2920f886 | |||
| 73273bc5cd | |||
| d3097038a5 | |||
| 96cb8ca891 | |||
| 1ad558171d | |||
| 873df8bd8e | |||
| b250c47cb3 | |||
| 6ed5b914e4 | |||
| feb6d27e24 | |||
| 0d598e75a2 | |||
| a3d234a754 | |||
| 8fcb8c5704 | |||
| 20d177192f | |||
| 27e0231a25 | |||
| 325f753d31 | |||
| b4794ca42f |
@ -2,7 +2,7 @@ name: Build Formula10 Docker Image
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
# branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- ".gitea/workflows/**"
|
- ".gitea/workflows/**"
|
||||||
- "Dockerfile"
|
- "Dockerfile"
|
||||||
|
|||||||
@ -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,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 +0,0 @@
|
|||||||
user_name,hot_take_correct,overtakes_correct
|
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
name
|
|
||||||
None
|
|
||||||
Alpine
|
|
||||||
Aston Martin
|
|
||||||
Ferrari
|
|
||||||
Haas
|
|
||||||
McLaren
|
|
||||||
Mercedes
|
|
||||||
Red Bull
|
|
||||||
Sauber
|
|
||||||
VCARB
|
|
||||||
Williams
|
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
flask
|
flask
|
||||||
flask-sqlalchemy
|
flask-sqlalchemy
|
||||||
sqlalchemy
|
sqlalchemy
|
||||||
|
requests
|
||||||
|
|
||||||
pytest
|
pytest
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -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"
|
||||||
@ -32,22 +29,3 @@ import formula10.controller.statistics_controller
|
|||||||
import formula10.controller.rules_controller
|
import formula10.controller.rules_controller
|
||||||
import formula10.controller.admin_controller
|
import formula10.controller.admin_controller
|
||||||
import formula10.controller.error_controller
|
import formula10.controller.error_controller
|
||||||
|
|
||||||
|
|
||||||
# TODO
|
|
||||||
# Leaderboard
|
|
||||||
|
|
||||||
# - For season guess calc there is missing: Fastest laps + sprint points + sprint DNFs (in race result)
|
|
||||||
|
|
||||||
# - Auto calculate season points (display season points?)
|
|
||||||
# - Generate static diagram using chart.js + templating the js (funny yikes)
|
|
||||||
# - Interesting stats:
|
|
||||||
# - Which driver was voted most for dnf (top 5)?
|
|
||||||
|
|
||||||
# General
|
|
||||||
# - Decouple names from IDs + Fix Valtteri/Russel spelling errors
|
|
||||||
# - Unit testing (as much as possible, but especially points calculation)
|
|
||||||
# - 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)?
|
|
||||||
|
|
||||||
# Possible but probably not
|
|
||||||
# - Show cards of previous race results, like with season guesses?
|
|
||||||
@ -1,75 +1,36 @@
|
|||||||
from typing import List
|
from flask import render_template, request
|
||||||
from urllib.parse import unquote
|
|
||||||
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_user
|
||||||
from formula10.database.import_export import export_dynamic_data, reload_dynamic_data, reload_season_guess_result_data, reload_static_data
|
|
||||||
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")
|
# @app.route("/result")
|
||||||
def save() -> Response:
|
# def result_root() -> Response:
|
||||||
export_dynamic_data()
|
# return redirect("/result/Current")
|
||||||
return redirect("/")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/load/all")
|
# @app.route("/result/<race_name>")
|
||||||
def load() -> Response:
|
# def result_active_race(race_name: str) -> str:
|
||||||
if not ENABLE_DEBUG_ENDPOINTS:
|
# race_name = unquote(race_name)
|
||||||
return error_redirect("Debug endpoints are disabled!")
|
# model = TemplateModel(active_user_name=None,
|
||||||
|
# active_result_race_name=race_name)
|
||||||
|
|
||||||
reload_static_data()
|
# return render_template("result.jinja", model=model)
|
||||||
reload_dynamic_data()
|
|
||||||
return redirect("/")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/load/static")
|
# @app.route("/result-enter/<race_name>", methods=["POST"])
|
||||||
def load_static() -> Response:
|
# def result_enter_post(race_name: str) -> Response:
|
||||||
reload_static_data()
|
# race_name = unquote(race_name)
|
||||||
return redirect("/")
|
# pxxs: List[str] = request.form.getlist("pxx-drivers")
|
||||||
|
# first_dnfs: List[str] = request.form.getlist("first-dnf-drivers")
|
||||||
|
# dnfs: List[str] = request.form.getlist("dnf-drivers")
|
||||||
|
# excluded: List[str] = request.form.getlist("excluded-drivers")
|
||||||
|
|
||||||
|
# # @todo Ugly
|
||||||
@app.route("/load/seasonresults")
|
# race_id: int = Model().race_by(race_name=race_name).id
|
||||||
def load_season_results() -> Response:
|
# return update_race_result(race_id, pxxs, first_dnfs, dnfs, excluded)
|
||||||
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")
|
|
||||||
def result_root() -> Response:
|
|
||||||
return redirect("/result/Current")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/result/<race_name>")
|
|
||||||
def result_active_race(race_name: str) -> str:
|
|
||||||
race_name = unquote(race_name)
|
|
||||||
model = TemplateModel(active_user_name=None,
|
|
||||||
active_result_race_name=race_name)
|
|
||||||
|
|
||||||
return render_template("result.jinja", model=model)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/result-enter/<race_name>", methods=["POST"])
|
|
||||||
def result_enter_post(race_name: str) -> Response:
|
|
||||||
race_name = unquote(race_name)
|
|
||||||
pxxs: List[str] = request.form.getlist("pxx-drivers")
|
|
||||||
first_dnfs: List[str] = request.form.getlist("first-dnf-drivers")
|
|
||||||
dnfs: List[str] = request.form.getlist("dnf-drivers")
|
|
||||||
excluded: List[str] = request.form.getlist("excluded-drivers")
|
|
||||||
|
|
||||||
return update_race_result(race_name, pxxs, first_dnfs, dnfs, excluded)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/user")
|
@app.route("/user")
|
||||||
|
|||||||
@ -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)
|
||||||
@ -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)
|
||||||
@ -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
|
||||||
@ -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)
|
|
||||||
@ -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])
|
||||||
@ -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
|
|
||||||
@ -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])
|
||||||
@ -1,6 +1,6 @@
|
|||||||
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
|
||||||
|
from formula10.database.model.db_driver import DbDriver
|
||||||
|
|
||||||
from formula10.database.model.db_race import DbRace
|
from formula10.database.model.db_race import DbRace
|
||||||
from formula10 import db
|
from formula10 import db
|
||||||
@ -11,39 +11,20 @@ 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
|
fastest_lap_id: Mapped[int] = mapped_column(ForeignKey("driver.id"), nullable=False)
|
||||||
def from_csv(cls, row: List[str]):
|
sprint_dnf_driver_ids_json: Mapped[str] = mapped_column(String(1024), nullable=False)
|
||||||
db_race_result: DbRaceResult = cls(race_name=str(row[0]),
|
sprint_points_json: Mapped[str] = mapped_column(String(1024), nullable=False)
|
||||||
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])
|
||||||
|
fastest_lap_driver: Mapped[DbDriver] = relationship("DbDriver", foreign_keys=[fastest_lap_id])
|
||||||
@ -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])
|
||||||
@ -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])
|
||||||
|
|||||||
@ -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)
|
|
||||||
@ -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)
|
|
||||||
0
formula10/database/open_f1_fetcher.py
Normal file
0
formula10/database/open_f1_fetcher.py
Normal file
@ -1,172 +1,188 @@
|
|||||||
import json
|
import json
|
||||||
from typing import Dict, List, cast
|
from typing import List, cast
|
||||||
from urllib.parse import quote
|
|
||||||
from flask import redirect
|
from flask import redirect
|
||||||
from werkzeug import Response
|
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_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_season_guess import DbSeasonGuess
|
from formula10.database.model.db_season_guess import DbSeasonGuess
|
||||||
from formula10.database.model.db_user import DbUser
|
from formula10.database.model.db_user import DbUser
|
||||||
from formula10.database.validation import any_is_none, positions_are_contiguous, race_has_started
|
from formula10.database.validation import any_is_none, race_has_started
|
||||||
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.commit()
|
|
||||||
|
|
||||||
# Double check if database insertion worked and obtain any values set by the database
|
# race_result.fastest_lap_id = 9999
|
||||||
race_result = db.session.query(DbRaceResult).filter_by(race_name=race_name).first()
|
# race_result.sprint_dnf_driver_ids_json = json.dumps(["9999"])
|
||||||
if race_result is None:
|
# race_result.sprint_points_json = json.dumps({"9999": "9999"})
|
||||||
raise Exception("Failed adding RaceResult to the database")
|
|
||||||
|
|
||||||
return race_result
|
# 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_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:
|
# 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()
|
# # @todo Dummy values
|
||||||
|
# race_result.fastest_lap_id = NONE_DRIVER.id
|
||||||
|
# race_result.sprint_dnf_driver_ids_json = json.dumps([NONE_DRIVER.id])
|
||||||
|
# race_result.sprint_points_json = json.dumps({NONE_DRIVER.id: 0})
|
||||||
|
|
||||||
return redirect(f"/result/{quote(race_name)}")
|
# db.session.commit()
|
||||||
|
|
||||||
|
# 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 +210,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()
|
||||||
|
|||||||
@ -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")
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -12,30 +12,41 @@ class RaceResult:
|
|||||||
def from_db_race_result(cls, db_race_result: DbRaceResult):
|
def from_db_race_result(cls, db_race_result: DbRaceResult):
|
||||||
race_result: RaceResult = cls()
|
race_result: RaceResult = cls()
|
||||||
race_result.race = Race.from_db_race(db_race_result.race)
|
race_result.race = Race.from_db_race(db_race_result.race)
|
||||||
|
race_result.fastest_lap_driver = Driver.from_db_driver(db_race_result.fastest_lap_driver)
|
||||||
|
|
||||||
# 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)
|
||||||
|
sprint_dnfs: List[str] = json.loads(db_race_result.sprint_dnf_driver_ids_json)
|
||||||
|
sprint_standing: Dict[str, str] = json.loads(db_race_result.sprint_points_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
|
||||||
]
|
]
|
||||||
|
race_result.sprint_dnfs = [
|
||||||
|
Driver.from_db_driver(find_single_driver_strict(int(driver_id)))
|
||||||
|
for driver_id in sprint_dnfs
|
||||||
|
]
|
||||||
|
race_result.sprint_standing = {
|
||||||
|
position: Driver.from_db_driver(find_single_driver_strict(int(driver_id)))
|
||||||
|
for position, driver_id in sprint_standing.items()
|
||||||
|
}
|
||||||
|
|
||||||
return race_result
|
return race_result
|
||||||
|
|
||||||
@ -45,21 +56,30 @@ 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
|
||||||
]
|
]
|
||||||
|
sprint_dnfs: List[str] = [
|
||||||
|
str(driver.id) for driver in self.sprint_dnfs if driver
|
||||||
|
]
|
||||||
|
sprint_standing: Dict[str, str] = {
|
||||||
|
position: driver.name for position, driver in self.sprint_standing.items()
|
||||||
|
}
|
||||||
|
|
||||||
# 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)
|
||||||
|
db_race_result.fastest_lap_id = self.fastest_lap_driver.id
|
||||||
|
db_race_result.sprint_dnf_driver_ids_json = json.dumps(sprint_dnfs)
|
||||||
|
db_race_result.sprint_points_json = json.dumps(sprint_standing)
|
||||||
|
|
||||||
return db_race_result
|
return db_race_result
|
||||||
|
|
||||||
@ -69,12 +89,19 @@ 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
|
||||||
all_dnfs: List[Driver]
|
all_dnfs: List[Driver]
|
||||||
standing_exclusions: List[Driver]
|
standing_exclusions: List[Driver]
|
||||||
|
|
||||||
|
fastest_lap_driver: Driver
|
||||||
|
sprint_dnfs: List[Driver]
|
||||||
|
sprint_standing: Dict[str, Driver]
|
||||||
|
|
||||||
def offset_from_place_to_guess(self, offset: int, respect_nc:bool = True) -> Driver:
|
def offset_from_place_to_guess(self, offset: int, respect_nc:bool = True) -> Driver:
|
||||||
position: str = str(self.race.place_to_guess + offset)
|
position: str = str(self.race.place_to_guess + offset)
|
||||||
|
|
||||||
|
|||||||
@ -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,19 +46,27 @@ 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
|
||||||
|
|
||||||
|
def __eq__(self, __value: object) -> bool:
|
||||||
|
if isinstance(__value, SeasonGuess):
|
||||||
|
return self.user == __value.user
|
||||||
|
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash(self.user)
|
||||||
|
|
||||||
user: User
|
user: User
|
||||||
hot_take: str | None
|
hot_take: str | None
|
||||||
p2_wcc: Team | None
|
p2_wcc: Team | None
|
||||||
|
|||||||
@ -12,6 +12,21 @@ 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
|
||||||
|
|
||||||
|
def __eq__(self, __value: object) -> bool:
|
||||||
|
if isinstance(__value, SeasonGuessResult):
|
||||||
|
return self.user == __value.user
|
||||||
|
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash(self.user)
|
||||||
|
|
||||||
user: User
|
user: User
|
||||||
hot_take_correct: bool
|
hot_take_correct: bool
|
||||||
overtakes_correct: bool
|
overtakes_correct: bool
|
||||||
@ -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"
|
||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
from typing import Callable, Dict, List, Tuple, overload
|
import json
|
||||||
|
from typing import Any, Callable, Dict, List, overload
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from formula10.domain.domain_model import Model
|
from formula10.domain.domain_model import Model
|
||||||
@ -42,7 +43,7 @@ DRIVER_RACE_POINTS: Dict[int, int] = {
|
|||||||
10: 1
|
10: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
STANDING_2023: Dict[str, int] = {
|
WDC_STANDING_2023: Dict[str, int] = {
|
||||||
"Max Verstappen": 1,
|
"Max Verstappen": 1,
|
||||||
"Sergio Perez": 2,
|
"Sergio Perez": 2,
|
||||||
"Lewis Hamilton": 3,
|
"Lewis Hamilton": 3,
|
||||||
@ -50,14 +51,14 @@ 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,
|
||||||
@ -65,6 +66,19 @@ STANDING_2023: Dict[str, int] = {
|
|||||||
"Logan Sargeant": 21
|
"Logan Sargeant": 21
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WCC_STANDING_2023: Dict[str, int] = {
|
||||||
|
"Red Bull": 1,
|
||||||
|
"Mercedes": 2,
|
||||||
|
"Ferrari": 3,
|
||||||
|
"McLaren": 4,
|
||||||
|
"Aston Martin": 5,
|
||||||
|
"Alpine": 6,
|
||||||
|
"Williams": 7,
|
||||||
|
"VCARB": 8,
|
||||||
|
"Sauber": 9,
|
||||||
|
"Haas": 10
|
||||||
|
}
|
||||||
|
|
||||||
def standing_points(race_guess: RaceGuess, race_result: RaceResult) -> int:
|
def standing_points(race_guess: RaceGuess, race_result: RaceResult) -> int:
|
||||||
guessed_driver_position: int | None = race_result.driver_standing_position(driver=race_guess.pxx_guess)
|
guessed_driver_position: int | None = race_result.driver_standing_position(driver=race_guess.pxx_guess)
|
||||||
if guessed_driver_position is None:
|
if guessed_driver_position is None:
|
||||||
@ -91,8 +105,8 @@ class PointsModel(Model):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
_points_per_step: Dict[str, List[int]] | None = None
|
_points_per_step: Dict[str, List[int]] | None = None
|
||||||
_wdc_points: Dict[str, int] | None = None
|
_driver_points_per_step: Dict[str, List[int]] | None = None
|
||||||
_wcc_points: Dict[str, int] | None = None
|
_team_points_per_step: Dict[str, List[int]] | None = None
|
||||||
_dnfs: Dict[str, int] | None = None
|
_dnfs: Dict[str, int] | None = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -105,7 +119,7 @@ class PointsModel(Model):
|
|||||||
if self._points_per_step is None:
|
if self._points_per_step is None:
|
||||||
self._points_per_step = dict()
|
self._points_per_step = dict()
|
||||||
for user in self.all_users():
|
for user in self.all_users():
|
||||||
self._points_per_step[user.name] = [0] * 24 # Start at index 1, like the race numbers
|
self._points_per_step[user.name] = [0] * (len(self.all_races()) + 1) # Start at index 1, like the race numbers
|
||||||
|
|
||||||
for race_guess in self.all_race_guesses():
|
for race_guess in self.all_race_guesses():
|
||||||
user_name: str = race_guess.user.name
|
user_name: str = race_guess.user.name
|
||||||
@ -120,32 +134,42 @@ class PointsModel(Model):
|
|||||||
return self._points_per_step
|
return self._points_per_step
|
||||||
|
|
||||||
# @todo Doesn't include fastest lap + sprint points
|
# @todo Doesn't include fastest lap + sprint points
|
||||||
def wdc_points(self) -> Dict[str, int]:
|
def driver_points_per_step(self) -> Dict[str, List[int]]:
|
||||||
if self._wdc_points is None:
|
"""
|
||||||
self._wdc_points = dict()
|
Returns a dictionary of lists, containing points per race for each driver.
|
||||||
|
"""
|
||||||
|
if self._driver_points_per_step is None:
|
||||||
|
self._driver_points_per_step = dict()
|
||||||
for driver in self.all_drivers(include_none=False):
|
for driver in self.all_drivers(include_none=False):
|
||||||
self._wdc_points[driver.name] = 0
|
self._driver_points_per_step[driver.name] = [0] * (len(self.all_races()) + 1) # Start at index 1, like the race numbers
|
||||||
|
|
||||||
for race_result in self.all_race_results():
|
for race_result in self.all_race_results():
|
||||||
for position, driver in race_result.standing.items():
|
for position, driver in race_result.standing.items():
|
||||||
self._wdc_points[driver.name] += DRIVER_RACE_POINTS[int(position)] if int(position) in DRIVER_RACE_POINTS else 0
|
driver_name: str = driver.name
|
||||||
|
race_number: int = race_result.race.number
|
||||||
|
|
||||||
|
self._driver_points_per_step[driver_name][race_number] = DRIVER_RACE_POINTS[int(position)] if int(position) in DRIVER_RACE_POINTS else 0
|
||||||
|
|
||||||
return self._wdc_points
|
return self._driver_points_per_step
|
||||||
|
|
||||||
def wcc_points(self) -> Dict[str, int]:
|
|
||||||
if self._wcc_points is None:
|
|
||||||
self._wcc_points = dict()
|
|
||||||
|
|
||||||
|
# @todo Doesn't include fastest lap + sprint points
|
||||||
|
def team_points_per_step(self) -> Dict[str, List[int]]:
|
||||||
|
"""
|
||||||
|
Returns a dictionary of lists, containing points per race for each team.
|
||||||
|
"""
|
||||||
|
if self._team_points_per_step is None:
|
||||||
|
self._team_points_per_step = dict()
|
||||||
for team in self.all_teams(include_none=False):
|
for team in self.all_teams(include_none=False):
|
||||||
self._wcc_points[team.name] = 0
|
self._team_points_per_step[team.name] = [0] * (len(self.all_races()) + 1) # Start at index 1, like the race numbers
|
||||||
|
|
||||||
for race_result in self.all_race_results():
|
for race_result in self.all_race_results():
|
||||||
for driver in race_result.standing.values():
|
for position, driver in race_result.standing.items():
|
||||||
self._wcc_points[driver.team.name] += self.wdc_points()[driver.name]
|
team_name: str = driver.team.name
|
||||||
|
race_number: int = race_result.race.number
|
||||||
|
|
||||||
return self._wcc_points
|
self._team_points_per_step[team_name][race_number] += DRIVER_RACE_POINTS[int(position)] if int(position) in DRIVER_RACE_POINTS else 0
|
||||||
|
|
||||||
|
return self._team_points_per_step
|
||||||
|
|
||||||
# @todo Doesn't include sprint dnfs
|
# @todo Doesn't include sprint dnfs
|
||||||
def dnfs(self) -> Dict[str, int]:
|
def dnfs(self) -> Dict[str, int]:
|
||||||
@ -161,30 +185,83 @@ class PointsModel(Model):
|
|||||||
|
|
||||||
return self._dnfs
|
return self._dnfs
|
||||||
|
|
||||||
def wdc_diff_2023(self) -> Dict[str, int]:
|
#
|
||||||
diff: Dict[str, int] = dict()
|
# Driver stats
|
||||||
|
#
|
||||||
|
|
||||||
for driver in self.all_drivers(include_none=False):
|
def driver_points_per_step_cumulative(self) -> Dict[str, List[int]]:
|
||||||
diff[driver.name] = STANDING_2023[driver.name] - self.wdc_standing_by_driver()[driver.name]
|
"""
|
||||||
|
Returns a dictionary of lists, containing cumulative points per race for each driver.
|
||||||
|
"""
|
||||||
|
points_per_step_cumulative: Dict[str, List[int]] = dict()
|
||||||
|
for driver_name, points in self.driver_points_per_step().items():
|
||||||
|
points_per_step_cumulative[driver_name] = np.cumsum(points).tolist()
|
||||||
|
|
||||||
return diff
|
return points_per_step_cumulative
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def driver_points_by(self, *, driver_name: str) -> List[int]:
|
||||||
|
"""
|
||||||
|
Returns a list of points per race for a specific driver.
|
||||||
|
"""
|
||||||
|
return self.driver_points_by(driver_name=driver_name)
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def driver_points_by(self, *, race_name: str) -> Dict[str, int]:
|
||||||
|
"""
|
||||||
|
Returns a dictionary of points per driver for a specific race.
|
||||||
|
"""
|
||||||
|
return self.driver_points_by(race_name=race_name)
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def driver_points_by(self, *, driver_name: str, race_name: str) -> int:
|
||||||
|
"""
|
||||||
|
Returns the points for a specific race for a specific driver.
|
||||||
|
"""
|
||||||
|
return self.driver_points_by(driver_name=driver_name, race_name=race_name)
|
||||||
|
|
||||||
|
def driver_points_by(self, *, driver_name: str | None = None, race_name: str | None = None) -> List[int] | Dict[str, int] | int:
|
||||||
|
if driver_name is not None and race_name is None:
|
||||||
|
return self.driver_points_per_step()[driver_name]
|
||||||
|
|
||||||
|
if driver_name is None and race_name is not None:
|
||||||
|
race_number: int = self.race_by(race_name=race_name).number
|
||||||
|
points_by_race: Dict[str, int] = dict()
|
||||||
|
|
||||||
|
for _driver_name, points in self.driver_points_per_step().items():
|
||||||
|
points_by_race[_driver_name] = points[race_number]
|
||||||
|
|
||||||
|
return points_by_race
|
||||||
|
|
||||||
|
if driver_name is not None and race_name is not None:
|
||||||
|
race_number: int = self.race_by(race_name=race_name).number
|
||||||
|
|
||||||
|
return self.driver_points_per_step()[driver_name][race_number]
|
||||||
|
|
||||||
|
raise Exception("driver_points_by received an illegal combination of arguments")
|
||||||
|
|
||||||
|
def total_driver_points_by(self, driver_name: str) -> int:
|
||||||
|
return sum(self.driver_points_by(driver_name=driver_name))
|
||||||
|
|
||||||
|
def drivers_sorted_by_points(self) -> List[Driver]:
|
||||||
|
comparator: Callable[[Driver], int] = lambda driver: self.total_driver_points_by(driver.name)
|
||||||
|
return sorted(self.all_drivers(include_none=False), key=comparator, reverse=True)
|
||||||
|
|
||||||
def wdc_standing_by_position(self) -> Dict[int, List[str]]:
|
def wdc_standing_by_position(self) -> Dict[int, List[str]]:
|
||||||
standing: Dict[int, List[str]] = dict()
|
standing: Dict[int, List[str]] = dict()
|
||||||
|
|
||||||
for position in range(1, 21):
|
for position in range(1, len(self.all_drivers(include_none=False)) + 1):
|
||||||
standing[position] = list()
|
standing[position] = list()
|
||||||
|
|
||||||
position: int = 1
|
position: int = 1
|
||||||
last_points: int = 0
|
last_points: int = 0
|
||||||
|
|
||||||
comparator: Callable[[Tuple[str, int]], int] = lambda item: item[1]
|
for driver in self.drivers_sorted_by_points():
|
||||||
for driver_name, points in sorted(self.wdc_points().items(), key=comparator, reverse=True):
|
points: int = self.total_driver_points_by(driver.name)
|
||||||
if points < last_points:
|
if points < last_points:
|
||||||
position += 1
|
position += 1
|
||||||
|
|
||||||
standing[position].append(driver_name)
|
standing[position].append(driver.name)
|
||||||
|
|
||||||
last_points = points
|
last_points = points
|
||||||
|
|
||||||
return standing
|
return standing
|
||||||
@ -195,53 +272,18 @@ class PointsModel(Model):
|
|||||||
position: int = 1
|
position: int = 1
|
||||||
last_points: int = 0
|
last_points: int = 0
|
||||||
|
|
||||||
comparator: Callable[[Tuple[str, int]], int] = lambda item: item[1]
|
for driver in self.drivers_sorted_by_points():
|
||||||
for driver_name, points in sorted(self.wdc_points().items(), key=comparator, reverse=True):
|
points: int = self.total_driver_points_by(driver.name)
|
||||||
if points < last_points:
|
if points < last_points:
|
||||||
position += 1
|
position += 1
|
||||||
|
|
||||||
standing[driver_name] = position
|
standing[driver.name] = position
|
||||||
|
|
||||||
last_points = points
|
last_points = points
|
||||||
|
|
||||||
return standing
|
return standing
|
||||||
|
|
||||||
def wcc_standing_by_position(self) -> Dict[int, List[str]]:
|
def wdc_diff_2023_by(self, driver_name: str) -> int:
|
||||||
standing: Dict[int, List[str]] = dict()
|
return WDC_STANDING_2023[driver_name] - self.wdc_standing_by_driver()[driver_name]
|
||||||
|
|
||||||
for position in range (1, 11):
|
|
||||||
standing[position] = list()
|
|
||||||
|
|
||||||
position: int = 1
|
|
||||||
last_points: int = 0
|
|
||||||
|
|
||||||
comparator: Callable[[Tuple[str, int]], int] = lambda item: item[1]
|
|
||||||
for team_name, points in sorted(self.wcc_points().items(), key=comparator, reverse=True):
|
|
||||||
if points < last_points:
|
|
||||||
position += 1
|
|
||||||
|
|
||||||
standing[position].append(team_name)
|
|
||||||
|
|
||||||
last_points = points
|
|
||||||
|
|
||||||
return standing
|
|
||||||
|
|
||||||
def wcc_standing_by_team(self) -> Dict[str, int]:
|
|
||||||
standing: Dict[str, int] = dict()
|
|
||||||
|
|
||||||
position: int = 1
|
|
||||||
last_points: int = 0
|
|
||||||
|
|
||||||
comparator: Callable[[Tuple[str, int]], int] = lambda item: item[1]
|
|
||||||
for team_name, points in sorted(self.wcc_points().items(), key=comparator, reverse=True):
|
|
||||||
if points < last_points:
|
|
||||||
position += 1
|
|
||||||
|
|
||||||
standing[team_name] = position
|
|
||||||
|
|
||||||
last_points = points
|
|
||||||
|
|
||||||
return standing
|
|
||||||
|
|
||||||
def most_dnf_names(self) -> List[str]:
|
def most_dnf_names(self) -> List[str]:
|
||||||
dnf_names: List[str] = list()
|
dnf_names: List[str] = list()
|
||||||
@ -262,13 +304,13 @@ class PointsModel(Model):
|
|||||||
most_gained: int = 0
|
most_gained: int = 0
|
||||||
|
|
||||||
for driver in self.all_drivers(include_none=False):
|
for driver in self.all_drivers(include_none=False):
|
||||||
gained: int = self.wdc_diff_2023()[driver.name]
|
gained: int = self.wdc_diff_2023_by(driver.name)
|
||||||
|
|
||||||
if gained > most_gained:
|
if gained > most_gained:
|
||||||
most_gained = gained
|
most_gained = gained
|
||||||
|
|
||||||
for driver in self.all_drivers(include_none=False):
|
for driver in self.all_drivers(include_none=False):
|
||||||
gained: int = self.wdc_diff_2023()[driver.name]
|
gained: int = self.wdc_diff_2023_by(driver.name)
|
||||||
|
|
||||||
if gained == most_gained:
|
if gained == most_gained:
|
||||||
most_gained_names.append(driver.name)
|
most_gained_names.append(driver.name)
|
||||||
@ -280,26 +322,82 @@ class PointsModel(Model):
|
|||||||
most_lost: int = 100
|
most_lost: int = 100
|
||||||
|
|
||||||
for driver in self.all_drivers(include_none=False):
|
for driver in self.all_drivers(include_none=False):
|
||||||
lost: int = self.wdc_diff_2023()[driver.name]
|
lost: int = self.wdc_diff_2023_by(driver.name)
|
||||||
|
|
||||||
if lost < most_lost:
|
if lost < most_lost:
|
||||||
most_lost = lost
|
most_lost = lost
|
||||||
|
|
||||||
for driver in self.all_drivers(include_none=False):
|
for driver in self.all_drivers(include_none=False):
|
||||||
lost: int = self.wdc_diff_2023()[driver.name]
|
lost: int = self.wdc_diff_2023_by(driver.name)
|
||||||
|
|
||||||
if lost == most_lost:
|
if lost == most_lost:
|
||||||
most_lost_names.append(driver.name)
|
most_lost_names.append(driver.name)
|
||||||
|
|
||||||
return most_lost_names
|
return most_lost_names
|
||||||
|
|
||||||
def drivers_sorted_by_points(self) -> List[Driver]:
|
#
|
||||||
comparator: Callable[[Driver], int] = lambda driver: self.wdc_standing_by_driver()[driver.name]
|
# Team points
|
||||||
return sorted(self.all_drivers(include_none=False), key=comparator)
|
#
|
||||||
|
|
||||||
|
def team_points_per_step_cumulative(self) -> Dict[str, List[int]]:
|
||||||
|
"""
|
||||||
|
Returns a dictionary of lists, containing cumulative points per race for each team.
|
||||||
|
"""
|
||||||
|
points_per_step_cumulative: Dict[str, List[int]] = dict()
|
||||||
|
for team_name, points in self.team_points_per_step().items():
|
||||||
|
points_per_step_cumulative[team_name] = np.cumsum(points).tolist()
|
||||||
|
|
||||||
|
return points_per_step_cumulative
|
||||||
|
|
||||||
|
def total_team_points_by(self, team_name: str) -> int:
|
||||||
|
teammates: List[Driver] = self.drivers_by(team_name=team_name)
|
||||||
|
return sum(self.driver_points_by(driver_name=teammates[0].name)) + sum(self.driver_points_by(driver_name=teammates[1].name))
|
||||||
|
|
||||||
def teams_sorted_by_points(self) -> List[Team]:
|
def teams_sorted_by_points(self) -> List[Team]:
|
||||||
comparator: Callable[[Team], int] = lambda team: self.wcc_standing_by_team()[team.name]
|
comparator: Callable[[Team], int] = lambda team: self.total_team_points_by(team.name)
|
||||||
return sorted(self.all_teams(include_none=False), key=comparator)
|
return sorted(self.all_teams(include_none=False), key=comparator, reverse=True)
|
||||||
|
|
||||||
|
def wcc_standing_by_position(self) -> Dict[int, List[str]]:
|
||||||
|
standing: Dict[int, List[str]] = dict()
|
||||||
|
|
||||||
|
for position in range (1, len(self.all_teams(include_none=False)) + 1):
|
||||||
|
standing[position] = list()
|
||||||
|
|
||||||
|
position: int = 1
|
||||||
|
last_points: int = 0
|
||||||
|
|
||||||
|
for team in self.teams_sorted_by_points():
|
||||||
|
points: int = self.total_team_points_by(team.name)
|
||||||
|
if points < last_points:
|
||||||
|
position += 1
|
||||||
|
|
||||||
|
standing[position].append(team.name)
|
||||||
|
last_points = points
|
||||||
|
|
||||||
|
return standing
|
||||||
|
|
||||||
|
def wcc_standing_by_team(self) -> Dict[str, int]:
|
||||||
|
standing: Dict[str, int] = dict()
|
||||||
|
|
||||||
|
position: int = 1
|
||||||
|
last_points: int = 0
|
||||||
|
|
||||||
|
for team in self.teams_sorted_by_points():
|
||||||
|
points: int = self.total_team_points_by(team.name)
|
||||||
|
if points < last_points:
|
||||||
|
position += 1
|
||||||
|
|
||||||
|
standing[team.name] = position
|
||||||
|
last_points = points
|
||||||
|
|
||||||
|
return standing
|
||||||
|
|
||||||
|
def wcc_diff_2023_by(self, team_name: str) -> int:
|
||||||
|
return WCC_STANDING_2023[team_name] - self.wcc_standing_by_team()[team_name]
|
||||||
|
|
||||||
|
#
|
||||||
|
# User stats
|
||||||
|
#
|
||||||
|
|
||||||
def points_per_step_cumulative(self) -> Dict[str, List[int]]:
|
def points_per_step_cumulative(self) -> Dict[str, List[int]]:
|
||||||
"""
|
"""
|
||||||
@ -466,3 +564,61 @@ class PointsModel(Model):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
#
|
||||||
|
# Diagram queries
|
||||||
|
#
|
||||||
|
|
||||||
|
def cumulative_points_data(self) -> str:
|
||||||
|
data: Dict[Any, Any] = dict()
|
||||||
|
|
||||||
|
data["labels"] = [0] + [
|
||||||
|
race.name for race in sorted(self.all_races(), key=lambda race: race.number)
|
||||||
|
]
|
||||||
|
|
||||||
|
data["datasets"] = [
|
||||||
|
{
|
||||||
|
"data": self.points_per_step_cumulative()[user.name],
|
||||||
|
"label": user.name,
|
||||||
|
"fill": False
|
||||||
|
}
|
||||||
|
for user in self.all_users()
|
||||||
|
]
|
||||||
|
|
||||||
|
return json.dumps(data)
|
||||||
|
|
||||||
|
def cumulative_driver_points_data(self) -> str:
|
||||||
|
data: Dict[Any, Any] = dict()
|
||||||
|
|
||||||
|
data["labels"] = [0] + [
|
||||||
|
race.name for race in sorted(self.all_races(), key=lambda race: race.number)
|
||||||
|
]
|
||||||
|
|
||||||
|
data["datasets"] = [
|
||||||
|
{
|
||||||
|
"data": self.driver_points_per_step_cumulative()[driver.name],
|
||||||
|
"label": driver.abbr,
|
||||||
|
"fill": False
|
||||||
|
}
|
||||||
|
for driver in self.all_drivers(include_none=False)
|
||||||
|
]
|
||||||
|
|
||||||
|
return json.dumps(data)
|
||||||
|
|
||||||
|
def cumulative_team_points_data(self) -> str:
|
||||||
|
data: Dict[Any, Any] = dict()
|
||||||
|
|
||||||
|
data["labels"] = [0] + [
|
||||||
|
race.name for race in sorted(self.all_races(), key=lambda race: race.number)
|
||||||
|
]
|
||||||
|
|
||||||
|
data["datasets"] = [
|
||||||
|
{
|
||||||
|
"data": self.team_points_per_step_cumulative()[team.name],
|
||||||
|
"label": team.name,
|
||||||
|
"fill": False
|
||||||
|
}
|
||||||
|
for team in self.all_teams(include_none=False)
|
||||||
|
]
|
||||||
|
|
||||||
|
return json.dumps(data)
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
18
formula10/static/style/diagram.css
Normal file
18
formula10/static/style/diagram.css
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 40vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 900px) {
|
||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 50vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (orientation: landscape) and (max-width: 900px) {
|
||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,3 +11,17 @@
|
|||||||
grid-column-gap: 8px;
|
grid-column-gap: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-grid-2 {
|
||||||
|
grid-template-columns: repeat(1, minmax(325px, 1fr));
|
||||||
|
grid-row-gap: 0;
|
||||||
|
grid-column-gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-width: 950px) {
|
||||||
|
.card-grid-2 {
|
||||||
|
grid-template-columns: repeat(2, minmax(450px, 1fr));
|
||||||
|
grid-row-gap: 0;
|
||||||
|
grid-column-gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -26,7 +26,8 @@
|
|||||||
{# Simple driver select for forms #}
|
{# Simple driver select for forms #}
|
||||||
{% macro driver_select(name, label, include_none, drivers=none, disabled=false, border="") %}
|
{% macro driver_select(name, label, include_none, drivers=none, disabled=false, border="") %}
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<select name="{{ name }}" id="{{ name }}" class="form-select {{ border }}" aria-label="{{ name }}" {% if disabled %}disabled="disabled"{% endif %}>
|
<select name="{{ name }}" id="{{ name }}" class="form-select {{ border }}" aria-label="{{ name }}"
|
||||||
|
{% if disabled %}disabled="disabled"{% endif %}>
|
||||||
<option value="" selected disabled hidden></option>
|
<option value="" selected disabled hidden></option>
|
||||||
|
|
||||||
{% if drivers == none %}
|
{% if drivers == none %}
|
||||||
@ -34,7 +35,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>
|
||||||
@ -44,7 +45,8 @@
|
|||||||
{# Driver select for forms where a value might be preselected #}
|
{# Driver select for forms where a value might be preselected #}
|
||||||
{% macro driver_select_with_preselect(driver_match, name, label, include_none, drivers=none, disabled=false, border="") %}
|
{% macro driver_select_with_preselect(driver_match, name, label, include_none, drivers=none, disabled=false, border="") %}
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<select name="{{ name }}" id="{{ name }}" class="form-select {{ border }}" aria-label="{{ name }}" {% if disabled %}disabled="disabled"{% endif %}>
|
<select name="{{ name }}" id="{{ name }}" class="form-select {{ border }}" aria-label="{{ name }}"
|
||||||
|
{% if disabled %}disabled="disabled"{% endif %}>
|
||||||
{# Use namespace wrapper to persist scope between loop iterations #}
|
{# Use namespace wrapper to persist scope between loop iterations #}
|
||||||
{% set user_has_chosen = namespace(driverpre=false) %}
|
{% set user_has_chosen = namespace(driverpre=false) %}
|
||||||
|
|
||||||
@ -55,13 +57,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 %}
|
||||||
|
|
||||||
@ -77,7 +79,8 @@
|
|||||||
{# Simple team select for forms #}
|
{# Simple team select for forms #}
|
||||||
{% macro team_select(name, label, include_none, teams=none, disabled=false, border="") %}
|
{% macro team_select(name, label, include_none, teams=none, disabled=false, border="") %}
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<select name="{{ name }}" id="{{ name }}" class="form-select {{ border }}" aria-label="{{ name }}" {% if disabled %}disabled="disabled"{% endif %}>
|
<select name="{{ name }}" id="{{ name }}" class="form-select {{ border }}" aria-label="{{ name }}"
|
||||||
|
{% if disabled %}disabled="disabled"{% endif %}>
|
||||||
<option value="" selected disabled hidden></option>
|
<option value="" selected disabled hidden></option>
|
||||||
|
|
||||||
{% if teams == none %}
|
{% if teams == none %}
|
||||||
@ -85,7 +88,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>
|
||||||
@ -95,7 +98,8 @@
|
|||||||
{# Team select for forms where a value might be preselected #}
|
{# Team select for forms where a value might be preselected #}
|
||||||
{% macro team_select_with_preselect(team_match, name, label, include_none, teams=none, disabled=false, border="") %}
|
{% macro team_select_with_preselect(team_match, name, label, include_none, teams=none, disabled=false, border="") %}
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<select name="{{ name }}" id="{{ name }}" class="form-select {{ border }}" aria-label="{{ name }}" {% if disabled %}disabled="disabled"{% endif %}>
|
<select name="{{ name }}" id="{{ name }}" class="form-select {{ border }}" aria-label="{{ name }}"
|
||||||
|
{% if disabled %}disabled="disabled"{% endif %}>
|
||||||
{# Use namespace wrapper to persist scope between loop iterations #}
|
{# Use namespace wrapper to persist scope between loop iterations #}
|
||||||
{% set user_has_chosen = namespace(teampre=false) %}
|
{% set user_has_chosen = namespace(teampre=false) %}
|
||||||
|
|
||||||
@ -106,13 +110,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 %}
|
||||||
|
|
||||||
@ -176,8 +180,12 @@
|
|||||||
<link href="../static/style/bootstrap.css" rel="stylesheet">
|
<link href="../static/style/bootstrap.css" rel="stylesheet">
|
||||||
<script src="../static/script/bootstrap.bundle.js"></script>
|
<script src="../static/script/bootstrap.bundle.js"></script>
|
||||||
|
|
||||||
<link href="../static/style/grid.css" rel="stylesheet">
|
<!-- ChartJS -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
|
||||||
|
<!-- Custom -->
|
||||||
|
<link href="../static/style/grid.css" rel="stylesheet">
|
||||||
|
<link href="../static/style/diagram.css" rel="stylesheet">
|
||||||
<script defer>
|
<script defer>
|
||||||
{# Initialize Bootstrap Tooltips #}
|
{# Initialize Bootstrap Tooltips #}
|
||||||
let tooltipTriggerList = document.querySelectorAll("[data-bs-toggle='tooltip']")
|
let tooltipTriggerList = document.querySelectorAll("[data-bs-toggle='tooltip']")
|
||||||
@ -214,7 +222,7 @@
|
|||||||
<div class="flex-grow-1"></div>
|
<div class="flex-grow-1"></div>
|
||||||
|
|
||||||
<div class="navbar-nav">
|
<div class="navbar-nav">
|
||||||
{{ nav_selector(page="/result", text="Enter Race Result") }}
|
{# {{ nav_selector(page="/result", text="Enter Race Result") }} #}
|
||||||
{{ nav_selector(page="/user", text="Manage Users") }}
|
{{ nav_selector(page="/user", text="Manage Users") }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -5,10 +5,12 @@
|
|||||||
{% set active_page = "/error" %}
|
{% set active_page = "/error" %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="card">
|
<div class="card shadow-sm mb-2">
|
||||||
|
<div class="card-header">
|
||||||
|
<span class="text-danger fw-bold">Error</span>
|
||||||
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title text-danger fw-bold">Error</h5>
|
{{ error_message }}
|
||||||
<h6 class="card-subtitle">{{ error_message }}</h6>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock body %}
|
{% endblock body %}
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
||||||
<div class="card">
|
<div class="card shadow-sm mb-2">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
Note
|
Note
|
||||||
</div>
|
</div>
|
||||||
@ -16,7 +16,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card mt-2">
|
<div class="card shadow-sm mb-2">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
Leaderboard
|
Leaderboard
|
||||||
</div>
|
</div>
|
||||||
@ -55,13 +55,45 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# <div class="card mt-2">#}
|
<div class="card shadow-sm mb-2">
|
||||||
{# <div class="card-body">#}
|
<div class="card-header">
|
||||||
{# <h5 class="card-title">History</h5>#}
|
History
|
||||||
|
</div>
|
||||||
|
|
||||||
{# Line chart of point history with a line per user #}
|
<div class="card-body">
|
||||||
{# </div>#}
|
<div class="chart-container">
|
||||||
{# </div>#}
|
<canvas id="line-chart"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function cumulative_points(data) {
|
||||||
|
return new Chart(document.getElementById("line-chart"), {
|
||||||
|
type: 'line',
|
||||||
|
data: data,
|
||||||
|
options: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'History'
|
||||||
|
},
|
||||||
|
{#tension: 0,#}
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
pointRadius: 5,
|
||||||
|
pointHoverRadius: 10,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
min: 0,
|
||||||
|
{#max: 100#}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cumulative_points({{ points.cumulative_points_data() | safe }})
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{# <div class="card mt-2">#}
|
{# <div class="card mt-2">#}
|
||||||
{# <div class="card-body">#}
|
{# <div class="card-body">#}
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
||||||
{# Put table in this div to make right padding work #}
|
{# Put table in this div to make right padding work #}
|
||||||
<div class="d-inline-block overflow-x-scroll w-100">
|
<div class="d-inline-block overflow-x-scroll w-100 mb-2">
|
||||||
<table class="table table-bordered table-sm table-responsive shadow-sm">
|
<table class="table table-bordered table-sm table-responsive shadow-sm">
|
||||||
|
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
{% set active_page = "/rules" %}
|
{% set active_page = "/rules" %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="card mb-2">
|
<div class="card shadow-sm mb-2">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
Format
|
Format
|
||||||
</div>
|
</div>
|
||||||
@ -22,7 +22,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card mb-2">
|
<div class="card shadow-sm mb-2">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
Renntipps
|
Renntipps
|
||||||
</div>
|
</div>
|
||||||
@ -43,7 +43,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card mb-2">
|
<div class="card shadow-sm mb-2">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
Punkte
|
Punkte
|
||||||
</div>
|
</div>
|
||||||
@ -62,7 +62,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card mb-2">
|
<div class="card shadow-sm mb-2">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
Bonustipps
|
Bonustipps
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
||||||
<div class="card mb-2">
|
<div class="card shadow-sm mb-2">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
Note
|
Note
|
||||||
</div>
|
</div>
|
||||||
@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
{% for user in model.all_users_or_active_user() %}
|
{% for user in model.all_users_or_active_user() %}
|
||||||
|
|
||||||
<div class="card mb-2 shadow-sm">
|
<div class="card shadow-sm mb-2">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
{# Link should only be visible if all users are visible #}
|
{# Link should only be visible if all users are visible #}
|
||||||
{% if model.active_user is not none %}
|
{% if model.active_user is not none %}
|
||||||
@ -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 %}
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
||||||
<div class="card mb-2">
|
<div class="card shadow-sm mb-2">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
Note
|
Note
|
||||||
</div>
|
</div>
|
||||||
@ -16,9 +16,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid card-grid">
|
<div class="grid card-grid-2">
|
||||||
|
|
||||||
<div class="card mb-2">
|
<div class="card shadow-sm mb-2">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
Drivers
|
Drivers
|
||||||
</div>
|
</div>
|
||||||
@ -32,6 +32,7 @@
|
|||||||
<th scope="col" class="text-center" style="min-width: 50px;">Driver</th>
|
<th scope="col" class="text-center" style="min-width: 50px;">Driver</th>
|
||||||
<th scope="col" class="text-center" style="min-width: 100px;">Points</th>
|
<th scope="col" class="text-center" style="min-width: 100px;">Points</th>
|
||||||
<th scope="col" class="text-center" style="min-width: 100px;">DNFs</th>
|
<th scope="col" class="text-center" style="min-width: 100px;">DNFs</th>
|
||||||
|
<th scope="col" class="text-center" style="min-width: 100px;">Place Delta</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
@ -41,8 +42,9 @@
|
|||||||
<tr class="{% if driver_standing == 1 %}table-danger{% endif %}">
|
<tr class="{% if driver_standing == 1 %}table-danger{% endif %}">
|
||||||
<td class="text-center text-nowrap">{{ driver_standing }}</td>
|
<td class="text-center text-nowrap">{{ driver_standing }}</td>
|
||||||
<td class="text-center text-nowrap">{{ driver.name }}</td>
|
<td class="text-center text-nowrap">{{ driver.name }}</td>
|
||||||
<td class="text-center text-nowrap">{{ points.wdc_points()[driver.name] }}</td>
|
<td class="text-center text-nowrap">{{ points.total_driver_points_by(driver.name) }}</td>
|
||||||
<td class="text-center text-nowrap">{{ points.dnfs()[driver.name] }}</td>
|
<td class="text-center text-nowrap">{{ points.dnfs()[driver.name] }}</td>
|
||||||
|
<td class="text-center text-nowrap">{{ "%+d" % points.wdc_diff_2023_by(driver.name) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -51,7 +53,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card mb-2">
|
<div class="card shadow-sm mb-2">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
Constructors
|
Constructors
|
||||||
</div>
|
</div>
|
||||||
@ -64,6 +66,7 @@
|
|||||||
<th scope="col" class="text-center" style="min-width: 50px;">Place</th>
|
<th scope="col" class="text-center" style="min-width: 50px;">Place</th>
|
||||||
<th scope="col" class="text-center" style="min-width: 50px;">Team</th>
|
<th scope="col" class="text-center" style="min-width: 50px;">Team</th>
|
||||||
<th scope="col" class="text-center" style="min-width: 100px;">Points</th>
|
<th scope="col" class="text-center" style="min-width: 100px;">Points</th>
|
||||||
|
<th scope="col" class="text-center" style="min-width: 100px;">Place Delta</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
@ -73,7 +76,8 @@
|
|||||||
<tr class="{% if team_standing == 1 %}table-danger{% endif %}">
|
<tr class="{% if team_standing == 1 %}table-danger{% endif %}">
|
||||||
<td class="text-center text-nowrap">{{ team_standing }}</td>
|
<td class="text-center text-nowrap">{{ team_standing }}</td>
|
||||||
<td class="text-center text-nowrap">{{ team.name }}</td>
|
<td class="text-center text-nowrap">{{ team.name }}</td>
|
||||||
<td class="text-center text-nowrap">{{ points.wcc_points()[team.name] }}</td>
|
<td class="text-center text-nowrap">{{ points.total_team_points_by(team.name) }}</td>
|
||||||
|
<td class="text-center text-nowrap">{{ points.wcc_diff_2023_by(team.name) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -82,6 +86,86 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm mb-2">
|
||||||
|
<div class="card-header">
|
||||||
|
Driver history
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="chart-container">
|
||||||
|
<canvas id="driver-line-chart"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function cumulative_driver_points(data) {
|
||||||
|
return new Chart(document.getElementById("driver-line-chart"), {
|
||||||
|
type: 'line',
|
||||||
|
data: data,
|
||||||
|
options: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'History'
|
||||||
|
},
|
||||||
|
{#tension: 0,#}
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
pointRadius: 5,
|
||||||
|
pointHoverRadius: 10,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
min: 0,
|
||||||
|
{#max: 100#}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cumulative_driver_points({{ points.cumulative_driver_points_data() | safe }})
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm mb-2">
|
||||||
|
<div class="card-header">
|
||||||
|
Team history
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="chart-container">
|
||||||
|
<canvas id="team-line-chart"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function cumulative_team_points(data) {
|
||||||
|
return new Chart(document.getElementById("team-line-chart"), {
|
||||||
|
type: 'line',
|
||||||
|
data: data,
|
||||||
|
options: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'History'
|
||||||
|
},
|
||||||
|
{#tension: 0,#}
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
pointRadius: 5,
|
||||||
|
pointHoverRadius: 10,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
min: 0,
|
||||||
|
{#max: 100#}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cumulative_team_points({{ points.cumulative_team_points_data() | safe }})
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock body %}
|
{% endblock body %}
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
||||||
<div class="card shadow-sm">
|
<div class="card shadow-sm mb-2">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
Add user
|
Add user
|
||||||
</div>
|
</div>
|
||||||
@ -27,7 +27,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if model.all_users() | length > 0 %}
|
{% if model.all_users() | length > 0 %}
|
||||||
<div class="card mt-2 shadow-sm">
|
<div class="card shadow-sm mb-2">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
Registered users
|
Registered users
|
||||||
</div>
|
</div>
|
||||||
@ -41,7 +41,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card mt-2 shadow-sm">
|
<div class="card shadow-sm mb-2">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
Delete user
|
Delete user
|
||||||
</div>
|
</div>
|
||||||
@ -72,16 +72,4 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# <div class="card mt-2 border-danger shadow-sm">#}
|
|
||||||
{# <div class="card-body">#}
|
|
||||||
{# <h5 class="card-title">Functions that should not be public</h5>#}
|
|
||||||
{# <h6 class="card-subtitle mb-2">(F you if you click this without knowing what it does)</h6>#}
|
|
||||||
|
|
||||||
{# <a class="btn btn-outline-danger" href="/save/all">Save all data</a>#}
|
|
||||||
{# <a class="btn btn-outline-danger" href="/load/all">Load all data</a>#}
|
|
||||||
{# <a class="btn btn-outline-danger" href="/load/static">Load static data</a>#}
|
|
||||||
{# <a class="btn btn-outline-danger" href="/load/dynamic">Load dynamic data</a>#}
|
|
||||||
{# </div>#}
|
|
||||||
{# </div>#}
|
|
||||||
|
|
||||||
{% endblock body %}
|
{% endblock body %}
|
||||||
|
|||||||
Reference in New Issue
Block a user