aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJan Tuomi <jans.tuomi@gmail.com>2024-02-29 09:35:55 +0200
committerJan Tuomi <jan@jantuomi.fi>2024-12-21 19:43:13 +0200
commit65b11d9d029558cacc7b4270dd7eaa2f1f63439e (patch)
treecfaff12ae1b2daba749336e8aecd878035488e54
parent513d520722fe86635965aec2fb5c6666b51c4be2 (diff)
Fix issue where an empty vote submission would not visually show
-rw-r--r--app.py48
-rw-r--r--db.py14
-rw-r--r--migrations/0002_vote_value.sql2
-rw-r--r--static/styles.css4
-rw-r--r--templates/poll_vote_table.html.j214
5 files changed, 49 insertions, 33 deletions
diff --git a/app.py b/app.py
index ca06e37..4af2a4c 100644
--- a/app.py
+++ b/app.py
@@ -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)
diff --git a/db.py b/db.py
index 694f53d..42680cb 100644
--- a/db.py
+++ b/db.py
@@ -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>