From 60a489cfb3fb7a12a06212c2c7ed3c05cd0757b5 Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Fri, 25 Oct 2024 18:03:18 +0200 Subject: Moved the files into a folder structure --- src/match_up/templates/controls.j2 | 56 +++++++++++++++++++++++++++++++ src/match_up/templates/games.j2 | 21 ++++++++++++ src/match_up/templates/index.j2 | 13 ++++++++ src/match_up/templates/macros.j2 | 67 ++++++++++++++++++++++++++++++++++++++ src/match_up/templates/players.j2 | 17 ++++++++++ 5 files changed, 174 insertions(+) create mode 100644 src/match_up/templates/controls.j2 create mode 100644 src/match_up/templates/games.j2 create mode 100644 src/match_up/templates/index.j2 create mode 100644 src/match_up/templates/macros.j2 create mode 100644 src/match_up/templates/players.j2 (limited to 'src/match_up/templates') diff --git a/src/match_up/templates/controls.j2 b/src/match_up/templates/controls.j2 new file mode 100644 index 0000000..6ba724a --- /dev/null +++ b/src/match_up/templates/controls.j2 @@ -0,0 +1,56 @@ +{% from "macros.j2" import court %} +
+
+ {% for key, value in courts.items() %} + {{ court(key, value) }} + {% endfor %} +
+
+
+
+
+
+ +
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+ +
+
\ No newline at end of file diff --git a/src/match_up/templates/games.j2 b/src/match_up/templates/games.j2 new file mode 100644 index 0000000..d5afef1 --- /dev/null +++ b/src/match_up/templates/games.j2 @@ -0,0 +1,21 @@ + + + + + + + + + + +{% for game in games %} + + + +{% endfor %} + +
{{ title }}
Court Team 1 Team 2
{{ game.court }}{{ game.team1.player1 }} + {{ game.team1.player2 }} + {{ game.team2.player1 }} + {{ game.team2.player2 }} +
\ No newline at end of file diff --git a/src/match_up/templates/index.j2 b/src/match_up/templates/index.j2 new file mode 100644 index 0000000..832dbfb --- /dev/null +++ b/src/match_up/templates/index.j2 @@ -0,0 +1,13 @@ +{% from "macros.j2" import header, footer, navbar %} + + + {{ header() }} + +
+ {{ navbar() }} +
+
+ {{ footer() }} +
+ + diff --git a/src/match_up/templates/macros.j2 b/src/match_up/templates/macros.j2 new file mode 100644 index 0000000..8d05bb0 --- /dev/null +++ b/src/match_up/templates/macros.j2 @@ -0,0 +1,67 @@ +{% macro court(id, status) -%} + +{%- endmacro %} + +{% macro player(id, status, name) -%} + +{%- endmacro %} + +{% macro guests(count) -%} +
+ +
+{%- endmacro %} + +{% macro timer(mins, secs) -%} +
+

⏲️ {{mins}}:{{secs}}

