Refactor route validations to decorators
This commit is contained in:
442
app.py
442
app.py
@@ -1,7 +1,10 @@
|
|||||||
from flask import Flask, jsonify, render_template, g, redirect, request, url_for
|
from flask import Flask, abort, render_template, g, redirect, request, url_for
|
||||||
from flasgger import Swagger
|
from flasgger import Swagger, swag_from
|
||||||
|
from functools import wraps
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import datetime
|
|
||||||
|
from db import DataBase
|
||||||
|
from decorators import validate_person, validate_topset, validate_workout
|
||||||
|
|
||||||
template = {
|
template = {
|
||||||
"swagger": "2.0",
|
"swagger": "2.0",
|
||||||
@@ -24,394 +27,88 @@ template = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
app.config.from_pyfile('config.py')
|
||||||
swagger = Swagger(app, template=template)
|
swagger = Swagger(app, template=template)
|
||||||
|
|
||||||
DATABASE = 'workout.db'
|
db = DataBase(app)
|
||||||
|
|
||||||
|
|
||||||
def get_db():
|
@ app.route("/")
|
||||||
db = getattr(g, '_database', None)
|
@ swag_from('swagger/dashboard.yml')
|
||||||
if db is None:
|
|
||||||
db = g._database = sqlite3.connect(DATABASE)
|
|
||||||
db.row_factory = sqlite3.Row
|
|
||||||
return db
|
|
||||||
|
|
||||||
|
|
||||||
def query_db(query, args=(), one=False):
|
|
||||||
cur = get_db().execute(query, args)
|
|
||||||
rv = cur.fetchall()
|
|
||||||
cur.connection.commit()
|
|
||||||
cur.close()
|
|
||||||
return (rv[0] if rv else None) if one else rv
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
def dashboard():
|
def dashboard():
|
||||||
"""Dashboard page
|
|
||||||
Displays stats and a list of all people and there rep maxes for each exercise
|
|
||||||
---
|
|
||||||
tags:
|
|
||||||
- Dashboard
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: A list of all people and there rep maxes for each exercise
|
|
||||||
"""
|
|
||||||
return render_template('index.html')
|
return render_template('index.html')
|
||||||
|
|
||||||
|
|
||||||
@app.route("/person/<int:person_id>")
|
@ app.route("/person/<int:person_id>")
|
||||||
def display_workouts_for_person(person_id):
|
@ swag_from('swagger/get_person.yml')
|
||||||
"""Display all workouts for a person
|
@ validate_person
|
||||||
Displays stats and a list of all people and there rep maxes for each exercise
|
def get_person(person_id):
|
||||||
---
|
person = db.get_person_final(person_id)
|
||||||
tags:
|
return render_template('workouts.html', person=person)
|
||||||
- Person
|
|
||||||
parameters:
|
|
||||||
- name: person_id
|
|
||||||
in: path
|
|
||||||
type: number
|
|
||||||
required: true
|
|
||||||
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: A list of all people and there rep maxes for each exercise
|
|
||||||
"""
|
|
||||||
person = query_db('SELECT * FROM Person WHERE PersonId=?',
|
|
||||||
[person_id], one=True)
|
|
||||||
|
|
||||||
if person is None:
|
|
||||||
return render_template('error.html', error='404', message=f'Unable to find Person({person_id})', url='/')
|
|
||||||
|
|
||||||
workouts = query_db("""
|
|
||||||
SELECT
|
|
||||||
W.WorkoutId,
|
|
||||||
W.StartDate,
|
|
||||||
T.TopSetId,
|
|
||||||
E.ExcerciseId,
|
|
||||||
E.Name,
|
|
||||||
T.Repetitions || ' x ' || T.Weight || 'kg' AS TopSet
|
|
||||||
FROM
|
|
||||||
Workout W
|
|
||||||
LEFT JOIN TopSet T ON W.WorkoutId = T.WorkoutId
|
|
||||||
LEFT JOIN Excercise E ON T.ExcerciseId = E.ExcerciseId
|
|
||||||
WHERE
|
|
||||||
W.PersonId = ?""",
|
|
||||||
[person_id])
|
|
||||||
|
|
||||||
exercises = query_db('select * from Excercise')
|
|
||||||
|
|
||||||
unique_workout_ids = set([w['WorkoutId'] for w in workouts])
|
|
||||||
|
|
||||||
transformed_workouts = []
|
|
||||||
for workout_id in unique_workout_ids:
|
|
||||||
# topsets = [w for w in workouts if w['WorkoutId' == workout_id]]
|
|
||||||
topsets = []
|
|
||||||
for workout in workouts:
|
|
||||||
if workout['WorkoutId'] == workout_id:
|
|
||||||
topsets.append(workout)
|
|
||||||
topset_exercises = {}
|
|
||||||
for exercise in exercises:
|
|
||||||
for topset in topsets:
|
|
||||||
if topset['ExcerciseId'] == exercise['ExcerciseId']:
|
|
||||||
topset_exercises[exercise['ExcerciseId']
|
|
||||||
] = topset['TopSet']
|
|
||||||
|
|
||||||
workout = [w for w in workouts if w['WorkoutId'] == workout_id][0]
|
|
||||||
transformed_workouts.append(
|
|
||||||
{"workout_id": workout['WorkoutId'], "start_date": workout['StartDate'], "topset_exercises": topset_exercises})
|
|
||||||
|
|
||||||
return render_template('workouts.html', person_id=person_id, person=person, workouts=transformed_workouts, exercises=exercises)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/person/<int:person_id>/workout", methods=['POST'])
|
@ app.route("/person/<int:person_id>/workout", methods=['POST'])
|
||||||
def new_workout_for_person(person_id):
|
@ swag_from('swagger/create_workout.yml')
|
||||||
"""Create new workout
|
@ validate_person
|
||||||
Creates a workout with current date and then redirects to newly created workout
|
def create_workout(person_id):
|
||||||
---
|
new_workout_id = db.create_workout(person_id)
|
||||||
tags:
|
return redirect(url_for('get_workout', person_id=person_id, workout_id=new_workout_id))
|
||||||
- Workout
|
|
||||||
parameters:
|
|
||||||
- name: person_id
|
|
||||||
in: path
|
|
||||||
type: number
|
|
||||||
required: true
|
|
||||||
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: View of newly created workout
|
|
||||||
"""
|
|
||||||
person = query_db('SELECT * FROM Person WHERE PersonId=?',
|
|
||||||
[person_id], one=True)
|
|
||||||
|
|
||||||
if person is None:
|
|
||||||
return render_template('error.html', error='404', message=f'Unable to find Person({person_id})', url='/')
|
|
||||||
|
|
||||||
now = datetime.datetime.now()
|
|
||||||
date_string = now.strftime('%Y-%m-%d')
|
|
||||||
print(f'Creating workout for {person_id} at {date_string}')
|
|
||||||
query_db('INSERT INTO Workout (PersonId, StartDate) VALUES (?, ?)', [
|
|
||||||
person_id, date_string])
|
|
||||||
w = query_db('SELECT MAX(WorkoutId) AS WorkoutId FROM Workout WHERE PersonId=?', [
|
|
||||||
person_id], one=True)
|
|
||||||
|
|
||||||
return redirect(url_for('show_workout_for_person', person_id=person_id, workout_id=w['WorkoutId']))
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/person/<int:person_id>/workout/<int:workout_id>")
|
@ app.route("/person/<int:person_id>/workout/<int:workout_id>")
|
||||||
def show_workout_for_person(person_id, workout_id):
|
@ swag_from('swagger/get_workout.yml')
|
||||||
"""Display a workout
|
@ validate_workout
|
||||||
Displays a selected workout with options to edit/delete existing and add new topsets
|
def get_workout(person_id, workout_id):
|
||||||
---
|
workout = db.get_workout_final(person_id, workout_id)
|
||||||
tags:
|
return render_template('workout.html', workout=workout)
|
||||||
- Workout
|
|
||||||
parameters:
|
|
||||||
- name: person_id
|
|
||||||
in: path
|
|
||||||
type: number
|
|
||||||
required: true
|
|
||||||
- name: workout_id
|
|
||||||
in: path
|
|
||||||
type: number
|
|
||||||
required: true
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: A list of topsets in a selected workout
|
|
||||||
"""
|
|
||||||
workout_info = query_db("""
|
|
||||||
SELECT
|
|
||||||
P.Name,
|
|
||||||
W.StartDate
|
|
||||||
FROM
|
|
||||||
Person P
|
|
||||||
LEFT JOIN Workout W ON P.PersonId = W.PersonId
|
|
||||||
WHERE
|
|
||||||
P.PersonId = ?
|
|
||||||
AND W.WorkoutId = ?
|
|
||||||
LIMIT 1""",
|
|
||||||
[person_id, workout_id], one=True)
|
|
||||||
|
|
||||||
if workout_info is None:
|
|
||||||
return render_template('error.html', error='404', message=f'Unable to find Workout({workout_id}) completed by Person({person_id})', url=url_for('display_workouts_for_person', person_id=person_id))
|
|
||||||
|
|
||||||
top_sets = query_db("""
|
|
||||||
SELECT
|
|
||||||
T.TopSetId,
|
|
||||||
E.Name,
|
|
||||||
T.Repetitions || ' x ' || T.Weight || 'kg' AS TopSet
|
|
||||||
FROM
|
|
||||||
Person P
|
|
||||||
LEFT JOIN Workout W ON P.PersonId = W.PersonId
|
|
||||||
INNER JOIN TopSet T ON W.WorkoutId = T.WorkoutId
|
|
||||||
INNER JOIN Excercise E ON T.ExcerciseId = E.ExcerciseId
|
|
||||||
WHERE
|
|
||||||
P.PersonId = ?
|
|
||||||
AND W.WorkoutId = ?""",
|
|
||||||
[person_id, workout_id])
|
|
||||||
|
|
||||||
return render_template('workout.html', person_id=person_id, workout_id=workout_id, workout_info=workout_info, top_sets=top_sets, exercises=query_db('select * from Excercise'))
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/person/<int:person_id>/workout/<int:workout_id>/delete", methods=['GET', 'DELETE'])
|
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/delete", methods=['GET', 'DELETE'])
|
||||||
def delete_workout_from_person(person_id, workout_id):
|
@ swag_from('swagger/delete_workout.yml')
|
||||||
"""Delete workout
|
@ validate_workout
|
||||||
Deletes selected workout completed by a person
|
def delete_workout(person_id, workout_id):
|
||||||
---
|
db.delete_workout(workout_id)
|
||||||
tags:
|
return redirect(url_for('get_person', person_id=person_id))
|
||||||
- Workout
|
|
||||||
parameters:
|
|
||||||
- name: person_id
|
|
||||||
in: path
|
|
||||||
type: number
|
|
||||||
required: true
|
|
||||||
- name: workout_id
|
|
||||||
in: path
|
|
||||||
type: number
|
|
||||||
required: true
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: Redirect to workouts list page for person
|
|
||||||
"""
|
|
||||||
|
|
||||||
workout_info = query_db("""
|
|
||||||
SELECT
|
|
||||||
P.Name,
|
|
||||||
W.StartDate
|
|
||||||
FROM
|
|
||||||
Person P
|
|
||||||
LEFT JOIN Workout W ON P.PersonId = W.PersonId
|
|
||||||
WHERE
|
|
||||||
P.PersonId = ?
|
|
||||||
AND W.WorkoutId = ?
|
|
||||||
LIMIT 1""",
|
|
||||||
[person_id, workout_id], one=True)
|
|
||||||
|
|
||||||
if workout_info is None:
|
|
||||||
return render_template('error.html', error='404', message=f'Unable to find Workout({workout_id}) completed by Person({person_id})', url=url_for('display_workouts_for_person', person_id=person_id))
|
|
||||||
|
|
||||||
query_db('DELETE FROM Workout WHERE WorkoutId=?',
|
|
||||||
[workout_id])
|
|
||||||
|
|
||||||
return redirect(url_for('display_workouts_for_person', person_id=person_id))
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/person/<int:person_id>/workout/<int:workout_id>/topset/<int:topset_id>", methods=['GET', 'POST'])
|
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/topset/<int:topset_id>", methods=['GET', 'POST'])
|
||||||
def show_topset_from_workout_for_person(person_id, workout_id, topset_id):
|
@ swag_from('swagger/get_topset.yml')
|
||||||
"""Display/Create new top set
|
@ validate_topset
|
||||||
Displays stats and a list of all people and there rep maxes for each exercise
|
def get_topset(person_id, workout_id, topset_id):
|
||||||
---
|
|
||||||
tags:
|
|
||||||
- Topset
|
|
||||||
parameters:
|
|
||||||
- name: person_id
|
|
||||||
in: path
|
|
||||||
type: number
|
|
||||||
required: true
|
|
||||||
- name: workout_id
|
|
||||||
in: path
|
|
||||||
type: number
|
|
||||||
required: true
|
|
||||||
- name: topset_id
|
|
||||||
in: path
|
|
||||||
type: number
|
|
||||||
required: true
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: A list of topsets in a selected workout
|
|
||||||
"""
|
|
||||||
topset = query_db("""
|
|
||||||
SELECT
|
|
||||||
P.Name,
|
|
||||||
W.StartDate,
|
|
||||||
E.ExcerciseId,
|
|
||||||
E.Name,
|
|
||||||
T.Repetitions,
|
|
||||||
T.Weight
|
|
||||||
FROM
|
|
||||||
Person P
|
|
||||||
LEFT JOIN Workout W ON P.PersonId = W.PersonId
|
|
||||||
INNER JOIN TopSet T ON W.WorkoutId = T.WorkoutId
|
|
||||||
INNER JOIN Excercise E ON T.ExcerciseId = E.ExcerciseId
|
|
||||||
WHERE
|
|
||||||
P.PersonId = ?
|
|
||||||
AND W.WorkoutId = ?
|
|
||||||
AND T.TopSetId = ?""",
|
|
||||||
[person_id, workout_id, topset_id], one=True)
|
|
||||||
|
|
||||||
if topset is None:
|
|
||||||
return render_template('error.html', error='404', message=f'Unable to find TopSet({topset_id}) in Workout({workout_id}) completed by Person({person_id})', url=url_for('show_workout_for_person', person_id=person_id, workout_id=workout_id))
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
exercise_id = request.form.get("exercise_id")
|
exercise_id = request.form.get("exercise_id")
|
||||||
repetitions = request.form.get("repetitions")
|
repetitions = request.form.get("repetitions")
|
||||||
weight = request.form.get("weight")
|
weight = request.form.get("weight")
|
||||||
|
|
||||||
query_db('UPDATE TopSet SET ExcerciseId=?, Repetitions=?, Weight=? WHERE TopSetId=?', [
|
db.update_topset(exercise_id, repetitions, weight, topset_id)
|
||||||
exercise_id, repetitions, weight, topset_id])
|
|
||||||
|
|
||||||
return redirect(url_for('show_workout_for_person', person_id=person_id, workout_id=workout_id))
|
return redirect(url_for('get_workout', person_id=person_id, workout_id=workout_id))
|
||||||
|
|
||||||
return render_template('topset.html', person_id=person_id, workout_id=workout_id, topset_id=topset_id, topset=topset, exercises=query_db('select * from Excercise'))
|
topset = db.get_topset_final(person_id, workout_id, topset_id)
|
||||||
|
return render_template('topset.html', topset=topset)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/person/<int:person_id>/workout/<int:workout_id>/topset", methods=['POST'])
|
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/topset", methods=['POST'])
|
||||||
def add_topset_to_workout_for_person(person_id, workout_id):
|
@ swag_from('swagger/create_topset.yml')
|
||||||
"""Add top set to workout
|
@ validate_workout
|
||||||
Add a topset to a workout completed by a person
|
def create_topset(person_id, workout_id):
|
||||||
---
|
|
||||||
tags:
|
|
||||||
- Topset
|
|
||||||
parameters:
|
|
||||||
- name: person_id
|
|
||||||
in: path
|
|
||||||
type: number
|
|
||||||
required: true
|
|
||||||
- name: workout_id
|
|
||||||
in: path
|
|
||||||
type: number
|
|
||||||
required: true
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: A list of topsets in a selected workout
|
|
||||||
"""
|
|
||||||
workout_info = query_db("""
|
|
||||||
SELECT
|
|
||||||
P.Name,
|
|
||||||
W.StartDate
|
|
||||||
FROM
|
|
||||||
Person P
|
|
||||||
LEFT JOIN Workout W ON P.PersonId = W.PersonId
|
|
||||||
WHERE
|
|
||||||
P.PersonId = ?
|
|
||||||
AND W.WorkoutId = ?
|
|
||||||
LIMIT 1""",
|
|
||||||
[person_id, workout_id], one=True)
|
|
||||||
|
|
||||||
if workout_info is None:
|
|
||||||
return render_template('error.html', error='404', message=f'Unable to find Workout({workout_id}) completed by Person({person_id})', url=url_for('display_workouts_for_person', person_id=person_id))
|
|
||||||
|
|
||||||
exercise_id = request.form.get("exercise_id")
|
exercise_id = request.form.get("exercise_id")
|
||||||
repetitions = request.form.get("repetitions")
|
repetitions = request.form.get("repetitions")
|
||||||
weight = request.form.get("weight")
|
weight = request.form.get("weight")
|
||||||
|
|
||||||
query_db('INSERT INTO TopSet (WorkoutId, ExcerciseId, Repetitions, Weight) VALUES (?, ?, ?, ?)', [
|
db.create_topset(workout_id, exercise_id, repetitions, weight)
|
||||||
workout_id, exercise_id, repetitions, weight])
|
return redirect(url_for('get_workout', person_id=person_id, workout_id=workout_id))
|
||||||
|
|
||||||
return redirect(url_for('show_workout_for_person', person_id=person_id, workout_id=workout_id))
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/person/<int:person_id>/workout/<int:workout_id>/topset/<int:topset_id>/delete", methods=['GET', 'DELETE'])
|
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/topset/<int:topset_id>/delete", methods=['GET', 'DELETE'])
|
||||||
def delete_topset_from_workout_for_person(person_id, workout_id, topset_id):
|
@ swag_from('swagger/delete_topset.yml')
|
||||||
"""Delete top set
|
@ validate_topset
|
||||||
Add a topset to a workout completed by a person
|
def delete_topset(person_id, workout_id, topset_id):
|
||||||
---
|
db.delete_topset(topset_id)
|
||||||
tags:
|
return redirect(url_for('get_workout', person_id=person_id, workout_id=workout_id))
|
||||||
- Topset
|
|
||||||
parameters:
|
|
||||||
- name: person_id
|
|
||||||
in: path
|
|
||||||
type: number
|
|
||||||
required: true
|
|
||||||
- name: workout_id
|
|
||||||
in: path
|
|
||||||
type: number
|
|
||||||
required: true
|
|
||||||
- name: topset_id
|
|
||||||
in: path
|
|
||||||
type: number
|
|
||||||
required: true
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: A list of topsets in a selected workout
|
|
||||||
"""
|
|
||||||
topset = query_db("""
|
|
||||||
SELECT
|
|
||||||
P.Name,
|
|
||||||
W.StartDate,
|
|
||||||
E.ExcerciseId,
|
|
||||||
E.Name,
|
|
||||||
T.Repetitions,
|
|
||||||
T.Weight
|
|
||||||
FROM
|
|
||||||
Person P
|
|
||||||
LEFT JOIN Workout W ON P.PersonId = W.PersonId
|
|
||||||
INNER JOIN TopSet T ON W.WorkoutId = T.WorkoutId
|
|
||||||
INNER JOIN Excercise E ON T.ExcerciseId = E.ExcerciseId
|
|
||||||
WHERE
|
|
||||||
P.PersonId = ?
|
|
||||||
AND W.WorkoutId = ?
|
|
||||||
AND T.TopSetId = ?""",
|
|
||||||
[person_id, workout_id, topset_id], one=True)
|
|
||||||
|
|
||||||
if topset is None:
|
|
||||||
return render_template('error.html', error='404', message=f'Unable to find TopSet({topset_id}) in Workout({workout_id}) completed by Person({person_id})', url=url_for('show_workout_for_person', person_id=person_id, workout_id=workout_id))
|
|
||||||
|
|
||||||
query_db('DELETE FROM TopSet WHERE TopSetId=?', [
|
|
||||||
topset_id])
|
|
||||||
|
|
||||||
return redirect(url_for('show_workout_for_person', person_id=person_id, workout_id=workout_id))
|
|
||||||
|
|
||||||
|
|
||||||
@app.context_processor
|
@ app.context_processor
|
||||||
def my_utility_processor():
|
def my_utility_processor():
|
||||||
|
|
||||||
def is_selected_page(url):
|
def is_selected_page(url):
|
||||||
@@ -424,27 +121,12 @@ def my_utility_processor():
|
|||||||
if 'person_id' in request.view_args:
|
if 'person_id' in request.view_args:
|
||||||
person_id = request.view_args['person_id']
|
person_id = request.view_args['person_id']
|
||||||
|
|
||||||
return query_db("""
|
return db.get_people_and_workout_count(person_id)
|
||||||
SELECT
|
|
||||||
P.PersonId,
|
|
||||||
P.Name,
|
|
||||||
COUNT(W.WorkoutId) AS NumberOfWorkouts,
|
|
||||||
CASE P.PersonId
|
|
||||||
WHEN ?
|
|
||||||
THEN 1
|
|
||||||
ELSE 0
|
|
||||||
END IsActive
|
|
||||||
FROM
|
|
||||||
Person P
|
|
||||||
LEFT JOIN Workout W ON P.PersonId = W.PersonId
|
|
||||||
GROUP BY
|
|
||||||
P.PersonId""", [person_id])
|
|
||||||
|
|
||||||
return dict(get_list_of_people_and_workout_count=get_list_of_people_and_workout_count, is_selected_page=is_selected_page)
|
def get_first_element_from_list_with_matching_attribute(list, attribute, value):
|
||||||
|
for element in list:
|
||||||
|
if element[attribute] == value:
|
||||||
|
return element
|
||||||
|
return None
|
||||||
|
|
||||||
|
return dict(get_list_of_people_and_workout_count=get_list_of_people_and_workout_count, is_selected_page=is_selected_page, get_first_element_from_list_with_matching_attribute=get_first_element_from_list_with_matching_attribute)
|
||||||
@app.teardown_appcontext
|
|
||||||
def close_connection(exception):
|
|
||||||
db = getattr(g, '_database', None)
|
|
||||||
if db is not None:
|
|
||||||
db.close()
|
|
||||||
|
|||||||
5
config.py
Normal file
5
config.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
TESTING = True
|
||||||
|
DEBUG = True
|
||||||
|
FLASK_ENV = 'development'
|
||||||
|
SECRET_KEY = ''
|
||||||
|
DATABASE_URI = 'workout.db'
|
||||||
170
db.py
Normal file
170
db.py
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
import datetime
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
from utils import get_all_exercises_from_topsets, get_workouts
|
||||||
|
|
||||||
|
|
||||||
|
class DataBase():
|
||||||
|
def __init__(self, app):
|
||||||
|
self.DATABASE_URI = app.config['DATABASE_URI']
|
||||||
|
|
||||||
|
def execute(self, query, args=(), one=False, commit=False):
|
||||||
|
conn = sqlite3.connect(self.DATABASE_URI)
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
cur = conn.execute(query, args)
|
||||||
|
rv = cur.fetchall()
|
||||||
|
if commit:
|
||||||
|
conn.commit()
|
||||||
|
cur.close()
|
||||||
|
return (rv[0] if rv else None) if one else rv
|
||||||
|
|
||||||
|
def get_exercises(self):
|
||||||
|
exercises = self.execute('SELECT * FROM Excercise')
|
||||||
|
return [{"ExcerciseId": e['ExcerciseId'], "Name": e['Name']} for e in exercises]
|
||||||
|
|
||||||
|
def get_person(self, person_id):
|
||||||
|
person = self.execute(
|
||||||
|
'SELECT * FROM Person WHERE PersonId=? LIMIT 1', [person_id], one=True)
|
||||||
|
return person
|
||||||
|
|
||||||
|
def get_workout(self, person_id, workout_id):
|
||||||
|
workout = self.execute('SELECT W.WorkoutId FROM Person P, Workout W WHERE P.PersonId=W.PersonId AND P.PersonId=? AND W.WorkoutId=? LIMIT 1', [
|
||||||
|
person_id, workout_id], one=True)
|
||||||
|
return workout
|
||||||
|
|
||||||
|
def get_topset(self, person_id, workout_id, topset_id):
|
||||||
|
topset = self.execute("""
|
||||||
|
SELECT T.TopSetId
|
||||||
|
FROM Person P, Workout W, TopSet T
|
||||||
|
WHERE W.PersonId=W.PersonId AND W.WorkoutId=T.WorkoutId AND P.PersonId=? AND W.WorkoutId = ? AND T.TopSetId = ?
|
||||||
|
LIMIT 1""", [person_id, workout_id, topset_id], one=True)
|
||||||
|
return topset
|
||||||
|
|
||||||
|
def delete_workout(self, workout_id):
|
||||||
|
self.execute('DELETE FROM Workout WHERE WorkoutId=?',
|
||||||
|
[workout_id], commit=True)
|
||||||
|
|
||||||
|
def update_topset(self, exercise_id, repetitions, weight, topset_id):
|
||||||
|
self.execute('UPDATE TopSet SET ExcerciseId=?, Repetitions=?, Weight=? WHERE TopSetId=?', [
|
||||||
|
exercise_id, repetitions, weight, topset_id], commit=True)
|
||||||
|
|
||||||
|
def create_topset(self, workout_id, exercise_id, repetitions, weight):
|
||||||
|
self.execute('INSERT INTO TopSet (WorkoutId, ExcerciseId, Repetitions, Weight) VALUES (?, ?, ?, ?)', [
|
||||||
|
workout_id, exercise_id, repetitions, weight], commit=True)
|
||||||
|
|
||||||
|
def delete_topset(self, topset_id):
|
||||||
|
self.execute('DELETE FROM TopSet WHERE TopSetId=?', [
|
||||||
|
topset_id], commit=True)
|
||||||
|
|
||||||
|
def create_workout(self, person_id):
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
date_string = now.strftime('%Y-%m-%d')
|
||||||
|
print(f'Creating workout for {person_id} at {date_string}')
|
||||||
|
self.execute('INSERT INTO Workout (PersonId, StartDate) VALUES (?, ?)', [
|
||||||
|
person_id, date_string], commit=True)
|
||||||
|
w = self.execute('SELECT MAX(WorkoutId) AS WorkoutId FROM Workout WHERE PersonId=?', [
|
||||||
|
person_id], one=True)
|
||||||
|
return w['WorkoutId']
|
||||||
|
|
||||||
|
def get_people_and_workout_count(self, person_id):
|
||||||
|
return self.execute("""
|
||||||
|
SELECT
|
||||||
|
P.PersonId,
|
||||||
|
P.Name,
|
||||||
|
COUNT(W.WorkoutId) AS NumberOfWorkouts,
|
||||||
|
CASE P.PersonId
|
||||||
|
WHEN ?
|
||||||
|
THEN 1
|
||||||
|
ELSE 0
|
||||||
|
END IsActive
|
||||||
|
FROM
|
||||||
|
Person P
|
||||||
|
LEFT JOIN Workout W ON P.PersonId = W.PersonId
|
||||||
|
GROUP BY
|
||||||
|
P.PersonId""", [person_id])
|
||||||
|
|
||||||
|
def get_person_final(self, person_id):
|
||||||
|
topsets = self.execute("""
|
||||||
|
SELECT
|
||||||
|
P.PersonId,
|
||||||
|
P.Name AS PersonName,
|
||||||
|
W.WorkoutId,
|
||||||
|
W.StartDate,
|
||||||
|
T.TopSetId,
|
||||||
|
E.ExcerciseId,
|
||||||
|
E.Name AS ExerciseName,
|
||||||
|
T.Repetitions,
|
||||||
|
T.Weight
|
||||||
|
FROM Person P
|
||||||
|
LEFT JOIN Workout W ON P.PersonId=W.PersonId
|
||||||
|
LEFT JOIN TopSet T ON W.WorkoutId=T.WorkoutId
|
||||||
|
LEFT JOIN Excercise E ON T.ExcerciseId=E.ExcerciseId
|
||||||
|
WHERE P.PersonId=?""", [person_id])
|
||||||
|
|
||||||
|
return {
|
||||||
|
'PersonId': next((t['PersonId'] for t in topsets), -1),
|
||||||
|
'PersonName': next((t['PersonName'] for t in topsets), 'Unknown'),
|
||||||
|
'Exercises': get_all_exercises_from_topsets(topsets),
|
||||||
|
'Workouts': get_workouts(topsets)
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_workout_final(self, person_id, workout_id):
|
||||||
|
topsets = self.execute("""
|
||||||
|
SELECT
|
||||||
|
P.PersonId,
|
||||||
|
P.Name AS PersonName,
|
||||||
|
W.WorkoutId,
|
||||||
|
W.StartDate,
|
||||||
|
T.TopSetId,
|
||||||
|
E.ExcerciseId,
|
||||||
|
E.Name AS ExerciseName,
|
||||||
|
T.Repetitions,
|
||||||
|
T.Weight
|
||||||
|
FROM Person P
|
||||||
|
LEFT JOIN Workout W ON P.PersonId=W.PersonId
|
||||||
|
LEFT JOIN TopSet T ON W.WorkoutId=T.WorkoutId
|
||||||
|
LEFT JOIN Excercise E ON T.ExcerciseId=E.ExcerciseId
|
||||||
|
WHERE P.PersonId=?
|
||||||
|
AND W.WorkoutId = ?""", [person_id, workout_id])
|
||||||
|
|
||||||
|
return {
|
||||||
|
'PersonId': next((t['PersonId'] for t in topsets), -1),
|
||||||
|
'PersonName': next((t['PersonName'] for t in topsets), 'Unknown'),
|
||||||
|
'WorkoutId': workout_id,
|
||||||
|
'StartDate': next((t['StartDate'] for t in topsets), 'Unknown'),
|
||||||
|
'Exercises': self.get_exercises(),
|
||||||
|
'TopSets': [{"TopSetId": t['TopSetId'], "ExcerciseId": t['ExcerciseId'], "ExerciseName": t['ExerciseName'], "Weight": t['Weight'], "Repetitions": t['Repetitions']} for t in topsets if t['TopSetId'] is not None]
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_topset_final(self, person_id, workout_id, topset_id):
|
||||||
|
topset = self.execute("""
|
||||||
|
SELECT
|
||||||
|
P.PersonId,
|
||||||
|
P.Name AS PersonName,
|
||||||
|
W.WorkoutId,
|
||||||
|
W.StartDate,
|
||||||
|
T.TopSetId,
|
||||||
|
E.ExcerciseId,
|
||||||
|
E.Name AS ExerciseName,
|
||||||
|
T.Repetitions,
|
||||||
|
T.Weight
|
||||||
|
FROM Person P
|
||||||
|
INNER JOIN Workout W ON P.PersonId=W.PersonId
|
||||||
|
INNER JOIN TopSet T ON W.WorkoutId=T.WorkoutId
|
||||||
|
INNER JOIN Excercise E ON T.ExcerciseId=E.ExcerciseId
|
||||||
|
WHERE P.PersonId=?
|
||||||
|
AND W.WorkoutId = ?
|
||||||
|
AND T.TopSetId = ?""", [person_id, workout_id, topset_id], one=True)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'PersonId': topset['PersonId'],
|
||||||
|
'PersonName': topset['PersonName'],
|
||||||
|
'WorkoutId': workout_id,
|
||||||
|
'StartDate': topset['StartDate'],
|
||||||
|
'Exercises': self.get_exercises(),
|
||||||
|
"TopSetId": topset['TopSetId'],
|
||||||
|
"ExcerciseId": topset['ExcerciseId'],
|
||||||
|
"ExerciseName": topset['ExerciseName'],
|
||||||
|
"Weight": topset['Weight'],
|
||||||
|
"Repetitions": topset['Repetitions']
|
||||||
|
}
|
||||||
42
decorators.py
Normal file
42
decorators.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
from flask import render_template, url_for
|
||||||
|
|
||||||
|
|
||||||
|
def validate_person(func):
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
person_id = kwargs.get('person_id')
|
||||||
|
from app import db
|
||||||
|
person = db.get_person(person_id)
|
||||||
|
if person is None:
|
||||||
|
return render_template('error.html', error='404', message=f'Unable to find Person({person_id})', url='/')
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def validate_workout(func):
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
person_id = kwargs.get('person_id')
|
||||||
|
workout_id = kwargs.get('workout_id')
|
||||||
|
from app import db
|
||||||
|
workout = db.get_workout(person_id, workout_id)
|
||||||
|
if workout is None:
|
||||||
|
return render_template('error.html', error='404', message=f'Unable to find Workout({workout_id}) completed by Person({person_id})', url=url_for('get_person', person_id=person_id))
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def validate_topset(func):
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
person_id = kwargs.get('person_id')
|
||||||
|
workout_id = kwargs.get('workout_id')
|
||||||
|
topset_id = kwargs.get('topset_id')
|
||||||
|
from app import db
|
||||||
|
topset = db.get_topset(person_id, workout_id, topset_id)
|
||||||
|
if topset is None:
|
||||||
|
return render_template('error.html', error='404', message=f'Unable to find TopSet({topset_id}) in Workout({workout_id}) completed by Person({person_id})', url=url_for('get_workout', person_id=person_id, workout_id=workout_id))
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
17
swagger/create_topset.yml
Normal file
17
swagger/create_topset.yml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
Add top set to workout
|
||||||
|
Add a topset to a workout completed by a person
|
||||||
|
---
|
||||||
|
tags:
|
||||||
|
- Topset
|
||||||
|
parameters:
|
||||||
|
- name: person_id
|
||||||
|
in: path
|
||||||
|
type: number
|
||||||
|
required: true
|
||||||
|
- name: workout_id
|
||||||
|
in: path
|
||||||
|
type: number
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: A list of topsets in a selected workout
|
||||||
13
swagger/create_workout.yml
Normal file
13
swagger/create_workout.yml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
Create new workout
|
||||||
|
Creates a workout with current date and then redirects to newly created workout
|
||||||
|
---
|
||||||
|
tags:
|
||||||
|
- Workout
|
||||||
|
parameters:
|
||||||
|
- name: person_id
|
||||||
|
in: path
|
||||||
|
type: number
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: View of newly created workout
|
||||||
8
swagger/dashboard.yml
Normal file
8
swagger/dashboard.yml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Dashboard page
|
||||||
|
Displays stats and a list of all people and there rep maxes for each exercise
|
||||||
|
---
|
||||||
|
tags:
|
||||||
|
- Dashboard
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: A list of all people and there rep maxes for each exercise
|
||||||
21
swagger/delete_topset.yml
Normal file
21
swagger/delete_topset.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
Delete top set
|
||||||
|
Add a topset to a workout completed by a person
|
||||||
|
---
|
||||||
|
tags:
|
||||||
|
- Topset
|
||||||
|
parameters:
|
||||||
|
- name: person_id
|
||||||
|
in: path
|
||||||
|
type: number
|
||||||
|
required: true
|
||||||
|
- name: workout_id
|
||||||
|
in: path
|
||||||
|
type: number
|
||||||
|
required: true
|
||||||
|
- name: topset_id
|
||||||
|
in: path
|
||||||
|
type: number
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: A list of topsets in a selected workout
|
||||||
17
swagger/delete_workout.yml
Normal file
17
swagger/delete_workout.yml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
Delete workout
|
||||||
|
Deletes selected workout completed by a person
|
||||||
|
---
|
||||||
|
tags:
|
||||||
|
- Workout
|
||||||
|
parameters:
|
||||||
|
- name: person_id
|
||||||
|
in: path
|
||||||
|
type: number
|
||||||
|
required: true
|
||||||
|
- name: workout_id
|
||||||
|
in: path
|
||||||
|
type: number
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Redirect to workouts list page for person
|
||||||
13
swagger/get_person.yml
Normal file
13
swagger/get_person.yml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
Display all workouts for a person
|
||||||
|
Displays stats and a list of all people and there rep maxes for each exercise
|
||||||
|
---
|
||||||
|
tags:
|
||||||
|
- Person
|
||||||
|
parameters:
|
||||||
|
- name: person_id
|
||||||
|
in: path
|
||||||
|
type: number
|
||||||
|
equired: true
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: A list of all people and there rep maxes for each exercise
|
||||||
21
swagger/get_topset.yml
Normal file
21
swagger/get_topset.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
Display/Create new top set
|
||||||
|
Displays stats and a list of all people and there rep maxes for each exercise
|
||||||
|
---
|
||||||
|
tags:
|
||||||
|
- Topset
|
||||||
|
parameters:
|
||||||
|
- name: person_id
|
||||||
|
in: path
|
||||||
|
type: number
|
||||||
|
required: true
|
||||||
|
- name: workout_id
|
||||||
|
in: path
|
||||||
|
type: number
|
||||||
|
required: true
|
||||||
|
- name: topset_id
|
||||||
|
in: path
|
||||||
|
type: number
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: A list of topsets in a selected workout
|
||||||
17
swagger/get_workout.yml
Normal file
17
swagger/get_workout.yml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
Display a workout
|
||||||
|
Displays a selected workout with options to edit/delete existing and add new topsets
|
||||||
|
---
|
||||||
|
tags:
|
||||||
|
- Workout
|
||||||
|
parameters:
|
||||||
|
- name: person_id
|
||||||
|
in: path
|
||||||
|
type: number
|
||||||
|
required: true
|
||||||
|
- name: workout_id
|
||||||
|
in: path
|
||||||
|
type: number
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: A list of topsets in a selected workout
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
|
|
||||||
{% for p in get_list_of_people_and_workout_count() %}
|
{% for p in get_list_of_people_and_workout_count() %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ url_for('display_workouts_for_person' ,person_id=p['PersonId']) }}"
|
<a href="{{ url_for('get_person' ,person_id=p['PersonId']) }}"
|
||||||
class="text-base text-gray-900 font-normal rounded-lg hover:bg-gray-100 flex items-center p-2 group {% if p['IsActive']==1 %} bg-gray-200 {% endif %}">
|
class="text-base text-gray-900 font-normal rounded-lg hover:bg-gray-100 flex items-center p-2 group {% if p['IsActive']==1 %} bg-gray-200 {% endif %}">
|
||||||
<svg class="w-6 h-6 text-gray-500 flex-shrink-0 group-hover:text-gray-900 transition duration-75"
|
<svg class="w-6 h-6 text-gray-500 flex-shrink-0 group-hover:text-gray-900 transition duration-75"
|
||||||
fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<span class="text-base font-normal text-gray-500">Current rep maxes</span>
|
<span class="text-base font-normal text-gray-500">Current rep maxes</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<a href="{{ url_for('display_workouts_for_person' ,person_id=1) }}"
|
<a href="{{ url_for('get_workout' ,person_id=1) }}"
|
||||||
class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg p-2">View workouts</a>
|
class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg p-2">View workouts</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -173,7 +173,7 @@
|
|||||||
<span class="text-base font-normal text-gray-500">Current rep maxes</span>
|
<span class="text-base font-normal text-gray-500">Current rep maxes</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<a href="{{ url_for('display_workouts_for_person' ,person_id=2) }}"
|
<a href="{{ url_for('get_workout' ,person_id=2) }}"
|
||||||
class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg p-2">View workouts</a>
|
class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg p-2">View workouts</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
|
|
||||||
<div class="mb-4 flex items-center justify-between">
|
<div class="mb-4 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-xl font-bold text-gray-900 mb-2">{{ topset['Name'] }}</h3>
|
<h3 class="text-xl font-bold text-gray-900 mb-2">{{ topset['PersonName'] }}</h3>
|
||||||
<span class="text-base font-normal text-gray-500">{{ topset['StartDate'] }}</span>
|
<span class="text-base font-normal text-gray-500">{{ topset['StartDate'] }}</span>
|
||||||
</div>
|
</div>
|
||||||
<a href="{{ url_for('delete_topset_from_workout_for_person', person_id=person_id, workout_id=workout_id, topset_id=topset_id)}}"
|
<a href="{{ url_for('delete_topset', person_id=topset['PersonId'], workout_id=topset['WorkoutId'], topset_id=topset['TopSetId'])}}"
|
||||||
class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg inline-flex items-center p-2">
|
class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg inline-flex items-center p-2">
|
||||||
Delete topset
|
Delete topset
|
||||||
</a>
|
</a>
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
<select
|
<select
|
||||||
class="block appearance-none w-full bg-gray-200 border border-gray-200 text-gray-700 py-3 px-4 pr-8 rounded leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
|
class="block appearance-none w-full bg-gray-200 border border-gray-200 text-gray-700 py-3 px-4 pr-8 rounded leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
|
||||||
id="grid-state" name="exercise_id">
|
id="grid-state" name="exercise_id">
|
||||||
{% for e in exercises %}
|
{% for e in topset['Exercises'] %}
|
||||||
<option value="{{ e['ExcerciseId'] }}" {% if topset['ExcerciseId']==e['ExcerciseId'] %}
|
<option value="{{ e['ExcerciseId'] }}" {% if topset['ExcerciseId']==e['ExcerciseId'] %}
|
||||||
selected {% endif %}>{{
|
selected {% endif %}>{{
|
||||||
e['Name']}}</option>
|
e['Name']}}</option>
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
|
|
||||||
<div class="mb-4 flex items-center justify-between">
|
<div class="mb-4 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-xl font-bold text-gray-900 mb-2">{{ workout_info['Name'] }}</h3>
|
<h3 class="text-xl font-bold text-gray-900 mb-2">{{ workout['PersonName'] }}</h3>
|
||||||
<span class="text-base font-normal text-gray-500">{{ workout_info['StartDate'] }}</span>
|
<span class="text-base font-normal text-gray-500">{{ workout['StartDate'] }}</span>
|
||||||
</div>
|
</div>
|
||||||
<form action="{{ url_for('delete_workout_from_person', person_id=person_id, workout_id=workout_id) }}"
|
<form action="{{ url_for('delete_workout', person_id=workout['PersonId'], workout_id=workout['WorkoutId']) }}"
|
||||||
method="delete">
|
method="delete">
|
||||||
<button
|
<button
|
||||||
class="sm:inline-flex text-white bg-cyan-600 hover:bg-cyan-700 focus:ring-4 focus:ring-cyan-200 font-medium rounded-lg text-sm px-5 py-2.5 text-center items-center mt-6"
|
class="sm:inline-flex text-white bg-cyan-600 hover:bg-cyan-700 focus:ring-4 focus:ring-cyan-200 font-medium rounded-lg text-sm px-5 py-2.5 text-center items-center mt-6"
|
||||||
@@ -41,19 +41,19 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-100">
|
<tbody class="divide-y divide-gray-100">
|
||||||
{% for t in top_sets %}
|
{% for t in workout['TopSets'] %}
|
||||||
<tr class="text-gray-500">
|
<tr class="text-gray-500">
|
||||||
<th class="border-t-0 px-4 align-middle text-l font-normal whitespace-nowrap p-4 text-left">
|
<th class="border-t-0 px-4 align-middle text-l font-normal whitespace-nowrap p-4 text-left">
|
||||||
{{ t['Name'] }}
|
{{ t['ExerciseName'] }}</th>
|
||||||
</th>
|
</th>
|
||||||
<td class="border-t-0 px-4 align-middle text-l font-medium text-gray-900 whitespace-nowrap p-4">
|
<td class="border-t-0 px-4 align-middle text-l font-medium text-gray-900 whitespace-nowrap p-4">
|
||||||
{{ t['TopSet'] }}</td>
|
{{ t['Repetitions'] }} x {{ t['Weight'] }}kg</td>
|
||||||
<td class="border-t-0 px-4 align-middle text-xs whitespace-nowrap p-4">
|
<td class="border-t-0 px-4 align-middle text-xs whitespace-nowrap p-4">
|
||||||
<a href="{{ url_for('show_topset_from_workout_for_person', person_id=person_id, workout_id=workout_id, topset_id=t['TopSetId']) }}"
|
<a href="{{ url_for('get_topset', person_id=workout['PersonId'], workout_id=workout['WorkoutId'], topset_id=t['TopSetId']) }}"
|
||||||
class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg inline-flex items-center p-2">
|
class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg inline-flex items-center p-2">
|
||||||
Edit
|
Edit
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ url_for('delete_topset_from_workout_for_person', person_id=person_id, workout_id=workout_id, topset_id=t['TopSetId'])}}"
|
<a href="{{ url_for('delete_topset', person_id=workout['PersonId'], workout_id=workout['WorkoutId'], topset_id=t['TopSetId'])}}"
|
||||||
class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg inline-flex items-center p-2">
|
class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg inline-flex items-center p-2">
|
||||||
Delete
|
Delete
|
||||||
</a>
|
</a>
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
<div class="bg-white shadow rounded-lg p-4 sm:p-6 xl:p-8 2xl:col-span-2 mt-4">
|
<div class="bg-white shadow rounded-lg p-4 sm:p-6 xl:p-8 2xl:col-span-2 mt-4">
|
||||||
<div class=" ">
|
<div class=" ">
|
||||||
<form class="w-full max-w-lg"
|
<form class="w-full max-w-lg"
|
||||||
action="{{ url_for('add_topset_to_workout_for_person', person_id=person_id, workout_id=workout_id) }}"
|
action="{{ url_for('create_topset', person_id=workout['PersonId'], workout_id=workout['WorkoutId']) }}"
|
||||||
method="post">
|
method="post">
|
||||||
|
|
||||||
<div class="flex flex-wrap -mx-3 mb-2">
|
<div class="flex flex-wrap -mx-3 mb-2">
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
<select
|
<select
|
||||||
class="block appearance-none w-full bg-gray-200 border border-gray-200 text-gray-700 py-3 px-4 pr-8 rounded leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
|
class="block appearance-none w-full bg-gray-200 border border-gray-200 text-gray-700 py-3 px-4 pr-8 rounded leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
|
||||||
id="grid-state" name="exercise_id">
|
id="grid-state" name="exercise_id">
|
||||||
{% for e in exercises %}
|
{% for e in workout['Exercises'] %}
|
||||||
<option value="{{ e['ExcerciseId'] }}">{{
|
<option value="{{ e['ExcerciseId'] }}">{{
|
||||||
e['Name']}}</option>
|
e['Name']}}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<div class="mb-4 flex items-center justify-between">
|
<div class="mb-4 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-xl font-bold text-gray-900 mb-2">{{ person['Name'] }}</h3>
|
<h3 class="text-xl font-bold text-gray-900 mb-2">{{ person['PersonName'] }}</h3>
|
||||||
<span class="text-base font-normal text-gray-500">List of workouts</span>
|
<span class="text-base font-normal text-gray-500">List of workouts</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -22,10 +22,10 @@
|
|||||||
class="p-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
class="p-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Date
|
Date
|
||||||
</th>
|
</th>
|
||||||
{% for e in exercises %}
|
{% for e in person['Exercises'] %}
|
||||||
<th scope="col"
|
<th scope="col"
|
||||||
class="p-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
class="p-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
{{ e['Name'] }}
|
{{ e['ExerciseName'] }}
|
||||||
</th>
|
</th>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<th scope="col"
|
<th scope="col"
|
||||||
@@ -35,28 +35,31 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody class="bg-white">
|
<tbody class="bg-white">
|
||||||
|
|
||||||
{% for w in workouts %}
|
{% for w in person['Workouts'] %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="p-4 whitespace-nowrap text-sm font-normal text-gray-500">
|
<td class="p-4 whitespace-nowrap text-sm font-normal text-gray-500">
|
||||||
{{ w['start_date'] }}
|
{{ w['StartDate'] }}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
{% for e in exercises %}
|
{% for e in person['Exercises'] %}
|
||||||
<td class="p-4 whitespace-nowrap text-sm font-semibold text-gray-900">
|
<td class="p-4 whitespace-nowrap text-sm font-semibold text-gray-900">
|
||||||
{% if e['ExcerciseId'] in w['topset_exercises'] %}
|
{% set topset_exercise =
|
||||||
{{ w['topset_exercises'][e['ExcerciseId']] }}
|
get_first_element_from_list_with_matching_attribute(w['TopSets'], 'ExcerciseId',
|
||||||
|
e['ExcerciseId']) %}
|
||||||
|
{% if topset_exercise %}
|
||||||
|
{{ topset_exercise['Repetitions'] }} x {{ topset_exercise['Weight'] }}kg
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<td class="p-4 whitespace-nowrap text-sm font-semibold text-gray-900">
|
<td class="p-4 whitespace-nowrap text-sm font-semibold text-gray-900">
|
||||||
<a href="{{ url_for('show_workout_for_person' ,person_id=person_id, workout_id=w['workout_id']) }}"
|
<a href="{{ url_for('get_workout' ,person_id=person['PersonId'], workout_id=w['WorkoutId']) }}"
|
||||||
class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg inline-flex items-center p-2">
|
class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg inline-flex items-center p-2">
|
||||||
Edit
|
Edit
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
action="{{ url_for('delete_workout_from_person', person_id=person_id, workout_id=w['workout_id']) }}"
|
action="{{ url_for('delete_workout', person_id=person['PersonId'], workout_id=w['WorkoutId']) }}"
|
||||||
method="delete" class="inline">
|
method="delete" class="inline">
|
||||||
<button
|
<button
|
||||||
class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg inline-flex items-center p-2"
|
class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg inline-flex items-center p-2"
|
||||||
@@ -69,7 +72,7 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<form action="{{ url_for('new_workout_for_person', person_id=person_id) }}" method="post">
|
<form action="{{ url_for('create_workout', person_id=person['PersonId']) }}" method="post">
|
||||||
<button
|
<button
|
||||||
class="sm:inline-flex text-white bg-cyan-600 hover:bg-cyan-700 focus:ring-4 focus:ring-cyan-200 font-medium rounded-lg text-sm px-5 py-2.5 text-center items-center mt-6">New
|
class="sm:inline-flex text-white bg-cyan-600 hover:bg-cyan-700 focus:ring-4 focus:ring-cyan-200 font-medium rounded-lg text-sm px-5 py-2.5 text-center items-center mt-6">New
|
||||||
workout</button>
|
workout</button>
|
||||||
|
|||||||
28
utils.py
Normal file
28
utils.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
def get_workouts(topsets):
|
||||||
|
# Get all unique workout_ids (No duplicates)
|
||||||
|
workout_ids = set([t['WorkoutId']
|
||||||
|
for t in topsets if t['WorkoutId'] is not None])
|
||||||
|
|
||||||
|
# Group topsets into workouts
|
||||||
|
workouts = []
|
||||||
|
for workout_id in workout_ids:
|
||||||
|
topsets_in_workout = [
|
||||||
|
t for t in topsets if t['WorkoutId'] == workout_id]
|
||||||
|
workouts.append({
|
||||||
|
'WorkoutId': workout_id,
|
||||||
|
'StartDate': topsets_in_workout[0]['StartDate'],
|
||||||
|
'TopSets': [{"TopSetId": t['TopSetId'], "ExcerciseId": t['ExcerciseId'], "ExerciseName": t['ExerciseName'], "Weight": t['Weight'], "Repetitions": t['Repetitions']} for t in topsets_in_workout]
|
||||||
|
})
|
||||||
|
return workouts
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_exercises_from_topsets(topsets):
|
||||||
|
exercise_ids = set([t['ExcerciseId']
|
||||||
|
for t in topsets if t['ExcerciseId'] is not None])
|
||||||
|
exercises = []
|
||||||
|
for exercise_id in exercise_ids:
|
||||||
|
exercises.append({
|
||||||
|
'ExcerciseId': exercise_id,
|
||||||
|
'ExerciseName': next((t['ExerciseName'] for t in topsets if t['ExcerciseId'] == exercise_id), 'Unknown')
|
||||||
|
})
|
||||||
|
return exercises
|
||||||
Reference in New Issue
Block a user