Split base model from template + points model

This commit is contained in:
2024-02-27 19:42:21 +01:00
parent dc9dc3d092
commit 44549f019d
24 changed files with 196 additions and 132 deletions

View File

@ -17,6 +17,7 @@ db.init_app(app)
# NOTE: These imports are required to register the routes. They need to be imported after "app" is declared
import formula10.controller.race_controller # type: ignore
import formula10.controller.season_controller
import formula10.controller.statistics_controller
import formula10.controller.rules_controller
import formula10.controller.admin_controller
import formula10.controller.error_controller
@ -25,11 +26,14 @@ import formula10.controller.error_controller
# TODO
# General
# Create a model baseclass that contains the cached teams/drivers/races etc., so the points + template model can be derived from it
# Statistics
# - Auto calculate points
# - Display points somewhere in race table?
# - Highlight currently correct values for some season guesses (e.g. current most dnfs)
# - Display points somewhere in race table? Below the name in the table header.
# - Highlight currently correct values for some season guesses (e.g. current most dnfs, team winners, podiums)
# - Generate static diagram using chart.js + templating the js (funny yikes)
# - Which driver was voted most for dnf?
# Possible but probably not
# - Show cards of previous race results, like with season guesses?

View File

@ -5,7 +5,7 @@ from werkzeug import Response
from formula10.database.update_queries import update_race_result, update_user
from formula10.database.import_export import export_dynamic_data, reload_static_data
from formula10.frontend.template_model import TemplateModel
from formula10.domain.template_model import TemplateModel
from formula10 import app

View File

@ -2,7 +2,7 @@ from typing import cast
from flask import redirect, render_template, session
from werkzeug import Response
from formula10.frontend.template_model import TemplateModel
from formula10.domain.template_model import TemplateModel
from formula10 import app
def error_redirect(error_message: str) -> Response:

View File

@ -3,7 +3,7 @@ from flask import redirect, render_template, request
from werkzeug import Response
from formula10.database.update_queries import delete_race_guess, update_race_guess
from formula10.frontend.template_model import TemplateModel
from formula10.domain.template_model import TemplateModel
from formula10 import app

View File

@ -1,7 +1,7 @@
from flask import render_template
from formula10 import app
from formula10.frontend.template_model import TemplateModel
from formula10.domain.template_model import TemplateModel
@app.route("/rules")
def rules_root() -> str:

View File

@ -5,8 +5,8 @@ from werkzeug import Response
from formula10.database.model.db_team import DbTeam
from formula10.database.update_queries import update_season_guess
from formula10.frontend.model.team import NONE_TEAM
from formula10.frontend.template_model import TemplateModel
from formula10.domain.model.team import NONE_TEAM
from formula10.domain.template_model import TemplateModel
from formula10 import app, db

View File

@ -0,0 +1,9 @@
from flask import render_template
from formula10 import app
from formula10.domain.template_model import TemplateModel
@app.route("/graphs")
def graphs_root() -> str:
model = TemplateModel(active_user_name=None, active_result_race_name=None)
return render_template("statistics.jinja", model=model)

View File

View File

View File

@ -3,7 +3,7 @@ from typing import Any, Callable, Iterable, List, TypeVar, overload
from formula10.database.model.db_race import DbRace
from formula10 import db
from formula10.frontend.model.race import Race
from formula10.domain.model.race import Race
_T = TypeVar("_T")

View File

View File

