From a355423ff7566ff1d38bfa1627ec5b7ffa4b6bdc Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Wed, 16 Oct 2024 23:57:24 +0200 Subject: Cleaned up the layout a bit --- app.py | 5 +++++ templates/controls.j2 | 10 +++++++++- templates/index.j2 | 35 +++++++++++++++-------------------- templates/macros.j2 | 4 ++-- 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/app.py b/app.py index f145354..2421ee9 100644 --- a/app.py +++ b/app.py @@ -27,6 +27,11 @@ def get_time(): return render_template(t, mins=mins, secs=secs) +@app.route("/control", methods=["GET"]) +def get_controls(): + return render_template("controls.j2", courts=rg.courts.get_courts()) + + @app.route("/guests", methods=["GET"]) def get_guests(): t = re.from_string('{% from "macros.j2" import guests %}{{guests(count)}}') diff --git a/templates/controls.j2 b/templates/controls.j2 index 1799e01..8e190c5 100644 --- a/templates/controls.j2 +++ b/templates/controls.j2 @@ -1,3 +1,5 @@ +{% from "macros.j2" import court %} +

Controls

@@ -48,4 +50,10 @@ hx-target="#games">Reset
-
+
+
+{% for key, value in courts.items() %} + {{ court(key, value) }} +{% endfor %} +
+
\ No newline at end of file diff --git a/templates/index.j2 b/templates/index.j2 index fcea4e3..22c72a0 100644 --- a/templates/index.j2 +++ b/templates/index.j2 @@ -11,29 +11,24 @@
- -
-
+
+
+
+ +

Games

- {% include 'controls.j2' %} -
-
-

Courts

-
+

Players

diff --git a/templates/macros.j2 b/templates/macros.j2 index 7117d11..65d886f 100644 --- a/templates/macros.j2 +++ b/templates/macros.j2 @@ -1,7 +1,7 @@ {% macro court(id, status) -%} + hx-swap="outerHTML"> Court {{ id }} {%- endmacro %} {% macro player(id, status, name) -%} @@ -18,6 +18,6 @@ {% macro timer(mins, secs) -%}
-

Timer: {{mins}}:{{secs}}

+

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

