diff options
| -rw-r--r-- | app.py | 48 | ||||
| -rw-r--r-- | db.py | 14 | ||||
| -rw-r--r-- | migrations/0002_vote_value.sql | 2 | ||||
| -rw-r--r-- | static/styles.css | 4 | ||||
| -rw-r--r-- | templates/poll_vote_table.html.j2 | 14 |
5 files changed, 49 insertions, 33 deletions
@@ -25,11 +25,11 @@ AUTHOR_NAME_MAX_LENGTH = 100 AUTHOR_EMAIL_MAX_LENGTH = 100 VOTER_NAME_MAX_LENGTH = 100 -def voter_voted_on_choice(voter_name: str, choice: db.Choice) -> bool: +def voter_selection_on_choice(voter_name: str, choice: db.Choice) -> int | None: for vote in choice.votes: if vote.voter_name == voter_name: - return True - return False + return vote.value + return None def validation_error(message: str): return render_template("error.html.j2", error=message), 400 @@ -80,6 +80,8 @@ def create(): resp.set_cookie(f"diddle_manage_code_{poll.manage_code}", "1") return resp + +VoterNameChoiceIdPair = tuple[str, str] @app.get("/poll/<id>") def poll(id): poll = db.get_poll(id) @@ -88,29 +90,21 @@ def poll(id): display_mode = request.cookies.get("diddle_display_mode", "table") - voter_names = [] + voter_names_set: set[str] = set() + selections: dict[VoterNameChoiceIdPair, int] = {} for choice in poll.choices: for vote in choice.votes: - if vote.voter_name not in voter_names: - voter_names.append(vote.voter_name) - - choices_by_voter = [] - for voter_name in voter_names: - votes: list[bool] = [] - for choice in poll.choices: - voted = voter_voted_on_choice(voter_name, choice) - votes.append(voted) - - choices_by_voter.append( - ChoicesByVoter(name=voter_name, votes=votes) - ) + selections[(vote.voter_name, choice.id)] = vote.value + voter_names_set.add(vote.voter_name) - choices_by_voter.sort(key=lambda x: x.name) + voter_names = list(voter_names_set) + voter_names.sort() return render_template('poll.html.j2', poll=poll, - choices_by_voter=choices_by_voter, + selections=selections, choices=poll.choices, + voter_names=voter_names, now=datetime.datetime.now(), display_mode=display_mode, ) @@ -123,14 +117,22 @@ def vote_poll(id): if len(form["voter_name"]) > VOTER_NAME_MAX_LENGTH: return validation_error(f"Voter name must be {VOTER_NAME_MAX_LENGTH} characters or fewer") - voter_name = form["voter_name"] - choice_ids = [] + poll = db.get_poll(id) + if poll is None: + return validation_error("Poll not found") + + voter_name: str = form["voter_name"] + + selections: dict[str, int] = {} + for choice in poll.choices: + selections[choice.id] = 0 + for k in form.keys(): if k.startswith("choice_"): choice_id = k.replace("choice_", "") - choice_ids.append(choice_id) + selections[choice_id] = 1 - db.vote_poll(id, voter_name, choice_ids) + db.vote_poll(id, voter_name, selections) email_client.send_participation_email_if_enabled(poll_id=id, voter_name=voter_name) @@ -1,4 +1,5 @@ import os +from typing import Literal from dataclasses import dataclass import datetime import psycopg2 @@ -53,6 +54,7 @@ class Vote: poll_id: str choice_id: str voter_name: str + value: int # 0 or 1 @dataclass class Choice: @@ -112,6 +114,7 @@ def tuple_to_vote(vote_t: tuple) -> Vote: poll_id=vote_t[1], choice_id=vote_t[2], voter_name=vote_t[3], + value=vote_t[4], ) def get_poll(id: str): @@ -178,13 +181,14 @@ def create_poll(title: str, conn.rollback() raise e -def vote_poll(poll_id: str, voter_name: str, choice_ids: list[str]): +def vote_poll(poll_id: str, voter_name: str, selections: dict[str, int]) -> None: with db.cursor() as (conn, cur): try: - for choice_id in choice_ids: - cur.execute("INSERT INTO votes (poll_id, voter_name, choice_id)" - "VALUES (%s, %s, %s)", - (poll_id, voter_name, choice_id)) + for choice_id in selections: + value = selections[choice_id] + cur.execute("INSERT INTO votes (poll_id, voter_name, choice_id, value)" + "VALUES (%s, %s, %s, %s)", + (poll_id, voter_name, choice_id, value)) conn.commit() except Exception as e: diff --git a/migrations/0002_vote_value.sql b/migrations/0002_vote_value.sql new file mode 100644 index 0000000..94f7afb --- /dev/null +++ b/migrations/0002_vote_value.sql @@ -0,0 +1,2 @@ +ALTER TABLE votes ADD COLUMN IF NOT EXISTS value INTEGER NOT NULL DEFAULT 1; +ALTER TABLE votes ALTER COLUMN value DROP DEFAULT; diff --git a/static/styles.css b/static/styles.css index 40a93fc..229149c 100644 --- a/static/styles.css +++ b/static/styles.css @@ -136,3 +136,7 @@ table { input[type="checkbox"] { cursor: pointer; } + +input[type="checkbox"]:disabled { + cursor: initial; +} diff --git a/templates/poll_vote_table.html.j2 b/templates/poll_vote_table.html.j2 index bfba968..072d17a 100644 --- a/templates/poll_vote_table.html.j2 +++ b/templates/poll_vote_table.html.j2 @@ -16,18 +16,22 @@ </th> {% endfor %} </tr> - {% for voter in choices_by_voter %} + {% for voter_name in voter_names %} <tr> - <td>{{ voter.name }}</td> - {% for voted in voter.votes %} - {% if voted %} + <td>{{ voter_name }}</td> + {% for choice in choices %} + {% if selections[(voter_name, choice.id)] == 1 %} <td> <input type="checkbox" checked disabled> </td> - {% else %} + {% elif selections[(voter_name, choice.id)] == 0 %} <td> <input type="checkbox" disabled> </td> + {% else %} + <td> + ?? + </td> {% endif %} {% endfor %} </tr> |