@ -0,0 +1,118 @@
from typing import Callable, List
from sqlalchemy import desc
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_team import DbTeam
from formula10.database.model.db_user import DbUser
from formula10.database.validation import find_multiple_strict
from formula10.domain.model.driver import NONE_DRIVER, Driver
from formula10.domain.model.race import Race
from formula10.domain.model.race_guess import RaceGuess
from formula10.domain.model.race_result import RaceResult
from formula10.domain.model.season_guess import SeasonGuess
from formula10.domain.model.team import NONE_TEAM, Team
from formula10.domain.model.user import User
from formula10 import db
class Model():
_all_users: List[User] | None = None
_all_race_results: List[RaceResult] | None = None
_all_race_guesses: List[RaceGuess] | None = None
_all_season_guesses: List[SeasonGuess] | None = None
_all_races: List[Race] | None = None
_all_drivers: List[Driver] | None = None
_all_teams: List[Team] | None = None
def all_users(self) -> List[User]:
"""
Returns a list of all users in the database.
"""
if self._all_users is None:
self._all_users = [
User.from_db_user(db_user)
for db_user in db.session.query(DbUser).filter_by(enabled=True).all()
]
return self._all_users
def all_race_results(self) -> List[RaceResult]:
"""
Returns a list of all race results in the database, in descending order (most recent first).
"""
if self._all_race_results is None:
self._all_race_results = [
RaceResult.from_db_race_result(db_race_result)
for db_race_result in db.session.query(DbRaceResult).join(DbRaceResult.race).order_by(desc(DbRace.number)).all()
]
return self._all_race_results
def all_race_guesses(self) -> List[RaceGuess]:
"""
Returns a list of all race guesses in the database.
"""
if self._all_race_guesses is None:
self._all_race_guesses = [
RaceGuess.from_db_race_guess(db_race_guess)
for db_race_guess in db.session.query(DbRaceGuess).join(DbRaceGuess.user).filter_by(enabled=True).all() # Ignore disabled users
]
return self._all_race_guesses
def all_season_guesses(self) -> List[SeasonGuess]:
if self._all_season_guesses is None:
self._all_season_guesses = [
SeasonGuess.from_db_season_guess(db_season_guess)
for db_season_guess in db.session.query(DbSeasonGuess).join(DbSeasonGuess.user).filter_by(enabled=True).all() # Ignore disabled users
]
return self._all_season_guesses
def all_races(self) -> List[Race]:
"""
Returns a list of all races in the database.
"""
if self._all_races is None:
self._all_races = [
Race.from_db_race(db_race)
for db_race in db.session.query(DbRace).order_by(desc(DbRace.number)).all()
]
return self._all_races
def all_drivers(self, *, include_none: bool) -> List[Driver]:
"""
Returns a list of all drivers in the database.
"""
if self._all_drivers is None:
self._all_drivers = [
Driver.from_db_driver(db_driver)
for db_driver in db.session.query(DbDriver).all()
]
if include_none:
return self._all_drivers
else:
predicate: Callable[[Driver], bool] = lambda driver: driver != NONE_DRIVER
return find_multiple_strict(predicate, self._all_drivers)
def all_teams(self, *, include_none: bool) -> List[Team]:
"""
Returns a list of all teams in the database.
"""
if self._all_teams is None:
self._all_teams = [
Team.from_db_team(db_team)
for db_team in db.session.query(DbTeam).all()
]
if include_none:
return self._all_teams
else:
predicate: Callable[[Team], bool] = lambda team: team != NONE_TEAM
return find_multiple_strict(predicate, self._all_teams)

View File

View File

@ -1,7 +1,7 @@
from urllib.parse import quote
from formula10.database.model.db_driver import DbDriver
from formula10.frontend.model.team import NONE_TEAM, Team
from formula10.domain.model.team import NONE_TEAM, Team
class Driver():

View File

@ -1,7 +1,7 @@
from formula10.database.model.db_race_guess import DbRaceGuess
from formula10.frontend.model.driver import Driver
from formula10.frontend.model.race import Race
from formula10.frontend.model.user import User
from formula10.domain.model.driver import Driver
from formula10.domain.model.race import Race
from formula10.domain.model.user import User
class RaceGuess():

View File

@ -3,8 +3,8 @@ from typing import Dict, List
from formula10.database.common_queries import find_single_driver_strict
from formula10.database.model.db_race_result import DbRaceResult
from formula10.frontend.model.driver import NONE_DRIVER, Driver
from formula10.frontend.model.race import Race
from formula10.domain.model.driver import NONE_DRIVER, Driver
from formula10.domain.model.race import Race
class RaceResult:

View File

@ -2,9 +2,9 @@ import json
from typing import List
from formula10.database.common_queries import find_single_driver_strict
from formula10.database.model.db_season_guess import DbSeasonGuess
from formula10.frontend.model.driver import Driver
from formula10.frontend.model.team import Team
from formula10.frontend.model.user import User
from formula10.domain.model.driver import Driver
from formula10.domain.model.team import Team
from formula10.domain.model.user import User
class SeasonGuess():

View File

@ -0,0 +1,5 @@
from formula10.domain.domain_model import Model
class PointsModel(Model):
def __init__(self):
Model.__init__(self)

View File