{%- endmacro %} \ No newline at end of file -- cgit v1.3.1 From fb6b013b821e2011a04d6ba85813a6a947c14239 Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Thu, 17 Oct 2024 00:01:05 +0200 Subject: Removed debug statements and old template --- app.py | 6 ++++-- model.py | 2 -- templates/courts.j2 | 8 -------- 3 files changed, 4 insertions(+), 12 deletions(-) delete mode 100644 templates/courts.j2 diff --git a/app.py b/app.py index 2421ee9..d7d3b65 100644 --- a/app.py +++ b/app.py @@ -40,12 +40,14 @@ def get_guests(): @app.route("/decrement", methods=["POST"]) def increment_guests(): - return render_template("guests.j2", guests=rg.players.decrement_guests()) + t = re.from_string('{% from "macros.j2" import guests %}{{guests(count)}}') + return render_template(t, count=rg.players.decrement_guests()) @app.route("/increment", methods=["POST"]) def decrement_guests(): - return render_template("guests.j2", guests=rg.players.increment_guests()) + t = re.from_string('{% from "macros.j2" import guests %}{{guests(count)}}') + return render_template(t, count=rg.players.increment_guests()) @app.route("/propose", methods=["POST"]) diff --git a/model.py b/model.py index c69ba48..904265f 100644 --- a/model.py +++ b/model.py @@ -131,6 +131,4 @@ class Timer: return self.default mins = f"{int(total_seconds // 60):02d}" secs = f"{int(total_seconds % 60):02d}" - print(mins) - print(secs) return mins, secs diff --git a/templates/courts.j2 b/templates/courts.j2 deleted file mode 100644 index d650298..0000000 --- a/templates/courts.j2 +++ /dev/null @@ -1,8 +0,0 @@ -{% from "macros.j2" import court %} -
-
-{% for key, value in courts.items() %} - {{ court(key, value) }} -{% endfor %} -
-
\ No newline at end of file -- cgit v1.3.1 From 8f1438964e1a336c44609cd4cc4c6aef61137011 Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Thu, 17 Oct 2024 10:38:41 +0200 Subject: Added content to the README and reordered the code --- README.md | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- controller.py | 4 ++-- model.py | 31 ------------------------------ utilities.py | 34 +++++++++++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 49c1d28..d98c5a2 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,59 @@ # Match Up -## Scope +Match Up! is a flask web application to create pairings of players from a pool and assign to a limited number of slots. -This is a simple application build using flask, htmx and Bulma to generate matches for badminton. +Some additional features of this application are: + +- Propose pairings to create a round +- Timer to limit the time for each round +- Create pairings based on player level +- Prioritize players with fewer played games +- Blocking spaces that aren't available any more +- Blocking players that aren't available any more +- Regular and guest players + +What this application ***IS***: + +- Simple: Easy to modify, extend and use + +What this application ***IS NOT***: + +- Secure: This was designed to be used locally on a machine +- Performant: It was designed for use by a single admin user + +## How to run + +Please install [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) and [python](https://realpython.com/installing-python/) on your machine before proceeding further. Once installed, open the terminal to a folder of your choosing and run the following commands to setup the application. + +```bash +git clone https://gitlab.com/KaranJayachandra/match_up.git +cd match_up +python -m venv .venv +.venv\Scripts\activate +pip install -r requirements.txt +``` + +Now create a file called .env with the environment variables needed by the application. An example is shown below. + +```text +DATABASE = "database.csv" +COURTS = 10 +APP_SETTINGS = "config.DevelopmentConfig" +``` + +The *DATABASE* variable contains the list of players which is just a simple CSV file with two columns, the first containing their name and the second containing their skill level from 1 to 10. The *COURTS* variable is the number of spaces or slots you have for creating pairings. *APP_SETTINGS* should be chosen between "config.DevelopmentConfig" or "config.ProductionConfig" based on the deployment. + +The application can then be run using (ensure you are in the virtual environment if you closed the terminal after the previous steps): + +```bash +flask run +``` + +The application should be running now and the terminal should display the ip and port number. Most modern terminals will allow you to navigate directly to the application by clicking on the displayed address. + +## Technology used + +This is a simple application build using [flask](https://flask.palletsprojects.com/en/3.0.x/), [htmx](https://htmx.org/) and [Bulma](https://bulma.io/). Flask acts as the backend that generates HTML responses using the wonderful [Jinja](https://jinja.palletsprojects.com/en/3.1.x/) templating language. Interactivity of the application is done using HTMX using simple GET and POST methods to the backend. The application is styled using the Bulma with its easy to used CSS classes. ## Startup Automation @@ -16,3 +67,9 @@ pythonw -m flask run -p ``` Create a task using Task Scheduler to run this script on start up. + +## Background / Motivation + +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. + +> 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 game me everything I needed. The application is currently about to be deployed at the badminton club. diff --git a/controller.py b/controller.py index 2e09e9f..e6dc827 100644 --- a/controller.py +++ b/controller.py @@ -1,8 +1,8 @@ from typing import Tuple from operator import attrgetter from dataclasses import dataclass -from utilities import sample_list -from model import Team, Game, PlayerList, CourtList, Timer, MAX_LEVEL, PLAYER_PER_COURT +from utilities import sample_list, Timer +from model import Team, Game, PlayerList, CourtList, MAX_LEVEL, PLAYER_PER_COURT def _create_placeholders(courts: dict, name: str) -> list[Game]: diff --git a/model.py b/model.py index 904265f..f768d0f 100644 --- a/model.py +++ b/model.py @@ -1,7 +1,6 @@ from math import floor from csv import reader from typing import Tuple -from time import time, strftime from dataclasses import dataclass MIN_LEVEL = 1 @@ -102,33 +101,3 @@ class PlayerList: } players.update(guests) return players - - -@dataclass -class Timer: - ref: float = None - running: bool = False - max_mins: int = 1 - default: str = ("00", "00") - - def __post_init__(self): - self.max_seconds = self.max_mins * 60 - - def start(self): - self.ref = time() + self.max_seconds - self.running = True - - def stop(self): - self.ref = None - self.running = False - - def get(self): - if not self.running: - return self.default - total_seconds = self.ref - time() - if total_seconds < 0: - self.stop() - return self.default - mins = f"{int(total_seconds // 60):02d}" - secs = f"{int(total_seconds % 60):02d}" - return mins, secs diff --git a/utilities.py b/utilities.py index 1ad24b8..7ba140e 100644 --- a/utilities.py +++ b/utilities.py @@ -1,5 +1,9 @@ +from time import time from typing import Tuple from random import shuffle +from dataclasses import dataclass + +SECS_IN_MIN = 60 def _shuffle_two_lists(a: list, b: list) -> Tuple[list, list]: @@ -21,3 +25,33 @@ def sample_list(indices: list, cost: list[int], count: int = None) -> list: indices, cost = _shuffle_two_lists(indices, cost) indices = _pick_from_list_after_sorting_other(indices, cost)[:count] return indices + + +@dataclass +class Timer: + ref: float = None + running: bool = False + max_mins: int = 20 + default: str = ("00", "00") + + def __post_init__(self): + self.max_seconds = self.max_mins * SECS_IN_MIN + + def start(self): + self.ref = time() + self.max_seconds + self.running = True + + def stop(self): + self.ref = None + self.running = False + + def get(self): + if not self.running: + return self.default + total_seconds = self.ref - time() + if total_seconds < 0: + self.stop() + return self.default + mins = f"{int(total_seconds // SECS_IN_MIN):02d}" + secs = f"{int(total_seconds % SECS_IN_MIN):02d}" + return mins, secs -- cgit v1.3.1 From e627108b44bb069b556a11b1b2581b5a87209066 Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Thu, 17 Oct 2024 10:55:33 +0200 Subject: Moved the player activation to a separate page --- templates/controls.j2 | 1 - templates/index.j2 | 21 +++++++++--------- templates/players.j2 | 59 ++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 62 insertions(+), 19 deletions(-) diff --git a/templates/controls.j2 b/templates/controls.j2 index 8e190c5..7a03f9a 100644 --- a/templates/controls.j2 +++ b/templates/controls.j2 @@ -1,5 +1,4 @@ {% from "macros.j2" import court %} -

Controls

diff --git a/templates/index.j2 b/templates/index.j2 index 22c72a0..8ff9eb1 100644 --- a/templates/index.j2 +++ b/templates/index.j2 @@ -19,20 +19,21 @@
-
-

Match Up!

+

+ Games +

+
+

Match Up!

+

+ Players +

-
+
-
-

Games

-
-
-
-

Players

-
+
+