Add meal tracker app: Flask + SQLite with auth, dashboard, 31 passing tests, Docker Compose
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
"""Meals blueprint — dashboard, responses, history."""
|
||||
|
||||
from datetime import date, datetime, timedelta
|
||||
|
||||
from flask import (
|
||||
Blueprint,
|
||||
render_template,
|
||||
request,
|
||||
redirect,
|
||||
url_for,
|
||||
session,
|
||||
flash,
|
||||
jsonify,
|
||||
)
|
||||
|
||||
from models import get_dashboard_data, upsert_response, ensure_meal_periods
|
||||
|
||||
meals_bp = Blueprint("meals", __name__)
|
||||
|
||||
|
||||
def login_required(f):
|
||||
"""Decorator: redirect to login if not authenticated."""
|
||||
from functools import wraps
|
||||
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
if "user_id" not in session:
|
||||
return redirect(url_for("auth.login"))
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@meals_bp.route("/")
|
||||
def index():
|
||||
if "user_id" in session:
|
||||
return redirect(url_for("meals.dashboard"))
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
|
||||
@meals_bp.route("/dashboard")
|
||||
@login_required
|
||||
def dashboard():
|
||||
today = date.today()
|
||||
|
||||
# Allow viewing other dates via query param
|
||||
date_str = request.args.get("date")
|
||||
if date_str:
|
||||
try:
|
||||
today = datetime.strptime(date_str, "%Y-%m-%d").date()
|
||||
except ValueError:
|
||||
flash("Invalid date format. Use YYYY-MM-DD.", "error")
|
||||
|
||||
data = get_dashboard_data(today)
|
||||
return render_template(
|
||||
"dashboard.html",
|
||||
dashboard=data,
|
||||
viewing_date=today,
|
||||
today=date.today(),
|
||||
prev_date=today - timedelta(days=1),
|
||||
next_date=today + timedelta(days=1),
|
||||
)
|
||||
|
||||
|
||||
@meals_bp.route("/respond", methods=["POST"])
|
||||
@login_required
|
||||
def respond():
|
||||
meal_type = request.form.get("meal_type", "").strip()
|
||||
status = request.form.get("status", "").strip()
|
||||
date_str = request.form.get("date", date.today().isoformat())
|
||||
|
||||
if meal_type not in ("lunch", "dinner"):
|
||||
flash("Invalid meal type.", "error")
|
||||
return redirect(url_for("meals.dashboard"))
|
||||
|
||||
if status not in ("yes", "no"):
|
||||
flash("Invalid status.", "error")
|
||||
return redirect(url_for("meals.dashboard"))
|
||||
|
||||
try:
|
||||
target_date = datetime.strptime(date_str, "%Y-%m-%d").date()
|
||||
except ValueError:
|
||||
flash("Invalid date.", "error")
|
||||
return redirect(url_for("meals.dashboard"))
|
||||
|
||||
# Only allow responding to today (and not in the past)
|
||||
if target_date < date.today():
|
||||
flash("Cannot change responses for past dates.", "error")
|
||||
return redirect(url_for("meals.dashboard"))
|
||||
|
||||
periods = ensure_meal_periods(target_date)
|
||||
period = next((p for p in periods if p["meal_type"] == meal_type), None)
|
||||
if period is None:
|
||||
flash("Meal period not found.", "error")
|
||||
return redirect(url_for("meals.dashboard"))
|
||||
|
||||
upsert_response(session["user_id"], period["id"], status)
|
||||
return redirect(url_for("meals.dashboard"))
|
||||
|
||||
|
||||
@meals_bp.route("/history")
|
||||
@login_required
|
||||
def history():
|
||||
date_str = request.args.get("date", date.today().isoformat())
|
||||
try:
|
||||
target_date = datetime.strptime(date_str, "%Y-%m-%d").date()
|
||||
except ValueError:
|
||||
target_date = date.today()
|
||||
|
||||
data = get_dashboard_data(target_date)
|
||||
return render_template(
|
||||
"dashboard.html",
|
||||
dashboard=data,
|
||||
viewing_date=target_date,
|
||||
today=date.today(),
|
||||
prev_date=target_date - timedelta(days=1),
|
||||
next_date=target_date + timedelta(days=1),
|
||||
history_mode=True,
|
||||
)
|
||||
Reference in New Issue
Block a user