@ -1,37 +1,21 @@
from typing import List, Callable, Dict, overload
from sqlalchemy import desc
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_team import DbTeam
from formula10.database.model.db_user import DbUser
from formula10.frontend.model.driver import NONE_DRIVER, Driver
from formula10.frontend.model.race import Race
from formula10.frontend.model.race_guess import RaceGuess
from formula10.frontend.model.race_result import RaceResult
from formula10.frontend.model.season_guess import SeasonGuess
from formula10.frontend.model.team import NONE_TEAM, Team
from formula10.frontend.model.user import User
from formula10.domain.domain_model import Model
from formula10.domain.model.driver import NONE_DRIVER, Driver
from formula10.domain.model.race import Race
from formula10.domain.model.race_guess import RaceGuess
from formula10.domain.model.race_result import RaceResult
from formula10.domain.model.season_guess import SeasonGuess
from formula10.domain.model.team import NONE_TEAM, Team
from formula10.domain.model.user import User
from formula10.database.validation import find_first_else_none, find_multiple_strict, find_single_strict, find_single_or_none_strict, race_has_started
from formula10 import db
class TemplateModel:
class TemplateModel(Model):
"""
This class bundles all data required from inside a template.
"""
_all_users: List[User] | None = None
_all_race_results: List[RaceResult] | None = None
_all_race_guesses: List[RaceGuess] | None = None
_all_season_guesses: List[SeasonGuess] | None = None
_all_races: List[Race] | None = None
_all_drivers: List[Driver] | None = None
_all_teams: List[Team] | None = None
active_user: User | None = None
active_result: RaceResult | None = None
@ -57,18 +41,6 @@ class TemplateModel:
def active_user_name_sanitized_or_everyone(self) -> str:
return self.active_user.name_sanitized if self.active_user is not None else "Everyone"
def all_users(self) -> List[User]:
"""
Returns a list of all users in the database.
"""
if self._all_users is None:
self._all_users = [
User.from_db_user(db_user)
for db_user in db.session.query(DbUser).filter_by(enabled=True).all()
]
return self._all_users
def all_users_or_active_user(self) -> List[User]:
if self.active_user is not None:
return [self.active_user]
@ -99,18 +71,6 @@ class TemplateModel:
predicate: Callable[[User], bool] = lambda user: user.name == user_name
return find_single_strict(predicate, self.all_users())
def all_race_results(self) -> List[RaceResult]:
"""
Returns a list of all race results in the database, in descending order (most recent first).
"""
if self._all_race_results is None:
self._all_race_results = [
RaceResult.from_db_race_result(db_race_result)
for db_race_result in db.session.query(DbRaceResult).join(DbRaceResult.race).order_by(desc(DbRace.number)).all()
]
return self._all_race_results
def race_result_by(self, *, race_name: str) -> RaceResult | None:
"""
Tries to obtain the race result corresponding to a race name.
@ -118,18 +78,6 @@ class TemplateModel:
predicate: Callable[[RaceResult], bool] = lambda result: result.race.name == race_name
return find_single_or_none_strict(predicate, self.all_race_results())
def all_race_guesses(self) -> List[RaceGuess]:
"""
Returns a list of all race guesses in the database.
"""
if self._all_race_guesses is None:
self._all_race_guesses = [
RaceGuess.from_db_race_guess(db_race_guess)
for db_race_guess in db.session.query(DbRaceGuess).join(DbRaceGuess.user).filter_by(enabled=True).all() # Ignore disabled users
]
return self._all_race_guesses
@overload
def race_guesses_by(self, *, user_name: str) -> List[RaceGuess]:
"""
@ -189,15 +137,6 @@ class TemplateModel:
raise Exception("race_guesses_by encountered illegal combination of arguments")
def all_season_guesses(self) -> List[SeasonGuess]:
if self._all_season_guesses is None:
self._all_season_guesses = [
SeasonGuess.from_db_season_guess(db_season_guess)
for db_season_guess in db.session.query(DbSeasonGuess).join(DbSeasonGuess.user).filter_by(enabled=True).all() # Ignore disabled users
]
return self._all_season_guesses
@overload
def season_guesses_by(self, *, user_name: str) -> SeasonGuess:
"""
@ -228,18 +167,6 @@ class TemplateModel:
raise Exception("season_guesses_by encountered illegal combination of arguments")
def all_races(self) -> List[Race]:
"""
Returns a list of all races in the database.
"""
if self._all_races is None:
self._all_races = [
Race.from_db_race(db_race)
for db_race in db.session.query(DbRace).order_by(desc(DbRace.number)).all()
]
return self._all_races
def first_race_without_result(self) -> Race | None:
"""
Returns the first race-object with no associated race result.
@ -273,41 +200,9 @@ class TemplateModel:
else:
return self.all_races()[0].name_sanitized
def all_teams(self, *, include_none: bool) -> List[Team]:
"""
Returns a list of all teams in the database.
"""
if self._all_teams is None:
self._all_teams = [
Team.from_db_team(db_team)
for db_team in db.session.query(DbTeam).all()
]
if include_none:
return self._all_teams
else:
predicate: Callable[[Team], bool] = lambda team: team != NONE_TEAM
return find_multiple_strict(predicate, self._all_teams)
def none_team(self) -> Team:
return NONE_TEAM
def all_drivers(self, *, include_none: bool) -> List[Driver]:
"""
Returns a list of all drivers in the database.
"""
if self._all_drivers is None:
self._all_drivers = [
Driver.from_db_driver(db_driver)
for db_driver in db.session.query(DbDriver).all()
]
if include_none:
return self._all_drivers
else:
predicate: Callable[[Driver], bool] = lambda driver: driver != NONE_DRIVER
return find_multiple_strict(predicate, self._all_drivers)
def all_drivers_or_active_result_standing_drivers(self) -> List[Driver]:
return self.active_result.ordered_standing_list() if self.active_result is not None else self.all_drivers(include_none=False)

View File

@ -0,0 +1,33 @@
{% extends 'base.jinja' %}
{% block title %}Formula 10 - Leaderboard{% endblock title %}
{% set active_page = "/graphs" %}
{% block body %}
<div class="card">
<div class="card-body">
<h5 class="card-title">Leaderboard</h5>
{# Table that lists each users + Total Points (?), Race guesses points, Season guesses points (missing overtakes + hottake), number of guesses that yielded points, average points per guess #}
</div>
</div>
<div class="card mt-2">
<div class="card-body">
<h5 class="card-title">History</h5>
{# Line chart of point history with a line per user #}
</div>
</div>
<div class="card mt-2">
<div class="card-body">
<h5 class="card-title">Statistics</h5>
{# Various statistics: Driver voted most for DNF #}
</div>
</div>
{% endblock body %}