From fc8a8905117c440eea91c4f3eed6076a173c215e Mon Sep 17 00:00:00 2001 From: Christoph Urlacher Date: Sat, 24 Feb 2024 16:22:48 +0100 Subject: [PATCH] Change file structure --- .../database/backend_model.py | 20 +- .../database/database_utils.py | 2 +- file_utils.py => app/database/file_utils.py | 2 +- model.py => app/database/model.py | 0 .../database/validation_utils.py | 0 app/frontend/controller.py | 174 +++++++++++++++++ .../frontend/template_model.py | 4 +- app/logic/points_model.py | 0 formula10.py | 179 +----------------- 9 files changed, 190 insertions(+), 191 deletions(-) rename backend_model.py => app/database/backend_model.py (92%) rename database_utils.py => app/database/database_utils.py (83%) rename file_utils.py => app/database/file_utils.py (96%) rename model.py => app/database/model.py (100%) rename validation_utils.py => app/database/validation_utils.py (100%) create mode 100644 app/frontend/controller.py rename template_model.py => app/frontend/template_model.py (97%) create mode 100644 app/logic/points_model.py diff --git a/backend_model.py b/app/database/backend_model.py similarity index 92% rename from backend_model.py rename to app/database/backend_model.py index 100d8c9..87b89ce 100644 --- a/backend_model.py +++ b/app/database/backend_model.py @@ -2,9 +2,9 @@ from typing import Dict, List, cast from urllib.parse import quote from flask import redirect from werkzeug import Response -from database_utils import race_has_result, user_exists -from model import PodiumDrivers, RaceResult, SeasonGuess, TeamWinners, User, db, RaceGuess -from validation_utils import any_is_none, positions_are_contiguous +from app.database.database_utils import race_has_result, user_exists +from app.database.model import PodiumDrivers, RaceResult, SeasonGuess, TeamWinners, User, db, RaceGuess +from app.database.validation_utils import any_is_none, positions_are_contiguous def find_or_create_race_guess(user_name: str, race_name: str) -> RaceGuess: @@ -127,13 +127,13 @@ def update_season_guess(user_name: str, guesses: List[str | None], team_winner_g # Pylance marks type errors here, but those are intended. Columns are marked nullable. season_guess: SeasonGuess = find_or_create_season_guess(user_name) - season_guess.hot_take = guesses[0] - season_guess.p2_team_name = guesses[1] - season_guess.overtake_driver_name = guesses[2] - season_guess.dnf_driver_name = guesses[3] - season_guess.gained_driver_name = guesses[4] - season_guess.lost_driver_name = guesses[5] - season_guess.team_winners.teamwinner_driver_names = team_winner_guesses + season_guess.hot_take = guesses[0] # type: ignore + season_guess.p2_team_name = guesses[1] # type: ignore + season_guess.overtake_driver_name = guesses[2] # type: ignore + season_guess.dnf_driver_name = guesses[3] # type: ignore + season_guess.gained_driver_name = guesses[4] # type: ignore + season_guess.lost_driver_name = guesses[5] # type: ignore + season_guess.team_winners.teamwinner_driver_names = team_winner_guesses # type: ignore season_guess.podium_drivers.podium_driver_names = podium_driver_guesses db.session.commit() diff --git a/database_utils.py b/app/database/database_utils.py similarity index 83% rename from database_utils.py rename to app/database/database_utils.py index fa73304..de923af 100644 --- a/database_utils.py +++ b/app/database/database_utils.py @@ -1,4 +1,4 @@ -from model import User, db, RaceResult +from app.database.model import User, db, RaceResult def race_has_result(race_name: str) -> bool: diff --git a/file_utils.py b/app/database/file_utils.py similarity index 96% rename from file_utils.py rename to app/database/file_utils.py index 751f5b9..1c7b222 100644 --- a/file_utils.py +++ b/app/database/file_utils.py @@ -1,7 +1,7 @@ import csv import os.path from typing import List, Any -from model import Team, Driver, Race, User, RaceResult, RaceGuess, TeamWinners, PodiumDrivers, SeasonGuess, db +from app.database.model import Team, Driver, Race, User, RaceResult, RaceGuess, TeamWinners, PodiumDrivers, SeasonGuess, db def load_csv(filename: str) -> List[List[str]]: diff --git a/model.py b/app/database/model.py similarity index 100% rename from model.py rename to app/database/model.py diff --git a/validation_utils.py b/app/database/validation_utils.py similarity index 100% rename from validation_utils.py rename to app/database/validation_utils.py diff --git a/app/frontend/controller.py b/app/frontend/controller.py new file mode 100644 index 0000000..5876145 --- /dev/null +++ b/app/frontend/controller.py @@ -0,0 +1,174 @@ +from typing import List +from urllib.parse import unquote +from flask import Flask, render_template, request, redirect +from werkzeug import Response +from app.database.model import Team, db +from app.database.file_utils import reload_static_data, reload_dynamic_data, export_dynamic_data +from app.frontend.template_model import TemplateModel +from app.database.backend_model import delete_race_guess, update_race_guess, update_race_result, update_season_guess, update_user + +app = Flask(__name__) + +app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:///formula10.db" +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False +app.url_map.strict_slashes = False + +db.init_app(app) + + +# TODO +# General + +# - Choose "place to guess" late before the race? Make a page for this +# - Rules page + +# - Make user order changeable using drag'n'drop? +# - Show place when entering race result (would require updating the drag'n'drop code...) +# - Show cards of previous race results, like with season guesses? +# - Make the season card grid left-aligned? So e.g. 2 cards are not spread over the whole screen with large gaps? + +# Statistics +# - Auto calculate points +# - Order user table by points + display points somewhere +# - Show current values for some season guesses (e.g. current most dnfs) +# - Generate static diagram using chart.js + templating the js (funny yikes) + + +@app.route("/") +def root() -> Response: + return redirect("/race/Everyone") + + +@app.route("/save/all") +def save() -> Response: + export_dynamic_data() + return redirect("/") + + +@app.route("/load/all") +def load() -> Response: + reload_static_data() + reload_dynamic_data() + return redirect("/") + + +@app.route("/load/static") +def load_static() -> Response: + reload_static_data() + return redirect("/") + + +@app.route("/load/dynamic") +def load_dynamic() -> Response: + reload_dynamic_data() + return redirect("/") + + +@app.route("/race") +def race_root() -> Response: + return redirect("/race/Everyone") + + +@app.route("/race/") +def race_active_user(user_name: str) -> str: + user_name = unquote(user_name) + model = TemplateModel() + return render_template("race.jinja", + active_user=model.user_by(user_name=user_name, ignore=["Everyone"]), + model=model) + + +@app.route("/race-guess//", methods=["POST"]) +def race_guess_post(race_name: str, user_name: str) -> Response: + race_name = unquote(race_name) + user_name = unquote(user_name) + + pxx: str | None = request.form.get("pxxselect") + dnf: str | None = request.form.get("dnfselect") + + return update_race_guess(race_name, user_name, pxx, dnf) + + +@app.route("/race-guess-delete//", methods=["POST"]) +def race_guess_delete_post(race_name: str, user_name: str) -> Response: + race_name = unquote(race_name) + user_name = unquote(user_name) + + return delete_race_guess(race_name, user_name) + + +@app.route("/season") +def season_root() -> Response: + return redirect("/season/Everyone") + + +@app.route("/season/") +def season_active_user(user_name: str) -> str: + user_name = unquote(user_name) + model = TemplateModel() + return render_template("season.jinja", + active_user=model.user_by(user_name=user_name, ignore=["Everyone"]), + model=model) + + +@app.route("/season-guess/", methods=["POST"]) +def season_guess_post(user_name: str) -> Response: + user_name = unquote(user_name) + guesses: List[str | None] = [ + request.form.get("hottakeselect"), + request.form.get("p2select"), + request.form.get("overtakeselect"), + request.form.get("dnfselect"), + request.form.get("gainedselect"), + request.form.get("lostselect") + ] + team_winner_guesses: List[str | None] = [ + request.form.get(f"teamwinner-{team.name}") for team in db.session.query(Team).all() + ] + podium_driver_guesses: List[str] = request.form.getlist("podiumdrivers") + + return update_season_guess(user_name, guesses, team_winner_guesses, podium_driver_guesses) + + +@app.route("/result") +def result_root() -> Response: + return redirect("/result/Current") + + +@app.route("/result/") +def result_active_race(race_name: str) -> str: + race_name = unquote(race_name) + model = TemplateModel() + return render_template("enter.jinja", + active_result=model.race_result_by(race_name=race_name), + model=model) + + +@app.route("/result-enter/", methods=["POST"]) +def result_enter_post(race_name: str) -> Response: + race_name = unquote(race_name) + pxxs: List[str] = request.form.getlist("pxx-drivers") + first_dnfs: List[str] = request.form.getlist("first-dnf-drivers") + dnfs: List[str] = request.form.getlist("dnf-drivers") + excluded: List[str] = request.form.getlist("excluded-drivers") + + return update_race_result(race_name, pxxs, first_dnfs, dnfs, excluded) + + +@app.route("/user") +def user_root() -> str: + model = TemplateModel() + return render_template("users.jinja", + model=model) + + +@app.route("/user-add", methods=["POST"]) +def user_add_post() -> Response: + username: str | None = request.form.get("select-add-user") + return update_user(username, add=True) + + +@app.route("/user-delete", methods=["POST"]) +def user_delete_post() -> Response: + username: str | None = request.form.get("select-delete-user") + return update_user(username, delete=True) \ No newline at end of file diff --git a/template_model.py b/app/frontend/template_model.py similarity index 97% rename from template_model.py rename to app/frontend/template_model.py index c3d7784..7af7798 100644 --- a/template_model.py +++ b/app/frontend/template_model.py @@ -1,7 +1,7 @@ from typing import List, Callable, Dict, overload from sqlalchemy import desc -from model import User, RaceResult, RaceGuess, Race, Driver, Team, SeasonGuess, db -from validation_utils import find_first_or_none, find_multiple, find_single, find_single_or_none +from app.database.model import User, RaceResult, RaceGuess, Race, Driver, Team, SeasonGuess, db +from app.database.validation_utils import find_first_or_none, find_multiple, find_single, find_single_or_none # This could also be moved to database_utils (at least partially), but I though the template should cache the database responses diff --git a/app/logic/points_model.py b/app/logic/points_model.py new file mode 100644 index 0000000..e69de29 diff --git a/formula10.py b/formula10.py index 4df3a43..aff3a35 100644 --- a/formula10.py +++ b/formula10.py @@ -1,179 +1,4 @@ -from typing import List -from urllib.parse import unquote -from flask import Flask, render_template, request, redirect -from werkzeug import Response -from model import Team, db -from file_utils import reload_static_data, reload_dynamic_data, export_dynamic_data -from template_model import TemplateModel -from backend_model import delete_race_guess, update_race_guess, update_race_result, update_season_guess, update_user - -app = Flask(__name__) - -app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:///formula10.db" -app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False -app.url_map.strict_slashes = False - -db.init_app(app) - - -# TODO -# General - -# - Choose "place to guess" late before the race? Make a page for this -# - Make user order changeable using drag'n'drop? - -# - Show place when entering race result (would require updating the drag'n'drop code...) -# - Show cards of previous race results, like with season guesses? -# - Make the season card grid left-aligned? So e.g. 2 cards are not spread over the whole screen with large gaps? - -# Statistics -# - Auto calculate points -# - Order user table by points + display points somewhere -# - Show current values for some season guesses (e.g. current most dnfs) -# - Generate static diagram using chart.js + templating the js (funny yikes) - -# Rules page - - -@app.route("/") -def root() -> Response: - return redirect("/race/Everyone") - - -@app.route("/save/all") -def save() -> Response: - export_dynamic_data() - return redirect("/") - - -@app.route("/load/all") -def load() -> Response: - reload_static_data() - reload_dynamic_data() - return redirect("/") - - -@app.route("/load/static") -def load_static() -> Response: - reload_static_data() - return redirect("/") - - -@app.route("/load/dynamic") -def load_dynamic() -> Response: - reload_dynamic_data() - return redirect("/") - - -@app.route("/race") -def race_root() -> Response: - return redirect("/race/Everyone") - - -@app.route("/race/") -def race_active_user(user_name: str) -> str: - user_name = unquote(user_name) - model = TemplateModel() - return render_template("race.jinja", - active_user=model.user_by(user_name=user_name, ignore=["Everyone"]), - model=model) - - -@app.route("/race-guess//", methods=["POST"]) -def race_guess_post(race_name: str, user_name: str) -> Response: - race_name = unquote(race_name) - user_name = unquote(user_name) - - pxx: str | None = request.form.get("pxxselect") - dnf: str | None = request.form.get("dnfselect") - - return update_race_guess(race_name, user_name, pxx, dnf) - - -@app.route("/race-guess-delete//", methods=["POST"]) -def race_guess_delete_post(race_name: str, user_name: str) -> Response: - race_name = unquote(race_name) - user_name = unquote(user_name) - - return delete_race_guess(race_name, user_name) - - -@app.route("/season") -def season_root() -> Response: - return redirect("/season/Everyone") - - -@app.route("/season/") -def season_active_user(user_name: str) -> str: - user_name = unquote(user_name) - model = TemplateModel() - return render_template("season.jinja", - active_user=model.user_by(user_name=user_name, ignore=["Everyone"]), - model=model) - - -@app.route("/season-guess/", methods=["POST"]) -def season_guess_post(user_name: str) -> Response: - user_name = unquote(user_name) - guesses: List[str | None] = [ - request.form.get("hottakeselect"), - request.form.get("p2select"), - request.form.get("overtakeselect"), - request.form.get("dnfselect"), - request.form.get("gainedselect"), - request.form.get("lostselect") - ] - team_winner_guesses: List[str | None] = [ - request.form.get(f"teamwinner-{team.name}") for team in db.session.query(Team).all() - ] - podium_driver_guesses: List[str] = request.form.getlist("podiumdrivers") - - return update_season_guess(user_name, guesses, team_winner_guesses, podium_driver_guesses) - - -@app.route("/result") -def result_root() -> Response: - return redirect("/result/Current") - - -@app.route("/result/") -def result_active_race(race_name: str) -> str: - race_name = unquote(race_name) - model = TemplateModel() - return render_template("enter.jinja", - active_result=model.race_result_by(race_name=race_name), - model=model) - - -@app.route("/result-enter/", methods=["POST"]) -def result_enter_post(race_name: str) -> Response: - race_name = unquote(race_name) - pxxs: List[str] = request.form.getlist("pxx-drivers") - first_dnfs: List[str] = request.form.getlist("first-dnf-drivers") - dnfs: List[str] = request.form.getlist("dnf-drivers") - excluded: List[str] = request.form.getlist("excluded-drivers") - - return update_race_result(race_name, pxxs, first_dnfs, dnfs, excluded) - - -@app.route("/user") -def user_root() -> str: - model = TemplateModel() - return render_template("users.jinja", - model=model) - - -@app.route("/user-add", methods=["POST"]) -def user_add_post() -> Response: - username: str | None = request.form.get("select-add-user") - return update_user(username, add=True) - - -@app.route("/user-delete", methods=["POST"]) -def user_delete_post() -> Response: - username: str | None = request.form.get("select-delete-user") - return update_user(username, delete=True) - +from app.frontend.controller import app if __name__ == "__main__": - app.run(debug=True, host="0.0.0.0") + app.run(debug=True, host="0.0.0.0") \ No newline at end of file