aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app.py22
-rw-r--r--db.py24
-rw-r--r--migrations/0003_whole_day_poll.sql1
-rw-r--r--static/styles.css17
-rw-r--r--templates/index.html.j221
-rw-r--r--templates/manage.html.j270
-rw-r--r--templates/poll_choice_datetime_range.html.j224
-rw-r--r--templates/poll_info_table.html.j224
-rw-r--r--templates/poll_vote_list.html.j221
-rw-r--r--templates/poll_vote_table.html.j220
10 files changed, 143 insertions, 101 deletions
diff --git a/app.py b/app.py
index 3cf9e71..f9e3b03 100644
--- a/app.py
+++ b/app.py
@@ -37,7 +37,7 @@ def validation_error(message: str):
return render_template("error.html.j2", error=message), 400
@app.route("/")
-def hello_world():
+def index():
created_poll_codes = []
for k, _ in request.cookies.items():
if k.startswith("diddle_manage_code_"):
@@ -71,6 +71,7 @@ def create():
form["description"],
form["author_name"],
form["author_email"],
+ "is_whole_day" in form,
choices,
)
@@ -177,6 +178,7 @@ def update_poll_info(code):
form["description"],
form["author_name"],
form["author_email"],
+ "is_whole_day" in form,
)
return redirect(f"/manage/{code}")
@@ -188,16 +190,24 @@ def add_choice(code):
return validation_error("Start datetime is required")
if "end_datetime" not in form or len(form["end_datetime"]) == 0:
return validation_error("End datetime is required")
- if form["start_datetime"] >= form["end_datetime"]:
+ if form["start_datetime"] > form["end_datetime"]:
return validation_error("Start datetime must be before end datetime")
+ start_datetime = form["start_datetime"]
+ if len(start_datetime) == 10:
+ start_datetime += "T00:00"
+
+ end_datetime = form["end_datetime"]
+ if len(end_datetime) == 10:
+ end_datetime += "T23:59"
+
db.add_choice_to_poll(
code,
- form["start_datetime"],
- form["end_datetime"],
+ start_datetime,
+ end_datetime,
)
- return redirect(f"/manage/{code}")
+ return redirect(f"/manage/{code}?focus_next=1")
@app.post("/manage/<code>/delete_choice/<choice_id>")
def delete_choice(code, choice_id):
@@ -207,7 +217,7 @@ def delete_choice(code, choice_id):
db.delete_choice(choice_id)
- return redirect(f"/manage/{code}")
+ return redirect(f"/manage/{code}?focus_next=1")
@app.get("/manage/<code>")
def manage(code):
diff --git a/db.py b/db.py
index f660d94..57b54e3 100644
--- a/db.py
+++ b/db.py
@@ -70,9 +70,19 @@ class Choice:
def end_datetime_notz(self):
return self.end_datetime.replace(tzinfo=None)
+ def start_date_notz(self):
+ return self.start_datetime.date()
+
+ def end_date_notz(self):
+ return self.end_datetime.date()
+
def ends_on_same_day(self):
return self.start_datetime.date() == self.end_datetime.date()
+ def ends_at_same_datetime(self):
+ return self.start_datetime.date() == self.end_datetime.date() \
+ and self.start_datetime.time() == self.end_datetime.time()
+
@dataclass
class Poll:
id: str
@@ -83,6 +93,7 @@ class Poll:
author_email: str | None
choices: list[Choice]
manage_code: str
+ is_whole_day: bool
def share_url(self):
return f"{BASE_URL}/poll/{self.id}"
@@ -99,6 +110,7 @@ def tuple_to_poll(poll_t: tuple) -> Poll:
author_name=poll_t[4],
author_email=poll_t[5],
manage_code=poll_t[6],
+ is_whole_day=poll_t[7],
choices=[]
)
@@ -159,13 +171,14 @@ def create_poll(title: str,
description: str,
author_name: str,
author_email: str,
+ is_whole_day: bool,
choices: list[Choice]):
with db.cursor() as (conn, cur):
try:
- cur.execute("INSERT INTO polls (title, description, author_name, author_email)"
- "VALUES (%s, %s, %s, %s) RETURNING *",
- (title, description, author_name, author_email))
+ cur.execute("INSERT INTO polls (title, description, author_name, author_email, whole_day)"
+ "VALUES (%s, %s, %s, %s, %s) RETURNING *",
+ (title, description, author_name, author_email, is_whole_day))
poll_t = cur.fetchone()
if poll_t is None:
@@ -228,12 +241,13 @@ def update_poll_info(
description,
author_name,
author_email,
+ is_whole_day,
) -> None:
with db.cursor() as (conn, cur):
try:
- cur.execute("UPDATE polls SET title = %s, description = %s, author_name = %s, author_email = %s "
+ cur.execute("UPDATE polls SET title = %s, description = %s, author_name = %s, author_email = %s, whole_day = %s "
"WHERE manage_code = %s",
- (title, description, author_name, author_email, code))
+ (title, description, author_name, author_email, is_whole_day, code))
conn.commit()
except Exception as e:
conn.rollback()
diff --git a/migrations/0003_whole_day_poll.sql b/migrations/0003_whole_day_poll.sql
new file mode 100644
index 0000000..e532d0c
--- /dev/null
+++ b/migrations/0003_whole_day_poll.sql
@@ -0,0 +1 @@
+ALTER TABLE polls ADD COLUMN IF NOT EXISTS whole_day BOOLEAN NOT NULL DEFAULT FALSE;
diff --git a/static/styles.css b/static/styles.css
index 229149c..0e538e8 100644
--- a/static/styles.css
+++ b/static/styles.css
@@ -43,13 +43,13 @@ input[type="submit"].yellow {
}
input[type="text"],
-input[type="datetime-local"] {
+input[type*="date"] {
font-size: 16px;
color: black;
}
input[type="text"]:disabled,
-input[type="datetime-local"]:disabled {
+input[type*="date"]:disabled {
color: #666;
}
@@ -73,6 +73,10 @@ table {
padding: 7px 5px;
}
+.vote-table th {
+ vertical-align: bottom;
+}
+
.vote-table th span {
display: block;
}
@@ -140,3 +144,12 @@ input[type="checkbox"] {
input[type="checkbox"]:disabled {
cursor: initial;
}
+
+td.checkbox-field {
+ text-align: left;
+}
+
+td.checkbox-field input[type="checkbox"] {
+ flex: initial;
+ margin-left: 0;
+}
diff --git a/templates/index.html.j2 b/templates/index.html.j2
index f84b428..db458da 100644
--- a/templates/index.html.j2
+++ b/templates/index.html.j2
@@ -3,26 +3,7 @@
{% block content %}
<h2>Create new diddle</h2>
<form class="poll-form" action="/poll/create" method="post">
- <table>
- <tbody>
- <tr>
- <td><label for="title">Title</label></td>
- <td><input type="text" name="title" required></td>
- </tr>
- <tr>
- <td><label for="description">Description (optional)</label></td>
- <td><textarea name="description"></textarea></td>
- </tr>
- <tr>
- <td><label for="author_name">Your name</label></td>
- <td><input type="text" name="author_name" required></td>
- </tr>
- <tr>
- <td><label for="author_email">Your email (optional)</label></td>
- <td><input type="text" name="author_email"></td>
- </tr>
- </tbody>
- </table>
+ {% include "poll_info_table.html.j2" %}
<p>You can add options after submitting.</p>
<input class="green" type="submit" value="Create">
diff --git a/templates/manage.html.j2 b/templates/manage.html.j2
index 55a14bd..4a5da8d 100644
--- a/templates/manage.html.j2
+++ b/templates/manage.html.j2
@@ -14,27 +14,8 @@
</p>
<form class="poll-form" action="/manage/{{ poll.manage_code }}/update_info" method="post">
- <table>
- <tbody>
- <tr>
- <td><label for="title">Title</label></td>
- <td><input type="text" name="title" value="{{ poll.title }}" required></td>
- </tr>
- <tr>
- <td><label for="description">Description (optional)</label></td>
- <td><textarea name="description">{{ poll.description }}</textarea></td>
- </tr>
- <tr>
- <td><label for="author_name">Your name</label></td>
- <td><input type="text" name="author_name" value="{{ poll.author_name }}" required></td>
- </tr>
- <tr>
- <td><label for="author_email">Your email (optional)</label></td>
- <td><input type="text" name="author_email" value="{{ poll.author_email }}"></td>
- </tr>
- </tbody>
- </table>
- <br>
+ {% include "poll_info_table.html.j2" %}
+ <p></p>
<input type="submit" value="Update">
</form>
@@ -55,11 +36,17 @@
<form action="/manage/{{ poll.manage_code }}/delete_choice/{{ choice.id }}" method="post">
<tr>
<td>
- <input type="datetime-local" name="start_datetime_{{ choice.id }}" value="{{ choice.start_datetime_notz() }}" disabled
+ <input type="{% if poll.is_whole_day %}date{% else %}datetime-local{% endif %}"
+ name="start_datetime_{{ choice.id }}"
+ value="{% if poll.is_whole_day %}{{ choice.start_date_notz() }}{% else %}{{ choice.start_datetime_notz() }}{% endif %}"
+ disabled
{% if last_choice_id == choice.id %}id="last-start-datetime"{% endif %}>
</td>
<td>
- <input type="datetime-local" name="end_datetime_{{ choice.id }}" value="{{ choice.end_datetime_notz() }}" disabled
+ <input type="{% if poll.is_whole_day %}date{% else %}datetime-local{% endif %}"
+ name="end_datetime_{{ choice.id }}"
+ value="{% if poll.is_whole_day %}{{ choice.end_date_notz() }}{% else %}{{ choice.end_datetime_notz() }}{% endif %}"
+ disabled
{% if last_choice_id == choice.id %}id="last-end-datetime"{% endif %}>
</td>
<td>
@@ -71,10 +58,10 @@
<form action="/manage/{{ poll.manage_code }}/add_choice" method="post">
<tr>
<td>
- <input type="datetime-local" name="start_datetime" required>
+ <input type="{% if poll.is_whole_day %}date{% else %}datetime-local{% endif %}" name="start_datetime" required>
</td>
<td>
- <input type="datetime-local" name="end_datetime" required>
+ <input type="{% if poll.is_whole_day %}date{% else %}datetime-local{% endif %}" name="end_datetime" required>
</td>
<td>
<input class="green" type="submit" value="Add">
@@ -94,7 +81,9 @@
</div>
<script>
+const isWholeDay = {% if poll.is_whole_day %}true{% else %}false{% endif %};
const hour = 1000 * 60 * 60;
+const day = hour * 24;
const startDatetimeInput = document.querySelector('input[name="start_datetime"]');
const endDatetimeInput = document.querySelector('input[name="end_datetime"]');
@@ -103,15 +92,38 @@ const lastStartDatetimeInput = document.getElementById('last-start-datetime');
const lastEndDatetimeInput = document.getElementById('last-end-datetime');
if (lastStartDatetimeInput !== null && startDatetimeInput.valueAsDate === null) {
- startDatetimeInput.valueAsDate = new Date(lastStartDatetimeInput.valueAsDate.getTime() + hour);
+ if (isWholeDay) {
+ // set the start date to the last end date + 1 day if in whole day mode
+ startDatetimeInput.valueAsDate = new Date(lastEndDatetimeInput.valueAsDate.getTime() + day);
+ endDatetimeInput.valueAsDate = new Date(startDatetimeInput.valueAsDate.getTime());
+ } else {
+ // set the start date to the last end date + 1 hour if not in whole day mode
+ startDatetimeInput.valueAsDate = new Date(lastEndDatetimeInput.valueAsDate.getTime());
+ endDatetimeInput.valueAsDate = new Date(startDatetimeInput.valueAsDate.getTime() + hour);
+ }
}
-startDatetimeInput.addEventListener('blur', onStartDatetimeBlur);
+startDatetimeInput.addEventListener('change', onStartDatetimeChange);
-function onStartDatetimeBlur(event) {
+function onStartDatetimeChange(event) {
const endDatetimeInput = document.querySelector('input[name="end_datetime"]');
if (endDatetimeInput.valueAsDate === null) {
- endDatetimeInput.valueAsDate = new Date(event.target.valueAsDate.getTime() + hour);
+ if (isWholeDay) {
+ // set the end date to the start date if in whole day mode
+ endDatetimeInput.valueAsDate = new Date(event.target.valueAsDate.getTime());
+ } else {
+ // set the end date to the start date + 1 hour if not in whole day mode
+ endDatetimeInput.valueAsDate = new Date(event.target.valueAsDate.getTime() + hour);
+ }
+ }
+}
+
+const query = new URLSearchParams(window.location.search);
+const focusNext = query.get('focus_next');
+if (focusNext !== null) {
+ const nextInput = document.querySelector(`input[name="start_datetime"]`);
+ if (nextInput !== null) {
+ nextInput.focus();
}
}
</script>
diff --git a/templates/poll_choice_datetime_range.html.j2 b/templates/poll_choice_datetime_range.html.j2
new file mode 100644
index 0000000..ddfa970
--- /dev/null
+++ b/templates/poll_choice_datetime_range.html.j2
@@ -0,0 +1,24 @@
+<span>
+{{ choice.start_datetime.strftime("%a %d.%m") }}
+</span>
+<span>
+ {% if now.year != choice.start_datetime.year %}.{{choice.start_datetime.year}}{% endif %}
+</span>
+<span>
+ {% if not poll.is_whole_day %}{{ choice.start_datetime.strftime("%H:%M") }}{% endif %}
+</span>
+{% if (poll.is_whole_day and not choice.ends_on_same_day()) or (not poll.is_whole_day and not choice.ends_at_same_datetime()) %}
+<span>&mdash;</span>
+{% endif %}
+
+{% if not choice.ends_on_same_day() %}
+<span>
+ {{ choice.end_datetime.strftime("%a %d.%m") }}
+</span>
+<span>
+ {% if now.year != choice.end_datetime.year %}.{{choice.end_datetime.year}}{% endif %}
+</span>
+{% endif %}
+<span>
+ {% if not poll.is_whole_day %}{{ choice.end_datetime.strftime("%H:%M") }}{% endif %}
+</span>
diff --git a/templates/poll_info_table.html.j2 b/templates/poll_info_table.html.j2
new file mode 100644
index 0000000..7955ca8
--- /dev/null
+++ b/templates/poll_info_table.html.j2
@@ -0,0 +1,24 @@
+<table>
+ <tbody>
+ <tr>
+ <td><label for="title">Title</label></td>
+ <td><input type="text" name="title" value="{% if poll %}{{ poll.title }}{% endif %}" required></td>
+ </tr>
+ <tr>
+ <td><label for="description">Description (optional)</label></td>
+ <td><textarea name="description">{% if poll %}{{ poll.description }}{% endif %}</textarea></td>
+ </tr>
+ <tr>
+ <td><label for="author_name">Your name</label></td>
+ <td><input type="text" name="author_name" value="{% if poll %}{{ poll.author_name }}{% endif %}" required></td>
+ </tr>
+ <tr>
+ <td><label for="author_email">Your email (optional)</label></td>
+ <td><input type="text" name="author_email" value="{% if poll %}{{ poll.author_email }}{% endif %}"></td>
+ </tr>
+ <tr>
+ <td><label for="is_whole_day">Whole day event?</label></td>
+ <td class="checkbox-field"><input type="checkbox" name="is_whole_day" {% if poll and poll.is_whole_day %}checked{% endif %}></td>
+ </tr>
+ </tbody>
+</table>
diff --git a/templates/poll_vote_list.html.j2 b/templates/poll_vote_list.html.j2
index c18b3e0..845356b 100644
--- a/templates/poll_vote_list.html.j2
+++ b/templates/poll_vote_list.html.j2
@@ -4,26 +4,7 @@
<label for="choice_{{ choice.id }}">
<input type="checkbox" name="choice_{{ choice.id }}" id="choice_{{ choice.id }}">
<strong>
- {{ choice.start_datetime.strftime("%a %d.%m") }}
- <span>
- {% if now.year != choice.start_datetime.year %}.{{choice.start_datetime.year}}{% endif %}
- </span>
- <span>
- {{ choice.start_datetime.strftime("%H:%M") }}
- </span>
- <span>&mdash;</span>
- {% if not choice.ends_on_same_day() %}
- <span>
- {{ choice.end_datetime.strftime("%a %d.%m") }}
- </span>
- <span>
- {% if now.year != choice.end_datetime.year %}.{{choice.end_datetime.year}}{% endif %}
- </span>
- {% endif %}
- <span>
- {{ choice.end_datetime.strftime("%H:%M") }}
- </span>
- </span>
+ {% include "poll_choice_datetime_range.html.j2" %}
</strong>
<span>
<i>{{ choice.votes | length }} votes</i>
diff --git a/templates/poll_vote_table.html.j2 b/templates/poll_vote_table.html.j2
index cfa336a..97fcac5 100644
--- a/templates/poll_vote_table.html.j2
+++ b/templates/poll_vote_table.html.j2
@@ -3,25 +3,7 @@
<th></th>
{% for choice in choices %}
<th>
- {{ choice.start_datetime.strftime("%a %d.%m") }}
- <span>
- {% if now.year != choice.start_datetime.year %}.{{choice.start_datetime.year}}{% endif %}
- </span>
- <span>
- {{ choice.start_datetime.strftime("%H:%M") }}
- </span>
- <span>&mdash;</span>
- {% if not choice.ends_on_same_day() %}
- <span>
- {{ choice.end_datetime.strftime("%a %d.%m") }}
- </span>
- <span>
- {% if now.year != choice.end_datetime.year %}.{{choice.end_datetime.year}}{% endif %}
- </span>
- {% endif %}
- <span>
- {{ choice.end_datetime.strftime("%H:%M") }}
- </span>
+ {% include "poll_choice_datetime_range.html.j2" %}
<span>
<i>{{ choice.votes | length }} votes</i>
</span>