aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaran Jayachandra <karan.jayachandra@nxp.com>2024-04-12 10:29:29 +0200
committerKaran Jayachandra <karan.jayachandra@nxp.com>2024-04-12 10:29:29 +0200
commitc58caf5b8b8801ba826b0ff6b241d7f25a333bc8 (patch)
tree45b22248fd4fbcf23046ef30707da8ff1f65c880
parente6939bb6421862a12048449094f2c55e6e9e401b (diff)
Added a more cleaner application
-rw-r--r--app.py73
-rw-r--r--config.py16
-rw-r--r--dummy_data_generation.py28
-rw-r--r--index.html0
-rw-r--r--model.py50
-rw-r--r--pyproject.toml35
-rw-r--r--requirements-dev.txt1
-rw-r--r--requirements.txt3
-rw-r--r--src/match_up/__init__.py60
-rw-r--r--src/match_up/__main__.py4
-rw-r--r--src/match_up/model.py153
-rw-r--r--src/match_up/templates/index.html15
-rw-r--r--src/match_up/tests/__init__.py44
-rw-r--r--src/match_up/view.py96
-rw-r--r--templates/index.html51
-rw-r--r--templates/index.j245
-rw-r--r--templates/macro.j23
-rw-r--r--templates/player.j21
-rw-r--r--templates/players.j27
19 files changed, 184 insertions, 501 deletions
diff --git a/app.py b/app.py
index 6ff3b9a..7d9f808 100644
--- a/app.py
+++ b/app.py
@@ -1,55 +1,42 @@
-from dash import Dash
-from pathlib import PurePath
-from importlib.resources import files
-from match_up.model import PlaySession
-from match_up.view import MainLayout
-from match_up.tests import (
- TEST_DATABASE_LOCATION,
-)
-from dash_bootstrap_components.themes import ZEPHYR
-from flask import Flask, render_template
+from os import getenv
+from model import PlayerList
+from flask import Flask, abort, render_template
+from dotenv import load_dotenv
-DATABASE_LOCATION = "storage.json"
+load_dotenv()
-# class Application:
-# def __init__(self, debug=False):
-# self._debug = debug
-# if debug:
-# database_location = TEST_DATABASE_LOCATION
-# else:
-# database_location = DATABASE_LOCATION
-# self._session = PlaySession(PurePath(database_location))
-# self._dashboard = Dash("Match Up!", external_stylesheets=[ZEPHYR])
-# self._layout = MainLayout(
-# self._session.data.get_players(), self._session.get_matches()
-# )
-# self._dashboard.layout = self._layout.get()
+print("Importing player list...\n")
+PLAYER_LIST = PlayerList(location="test_data.csv")
+print("List of player:\n")
+print(PLAYER_LIST)
-# def run(self):
-# self._dashboard.run(debug=self._debug)
+app = Flask("Match Up! Backend")
+env_config = getenv("PROD_APP_SETTINGS", "config.DevelopmentConfig")
+app.config.from_object(env_config)
-# def main():
-# a = Application(debug=True)
-# a.run()
-
-
-app = Flask("Match Up")
-session = PlaySession(PurePath("test_storage.json"))
-base_template_path = "match_up.templates"
+@app.route("/", methods=["GET"])
+def home():
+ return render_template("index.j2")
-@app.route("/")
-def home():
- print(files(base_template_path).joinpath("index.html").name)
+@app.route("/player-toggle/<player_request>", methods=["POST"])
+def toggle_player(player_request):
+ selection = PLAYER_LIST.get_player(player_request)
+ if len(selection) != 1:
+ abort(404)
+ PLAYER_LIST.toggle_player(player_request)
+ selection = PLAYER_LIST.get_player(player_request)
return render_template(
- "index.html",
- player_list=session.data.get_players(),
- game_list=session.get_matches(),
+ "player.j2",
+ player=selection[0],
)
-if __name__ == "__main__":
- print("Starting app")
- app.run(host="localhost", debug=True, port=80)
+@app.route("/player-list", methods=["GET"])
+def get_list_of_players():
+ return render_template(
+ "players.j2",
+ players=PLAYER_LIST.players,
+ )
diff --git a/config.py b/config.py
new file mode 100644
index 0000000..7d01933
--- /dev/null
+++ b/config.py
@@ -0,0 +1,16 @@
+class Config:
+ DEBUG = False
+ DEVELOPMENT = False
+ CSRF_ENABLED = True
+ ASSETS_DEBUG = False
+
+
+class ProductionConfig(Config):
+ pass
+
+
+class DevelopmentConfig(Config):
+ DEBUG = True
+ DEVELOPMENT = True
+ TEMPLATES_AUTO_RELOAD = True
+ ASSETS_DEBUG = True
diff --git a/dummy_data_generation.py b/dummy_data_generation.py
new file mode 100644
index 0000000..3ee6e42
--- /dev/null
+++ b/dummy_data_generation.py
@@ -0,0 +1,28 @@
+from csv import writer
+from model import Player
+from names import get_full_name
+from random import randint, getrandbits
+
+
+def get_test_players(total_player: int = 20, min_skill: int = 1, max_skill: int = 10):
+ return [
+ Player(
+ name=get_full_name(),
+ skill=randint(min_skill, max_skill),
+ status=bool(getrandbits(1)),
+ )
+ for _ in range(total_player)
+ ]
+
+
+def main():
+ dummy_list = get_test_players()
+ with open("test_data.csv", "w", newline="") as file:
+ data_writer = writer(file)
+ data_writer.writerow(["name", "skill", "status"])
+ for player in dummy_list:
+ data_writer.writerow([player.name, player.skill, player.status])
+
+
+if __name__ == "__main__":
+ main()
diff --git a/index.html b/index.html
deleted file mode 100644
index e69de29..0000000
--- a/index.html
+++ /dev/null
diff --git a/model.py b/model.py
new file mode 100644
index 0000000..53119eb
--- /dev/null
+++ b/model.py
@@ -0,0 +1,50 @@
+from csv import reader
+from pathlib import Path
+from dataclasses import dataclass
+
+
+@dataclass
+class Player:
+ name: str
+ skill: int
+ status: bool
+
+
+class PlayerList:
+ def __init__(self, location: Path = "players.csv"):
+ with open(location, newline="") as file:
+ data_reader = reader(file)
+ next(data_reader, None)
+ self.players = [
+ (
+ Player(
+ name=row[0],
+ skill=row[1],
+ status=(row[2] == "True"),
+ )
+ )
+ for row in data_reader
+ ]
+
+ def get_player(self, player_request: str):
+ return [player for player in self.players if player.name == player_request]
+
+ def toggle_player(self, player_request: Player):
+ self.players = [
+ (
+ Player(
+ name=player.name,
+ skill=player.skill,
+ status=not player.status,
+ )
+ if player.name == player_request
+ else player
+ )
+ for player in self.players
+ ]
+
+ def __str__(self):
+ display_string = ""
+ for player in self.players:
+ display_string += player.__str__() + "\n"
+ return display_string
diff --git a/pyproject.toml b/pyproject.toml
deleted file mode 100644
index a82318c..0000000
--- a/pyproject.toml
+++ /dev/null
@@ -1,35 +0,0 @@
-[build-system]
-requires = ["setuptools"]
-build-backend = "setuptools.build_meta"
-
-[project]
-name = "match_up"
-version = "0.1"
-license = {file = "LICENSE"}
-authors = [
- { name="Karan Jayachandra", email="mail@karanjayachandra.com" },
-]
-description = "Plotly dashboard for skill based matchmaking."
-readme = "README.md"
-requires-python = ">=3.10"
-classifiers = [
- "Programming Language :: Python :: 3",
- "Operating System :: OS Independent",
-]
-dependencies = [
- 'tinydb',
- 'plotly',
- 'dash',
- 'dash_bootstrap_components',
-]
-
-[project.scripts]
-match_up = "match_up:main"
-
-[project.urls]
-Repository = "https://gitlab.com/KaranJayachandra/match_up"
-
-[project.optional-dependencies]
-tests = ['names', 'pytest', 'pytest-cov']
-docs = ['sphinx', 'sphinx-autodoc-typehints']
-ci = ['coverage2clover'] \ No newline at end of file
diff --git a/requirements-dev.txt b/requirements-dev.txt
new file mode 100644
index 0000000..ee8bbca
--- /dev/null
+++ b/requirements-dev.txt
@@ -0,0 +1 @@
+names \ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..886c3db
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+flask
+python-dotenv
+gunicorn \ No newline at end of file
diff --git a/src/match_up/__init__.py b/src/match_up/__init__.py
deleted file mode 100644
index 95f767e..0000000
--- a/src/match_up/__init__.py
+++ /dev/null
@@ -1,60 +0,0 @@
-from dash import Dash
-from pathlib import PurePath
-from importlib.resources import files
-from match_up.model import PlaySession
-from match_up.view import MainLayout
-from match_up.tests import (
- TEST_DATABASE_LOCATION,
-)
-from dash_bootstrap_components.themes import ZEPHYR
-from flask import Flask, render_template
-
-DATABASE_LOCATION = "storage.json"
-
-
-# class Application:
-# def __init__(self, debug=False):
-# self._debug = debug
-# if debug:
-# database_location = TEST_DATABASE_LOCATION
-# else:
-# database_location = DATABASE_LOCATION
-# self._session = PlaySession(PurePath(database_location))
-# self._dashboard = Dash("Match Up!", external_stylesheets=[ZEPHYR])
-# self._layout = MainLayout(
-# self._session.data.get_players(), self._session.get_matches()
-# )
-# self._dashboard.layout = self._layout.get()
-
-# def run(self):
-# self._dashboard.run(debug=self._debug)
-
-
-# def main():
-# a = Application(debug=True)
-# a.run()
-
-
-app = Flask("Match Up", template_folder="templates")
-session = PlaySession(PurePath("test_storage.json"))
-base_template_path = "match_up.templates"
-
-
-@app.route("/")
-def home():
- print(files(base_template_path).joinpath("index.html").name)
- # return render_template(
- # files(base_template_path).joinpath("index.html").name,
- # title="My Generated Page",
- # people=session.data.get_players(),
- # )
- return render_template(
- "index.html",
- title="My Generated Page",
- people=session.data.get_players(),
- )
-
-
-if __name__ == "__main__":
- print("Starting app")
- app.run(host="localhost", debug=True, port=80)
diff --git a/src/match_up/__main__.py b/src/match_up/__main__.py
deleted file mode 100644
index 3500cdb..0000000
--- a/src/match_up/__main__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from match_up import main
-
-if __name__ == "__main__":
- main()
diff --git a/src/match_up/model.py b/src/match_up/model.py
deleted file mode 100644
index d27893d..0000000
--- a/src/match_up/model.py
+++ /dev/null
@@ -1,153 +0,0 @@
-from pathlib import PurePath
-from tinydb import TinyDB, where
-from dataclasses import dataclass
-from random import randint
-
-MIN_SKILL = 1
-MAX_SKILL = 10
-
-
-class PlaySession:
- def __init__(self, database_location: PurePath):
- self.data = PlayerDatabase(database_location)
-
- def get_matches(self, number_of_courts: int = 4):
- player_list = self.data.get_players()
- game_list = []
- for i in range(number_of_courts):
- game_list.append(
- Game(
- court_number=i + 1,
- team_1=Team(
- player_1=player_list[randint(0, len(player_list) - 1)],
- player_2=player_list[randint(0, len(player_list) - 1)],
- ),
- team_2=Team(
- player_1=player_list[randint(0, len(player_list) - 1)],
- player_2=player_list[randint(0, len(player_list) - 1)],
- ),
- )
- )
- return game_list
-
- def end(self):
- self.data.close()
-
- def __str__(self):
- display_string = ""
- for player in self.data.get_players():
- display_string = display_string + player.__str__() + "\n"
- return display_string.rstrip()
-
- def __del__(self):
- self.end()
-
-
-@dataclass
-class Player:
- first_name: str
- last_name: str
- skill: int = 1
- status: bool = False
- wait_time: int = 0
-
- def get_name(self):
- return self.first_name + " " + self.last_name
-
-
-@dataclass
-class Team:
- player_1: Player
- player_2: Player
-
- def __str__(self):
- return self.player_1.get_name() + " & " + self.player_2.get_name()
-
-
-@dataclass
-class Game:
- court_number: int
- team_1: Team
- team_2: Team
-
-
-class PlayerDatabase:
- def __init__(self, database_location: PurePath = PurePath("storage.json")):
- print(f"Using database location: {database_location.name}")
- self._database = TinyDB(database_location.name)
-
- def get_players(self) -> list[Player]:
- player_list = []
- for row in self._database:
- player_list.append(
- Player(
- first_name=row["first_name"],
- last_name=row["last_name"],
- skill=row["skill"],
- status=row["status"],
- wait_time=row["wait_time"],
- )
- )
- return player_list
-
- def activate_player(self, player: Player):
- self._database.update(
- {"status": True},
- where("first_name") == player.first_name
- and where("last_name") == player.last_name,
- )
-
- def deactivate_player(self, player: Player):
- self._database.update(
- {"wait_time": False},
- where("first_name") == player.first_name
- and where("last_name") == player.last_name,
- )
-
- def increment_player_wait(self, player: Player):
- self._database.update(
- {"wait_time": 1},
- where("first_name") == player.first_name
- and where("last_name") == player.last_name,
- )
-
- def reset_player_wait(self, player: Player):
- self._database.update(
- {"wait_time": 0},
- where("first_name") == player.first_name
- and where("last_name") == player.last_name,
- )
-
- def deactivate_all_players(self) -> bool:
- for row in self._database:
- row["status"] = False
-
- def add_player(self, player: Player) -> bool:
- self._database.insert(
- {
- "first_name": player.first_name,
- "last_name": player.last_name,
- "skill": player.skill,
- "status": player.status,
- "wait_time": player.wait_time,
- }
- )
-
- def remove_player(self, player: Player) -> bool:
- self._database.remove(
- where("first_name") == player.first_name
- and where("last_name") == player.last_name
- )
-
- def update_player_skill(self, player: Player):
- self._database.update(
- {"skill": player.skill},
- where("first_name") == player.first_name
- and where("last_name") == player.last_name,
- )
-
- def close(self) -> None:
- self._database.close()
-
- def __del__(self):
- self.close()
diff --git a/src/match_up/templates/index.html b/src/match_up/templates/index.html
deleted file mode 100644
index e292734..0000000
--- a/src/match_up/templates/index.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<!doctype html>
-<link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">
-<html>
- <head>
- <title>{{ title }}</title>
- </head>
- <body>
- <h1>Match Up!</h1>
- <ul>
- {% for person in people %}
- <li>{{ person.first_name }}</li>
- {% endfor %}
- </ul>
- </body>
-</html> \ No newline at end of file
diff --git a/src/match_up/tests/__init__.py b/src/match_up/tests/__init__.py
deleted file mode 100644
index 3166746..0000000
--- a/src/match_up/tests/__init__.py
+++ /dev/null
@@ -1,44 +0,0 @@
-from os import remove
-from os.path import isfile
-from random import randint
-from pathlib import PurePath
-from names import get_first_name, get_last_name
-from match_up.model import Player, PlayerDatabase, MIN_SKILL, MAX_SKILL
-
-TEST_DATABASE_LOCATION = PurePath("test_storage.json")
-
-
-def generate_test_database(total_players: int = 20) -> None:
- try:
- if isfile(TEST_DATABASE_LOCATION):
- remove(TEST_DATABASE_LOCATION)
- db = PlayerDatabase(TEST_DATABASE_LOCATION)
- for _ in range(total_players):
- db.add_player(
- Player(
- first_name=get_first_name(),
- last_name=get_last_name(),
- skill=randint(MIN_SKILL, MAX_SKILL),
- )
- )
- finally:
- db.close()
-
-
-def activate_random_players(activate_players: int = 17) -> None:
- try:
- db = PlayerDatabase(TEST_DATABASE_LOCATION)
- player_list = db.get_players()
- for index, player in enumerate(player_list):
- if index < activate_players:
- db.activate_player(player)
- finally:
- db.close()
-
-
-def main():
- pass
-
-
-if __name__ == "__main__":
- main()
diff --git a/src/match_up/view.py b/src/match_up/view.py
deleted file mode 100644
index 8fa1c54..0000000
--- a/src/match_up/view.py
+++ /dev/null
@@ -1,96 +0,0 @@
-from dash.html import Div, H4, Thead, Tbody, Tr, Td, Th
-from match_up.model import Player, Game
-from dash_bootstrap_components import (
- Row,
- Col,
- Container,
- Input,
- Select,
- Table,
- NavbarSimple,
-)
-
-
-class GameLayout:
- def __init__(self, game_list: list[Game]):
- table_header = [Thead(Tr([Th("Court"), Th("Team 1"), Th("Team 2")]))]
- table_body = [
- Tbody(
- [
- Tr(
- [
- Td(children=game.court_number),
- Td(children=game.team_1.__str__()),
- Td(children=game.team_2.__str__()),
- ]
- )
- for game in game_list
- ]
- )
- ]
- self._layout = Div(
- [
- H4(children="Game List", style={"textAlign": "center"}),
- Table(table_header + table_body, bordered=True),
- ]
- )
-
- def get(self):
- return self._layout
-
-
-class PlayerLayout:
- def __init__(self, player_list: list[Player]):
- table_header = [Thead(Tr([Th("Player Name")]))]
- table_body = [
- Tbody(
- [
- Tr(
- [
- Td(
- children=player.first_name + " " + player.last_name,
- style={"color": ("green" if player.status else "red")},
- )
- ]
- )
- for player in player_list
- ]
- )
- ]
- self._layout = Div(
- [
- H4(children="Player List", style={"textAlign": "center"}),
- Table(table_header + table_body, bordered=True),
- ]
- )
-
- def get(self):
- return self._layout
-
-
-class MainLayout:
- def __init__(self, player_list: list[Player], game_list: list[Game]):
- self._player_layout = PlayerLayout(player_list)
- self._game_layout = GameLayout(game_list)
-
- def refresh(): ...
-
- def get(self):
- full_layout = Div(
- [
- NavbarSimple(brand="Match Up!"),
- Container(
- [
- Row(
- [
- Col([self._game_layout.get()], width=9),
- Col([self._player_layout.get()], width=3),
- ],
- style={"margin": "15px"},
- )
- ]
- ),
- ]
- )
- # full_layout = Div([Div(children="Hello World")])
- return full_layout
diff --git a/templates/index.html b/templates/index.html
deleted file mode 100644
index db4a991..0000000
--- a/templates/index.html
+++ /dev/null
@@ -1,51 +0,0 @@
-<!doctype html>
-<link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">
-
-<html>
- <head>
- <title>Match Up!</title>
- </head>
- <body>
- <h1>Match Up!</h1>
- <div class="row">
- <div style=" float: left; width: 60%">
- <table>
- <thead>
- <tr>
- <th> Court </th>
- <th> Team 1 </th>
- <th> Team 2 </th>
- </tr>
- </thead>
- <tbody>
- {% for game in game_list %}
- <tr>
- <td width="10%"> {{game.court_number}} </td>
- <td width="40%"> {{game.team_1.player_1.get_name()}} <br> {{game.team_1.player_2.get_name()}} </td>
- <td width="40%"> {{game.team_2.player_1.get_name()}} <br> {{game.team_2.player_2.get_name()}} </td>
- </tr>
- {% endfor %}
- </tbody>
- </table>
- </div>
- <div style=" float: left; width: 30%">
- <table>
- <thead>
- <tr>
- <th> Player </th>
- </tr>
- </thead>
- <tbody>
- {% for player in player_list %}
- <tr>
- <td style={% if player.status %} "color : green" {% else %} "color : red" {% endif %}>
- {{ player.get_name() }}
- </td>
- </tr>
- {% endfor %}
- </tbody>
- </table>
- </div>
- </div>
- </body>
-</html> \ No newline at end of file
diff --git a/templates/index.j2 b/templates/index.j2
new file mode 100644
index 0000000..82c3c80
--- /dev/null
+++ b/templates/index.j2
@@ -0,0 +1,45 @@
+<!doctype html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>🏸Match Up!</title>
+ <link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">
+ <script src="https://unpkg.com/htmx.org@1.9.11"></script>
+ <style>
+ button {
+ height: 50px;
+ width: 200px;
+ }
+ .active {
+ background-color: #27ae60;
+ }
+ .dormant {
+ background-color: #a93226;
+ }
+ .grid-container {
+ display: grid;
+ grid-template-columns: auto auto auto;
+ row-gap: 20px;
+ column-gap: 20px;
+ }
+ .tagline {
+ text-align: center;
+ }
+ </style>
+ </head>
+ <body>
+ <header>
+ <h1>🏸Match Up!</h1>
+ <p class="tagline"> Come play with the <a href="https://www.bctsf.nl/">Smashing Fellows</a>! </p>
+ </header>
+ <hr>
+ <div class="players">
+ <h2>Players</h2>
+ <div hx-get="/player-list" hx-swap="outerHTML" hx-trigger="revealed"></div>
+ </div>
+ <footer>
+ Made by <a href="https://karanjayachandra.com/">Karan Jayachandra</a> using <a href="https://htmx.org/">HTMX</a> and <a href="https://flask.palletsprojects.com">Flask</a>
+ </footer>
+ </body>
+</html> \ No newline at end of file
diff --git a/templates/macro.j2 b/templates/macro.j2
new file mode 100644
index 0000000..e0089bc
--- /dev/null
+++ b/templates/macro.j2
@@ -0,0 +1,3 @@
+{% macro player_button(player) %}
+<button class={% if player.status %} "active" {% else %} "dormant" {% endif %} hx-post="{{ "/player-toggle/" ~ player.name}}" hx-trigger="click" hx-swap="outerHTML">{{ player.name }}</button>
+{%- endmacro %} \ No newline at end of file
diff --git a/templates/player.j2 b/templates/player.j2
new file mode 100644
index 0000000..1176d65
--- /dev/null
+++ b/templates/player.j2
@@ -0,0 +1 @@
+<button class={% if player.status %} "active" {% else %} "dormant" {% endif %} hx-post="{{ "/player-toggle/" ~ player.name}}" hx-trigger="click" hx-swap="outerHTML">{{ player.name }}</button> \ No newline at end of file
diff --git a/templates/players.j2 b/templates/players.j2
new file mode 100644
index 0000000..3005fa4
--- /dev/null
+++ b/templates/players.j2
@@ -0,0 +1,7 @@
+<div class="grid-container">
+{% for player in players %}
+ <div class="grid-item">
+ <button class={% if player.status %} "active" {% else %} "dormant" {% endif %} hx-post="{{ "/player-toggle/" ~ player.name}}" hx-trigger="click" hx-swap="outerHTML">{{ player.name }}</button>
+ </div>
+{% endfor %}
+</div> \ No newline at end of file