674 lines
28 KiB
Python
674 lines
28 KiB
Python
from datetime import datetime, date, timedelta
|
|
from dateutil.relativedelta import relativedelta
|
|
import os
|
|
from flask import Flask, abort, render_template, redirect, request, url_for
|
|
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
|
import jinja_partials
|
|
from jinja2_fragments import render_block
|
|
from decorators import validate_person, validate_topset, validate_workout
|
|
from db import DataBase
|
|
from utils import count_prs_over_time, get_people_and_exercise_rep_maxes, convert_str_to_date, get_earliest_and_latest_workout_date, filter_workout_topsets, first_and_last_visible_days_in_month, get_weekly_pr_graph_model, get_workout_counts, generate_plot
|
|
from flask_htmx import HTMX
|
|
import minify_html
|
|
from urllib.parse import quote
|
|
import os
|
|
from dotenv import load_dotenv
|
|
|
|
# Load environment variables from .env file in non-production environments
|
|
if os.environ.get('FLASK_ENV') != 'production':
|
|
load_dotenv()
|
|
|
|
app = Flask(__name__)
|
|
app.config.from_pyfile('config.py')
|
|
jinja_partials.register_extensions(app)
|
|
db = DataBase(app)
|
|
htmx = HTMX(app)
|
|
|
|
|
|
@app.after_request
|
|
def response_minify(response):
|
|
"""
|
|
minify html response to decrease site traffic
|
|
"""
|
|
if response.content_type == u'text/html; charset=utf-8':
|
|
response.set_data(
|
|
minify_html.minify(response.get_data(
|
|
as_text=True), minify_js=True, remove_processing_instructions=True)
|
|
)
|
|
|
|
return response
|
|
return response
|
|
|
|
|
|
@ app.route("/")
|
|
def dashboard():
|
|
all_topsets = db.get_all_topsets()
|
|
|
|
exercises = db.get_all_exercises()
|
|
people = db.get_people()
|
|
tags = db.get_tags_for_dashboard()
|
|
|
|
selected_person_ids = [int(i)
|
|
for i in request.args.getlist('person_id')]
|
|
if not selected_person_ids and htmx.trigger_name != 'person_id':
|
|
selected_person_ids = [p['PersonId'] for p in people]
|
|
|
|
selected_exercise_ids = [int(i)
|
|
for i in request.args.getlist('exercise_id')]
|
|
if not selected_exercise_ids and htmx.trigger_name != 'exercise_id':
|
|
selected_exercise_ids = [e['exercise_id'] for e in exercises]
|
|
|
|
min_date = convert_str_to_date(request.args.get(
|
|
'min_date'), '%Y-%m-%d') or min([t['StartDate'] for t in all_topsets])
|
|
max_date = convert_str_to_date(request.args.get(
|
|
'max_date'), '%Y-%m-%d') or max([t['StartDate'] for t in all_topsets])
|
|
|
|
people_and_exercise_rep_maxes = get_people_and_exercise_rep_maxes(
|
|
all_topsets, selected_person_ids, selected_exercise_ids, min_date, max_date)
|
|
|
|
weekly_counts = get_workout_counts(all_topsets, 'week')
|
|
weekly_pr_counts = count_prs_over_time(all_topsets, 'week')
|
|
dashboard_graphs = [get_weekly_pr_graph_model('Workouts per week', weekly_counts), get_weekly_pr_graph_model('PRs per week', weekly_pr_counts)]
|
|
|
|
if htmx:
|
|
return render_block(app.jinja_env, 'dashboard.html', 'content', model=people_and_exercise_rep_maxes, people=people, exercises=exercises, min_date=min_date, max_date=max_date, selected_person_ids=selected_person_ids, selected_exercise_ids=selected_exercise_ids, tags=tags, dashboard_graphs=dashboard_graphs)
|
|
return render_template('dashboard.html', model=people_and_exercise_rep_maxes, people=people, exercises=exercises, min_date=min_date, max_date=max_date, selected_person_ids=selected_person_ids, selected_exercise_ids=selected_exercise_ids, tags=tags, dashboard_graphs=dashboard_graphs)
|
|
|
|
|
|
@ app.route("/person/list", methods=['GET'])
|
|
def get_person_list():
|
|
people = db.get_people_and_workout_count(-1)
|
|
return render_template('partials/people_link.html', people=people)
|
|
|
|
|
|
@ app.route("/person/<int:person_id>/workout/list", methods=['GET'])
|
|
@ validate_person
|
|
def get_person(person_id):
|
|
person = db.get_person(person_id)
|
|
tags = db.get_tags_for_person(person_id)
|
|
|
|
(min_date, max_date) = get_earliest_and_latest_workout_date(person)
|
|
|
|
min_date = request.args.get(
|
|
'min_date', default=min_date, type=convert_str_to_date)
|
|
max_date = request.args.get(
|
|
'max_date', default=max_date, type=convert_str_to_date)
|
|
|
|
selected_exercise_ids = request.args.getlist('exercise_id', type=int)
|
|
all_exercise_ids_for_person = [e['ExerciseId']
|
|
for e in person['Exercises']]
|
|
|
|
if not selected_exercise_ids and htmx.trigger_name != 'exercise_id':
|
|
selected_exercise_ids = all_exercise_ids_for_person
|
|
|
|
person['Workouts'] = [filter_workout_topsets(workout, selected_exercise_ids) for workout in person['Workouts'] if
|
|
workout['StartDate'] <= max_date and workout['StartDate'] >= min_date]
|
|
|
|
# Filter out workouts that dont contain any of the selected exercises
|
|
person['Workouts'] = [workout for workout in person['Workouts'] if
|
|
workout['TopSets']]
|
|
|
|
filtered_exercises = filter(
|
|
lambda e: e['ExerciseId'] in selected_exercise_ids, person['Exercises'])
|
|
person['FilteredExercises'] = list(filtered_exercises)
|
|
|
|
person['ExerciseProgressGraphs'] = list(filter(lambda e: e['ExerciseId'] in selected_exercise_ids, person['ExerciseProgressGraphs']))
|
|
|
|
if htmx:
|
|
return render_block(app.jinja_env, 'person.html', 'content', person=person, selected_exercise_ids=selected_exercise_ids, max_date=max_date, min_date=min_date, tags=tags), 200, {"HX-Trigger": "updatedPeople"}
|
|
|
|
return render_template('person.html', person=person, selected_exercise_ids=selected_exercise_ids, max_date=max_date, min_date=min_date, tags=tags), 200, {"HX-Trigger": "updatedPeople"}
|
|
|
|
@ app.route("/person/<int:person_id>/workout/overview", methods=['GET'])
|
|
def person_overview(person_id):
|
|
min_date = request.args.get('min_date', type=convert_str_to_date)
|
|
max_date = request.args.get('max_date', type=convert_str_to_date)
|
|
selected_exercise_ids = request.args.getlist('exercise_id', type=int)
|
|
|
|
if not min_date or not max_date:
|
|
db_min_date, db_max_date = db.person_overview.get_earliest_and_latest_workout_dates(person_id)
|
|
min_date = min_date or db_min_date
|
|
max_date = max_date or db_max_date
|
|
|
|
if not selected_exercise_ids and htmx.trigger_name != 'exercise_id':
|
|
selected_exercise_ids = db.person_overview.list_of_performed_exercise_ids(person_id, min_date, max_date)
|
|
|
|
person = db.person_overview.get(person_id, min_date, max_date, selected_exercise_ids)
|
|
exercises = db.person_overview.get_exercises_with_selection(person_id, min_date, max_date, selected_exercise_ids)
|
|
tags = db.get_tags_for_person(person_id)
|
|
|
|
# Render the appropriate response for HTMX or full page
|
|
render_args = {
|
|
**person,
|
|
"exercises": exercises,
|
|
"tags": tags,
|
|
"selected_exercise_ids": selected_exercise_ids,
|
|
"max_date": max_date,
|
|
"min_date": min_date
|
|
}
|
|
|
|
if htmx:
|
|
return render_block(app.jinja_env, 'person_overview.html', 'content', **render_args), 200, {"HX-Trigger": "updatedPeople"}
|
|
|
|
return render_template('person_overview.html', **render_args), 200, {"HX-Trigger": "updatedPeople"}
|
|
|
|
|
|
|
|
@ app.route("/person/<int:person_id>/calendar")
|
|
@ validate_person
|
|
def get_calendar(person_id):
|
|
selected_date = convert_str_to_date(request.args.get(
|
|
'date'), '%Y-%m-%d') or date.today()
|
|
selected_view = request.args.get('view') or 'month'
|
|
|
|
if selected_view == 'all':
|
|
return redirect(url_for('get_person', person_id=person_id))
|
|
elif selected_view == 'notes':
|
|
return redirect(url_for('get_person_notes', person_id=person_id))
|
|
|
|
calendar_view = db.calendar.fetch_workouts_for_person(person_id, selected_date, selected_view)
|
|
|
|
if htmx:
|
|
return render_block(app.jinja_env, 'calendar.html', 'content', **calendar_view), 200, {"HX-Push-Url": url_for('get_calendar', person_id=person_id, view=selected_view, date=selected_date)}
|
|
return render_template('calendar.html', **calendar_view), 200, {"HX-Push-Url": url_for('get_calendar', person_id=person_id, view=selected_view, date=selected_date)}
|
|
|
|
@ app.route("/person/<int:person_id>/notes", methods=['GET'])
|
|
@ validate_person
|
|
def get_person_notes(person_id):
|
|
(person_name, workout_notes) = db.get_workout_notes_for_person(person_id)
|
|
if htmx:
|
|
return render_block(app.jinja_env, 'notes.html', 'content', person_id=person_id, person_name=person_name, workout_notes=workout_notes)
|
|
return render_template('notes.html', person_id=person_id, person_name=person_name, workout_notes=workout_notes)
|
|
|
|
@ app.route("/person/<int:person_id>/workout", methods=['POST'])
|
|
def create_workout(person_id):
|
|
new_workout_id = db.create_workout(person_id)
|
|
workout = db.get_workout(person_id, new_workout_id)
|
|
(person_tags, workout_tags, selected_workout_tag_ids) = db.get_workout_tags(
|
|
person_id, new_workout_id)
|
|
|
|
view_model = db.workout.get(person_id, new_workout_id)
|
|
return render_block(app.jinja_env, 'workout.html', 'content', **view_model)
|
|
|
|
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/delete", methods=['GET'])
|
|
@ validate_workout
|
|
def delete_workout(person_id, workout_id):
|
|
db.delete_workout(workout_id)
|
|
return redirect(url_for('get_calendar', person_id=person_id))
|
|
#return "", 200, {"HX-Trigger": "updatedPeople", "HX-Push-Url": url_for('get_calendar', person_id=person_id)}
|
|
|
|
|
|
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/start_date_edit_form", methods=['GET'])
|
|
@ validate_workout
|
|
def get_workout_start_date_edit_form(person_id, workout_id):
|
|
workout = db.get_workout(person_id, workout_id)
|
|
return render_template('partials/start_date.html', person_id=person_id, workout_id=workout_id, start_date=workout['start_date'], is_edit=True)
|
|
|
|
|
|
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/start_date", methods=['PUT'])
|
|
@ validate_workout
|
|
def update_workout_start_date(person_id, workout_id):
|
|
new_start_date = request.form.get('start-date')
|
|
db.update_workout_start_date(workout_id, new_start_date)
|
|
return render_template('partials/start_date.html', person_id=person_id, workout_id=workout_id, start_date=convert_str_to_date(new_start_date, '%Y-%m-%d'))
|
|
|
|
|
|
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/start_date", methods=['GET'])
|
|
@ validate_workout
|
|
def get_workout_start_date(person_id, workout_id):
|
|
workout = db.get_workout(person_id, workout_id)
|
|
return render_template('partials/start_date.html', person_id=person_id, workout_id=workout_id, start_date=workout['start_date'])
|
|
|
|
|
|
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/topset/<int:topset_id>", methods=['GET'])
|
|
@ validate_topset
|
|
def get_topset(person_id, workout_id, topset_id):
|
|
topset = db.get_topset(topset_id)
|
|
return render_template('partials/topset.html', person_id=person_id, workout_id=workout_id, topset_id=topset_id, exercise_id=topset['exercise_id'], exercise_name=topset['exercise_name'], repetitions=topset['repetitions'], weight=topset['weight'])
|
|
|
|
|
|
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/topset/<int:topset_id>/edit_form", methods=['GET'])
|
|
@ validate_topset
|
|
def get_topset_edit_form(person_id, workout_id, topset_id):
|
|
exercises = db.get_all_exercises()
|
|
topset = db.get_topset(topset_id)
|
|
return render_template('partials/topset.html', person_id=person_id, workout_id=workout_id, topset_id=topset_id, exercises=exercises, exercise_id=topset['exercise_id'], exercise_name=topset['exercise_name'], repetitions=topset['repetitions'], weight=topset['weight'], is_edit=True)
|
|
|
|
|
|
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/topset", methods=['POST'])
|
|
@ validate_workout
|
|
def create_topset(person_id, workout_id):
|
|
exercise_id = request.form.get("exercise_id")
|
|
repetitions = request.form.get("repetitions")
|
|
weight = request.form.get("weight")
|
|
|
|
new_topset_id = db.create_topset(
|
|
workout_id, exercise_id, repetitions, weight)
|
|
exercise = db.get_exercise(exercise_id)
|
|
|
|
return render_template('partials/topset.html', person_id=person_id, workout_id=workout_id, topset_id=new_topset_id, exercise_id=exercise_id, exercise_name=exercise['name'], repetitions=repetitions, weight=weight), 200, {"HX-Trigger": "topsetAdded"}
|
|
|
|
|
|
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/topset/<int:topset_id>", methods=['PUT'])
|
|
@ validate_workout
|
|
def update_topset(person_id, workout_id, topset_id):
|
|
exercise_id = request.form.get("exercise_id")
|
|
repetitions = request.form.get("repetitions")
|
|
weight = request.form.get("weight")
|
|
|
|
db.update_topset(exercise_id, repetitions, weight, topset_id)
|
|
exercise = db.get_exercise(exercise_id)
|
|
|
|
return render_template('partials/topset.html', person_id=person_id, workout_id=workout_id, topset_id=topset_id, exercise_name=exercise['name'], repetitions=repetitions, weight=weight)
|
|
|
|
|
|
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/topset/<int:topset_id>/delete", methods=['DELETE'])
|
|
@ validate_topset
|
|
def delete_topset(person_id, workout_id, topset_id):
|
|
db.delete_topset(topset_id)
|
|
return ""
|
|
|
|
|
|
@ app.route("/person", methods=['POST'])
|
|
def create_person():
|
|
name = request.form.get("name")
|
|
new_person_id = db.create_person(name)
|
|
return render_template('partials/person.html', person_id=new_person_id, name=name), 200, {"HX-Trigger": "updatedPeople"}
|
|
|
|
|
|
@ app.route("/person/<int:person_id>/delete", methods=['DELETE'])
|
|
def delete_person(person_id):
|
|
db.delete_person(person_id)
|
|
return "", 200, {"HX-Trigger": "updatedPeople"}
|
|
|
|
|
|
@ app.route("/person/<int:person_id>/edit_form", methods=['GET'])
|
|
def get_person_edit_form(person_id):
|
|
person = db.get_person(person_id)
|
|
return render_template('partials/person.html', person_id=person_id, name=person['PersonName'], is_edit=True)
|
|
|
|
|
|
@ app.route("/person/<int:person_id>/name", methods=['PUT'])
|
|
def update_person_name(person_id):
|
|
new_name = request.form.get("name")
|
|
db.update_person_name(person_id, new_name)
|
|
return render_template('partials/person.html', person_id=person_id, name=new_name), 200, {"HX-Trigger": "updatedPeople"}
|
|
|
|
|
|
@ app.route("/person/<int:person_id>/name", methods=['GET'])
|
|
def get_person_name(person_id):
|
|
person = db.get_person(person_id)
|
|
return render_template('partials/person.html', person_id=person_id, name=person['PersonName'])
|
|
|
|
|
|
@ app.route("/exercise", methods=['POST'])
|
|
def create_exercise():
|
|
name = request.form.get("name")
|
|
new_exercise_id = db.create_exercise(name)
|
|
return render_template('partials/exercise.html', exercise_id=new_exercise_id, name=name)
|
|
|
|
|
|
@ app.route("/exercise/<int:exercise_id>", methods=['GET'])
|
|
def get_exercise(exercise_id):
|
|
exercise = db.get_exercise(exercise_id)
|
|
return render_template('partials/exercise.html', exercise_id=exercise_id, name=exercise.name)
|
|
|
|
|
|
@ app.route("/exercise/<int:exercise_id>/edit_form", methods=['GET'])
|
|
def get_exercise_edit_form(exercise_id):
|
|
exercise = db.get_exercise(exercise_id)
|
|
return render_template('partials/exercise.html', exercise_id=exercise_id, name=exercise['name'], is_edit=True)
|
|
|
|
|
|
@ app.route("/exercise/<int:exercise_id>/update", methods=['PUT'])
|
|
def update_exercise(exercise_id):
|
|
new_name = request.form.get('name')
|
|
db.update_exercise(exercise_id, new_name)
|
|
return render_template('partials/exercise.html', exercise_id=exercise_id, name=new_name)
|
|
|
|
|
|
""" @ app.route("/exercise/<int:exercise_id>/delete", methods=['DELETE'])
|
|
def delete_exercise(exercise_id):
|
|
db.delete_exercise(exercise_id)
|
|
return "" """
|
|
|
|
|
|
@ app.route("/settings")
|
|
def settings():
|
|
people = db.get_people()
|
|
exercises = db.get_all_exercises()
|
|
if htmx:
|
|
return render_block(app.jinja_env, "settings.html", "content", people=people, exercises=exercises), 200, {"HX-Trigger": "updatedPeople"}
|
|
return render_template('settings.html', people=people, exercises=exercises)
|
|
|
|
|
|
@ app.route("/tag/redirect", methods=['GET'])
|
|
def goto_tag():
|
|
person_id = request.args.get("person_id")
|
|
tag_filter = request.args.get('filter')
|
|
if person_id:
|
|
return redirect(url_for('person_overview', person_id=int(person_id)) + tag_filter)
|
|
return redirect(url_for('dashboard') + tag_filter)
|
|
|
|
|
|
@ app.route("/tag/add", methods=['GET'])
|
|
def add_tag():
|
|
person_id = request.args.get("person_id")
|
|
tag = request.args.get('tag')
|
|
tag_filter = request.args.get('filter')
|
|
if person_id:
|
|
db.add_or_update_tag_for_person(person_id, tag, tag_filter)
|
|
else:
|
|
db.add_or_update_tag_for_dashboard(tag, tag_filter)
|
|
return ""
|
|
|
|
|
|
@ app.route("/tag/<int:tag_id>/delete", methods=['GET'])
|
|
def delete_tag(tag_id):
|
|
person_id = request.args.get("person_id")
|
|
tag_filter = request.args.get("filter")
|
|
if person_id:
|
|
db.delete_tag_for_person(person_id=person_id, tag_id=tag_id)
|
|
return redirect(url_for('get_person', person_id=person_id) + tag_filter)
|
|
db.delete_tag_for_dashboard(tag_id)
|
|
return redirect(url_for('dashboard') + tag_filter)
|
|
|
|
|
|
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/note/edit", methods=['GET'])
|
|
@ validate_workout
|
|
def get_workout_note_edit_form(person_id, workout_id):
|
|
workout = db.get_workout(person_id, workout_id)
|
|
return render_template('partials/workout_note.html', person_id=person_id, workout_id=workout_id, note=workout['note'], is_edit=True)
|
|
|
|
|
|
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/note", methods=['PUT'])
|
|
@ validate_workout
|
|
def update_workout_note(person_id, workout_id):
|
|
note = request.form.get('note')
|
|
db.update_workout_note_for_person(person_id, workout_id, note)
|
|
return render_template('partials/workout_note.html', person_id=person_id, workout_id=workout_id, note=note)
|
|
|
|
|
|
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/note", methods=['GET'])
|
|
@ validate_workout
|
|
def get_workout_note(person_id, workout_id):
|
|
workout = db.get_workout(person_id, workout_id)
|
|
return render_template('partials/workout_note.html', person_id=person_id, workout_id=workout_id, note=workout['note'])
|
|
|
|
|
|
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/tag/add", methods=['POST'])
|
|
def add_tag_to_workout(person_id, workout_id):
|
|
tags_id = [int(i) for i in request.form.getlist('tag_id')]
|
|
tags = db.add_tag_for_workout(workout_id, tags_id)
|
|
return render_template('partials/workout_tags_list.html', tags=tags)
|
|
|
|
|
|
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/tag/new", methods=['POST'])
|
|
def create_new_tag_for_workout(person_id, workout_id):
|
|
tag_name = request.form.get('tag_name')
|
|
workout_tags = db.create_tag_for_workout(person_id, workout_id, tag_name)
|
|
return render_template('partials/workout_tags_list.html', workout_tags=workout_tags)
|
|
|
|
|
|
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/exercise/most_recent_topset_for_exercise", methods=['GET'])
|
|
def get_most_recent_topset_for_exercise(person_id, workout_id):
|
|
exercise_id = request.args.get('exercise_id', type=int)
|
|
exercises = db.get_all_exercises()
|
|
|
|
if not exercise_id:
|
|
return render_template('partials/new_set_form.html', person_id=person_id, workout_id=workout_id, exercises=exercises)
|
|
|
|
topset = db.get_most_recent_topset_for_exercise(person_id, exercise_id)
|
|
if not topset:
|
|
return render_template('partials/new_set_form.html', person_id=person_id, workout_id=workout_id, exercises=exercises, exercise_id=exercise_id)
|
|
|
|
(repetitions, weight, exercise_name) = topset
|
|
return render_template('partials/new_set_form.html', person_id=person_id, workout_id=workout_id, exercise_id=exercise_id, exercise_name=exercise_name, repetitions=repetitions, weight=weight)
|
|
|
|
|
|
def calculate_relative_positions(start_dates):
|
|
min_date = min(start_dates)
|
|
max_date = max(start_dates)
|
|
total_span = (max_date - min_date).days if max_date != min_date else 1
|
|
return [(date - min_date).days / total_span for date in start_dates]
|
|
|
|
@ app.route("/person/<int:person_id>/exercise/<int:exercise_id>/sparkline", methods=['GET'])
|
|
def get_exercise_progress_for_user(person_id, exercise_id):
|
|
min_date = convert_str_to_date(request.args.get(
|
|
'min_date'), '%Y-%m-%d')
|
|
max_date = convert_str_to_date(request.args.get(
|
|
'max_date'), '%Y-%m-%d')
|
|
epoch = request.args.get('epoch', default='All')
|
|
|
|
if epoch == 'Custom' and (min_date is None or max_date is None):
|
|
(min_date, max_date) = db.get_exercise_earliest_and_latest_dates(person_id, exercise_id)
|
|
|
|
exercise_progress = db.get_exercise_progress_for_user(person_id, exercise_id, min_date, max_date, epoch)
|
|
|
|
if not exercise_progress:
|
|
abort(404)
|
|
|
|
return render_template('partials/sparkline.html', **exercise_progress)
|
|
|
|
@app.route("/stats/person/<int:person_id>", methods=['GET'])
|
|
def get_stats_for_person(person_id):
|
|
stats = db.stats.fetch_stats_for_person(person_id)
|
|
return render_template('partials/stats.html', stats=stats)
|
|
|
|
@ app.route("/person/<int:person_id>/workout/<int:workout_id>", methods=['GET'])
|
|
def show_workout(person_id, workout_id):
|
|
view_model = db.workout.get(person_id, workout_id)
|
|
if htmx:
|
|
return render_block(app.jinja_env, 'workout.html', 'content', **view_model)
|
|
return render_template('workout.html', **view_model)
|
|
|
|
@app.route("/exercises/get")
|
|
def get_exercises():
|
|
query = request.args.get('query')
|
|
person_id = request.args.get('person_id', type=int)
|
|
exercises = db.exercises.get(query)
|
|
return render_template('partials/exercise/exercise_dropdown.html', exercises=exercises, person_id=person_id)
|
|
|
|
@app.route("/exercise/<int:exercise_id>/edit_name", methods=['GET', 'POST'])
|
|
def edit_exercise_name(exercise_id):
|
|
exercise = db.exercises.get_exercise(exercise_id)
|
|
person_id = request.args.get('person_id', type=int)
|
|
if request.method == 'GET':
|
|
return render_template('partials/exercise/edit_exercise_name.html', exercise=exercise, person_id=person_id)
|
|
else:
|
|
updated_name = request.form['name']
|
|
updated_exercise = db.exercises.update_exercise_name(exercise_id, updated_name)
|
|
return render_template('partials/exercise/exercise_list_item.html', exercise=updated_exercise, person_id=person_id)
|
|
|
|
@app.route("/exercises/add", methods=['POST'])
|
|
def add_exercise():
|
|
exercise_name = request.form['query']
|
|
new_exercise = db.exercises.add_exercise(exercise_name)
|
|
person_id = request.args.get('person_id', type=int)
|
|
return render_template('partials/exercise/exercise_list_item.html', exercise=new_exercise, person_id=person_id)
|
|
|
|
@ app.route("/exercise/<int:exercise_id>/delete", methods=['DELETE'])
|
|
def delete_exercise(exercise_id):
|
|
db.exercises.delete_exercise(exercise_id)
|
|
return ""
|
|
|
|
@app.route("/sql_explorer", methods=['GET'])
|
|
def sql_explorer():
|
|
saved_queries = db.sql_explorer.list_saved_queries()
|
|
if htmx:
|
|
return render_block(app.jinja_env, 'sql_explorer.html', 'content', saved_queries=saved_queries)
|
|
return render_template('sql_explorer.html', saved_queries=saved_queries)
|
|
|
|
@app.route("/sql_query", methods=['POST'])
|
|
def sql_query():
|
|
query = request.form.get('query')
|
|
action = request.form.get('action')
|
|
title = request.form.get('title')
|
|
|
|
if action == 'execute':
|
|
(results, columns, error) = db.sql_explorer.execute_sql(query)
|
|
saved_queries = db.sql_explorer.list_saved_queries()
|
|
return render_template('partials/sql_explorer/sql_query.html',
|
|
title=title,
|
|
query=query,
|
|
results=results,
|
|
columns=columns,
|
|
error=error,
|
|
saved_queries=saved_queries)
|
|
else:
|
|
error = db.sql_explorer.save_query(title, query)
|
|
saved_queries = db.sql_explorer.list_saved_queries()
|
|
return render_template('partials/sql_explorer/sql_query.html',
|
|
title=title,
|
|
query=query,
|
|
error=error,
|
|
saved_queries=saved_queries)
|
|
|
|
@app.route('/load_sql_query/<int:query_id>', methods=['GET'])
|
|
def load_sql_query(query_id):
|
|
(title, query) = db.sql_explorer.get_saved_query(query_id)
|
|
saved_queries = db.sql_explorer.list_saved_queries()
|
|
return render_template('partials/sql_explorer/sql_query.html',
|
|
title=title,
|
|
query=query,
|
|
saved_queries=saved_queries)
|
|
|
|
@app.route('/delete_sql_query/<int:query_id>', methods=['DELETE'])
|
|
def delete_sql_query(query_id):
|
|
db.sql_explorer.delete_saved_query(query_id)
|
|
saved_queries = db.sql_explorer.list_saved_queries()
|
|
return render_template('partials/sql_explorer/sql_query.html',
|
|
title="",
|
|
query="",
|
|
saved_queries=saved_queries)
|
|
|
|
|
|
@ app.route("/sql_schema", methods=['GET'])
|
|
def sql_schema():
|
|
schema_info = db.sql_explorer.get_schema_info()
|
|
mermaid_code = db.sql_explorer.generate_mermaid_er(schema_info)
|
|
return render_template('partials/sql_explorer/schema.html', mermaid_code=mermaid_code)
|
|
|
|
@app.route("/plot/<int:query_id>", methods=['GET'])
|
|
def plot_query(query_id):
|
|
(title, query) = db.sql_explorer.get_saved_query(query_id)
|
|
#(results, columns, error) = db.sql_explorer.execute_sql(query)
|
|
results_df = db.read_sql_as_df(query)
|
|
plot_div = generate_plot(results_df, title)
|
|
return plot_div
|
|
|
|
def get_routes():
|
|
routes = []
|
|
for rule in app.url_map.iter_rules():
|
|
if rule.endpoint == 'static':
|
|
continue
|
|
methods = ', '.join(sorted(rule.methods - {'HEAD', 'OPTIONS'}))
|
|
route_info = {
|
|
'endpoint': rule.endpoint,
|
|
'url': rule.rule,
|
|
'methods': methods,
|
|
'view_func': app.view_functions[rule.endpoint].__name__,
|
|
'doc': app.view_functions[rule.endpoint].__doc__
|
|
}
|
|
routes.append(route_info)
|
|
return routes
|
|
|
|
@app.route('/endpoints')
|
|
def list_endpoints():
|
|
"""
|
|
Lists all API endpoints available in the Flask application.
|
|
|
|
This endpoint retrieves all registered routes, excluding static routes,
|
|
and displays their details such as endpoint name, URL, allowed HTTP methods,
|
|
view function name, and a brief description.
|
|
"""
|
|
routes = get_routes()
|
|
|
|
if htmx:
|
|
return render_block(app.jinja_env, 'endpoints.html', 'content', routes=routes)
|
|
return render_template('endpoints.html', routes=routes)
|
|
|
|
@app.route('/endpoints/search')
|
|
def search_endpoints():
|
|
routes = get_routes()
|
|
search = request.args.get('search', '').lower()
|
|
if search:
|
|
routes = [
|
|
route for route in routes
|
|
if search in route['endpoint'].lower()
|
|
or search in route['url'].lower()
|
|
or search in route['methods'].lower()
|
|
or search in route['view_func'].lower()
|
|
or (route['doc'] and search in route['doc'].lower())
|
|
]
|
|
|
|
return render_template('partials/endpoints_table.html', routes=routes)
|
|
|
|
@app.teardown_appcontext
|
|
def closeConnection(exception):
|
|
db.close_connection()
|
|
|
|
|
|
@app.template_filter('list_to_string')
|
|
def list_to_string(list):
|
|
return [str(i) for i in list]
|
|
|
|
|
|
@app.template_filter('strftime')
|
|
def strftime(date, format="%b %d %Y"):
|
|
return date.strftime(format)
|
|
|
|
|
|
@app.template_filter('get_first_element_from_list_with_matching_attribute')
|
|
def get_first_element_from_list_with_matching_attribute(list, attribute, value):
|
|
if not list:
|
|
return None
|
|
for element in list:
|
|
if element[attribute] == value:
|
|
return element
|
|
return None
|
|
|
|
|
|
@app.template_filter('replace_double_quote_strings_with_single_quote')
|
|
def replace_double_quote_strings_with_single_quote(str):
|
|
return str.replace('"', "'")
|
|
|
|
|
|
@ app.context_processor
|
|
def my_utility_processor():
|
|
|
|
def is_selected_page(url):
|
|
# if htmx:
|
|
# parsed_url = urlparse(htmx.current_url)
|
|
# return 'bg-gray-200' if url == parsed_url.path else ''
|
|
if url == request.path:
|
|
return 'bg-gray-200'
|
|
return ''
|
|
|
|
def get_first_element_from_list_with_matching_attribute(list, attribute, value):
|
|
if not list:
|
|
return None
|
|
for element in list:
|
|
if element[attribute] == value:
|
|
return element
|
|
return None
|
|
|
|
def in_list(val, checked_vals, attr='checked'):
|
|
if not checked_vals:
|
|
return attr
|
|
return attr if val in checked_vals else ''
|
|
|
|
def strftime(date, format="%b %d %Y"):
|
|
return date.strftime(format)
|
|
|
|
def list_to_string(list):
|
|
return [str(i) for i in list]
|
|
|
|
return dict(is_selected_page=is_selected_page, get_first_element_from_list_with_matching_attribute=get_first_element_from_list_with_matching_attribute, in_list=in_list, strftime=strftime, datetime=datetime, timedelta=timedelta, relativedelta=relativedelta, first_and_last_visible_days_in_month=first_and_last_visible_days_in_month, list_to_string=list_to_string, quote=quote)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
# Bind to PORT if defined, otherwise default to 5000.
|
|
port = int(os.environ.get('PORT', 5000))
|
|
app.run(host='127.0.0.1', port=port)
|