Jul 28 - Update schedule page's referred time

This commit is contained in:
2026-07-28 15:09:12 -04:00
parent 4bb345da39
commit 81aeeac3d2
6 changed files with 203 additions and 18 deletions
+68 -11
View File
@@ -9,7 +9,9 @@ from logging.handlers import RotatingFileHandler
import bleach
from bleach.css_sanitizer import CSSSanitizer
from flask import Flask, flash, redirect, render_template, request, url_for
from flask import (
Flask, flash, jsonify, redirect, render_template, request, url_for,
)
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import CSRFProtect
from markupsafe import Markup
@@ -200,6 +202,40 @@ def demo_slots(config):
return slots
def taken_slots(day):
"""Times already booked on `day`. Cancelled requests free their slot."""
rows = (
DemoRequest.query
.with_entities(DemoRequest.preferred_time)
.filter(DemoRequest.preferred_date == day,
DemoRequest.status != "cancelled")
.all()
)
return {row[0] for row in rows}
def available_slots(config, day):
"""Bookable times left on `day` — the full grid minus what's taken."""
if day is None:
return demo_slots(config)
booked = taken_slots(day)
return [slot for slot in demo_slots(config) if slot not in booked]
def demo_day_status(config, day):
"""Why a date can't be booked, or None if it's open. Shared by the form and
the /demo/slots endpoint so both give the same answer."""
today = date.today()
if day < today:
return "That date has already passed."
if day > today + timedelta(days=config["DEMO_MAX_DAYS_AHEAD"]):
return (f"Please pick a date within the next "
f"{config['DEMO_MAX_DAYS_AHEAD']} days.")
if day.weekday() >= 5:
return "Demonstrations run Monday through Friday."
return None
def _rate_limited(ip, limit):
"""True if `ip` has already submitted `limit` requests in the last hour."""
if limit <= 0:
@@ -350,15 +386,9 @@ def create_app():
wanted = None
errors.append("Please pick a date for the demonstration.")
if wanted:
if wanted < today:
errors.append("Please pick a date that hasn't passed yet.")
elif wanted > max_date:
errors.append(
f"Please pick a date within the next "
f"{cfg['DEMO_MAX_DAYS_AHEAD']} days."
)
elif wanted.weekday() >= 5:
errors.append("Demonstrations run Monday through Friday.")
closed = demo_day_status(cfg, wanted)
if closed:
errors.append(closed)
if form["preferred_time"] not in slots:
errors.append("Please pick an available time.")
@@ -395,9 +425,19 @@ def create_app():
_notify_demo(app, req)
return redirect(url_for("demo_thanks"))
# Show only what's still free on the date in hand, so a re-render after
# an error can't offer a slot that was taken in the meantime. With no
# date chosen yet the full grid is listed and the JS narrows it as soon
# as one is picked (and without JS the checks above are the backstop).
chosen = None
if form.get("preferred_date"):
try:
chosen = datetime.strptime(form["preferred_date"], "%Y-%m-%d").date()
except ValueError:
chosen = None
return render_template(
"demo.html",
slots=slots,
slots=available_slots(cfg, chosen) if chosen else slots,
form=form,
min_date=today.isoformat(),
max_date=max_date.isoformat(),
@@ -405,6 +445,23 @@ def create_app():
demo_url=cfg["DEMO_CONTACT_URL"],
)
@app.route("/demo/slots")
def demo_slots_api():
"""Times still bookable on ?date=YYYY-MM-DD, for the form's time picker.
Public and read-only: it exposes which slots are free, which is exactly
what the form shows anyway, and nothing about who booked them."""
cfg = app.config
raw = request.args.get("date", "")
try:
day = datetime.strptime(raw, "%Y-%m-%d").date()
except ValueError:
return jsonify(ok=False, error="Invalid date."), 400
closed = demo_day_status(cfg, day)
if closed:
return jsonify(ok=True, date=raw, open=False, reason=closed, slots=[])
return jsonify(ok=True, date=raw, open=True, slots=available_slots(cfg, day))
@app.route("/demo/thanks")
def demo_thanks():
return render_template("demo_thanks.html", demo_url=app.config["DEMO_CONTACT_URL"])