+
+{%- endmacro %} + +{% macro header() -%} + + + + Match Up! + + + + + +{%- endmacro %} + +{% macro footer() -%} +
Made by Karan using Flask, HTMX and Bulma
+{%- endmacro %} + +{% macro navbar() -%} + +{%- endmacro %} \ No newline at end of file diff --git a/src/match_up/templates/players.j2 b/src/match_up/templates/players.j2 new file mode 100644 index 0000000..ae6afe0 --- /dev/null +++ b/src/match_up/templates/players.j2 @@ -0,0 +1,17 @@ +{% from "macros.j2" import header, navbar, footer, player %} + + + {{ header() }} + +
+ {{ navbar() }} +
+
+ {% for id, value in players.items() %} +
{{ player(id, value.status, value.name) }}
+ {% endfor %} +
+ {{ footer() }} +
+ + \ No newline at end of file -- cgit v1.3.1 From 5b01707a04c362ced66428bef8e0234176d212d9 Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Fri, 8 Nov 2024 17:30:19 +0100 Subject: Added the changes to the templates to pass the url_for function --- pyproject.toml | 4 ++-- src/match_up/__init__.py | 20 +++++++++++++------- src/match_up/templates/index.j2 | 4 ++-- src/match_up/templates/macros.j2 | 4 ++-- src/match_up/templates/players.j2 | 4 ++-- 5 files changed, 21 insertions(+), 15 deletions(-) (limited to 'src/match_up/templates') diff --git a/pyproject.toml b/pyproject.toml index 9ef21d8..9e8a793 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,8 +26,8 @@ Issues = "https://gitlab.com/KaranJayachandra/match_up/-/issues" where = ["src"] [tool.setuptools.package-data] -"*" = ["*.txt"] -mypkg1 = ["data1.rst"] +"templates" = ["*.j2"] +"static" = ["*.css", "*.js", "*.png", "*.ico"] [project.scripts] match_up = "match_up:main" \ No newline at end of file diff --git a/src/match_up/__init__.py b/src/match_up/__init__.py index badf3f0..3a78b8d 100644 --- a/src/match_up/__init__.py +++ b/src/match_up/__init__.py @@ -1,19 +1,24 @@ from dotenv import load_dotenv +from importlib.resources import path from match_up.controller import RoundGenerator from match_up.model import CourtList, PlayerList -from jinja2 import FileSystemLoader, Environment -from flask import Flask, render_template, request - +from jinja2 import FileSystemLoader, Environment, PackageLoader +from flask import Flask, render_template, request, url_for load_dotenv() -app = Flask("Match Up! Backend") +with path("match_up", "static") as p: + static_folder = p +with path("match_up", "templates") as p: + template_folder = p +app = Flask("Match Up! Backend", static_folder=static_folder, template_folder=template_folder) rg = RoundGenerator(courts=CourtList(), players=PlayerList()) -re = Environment(loader=FileSystemLoader("templates")) +re = Environment(loader=PackageLoader("match_up")) @app.route("/", methods=["GET"]) def home(): - return render_template("index.j2") + template = re.get_template("index.j2").render(url_for=url_for) + return template @app.route("/time", methods=["GET"]) @@ -74,7 +79,8 @@ def toggle_player(player_request): @app.route("/players", methods=["GET"]) def get_list_of_players(): - return render_template("players.j2", players=rg.players.get_players()) + template = re.get_template("players.j2").render(players=rg.players.get_players(), url_for=url_for) + return template @app.route("/court-toggle/", methods=["POST"]) diff --git a/src/match_up/templates/index.j2 b/src/match_up/templates/index.j2 index 832dbfb..ce0d531 100644 --- a/src/match_up/templates/index.j2 +++ b/src/match_up/templates/index.j2 @@ -1,10 +1,10 @@ {% from "macros.j2" import header, footer, navbar %} - {{ header() }} + {{ header(url_for) }}
- {{ navbar() }} + {{ navbar(url_for) }}
{{ footer() }} diff --git a/src/match_up/templates/macros.j2 b/src/match_up/templates/macros.j2 index 8d05bb0..dedbf48 100644 --- a/src/match_up/templates/macros.j2 +++ b/src/match_up/templates/macros.j2 @@ -22,7 +22,7 @@ {%- endmacro %} -{% macro header() -%} +{% macro header(url_for) -%} @@ -38,7 +38,7 @@
Made by Karan using Flask, HTMX and Bulma
{%- endmacro %} -{% macro navbar() -%} +{% macro navbar(url_for) -%} {%- endmacro %} \ No newline at end of file -- cgit v1.3.1 From 100670e1d0b374e3c495407e55f4660cba62a0f6 Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Fri, 15 Nov 2024 12:09:54 +0100 Subject: Downloaded some dependencies --- src/match_up/static/notyf.min.css | 1 + src/match_up/static/notyf.min.js | 1 + src/match_up/templates/macros.j2 | 6 +++--- 3 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 src/match_up/static/notyf.min.css create mode 100644 src/match_up/static/notyf.min.js (limited to 'src/match_up/templates') diff --git a/src/match_up/static/notyf.min.css b/src/match_up/static/notyf.min.css new file mode 100644 index 0000000..dbb5a16 --- /dev/null +++ b/src/match_up/static/notyf.min.css @@ -0,0 +1 @@ +@-webkit-keyframes notyf-fadeinup{0%{opacity:0;transform:translateY(25%)}to{opacity:1;transform:translateY(0)}}@keyframes notyf-fadeinup{0%{opacity:0;transform:translateY(25%)}to{opacity:1;transform:translateY(0)}}@-webkit-keyframes notyf-fadeinleft{0%{opacity:0;transform:translateX(25%)}to{opacity:1;transform:translateX(0)}}@keyframes notyf-fadeinleft{0%{opacity:0;transform:translateX(25%)}to{opacity:1;transform:translateX(0)}}@-webkit-keyframes notyf-fadeoutright{0%{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(25%)}}@keyframes notyf-fadeoutright{0%{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(25%)}}@-webkit-keyframes notyf-fadeoutdown{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(25%)}}@keyframes notyf-fadeoutdown{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(25%)}}@-webkit-keyframes ripple{0%{transform:scale(0) translateY(-45%) translateX(13%)}to{transform:scale(1) translateY(-45%) translateX(13%)}}@keyframes ripple{0%{transform:scale(0) translateY(-45%) translateX(13%)}to{transform:scale(1) translateY(-45%) translateX(13%)}}.notyf{position:fixed;top:0;left:0;height:100%;width:100%;color:#fff;z-index:9999;display:flex;flex-direction:column;align-items:flex-end;justify-content:flex-end;pointer-events:none;box-sizing:border-box;padding:20px}.notyf__icon--error,.notyf__icon--success{height:21px;width:21px;background:#fff;border-radius:50%;display:block;margin:0 auto;position:relative}.notyf__icon--error:after,.notyf__icon--error:before{content:"";background:currentColor;display:block;position:absolute;width:3px;border-radius:3px;left:9px;height:12px;top:5px}.notyf__icon--error:after{transform:rotate(-45deg)}.notyf__icon--error:before{transform:rotate(45deg)}.notyf__icon--success:after,.notyf__icon--success:before{content:"";background:currentColor;display:block;position:absolute;width:3px;border-radius:3px}.notyf__icon--success:after{height:6px;transform:rotate(-45deg);top:9px;left:6px}.notyf__icon--success:before{height:11px;transform:rotate(45deg);top:5px;left:10px}.notyf__toast{display:block;overflow:hidden;pointer-events:auto;-webkit-animation:notyf-fadeinup .3s ease-in forwards;animation:notyf-fadeinup .3s ease-in forwards;box-shadow:0 3px 7px 0 rgba(0,0,0,.25);position:relative;padding:0 15px;border-radius:2px;max-width:300px;transform:translateY(25%);box-sizing:border-box;flex-shrink:0}.notyf__toast--disappear{transform:translateY(0);-webkit-animation:notyf-fadeoutdown .3s forwards;animation:notyf-fadeoutdown .3s forwards;-webkit-animation-delay:.25s;animation-delay:.25s}.notyf__toast--disappear .notyf__icon,.notyf__toast--disappear .notyf__message{-webkit-animation:notyf-fadeoutdown .3s forwards;animation:notyf-fadeoutdown .3s forwards;opacity:1;transform:translateY(0)}.notyf__toast--disappear .notyf__dismiss{-webkit-animation:notyf-fadeoutright .3s forwards;animation:notyf-fadeoutright .3s forwards;opacity:1;transform:translateX(0)}.notyf__toast--disappear .notyf__message{-webkit-animation-delay:.05s;animation-delay:.05s}.notyf__toast--upper{margin-bottom:20px}.notyf__toast--lower{margin-top:20px}.notyf__toast--dismissible .notyf__wrapper{padding-right:30px}.notyf__ripple{height:400px;width:400px;position:absolute;transform-origin:bottom right;right:0;top:0;border-radius:50%;transform:scale(0) translateY(-51%) translateX(13%);z-index:5;-webkit-animation:ripple .4s ease-out forwards;animation:ripple .4s ease-out forwards}.notyf__wrapper{display:flex;align-items:center;padding-top:17px;padding-bottom:17px;padding-right:15px;border-radius:3px;position:relative;z-index:10}.notyf__icon{width:22px;text-align:center;font-size:1.3em;opacity:0;-webkit-animation:notyf-fadeinup .3s forwards;animation:notyf-fadeinup .3s forwards;-webkit-animation-delay:.3s;animation-delay:.3s;margin-right:13px}.notyf__dismiss{position:absolute;top:0;right:0;height:100%;width:26px;margin-right:-15px;-webkit-animation:notyf-fadeinleft .3s forwards;animation:notyf-fadeinleft .3s forwards;-webkit-animation-delay:.35s;animation-delay:.35s;opacity:0}.notyf__dismiss-btn{background-color:rgba(0,0,0,.25);border:none;cursor:pointer;transition:opacity .2s ease,background-color .2s ease;outline:none;opacity:.35;height:100%;width:100%}.notyf__dismiss-btn:after,.notyf__dismiss-btn:before{content:"";background:#fff;height:12px;width:2px;border-radius:3px;position:absolute;left:calc(50% - 1px);top:calc(50% - 5px)}.notyf__dismiss-btn:after{transform:rotate(-45deg)}.notyf__dismiss-btn:before{transform:rotate(45deg)}.notyf__dismiss-btn:hover{opacity:.7;background-color:rgba(0,0,0,.15)}.notyf__dismiss-btn:active{opacity:.8}.notyf__message{vertical-align:middle;position:relative;opacity:0;-webkit-animation:notyf-fadeinup .3s forwards;animation:notyf-fadeinup .3s forwards;-webkit-animation-delay:.25s;animation-delay:.25s;line-height:1.5em}@media only screen and (max-width:480px){.notyf{padding:0}.notyf__ripple{height:600px;width:600px;-webkit-animation-duration:.5s;animation-duration:.5s}.notyf__toast{max-width:none;border-radius:0;box-shadow:0 -2px 7px 0 rgba(0,0,0,.13);width:100%}.notyf__dismiss{width:56px}} \ No newline at end of file diff --git a/src/match_up/static/notyf.min.js b/src/match_up/static/notyf.min.js new file mode 100644 index 0000000..f85c84f --- /dev/null +++ b/src/match_up/static/notyf.min.js @@ -0,0 +1 @@ +var Notyf=function(){"use strict";var e,o=function(){return(o=Object.assign||function(t){for(var i,e=1,n=arguments.length;eMatch Up! - + - + + - {%- endmacro %} -- cgit v1.3.1 From ffaca550cbbf89942fbf5b82ad911d75da1c3c51 Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Fri, 15 Nov 2024 12:42:34 +0100 Subject: Cleaned up the layout of the timer --- src/match_up/templates/macros.j2 | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'src/match_up/templates') diff --git a/src/match_up/templates/macros.j2 b/src/match_up/templates/macros.j2 index c6e6d1d..2a8ad1e 100644 --- a/src/match_up/templates/macros.j2 +++ b/src/match_up/templates/macros.j2 @@ -38,6 +38,15 @@ {% macro navbar(url_for) -%} {%- endmacro %} \ No newline at end of file -- cgit v1.3.1 From 6ca047f36fb688cdb72f0170e0fb3aaf3701ff95 Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Fri, 15 Nov 2024 14:55:05 +0100 Subject: Added multiple levels for the guests --- src/match_up/model.py | 35 ++++++++++++++----- src/match_up/routes.py | 72 ++++++++++++--------------------------- src/match_up/templates/macros.j2 | 22 ++++++++++-- src/match_up/templates/players.j2 | 68 +++++++----------------------------- src/match_up/utilities.py | 1 - 5 files changed, 78 insertions(+), 120 deletions(-) (limited to 'src/match_up/templates') diff --git a/src/match_up/model.py b/src/match_up/model.py index b1e77f5..9b2dce7 100644 --- a/src/match_up/model.py +++ b/src/match_up/model.py @@ -10,7 +10,6 @@ from names import get_first_name, get_last_name from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import create_engine, Column, String, Integer, not_ - MIN_LEVEL = 1 MAX_LEVEL = 10 DEFAULT_COURTS = 12 @@ -104,8 +103,21 @@ class CourtList: return active_courts, inactive_courts +def _textualize_guest(level, value) -> str: + match level: + case 1: + text = "Beginners" + case 3: + text = "Novices" + case 5: + text = "Intermediates" + if value == 1: + text = text[:-1] + return f"{value} {text}" + + class PlayerList: - guests: dict = {"low": 0, "mid": 0, "high": 0} + guests: dict = {1: 0, 3: 0, 5: 0} def __init__(self, csv_location: str = None) -> None: self._init_database() @@ -122,15 +134,15 @@ class PlayerList: Base.metadata.create_all(bind=engine) self.session = sessionmaker(bind=engine)() - def increment_guests(self, level: str): + def increment_guests(self, level: int): self.guests[level] = self.guests[level] + 1 - return self.guests[level] + return _textualize_guest(level, self.guests[level]) - def decrement_guests(self, level: str): + def decrement_guests(self, level: int): if self.guests[level] == 0: return 0 self.guests[level] = self.guests[level] - 1 - return self.guests[level] + return _textualize_guest(level, self.guests[level]) def get_player_status(self, id: int) -> bool: return self.session.query(Player).filter(Player.id == id).first().status @@ -171,7 +183,7 @@ class PlayerList: status=True, count=rounds, ) - for i in range(1, self.guests["low"] + 1) + for i in range(1, self.guests[1] + 1) ] mid_guests = [ Player( @@ -182,7 +194,7 @@ class PlayerList: status=True, count=rounds, ) - for i in range(1, self.guests["mid"] + 1) + for i in range(1, self.guests[3] + 1) ] high_guests = [ Player( @@ -193,10 +205,15 @@ class PlayerList: status=True, count=rounds, ) - for i in range(1, self.guests["high"] + 1) + for i in range(1, self.guests[5] + 1) ] return active_players + low_guests + mid_guests + high_guests + def get_guests(self): + return { + level: _textualize_guest(level, self.guests[level]) for level in self.guests + } + def increment_game_count(self, player_ids: list[int]): players = self.session.query(Player).filter(Player.id.in_(player_ids)) for player in players: diff --git a/src/match_up/routes.py b/src/match_up/routes.py index 57e9812..14b36d2 100644 --- a/src/match_up/routes.py +++ b/src/match_up/routes.py @@ -15,58 +15,25 @@ def get_controls(): return render_template("controls.j2", courts=rg.courts.get_courts()) -@app.route("/low_guests", methods=["GET"]) -def get_low_guests(): - t = re.from_string('{% from "macros.j2" import guests %}{{guests(count, level)}}') - return render_template(t, count=rg.players.guests["low"], level="low") - - -@app.route("/mid_guests", methods=["GET"]) -def get_mid_guests(): - t = re.from_string('{% from "macros.j2" import guests %}{{guests(count, level)}}') - return render_template(t, count=rg.players.guests["mid"], level="mid") - - -@app.route("/high_guests", methods=["GET"]) -def get_high_guests(): - t = re.from_string('{% from "macros.j2" import guests %}{{guests(count, level)}}') - return render_template(t, count=rg.players.guests["high"], level="high") - - -@app.route("/increment_low_guest", methods=["POST"]) -def increment_low_guests(): - t = re.from_string('{% from "macros.j2" import guests %}{{guests(count)}}') - return render_template(t, count=rg.players.increment_guests("low")) - - -@app.route("/decrement_low_guest", methods=["POST"]) -def decrement_low_guests(): - t = re.from_string('{% from "macros.j2" import guests %}{{guests(count)}}') - return render_template(t, count=rg.players.decrement_guests("low")) - - -@app.route("/increment_mid_guest", methods=["POST"]) -def increment_mid_guests(): - t = re.from_string('{% from "macros.j2" import guests %}{{guests(count)}}') - return render_template(t, count=rg.players.increment_guests("mid")) - - -@app.route("/decrement_mid_guest", methods=["POST"]) -def decrement_mid_guests(): - t = re.from_string('{% from "macros.j2" import guests %}{{guests(count)}}') - return render_template(t, count=rg.players.decrement_guests("mid")) - - -@app.route("/increment_high_guest", methods=["POST"]) -def increment_high_guests(): - t = re.from_string('{% from "macros.j2" import guests %}{{guests(count)}}') - return render_template(t, count=rg.players.increment_guests("high")) +@app.route("/increment_guest", methods=["POST"]) +def increment_guests(): + level = int(request.form["level"].split()[0]) + t = re.from_string( + '{% from "macros.j2" import guest_control %}{{guest_control(level, value)}}' + ) + value = rg.players.increment_guests(level) + return render_template(t, level=level, value=value) -@app.route("/decrement_high_guest", methods=["POST"]) -def decrement_high_guests(): - t = re.from_string('{% from "macros.j2" import guests %}{{guests(count)}}') - return render_template(t, count=rg.players.decrement_guests("high")) +@app.route("/decrement_guest", methods=["POST"]) +def decrement_guests(): + print(request.form["level"]) + level = int(request.form["level"]) + t = re.from_string( + '{% from "macros.j2" import guest_control %}{{guest_control(level, value)}}' + ) + value = rg.players.decrement_guests(level) + return render_template(t, level=level, value=value) @app.route("/propose", methods=["POST"]) @@ -98,7 +65,10 @@ def toggle_player(player_request): @app.route("/players", methods=["GET"]) def get_list_of_players(): all_players = rg.players.get_all_players() - return re.get_template("players.j2").render(players=all_players, url_for=url_for) + guests = rg.players.get_guests() + return re.get_template("players.j2").render( + players=all_players, guests=guests, url_for=url_for + ) @app.route("/reset_players", methods=["POST"]) diff --git a/src/match_up/templates/macros.j2 b/src/match_up/templates/macros.j2 index 2a8ad1e..83fc823 100644 --- a/src/match_up/templates/macros.j2 +++ b/src/match_up/templates/macros.j2 @@ -10,9 +10,25 @@ hx-swap="outerHTML"> {{ player.first + " " + player.last}} {%- endmacro %} -{% macro guests(count, level) -%} -
- +{% macro guest_control(level, value) -%} +
+
+ +
+
+ +
+
+ +
{%- endmacro %} diff --git a/src/match_up/templates/players.j2 b/src/match_up/templates/players.j2 index f25889c..46ff172 100644 --- a/src/match_up/templates/players.j2 +++ b/src/match_up/templates/players.j2 @@ -1,72 +1,28 @@ -{% from "macros.j2" import header, navbar, footer, player_button %} +{% from "macros.j2" import header, navbar, footer, player_button, guest_control %} {{ header(url_for) }}
{{ navbar(url_for) }} +

Players

{% for player in players %}
{{ player_button(player) }}
{% endfor %}
+

Guests

-
-
-
- -
-
-
- -
-
-
-
-
-
- -
-
-
- -
-
-
-
-
-
- -
-
-
- -
-
-
-
- -
+ {% for level in guests %} +
{{ guest_control(level, guests[level]) }}
+ {% endfor %} +
+
+
+
+
{{ footer() }}
diff --git a/src/match_up/utilities.py b/src/match_up/utilities.py index 8aadfd2..56fb78e 100644 --- a/src/match_up/utilities.py +++ b/src/match_up/utilities.py @@ -1,7 +1,6 @@ from time import time from typing import Tuple from random import shuffle -from dataclasses import dataclass SECS_IN_MIN = 60 -- cgit v1.3.1 From 765fe232ac9fded73387d5cb9ba9b0a07f05b4e4 Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Fri, 15 Nov 2024 16:54:29 +0100 Subject: Added the history and reset functionality --- src/match_up/__init__.py | 12 +-- src/match_up/controller.py | 112 ++++++-------------- src/match_up/data.py | 70 +++++++++++++ src/match_up/model.py | 202 +++++++------------------------------ src/match_up/routes.py | 55 +++++----- src/match_up/templates/controls.j2 | 5 + src/match_up/utilities.py | 136 +++++++++++++++++++++++-- 7 files changed, 307 insertions(+), 285 deletions(-) create mode 100644 src/match_up/data.py (limited to 'src/match_up/templates') diff --git a/src/match_up/__init__.py b/src/match_up/__init__.py index e6c472a..bccd0e7 100644 --- a/src/match_up/__init__.py +++ b/src/match_up/__init__.py @@ -3,17 +3,16 @@ from waitress import serve from argparse import ArgumentParser from importlib.resources import path from match_up.controller import RoundGenerator -from match_up.model import CourtList, PlayerList with path("match_up", "static") as p: static_folder = p with path("match_up", "templates") as p: template_folder = p -app = Flask( +_app = Flask( "Match Up! Backend", static_folder=static_folder, template_folder=template_folder ) -rg = RoundGenerator() +_rg = RoundGenerator() from match_up import routes @@ -21,10 +20,7 @@ from match_up import routes def main(): parser = ArgumentParser() - parser.add_argument("-c", "--courts", help="Total courts", type=int, default=12) parser.add_argument("-d", "--database", help="CSV database", default=None) args = parser.parse_args() - global rg - rg.courts = CourtList(total=args.courts) - rg.players = PlayerList(csv_location=args.database) - serve(app, port=80) + _rg.update_player_list(csv_location=args.database) + serve(_app, port=80) diff --git a/src/match_up/controller.py b/src/match_up/controller.py index aa72b12..8e89ffd 100644 --- a/src/match_up/controller.py +++ b/src/match_up/controller.py @@ -1,78 +1,18 @@ from typing import Tuple from operator import attrgetter -from dataclasses import dataclass, field -from match_up.utilities import sample_list -from match_up.model import ( - Team, - Game, - Player, - DisplayPlayer, - PlayerList, - CourtList, - MAX_LEVEL, - PLAYER_PER_COURT, -) +from match_up.data import Game, PLAYER_PER_COURT +from match_up.model import Player, PlayerList, CourtList +from match_up.utilities import _create_placeholders, _select_players, _create_games -def _create_placeholders(courts: dict, name: str) -> list[Game]: - dummy_player = Player(first=name) - dummy_team = Team( - player1=DisplayPlayer(dummy_player), player2=DisplayPlayer(dummy_player) - ) - games = [Game(court, dummy_team, dummy_team) for court in courts] - return games - - -def _normalize_skill(level: int, new_max: int) -> int: - new_level = round((new_max - 1) * (level / MAX_LEVEL)) + 1 - return new_level - - -def _select_players(players: list[Player], count: int) -> dict: - identifiers = [player.id for player in players] - play_count = [player.count for player in players] - ids = sample_list(identifiers, play_count, count) - selected_players = [player for player in players if player.id in ids] - return selected_players - - -def _create_groups_of_four_players( - players: list[Player], new_max: int -) -> list[list[Player]]: - identifiers = [player.id for player in players] - player_levels = [_normalize_skill(player.level, new_max) for player in players] - identifiers = sample_list(identifiers, player_levels) - court_players = [] - for i in range(0, len(players), PLAYER_PER_COURT): - court_ids = identifiers[i : i + PLAYER_PER_COURT] - selected_players = [player for player in players if player.id in court_ids] - court_players.append(selected_players) - return court_players - - -def _create_games(courts: dict, players: list[Player], levels: int): - court_players = _create_groups_of_four_players(players, levels) - games = [] - for court, players in zip(courts, court_players): - team1 = Team( - player1=DisplayPlayer(players[0]), player2=DisplayPlayer(players[3]) - ) - team2 = Team( - player1=DisplayPlayer(players[1]), player2=DisplayPlayer(players[2]) - ) - games.append(Game(court, team1, team2)) - return games - - -@dataclass class RoundGenerator: - courts: CourtList = field(default_factory=lambda: CourtList()) - players: PlayerList = field(default_factory=lambda: PlayerList()) - round: int = 0 - proposal: tuple = tuple() + proposed_games: list[Game] = [] + proposed_players: list[Player] = [] - def __post_init__(self) -> None: - self.games = _create_placeholders(self.courts.get_courts(), "---") + def __init__(self) -> None: + self.courts = CourtList() + self.players = PlayerList() + self.reset() def _separate_courts(self, courts: dict) -> Tuple[dict, dict]: possible_games = min(len(courts), self.players.get_possible_game_count()) @@ -89,7 +29,9 @@ class RoundGenerator: games = reserved_games + blank_games return games - def _create_active_games(self, total_levels: int): + def _create_active_games( + self, total_levels: int + ) -> Tuple[list[Game], list[Player]]: active_courts, _ = self.courts.separate_courts() used_courts, _ = self._separate_courts(active_courts) required_player_count = len(used_courts) * PLAYER_PER_COURT @@ -98,28 +40,38 @@ class RoundGenerator: games = _create_games(used_courts, selected_players, total_levels) return games, selected_players - def _generate_proposal(self, total_levels: int) -> Tuple[list[Game], dict]: + def _generate_proposal(self, total_levels: int) -> Tuple[list[Game], list[Player]]: inactive_games = self._create_inactive_games() active_games, selected_players = self._create_active_games(total_levels) games = active_games + inactive_games games.sort(key=attrgetter("court")) return games, selected_players + def update_player_list(self, csv_location): + self.players = PlayerList(csv_location) + def clear(self) -> list[Game]: - self.proposal = tuple() + self.proposed_games = [] + self.proposed_players = [] return self.games def propose(self, levels: int) -> list[Game]: - self.proposal = self._generate_proposal(levels) - return self.proposal[0] + proposal = self._generate_proposal(levels) + self.proposed_games = proposal[0] + self.proposed_players = proposal[1] + return self.proposed_games def confirm(self) -> list[Game]: - proposed_games, proposed_players = self.proposal - if self.games == proposed_games or len(proposed_games) == 0: + if len(self.proposed_games) == 0: return self.games - self.games = proposed_games - identifiers = [player.id for player in proposed_players] + self.games = self.proposed_games + identifiers = [int(player.id) for player in self.proposed_players] self.players.increment_game_count(identifiers) - self.proposal = tuple() self.round += 1 - return self.games + return self.clear() + + def reset(self): + self.round = 0 + self.games = _create_placeholders(self.courts.get_courts(), "---") + self.players.reset_game_count() + return self.games diff --git a/src/match_up/data.py b/src/match_up/data.py new file mode 100644 index 0000000..62aa5d8 --- /dev/null +++ b/src/match_up/data.py @@ -0,0 +1,70 @@ +from dataclasses import dataclass +from sqlalchemy import Column, String, Integer +from sqlalchemy.ext.declarative import declarative_base + + +MIN_LEVEL = 1 +MAX_LEVEL = 10 +PLAYER_PER_COURT = 4 + +BASE = declarative_base() + + +class Player(BASE): + __tablename__ = "players" + id = Column("id", Integer, primary_key=True) + first = Column("first", String) + last = Column("last", String) + level = Column("level", Integer) + status = Column("status", Integer) + count = Column("count", Integer) + + def __init__( + self, + id: int, + first: str = "", + last: str = "", + level: int = 0, + status: bool = True, + count: int = 0, + ): + self.id = id + self.first = first + self.last = last + self.status = status + self.level = level + self.count = count + + def __repr__(self): + status = "active" if self.status else "inactive" + return f"{self.first} {self.last} (Level {self.level}) is {status} and has played {self.count} games" + + +class DisplayPlayer: + id: int + first: str + last: str + status: bool + + def __init__(self, player: Player): + self.id = int(player.id) + self.first = str(player.first) + self.last = str(player.last) + self.status = bool(player.status) + + def __repr__(self): + status = "active" if self.status else "inactive" + return f"{self.first} {self.last} is {status}" + + +@dataclass +class Team: + player1: DisplayPlayer + player2: DisplayPlayer + + +@dataclass +class Game: + court: int + team1: Team + team2: Team diff --git a/src/match_up/model.py b/src/match_up/model.py index 9b2dce7..abce73c 100644 --- a/src/match_up/model.py +++ b/src/match_up/model.py @@ -3,85 +3,25 @@ from typing import Tuple from os.path import join, isfile from dataclasses import dataclass from importlib.resources import path -from pandas import read_csv, DataFrame from sqlalchemy.orm import sessionmaker -from random import randint, getrandbits -from names import get_first_name, get_last_name -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy import create_engine, Column, String, Integer, not_ - -MIN_LEVEL = 1 -MAX_LEVEL = 10 -DEFAULT_COURTS = 12 -PLAYER_PER_COURT = 4 - - -Base = declarative_base() - - -class Player(Base): - __tablename__ = "players" - id = Column("id", Integer, primary_key=True) - first = Column("first", String) - last = Column("last", String) - level = Column("level", Integer) - status = Column("status", Integer) - count = Column("count", Integer) - - def __init__( - self, - id: int = None, - first: str = "", - last: str = "", - level: int = None, - status: bool = None, - count: int = None, - ): - self.id = id - self.first = first - self.last = last - self.status = status - self.level = level - self.count = count - - def __repr__(self): - status = "active" if self.status else "inactive" - return f"{self.first} {self.last} (Level {self.level}) is {status} and has played {self.count} games" - - -class DisplayPlayer: - id: int - first: str - last: str - status: bool - - def __init__(self, player: Player): - self.id = player.id - self.first = player.first - self.last = player.last - self.status = player.status - - def __repr__(self): - status = "active" if self.status else "inactive" - return f"{self.first} {self.last} is {status}" - - -@dataclass -class Team: - player1: DisplayPlayer - player2: DisplayPlayer - - -@dataclass -class Game: - court: int - team1: Team - team2: Team +from sqlalchemy import create_engine, not_ +from match_up.utilities import ( + generate_guests, + _init_database_from_csv, + _init_random_database, + _textualize_guest, +) +from match_up.data import ( + Player, + DisplayPlayer, + PLAYER_PER_COURT, + BASE, +) @dataclass class CourtList: - total: int = DEFAULT_COURTS + total: int = 12 def __post_init__(self) -> None: self.courts = {i + 1: True for i in range(self.total)} @@ -89,7 +29,7 @@ class CourtList: def get_court_status(self, court_number: int) -> bool: return self.courts[court_number] - def toggle_court_status(self, court_number: int) -> None: + def toggle_court_status(self, court_number: int) -> bool: new_status = not self.courts[court_number] self.courts[court_number] = new_status return new_status @@ -103,46 +43,36 @@ class CourtList: return active_courts, inactive_courts -def _textualize_guest(level, value) -> str: - match level: - case 1: - text = "Beginners" - case 3: - text = "Novices" - case 5: - text = "Intermediates" - if value == 1: - text = text[:-1] - return f"{value} {text}" - - class PlayerList: - guests: dict = {1: 0, 3: 0, 5: 0} + guest_count: dict = {1: 0, 3: 0, 5: 0} - def __init__(self, csv_location: str = None) -> None: + def __init__(self, csv_location=None) -> None: + with path("match_up") as p: + self.db_location = join(p, "players.db") + db_file_found = True + if not isfile(self.db_location): + db_file_found = False self._init_database() if csv_location is not None: - self._init_database_from_csv(read_csv(csv_location)) + _init_database_from_csv(self.session, csv_location) else: - if not isfile(self.db_location): - self._init_random_database() + if not db_file_found: + _init_random_database(self.session) def _init_database(self): - with path("match_up") as p: - self.db_location = join(p, "players.db") engine = create_engine("sqlite:///" + self.db_location, echo=True) - Base.metadata.create_all(bind=engine) + BASE.metadata.create_all(bind=engine) self.session = sessionmaker(bind=engine)() def increment_guests(self, level: int): - self.guests[level] = self.guests[level] + 1 - return _textualize_guest(level, self.guests[level]) + self.guest_count[level] = self.guest_count[level] + 1 + return _textualize_guest(level, self.guest_count[level]) def decrement_guests(self, level: int): - if self.guests[level] == 0: + if self.guest_count[level] == 0: return 0 - self.guests[level] = self.guests[level] - 1 - return _textualize_guest(level, self.guests[level]) + self.guest_count[level] = self.guest_count[level] - 1 + return _textualize_guest(level, self.guest_count[level]) def get_player_status(self, id: int) -> bool: return self.session.query(Player).filter(Player.id == id).first().status @@ -153,7 +83,7 @@ class PlayerList: self.session.commit() return DisplayPlayer(player) - def get_all_players(self) -> list[Player]: + def get_all_players(self) -> list[DisplayPlayer]: all_players = [ DisplayPlayer(p) for p in self.session.query(Player) @@ -167,51 +97,20 @@ class PlayerList: possible_games = floor(total_players / PLAYER_PER_COURT) return possible_games - def get_players(self, rounds: int = 0) -> list[Player]: + def get_players(self) -> list[Player]: active_players = ( self.session.query(Player) .order_by(Player.first, Player.last) .filter(not_(Player.status == 0)) .all() ) - low_guests = [ - Player( - id=1000 + i, - first=f"Beginner", - last=f"{i}", - level=1, - status=True, - count=rounds, - ) - for i in range(1, self.guests[1] + 1) - ] - mid_guests = [ - Player( - id=2000 + i, - first=f"Novice", - last=f"{i}", - level=4, - status=True, - count=rounds, - ) - for i in range(1, self.guests[3] + 1) - ] - high_guests = [ - Player( - id=3000 + i, - first=f"Intermediate", - last=f"{i}", - level=7, - status=True, - count=rounds, - ) - for i in range(1, self.guests[5] + 1) - ] - return active_players + low_guests + mid_guests + high_guests + guests = generate_guests(self.guest_count) + return active_players + guests def get_guests(self): return { - level: _textualize_guest(level, self.guests[level]) for level in self.guests + level: _textualize_guest(level, self.guest_count[level]) + for level in self.guest_count } def increment_game_count(self, player_ids: list[int]): @@ -231,30 +130,3 @@ class PlayerList: for player in players: player.status = 0 self.session.commit() - - def _init_database_from_csv(self, data: DataFrame) -> None: - self.session.query(Player).delete() - for _, entry in data.iterrows(): - player = Player( - id=entry["id"], - first=entry["first"], - last=entry["last"], - level=entry["skill"], - status=False, - count=0, - ) - self.session.add(player) - self.session.commit() - - def _init_random_database(self, player_count: int = 80) -> None: - for i in range(player_count): - player = Player( - id=i, - first=get_first_name(), - last=get_last_name(), - level=randint(MIN_LEVEL, MAX_LEVEL), - status=getrandbits(1), - count=0, - ) - self.session.add(player) - self.session.commit() diff --git a/src/match_up/routes.py b/src/match_up/routes.py index 14b36d2..c0e77aa 100644 --- a/src/match_up/routes.py +++ b/src/match_up/routes.py @@ -1,94 +1,99 @@ -from match_up import app, rg +from match_up import _app, _rg from jinja2 import Environment, PackageLoader from flask import render_template, request, url_for, Response re = Environment(loader=PackageLoader("match_up")) -@app.route("/", methods=["GET"]) +@_app.route("/", methods=["GET"]) def home(): return re.get_template("index.j2").render(url_for=url_for) -@app.route("/control", methods=["GET"]) +@_app.route("/control", methods=["GET"]) def get_controls(): - return render_template("controls.j2", courts=rg.courts.get_courts()) + return render_template("controls.j2", courts=_rg.courts.get_courts()) -@app.route("/increment_guest", methods=["POST"]) +@_app.route("/increment_guest", methods=["POST"]) def increment_guests(): level = int(request.form["level"].split()[0]) t = re.from_string( '{% from "macros.j2" import guest_control %}{{guest_control(level, value)}}' ) - value = rg.players.increment_guests(level) + value = _rg.players.increment_guests(level) return render_template(t, level=level, value=value) -@app.route("/decrement_guest", methods=["POST"]) +@_app.route("/decrement_guest", methods=["POST"]) def decrement_guests(): print(request.form["level"]) level = int(request.form["level"]) t = re.from_string( '{% from "macros.j2" import guest_control %}{{guest_control(level, value)}}' ) - value = rg.players.decrement_guests(level) + value = _rg.players.decrement_guests(level) return render_template(t, level=level, value=value) -@app.route("/propose", methods=["POST"]) +@_app.route("/propose", methods=["POST"]) def get_random_games(): levels = int(request.form["type"].split()[0]) - return render_template("games.j2", games=rg.propose(levels), title="Proposal") + return render_template("games.j2", games=_rg.propose(levels), title="Proposal") -@app.route("/confirm", methods=["POST"]) +@_app.route("/confirm", methods=["POST"]) def confirm_games(): - return render_template("games.j2", games=rg.confirm(), title=f"Round: {rg.round}") + return render_template("games.j2", games=_rg.confirm(), title=f"Round: {_rg.round}") -@app.route("/clear", methods=["POST"]) +@_app.route("/clear", methods=["POST"]) def clear_games(): - return render_template("games.j2", games=rg.clear(), title=f"Round: {rg.round}") + return render_template("games.j2", games=_rg.clear(), title=f"Round: {_rg.round}") -@app.route("/player-toggle/", methods=["POST"]) +@_app.route("/reset", methods=["POST"]) +def reset_games(): + return render_template("games.j2", games=_rg.reset(), title=f"Round: {_rg.round}") + + +@_app.route("/player-toggle/", methods=["POST"]) def toggle_player(player_request): id = int(player_request) - player = rg.players.toggle_player_status(id) + player = _rg.players.toggle_player_status(id) t = re.from_string( '{% from "macros.j2" import player_button %}{{player_button(player)}}' ) return render_template(t, player=player) -@app.route("/players", methods=["GET"]) +@_app.route("/players", methods=["GET"]) def get_list_of_players(): - all_players = rg.players.get_all_players() - guests = rg.players.get_guests() + all_players = _rg.players.get_all_players() + guests = _rg.players.get_guests() return re.get_template("players.j2").render( players=all_players, guests=guests, url_for=url_for ) -@app.route("/reset_players", methods=["POST"]) +@_app.route("/reset_players", methods=["POST"]) def reset_all_players(): - rg.players.reset_all_players() + _rg.players.reset_all_players() response = Response("Players reset!") response.headers["HX-Refresh"] = "true" return response -@app.route("/court-toggle/", methods=["POST"]) +@_app.route("/court-toggle/", methods=["POST"]) def toggle_court(court_number): id = int(court_number) - status = rg.courts.toggle_court_status(id) + status = _rg.courts.toggle_court_status(id) t = re.from_string( '{% from "macros.j2" import court_button %}{{ court_button(id, status) }}' ) return render_template(t, id=id, status=status) -@app.route("/courts", methods=["GET"]) +@_app.route("/courts", methods=["GET"]) def get_list_of_courts(): - return render_template("courts.j2", courts=rg.courts.get_courts()) + return render_template("courts.j2", courts=_rg.courts.get_courts()) diff --git a/src/match_up/templates/controls.j2 b/src/match_up/templates/controls.j2 index e9e37f3..57a605e 100644 --- a/src/match_up/templates/controls.j2 +++ b/src/match_up/templates/controls.j2 @@ -31,4 +31,9 @@ hx-post="/confirm" hx-target="#games">Start Round
+
+ +
\ No newline at end of file diff --git a/src/match_up/utilities.py b/src/match_up/utilities.py index 56fb78e..db7c76a 100644 --- a/src/match_up/utilities.py +++ b/src/match_up/utilities.py @@ -1,10 +1,17 @@ -from time import time from typing import Tuple -from random import shuffle - - -SECS_IN_MIN = 60 -DEFAULT_TIMER = 12.0 +from pandas import read_csv +from random import shuffle, choice +from random import randint, getrandbits +from names import get_first_name, get_last_name +from match_up.data import ( + Player, + DisplayPlayer, + Team, + Game, + MIN_LEVEL, + MAX_LEVEL, + PLAYER_PER_COURT, +) def _shuffle_two_lists(a: list, b: list) -> Tuple[list, list]: @@ -20,9 +27,124 @@ def _pick_from_list_after_sorting_other(a: list, b: list[int]) -> list: return [a[index] for index in indices] -def sample_list(indices: list, cost: list[int], count: int = None) -> list: +def sample_list(indices: list, cost: list[int], count=None) -> list: if count is None: count = len(indices) indices, cost = _shuffle_two_lists(indices, cost) indices = _pick_from_list_after_sorting_other(indices, cost)[:count] return indices + + +def _create_placeholders(courts: dict, name: str) -> list[Game]: + dummy_player = Player(id=-1, first=name) + dummy_team = Team( + player1=DisplayPlayer(dummy_player), player2=DisplayPlayer(dummy_player) + ) + games = [Game(court, dummy_team, dummy_team) for court in courts] + return games + + +def _normalize_skill(level: int, new_max: int) -> int: + new_level = round((new_max - 1) * (level / MAX_LEVEL)) + 1 + return new_level + + +def _select_players(players: list[Player], count: int) -> list[Player]: + identifiers = [player.id for player in players] + play_count = [int(player.count) for player in players] + ids = sample_list(identifiers, play_count, count) + selected_players = [player for player in players if player.id in ids] + return selected_players + + +def _create_groups_of_four_players( + players: list[Player], new_max: int +) -> list[list[Player]]: + identifiers = [player.id for player in players] + player_levels = [_normalize_skill(int(player.level), new_max) for player in players] + identifiers = sample_list(identifiers, player_levels) + court_players = [] + for i in range(0, len(players), PLAYER_PER_COURT): + court_ids = identifiers[i : i + PLAYER_PER_COURT] + selected_players = [player for player in players if player.id in court_ids] + court_players.append(selected_players) + return court_players + + +def _create_games(courts: dict, players: list[Player], levels: int) -> list[Game]: + court_players = _create_groups_of_four_players(players, levels) + games = [] + for court, players in zip(courts, court_players): + team1 = Team( + player1=DisplayPlayer(players[0]), player2=DisplayPlayer(players[3]) + ) + team2 = Team( + player1=DisplayPlayer(players[1]), player2=DisplayPlayer(players[2]) + ) + games.append(Game(court, team1, team2)) + return games + + +def generate_guest_type(count: int, level: int, description: str): + base_id = level * 10000 + return [ + Player( + id=base_id + i, + first=description, + last=f"{i + 1}", + level=level, + status=True, + count=choice([0, 1000]), + ) + for i in range(count) + ] + + +def generate_guests(guests): + beginner_guests = generate_guest_type(guests[1], 1, "Beginner") + novice_guests = generate_guest_type(guests[3], 3, "Novice") + intermediate_guests = generate_guest_type(guests[5], 3, "Intermediate") + return beginner_guests + novice_guests + intermediate_guests + + +def _textualize_guest(level, value) -> str: + match level: + case 1: + text = "Beginners" + case 3: + text = "Novices" + case 5: + text = "Intermediates" + if value == 1: + text = text[:-1] + return f"{value} {text}" + + +def _init_database_from_csv(session, csv_location: str) -> None: + data = read_csv(csv_location) + session.query(Player).delete() + for _, entry in data.iterrows(): + player = Player( + id=entry["id"], + first=entry["first"], + last=entry["last"], + level=entry["skill"], + status=False, + count=0, + ) + session.add(player) + session.commit() + + +def _init_random_database(session, player_count: int = 80) -> None: + for i in range(player_count): + player = Player( + id=i, + first=get_first_name(), + last=get_last_name(), + level=randint(MIN_LEVEL, MAX_LEVEL), + status=bool(getrandbits(1)), + count=0, + ) + session.add(player) + session.commit() -- cgit v1.3.1 From 0427e575e9cd32f3a38791fe26f2fd920a3f3f3f Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Fri, 15 Nov 2024 16:59:46 +0100 Subject: Made the navbar a bit cleaner --- src/match_up/templates/macros.j2 | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'src/match_up/templates') diff --git a/src/match_up/templates/macros.j2 b/src/match_up/templates/macros.j2 index 83fc823..6624c30 100644 --- a/src/match_up/templates/macros.j2 +++ b/src/match_up/templates/macros.j2 @@ -54,8 +54,6 @@ {% macro navbar(url_for) -%} {%- endmacro %} \ No newline at end of file -- cgit v1.3.1 From 7b7af30b84ec430a6fccfe4d81df0119c773665c Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Fri, 15 Nov 2024 17:18:00 +0100 Subject: Changed the code base with an ADR --- README.md | 8 +++++--- src/match_up/templates/macros.j2 | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'src/match_up/templates') diff --git a/README.md b/README.md index 42ca714..93f656e 100644 --- a/README.md +++ b/README.md @@ -74,8 +74,10 @@ pythonw -m flask run -p Create a task using Task Scheduler to run this script on start up. -## Background / Motivation +## Architectural Design Record -This part of the README focuses on the motivation of why this application was built and contains no technical value. Please read on in case you want to know more about the technological choices made. +This part of the README focuses on the motivation of why this application was built and contains background information. Please read on in case you want to know more about the technological choices made. -> I am part of a badminton club where an old Microsoft Access application was being used to run the games played every week. The application started crashing after a good run of a few years and also lacked a few badly needed features. Instead of fixing the old application, I took it upon myself to create something using tools that are more suited for such an application. I wanted to prioritize delivery speed for performance and hence choose Python. More over, the sheer number of Python programmers allows for the application to be easily maintained by others. I myself have no idea how Microsoft Access works. Since it was quite a small application, I went with Flask as it was more than enough for the features that I needed instead of Django. I have a background in electrical engineering and having never made frontends before, went with the simplest option for the user interface after a bit of research. HTMX required no need of me writing JavaScript and was by far the most appealing option. The website needed some basic styling and after looking at a few CSS frameworks, I settled with Bulma. It gives me everything I need. The application is scheduled to be deployed. +- Python was choose to prioritize delivery speed not for performance. More over, the sheer number of Python programmers allows for the application to be easily maintained by others. +- Flask was choosed as it more than enough for the features that I needed instead of Django. The application is to run locally on a laptop and doesn't really need to have complicated features. +- Due to a lack of frontend developers, the simplest UI stack was choosen. HTMX requires no need of JavaScript and was a natural choice. The website needed some basic styling and after looking at a few CSS frameworks, Bulma provided most of the styling. A toasting library, "Notyf" was also used for simple notifications to be sent via some custom javascript code for a simple timer. diff --git a/src/match_up/templates/macros.j2 b/src/match_up/templates/macros.j2 index 6624c30..3e6c4d6 100644 --- a/src/match_up/templates/macros.j2 +++ b/src/match_up/templates/macros.j2 @@ -49,7 +49,7 @@ {%- endmacro %} {% macro footer() -%} -
Made by Karan using Flask, HTMX and Bulma
+
Made by Karan using Flask, HTMX, Notyf and Bulma
{%- endmacro %} {% macro navbar(url_for) -%} -- cgit v1.3.1 From c4a4e35f93f18a13c6e4fc1401caad2b095a7ea7 Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Fri, 15 Nov 2024 17:46:31 +0100 Subject: Added the changes to clean up the guests layout --- src/match_up/static/custom.css | 4 ++++ src/match_up/templates/controls.j2 | 4 ++-- src/match_up/templates/macros.j2 | 2 +- src/match_up/templates/players.j2 | 5 +++-- 4 files changed, 10 insertions(+), 5 deletions(-) (limited to 'src/match_up/templates') diff --git a/src/match_up/static/custom.css b/src/match_up/static/custom.css index 11d583f..fafc51a 100644 --- a/src/match_up/static/custom.css +++ b/src/match_up/static/custom.css @@ -12,4 +12,8 @@ img { top: 50%; left: 50%; transform: translate(-50%,-50%); +} +.center { + margin: auto; + width: 50%; } \ No newline at end of file diff --git a/src/match_up/templates/controls.j2 b/src/match_up/templates/controls.j2 index 57a605e..cf591c5 100644 --- a/src/match_up/templates/controls.j2 +++ b/src/match_up/templates/controls.j2 @@ -29,11 +29,11 @@
+ hx-target="#games">Confirm
+ hx-target="#games">RESET!
\ No newline at end of file diff --git a/src/match_up/templates/macros.j2 b/src/match_up/templates/macros.j2 index 3e6c4d6..2974744 100644 --- a/src/match_up/templates/macros.j2 +++ b/src/match_up/templates/macros.j2 @@ -11,7 +11,7 @@ {%- endmacro %} {% macro guest_control(level, value) -%} -
+
-- cgit v1.3.1 From 7b8bbe6db3680af65c3701afbcd31ac1f61773af Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Fri, 15 Nov 2024 18:24:31 +0100 Subject: Changed the shuffle button to create --- src/match_up/templates/controls.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/match_up/templates') diff --git a/src/match_up/templates/controls.j2 b/src/match_up/templates/controls.j2 index cf591c5..24b42c6 100644 --- a/src/match_up/templates/controls.j2 +++ b/src/match_up/templates/controls.j2 @@ -23,7 +23,7 @@ + hx-include="[name='type']">Create
-- cgit v1.3.1