117 lines
3.7 KiB
Python
117 lines
3.7 KiB
Python
"""Private training preferences and session observations."""
|
|
|
|
from flask import Blueprint, abort, flash, jsonify, redirect, render_template, request, url_for
|
|
from flask_login import current_user, login_required
|
|
|
|
from .extensions import db
|
|
from .models import Activity
|
|
|
|
training = Blueprint("training", __name__)
|
|
|
|
|
|
@training.get("/fitness")
|
|
@login_required
|
|
def fitness():
|
|
from .fitness import history
|
|
|
|
return render_template("fitness.html", data=history(current_user))
|
|
|
|
|
|
@training.get("/fitness/data")
|
|
@login_required
|
|
def fitness_data():
|
|
from .fitness import history
|
|
|
|
return jsonify(history(current_user))
|
|
|
|
|
|
@training.app_context_processor
|
|
def fitness_helpers():
|
|
from .fitness import effort
|
|
from .heart_rate import zones
|
|
|
|
return {"run_effort": effort, "fitness_zones": zones}
|
|
|
|
|
|
@training.post("/training/zones")
|
|
@login_required
|
|
def zones():
|
|
from datetime import date, datetime
|
|
from zoneinfo import ZoneInfo
|
|
|
|
mode = request.form.get("mode")
|
|
if mode not in {"auto", "max", "custom"}:
|
|
abort(400)
|
|
config = {"mode": mode}
|
|
try:
|
|
birthday = request.form.get("birthday", "").strip()
|
|
if birthday:
|
|
birthday_date = date.fromisoformat(birthday)
|
|
today = datetime.now(ZoneInfo(current_user.timezone)).date()
|
|
age = today.year - birthday_date.year - ((today.month, today.day) <
|
|
(birthday_date.month, birthday_date.day))
|
|
if not 5 <= age <= 110:
|
|
abort(400)
|
|
config["birthday"] = birthday
|
|
if mode == "max":
|
|
maximum = int(request.form.get("max_hr", ""))
|
|
if not 100 <= maximum <= 230:
|
|
abort(400)
|
|
config["max_hr"] = maximum
|
|
if mode == "custom":
|
|
boundaries = [int(request.form.get(f"z{i}", "")) for i in range(2, 6)]
|
|
if not 30 <= boundaries[0] < boundaries[1] < boundaries[2] < boundaries[3] <= 230:
|
|
abort(400)
|
|
config["boundaries"] = boundaries
|
|
except (ValueError, TypeError):
|
|
abort(400)
|
|
current_user.hr_zones = config
|
|
db.session.commit()
|
|
flash("Fitness zones saved. Your full fitness history recalculates automatically.")
|
|
return redirect(url_for("training.fitness"))
|
|
|
|
|
|
@training.post("/training/bands")
|
|
@login_required
|
|
def bands():
|
|
raw = [request.form.get("lower", "").strip(), request.form.get("upper", "").strip()]
|
|
if not any(raw):
|
|
current_user.hr_bands = None
|
|
else:
|
|
try:
|
|
low, high = [int(v) for v in raw]
|
|
except ValueError:
|
|
abort(400)
|
|
if not 30 <= low < high <= 240:
|
|
abort(400)
|
|
current_user.hr_bands = [low, high]
|
|
db.session.commit()
|
|
flash("Personal heart-rate bands saved.")
|
|
return redirect(url_for("web.settings"))
|
|
|
|
|
|
@training.post("/activities/<int:activity_id>/context")
|
|
@login_required
|
|
def context(activity_id):
|
|
row = db.first_or_404(db.select(Activity).where(Activity.id == activity_id,
|
|
Activity.owner_id == current_user.id))
|
|
values = {}
|
|
for field, low in (("perceived_effort", 1), ("fatigue", 0)):
|
|
raw = request.form.get(field, "").strip()
|
|
if raw:
|
|
try:
|
|
value = int(raw)
|
|
except ValueError:
|
|
abort(400)
|
|
if not low <= value <= 10:
|
|
abort(400)
|
|
values[field] = value
|
|
notes = request.form.get("notes", "").strip()
|
|
if len(notes) > 2000:
|
|
abort(400)
|
|
values["notes"] = notes
|
|
row.session_context = values
|
|
db.session.commit()
|
|
flash("Private session context saved.")
|
|
return redirect(url_for("web.activity", activity_id=row.id))
|