Convert edit/add/remove people form in settings to htmx driven and trigger event to refresh list of people and workout count on insert/update/remove person

This commit is contained in:
Peter Stockings
2022-11-20 14:14:04 +11:00
parent 93fc2fcfb8
commit 3dcc61005e
5 changed files with 161 additions and 74 deletions

91
app.py
View File

@@ -19,6 +19,12 @@ def dashboard():
return render_template('index.html', model=people_and_exercise_rep_maxes)
@ 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>")
@ validate_person
def get_person(person_id):
@@ -216,14 +222,91 @@ def delete_topset(person_id, workout_id, topset_id):
@ app.route("/person", methods=['POST'])
def create_person():
name = request.form.get("name")
db.create_person(name)
return redirect(url_for('settings'))
new_person_id = db.create_person(name)
return f"""
<tr>
<td class="p-4 whitespace-nowrap text-sm font-semibold text-gray-900">
{ name }
</td>
<td class="p-4 whitespace-nowrap text-sm font-semibold text-gray-900">
<a hx-get="{ url_for('get_person_edit_form', person_id=new_person_id) }"
class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg inline-flex items-center p-2 cursor-pointer">
Edit
</a>
<a hx-delete="{ url_for('delete_person', person_id=new_person_id) }"
class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg inline-flex items-center p-2 cursor-pointer">
Remove
</a>
</td>
</tr>
""", 200, {"HX-Trigger": "updatedPeople"}
@ app.route("/person/<int:person_id>/delete", methods=['GET', 'POST'])
@ app.route("/person/<int:person_id>/delete", methods=['DELETE'])
def delete_person(person_id):
db.delete_person(person_id)
return redirect(url_for('settings'))
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 f"""
<tr>
<td class="p-4 whitespace-nowrap text-sm font-semibold text-gray-900">
<input class="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500" type="text" name="name" value="{person['PersonName']}">
</td>
<td class="p-4 whitespace-nowrap text-sm font-semibold text-gray-900">
<a hx-put="{ url_for('update_person_name', person_id=person_id) }" hx-include="closest tr" class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg inline-flex items-center p-2 cursor-pointer">
Update
</a>
<a hx-get="{url_for('get_person_name', person_id=person_id)}" class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg inline-flex items-center p-2 cursor-pointer">
Cancel
</a>
</td>
</tr>
"""
@ 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 f"""
<tr>
<td class="p-4 whitespace-nowrap text-sm font-semibold text-gray-900">
{new_name}
</td>
<td class="p-4 whitespace-nowrap text-sm font-semibold text-gray-900">
<a hx-get="{ url_for('get_person_edit_form', person_id=person_id) }" hx-include="closest tr" class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg inline-flex items-center p-2 cursor-pointer">
Edit
</a>
<a href="{ url_for('delete_person', person_id=person_id) }" class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg inline-flex items-center p-2 cursor-pointer">
Remove
</a>
</td>
</tr>
""", 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 f"""
<tr>
<td class="p-4 whitespace-nowrap text-sm font-semibold text-gray-900">
{person['PersonName']}
</td>
<td class="p-4 whitespace-nowrap text-sm font-semibold text-gray-900">
<a hx-get="{ url_for('get_person_edit_form', person_id=person_id) }" hx-include="closest tr" class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg inline-flex items-center p-2 cursor-pointer">
Edit
</a>
<a href="{ url_for('delete_person', person_id=person_id) }" class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg inline-flex items-center p-2 cursor-pointer">
Remove
</a>
</td>
</tr>
"""
@ app.route("/exercise", methods=['POST'])

87
db.py
View File

@@ -65,8 +65,9 @@ class DataBase():
return person
def create_person(self, name):
self.execute('INSERT INTO Person (Name) VALUES (%s)',
[name], commit=True)
new_person = self.execute('INSERT INTO Person (Name) VALUES (%s) RETURNING PersonId AS "PersonId"', [
name], commit=True, one=True)
return new_person['PersonId']
def delete_person(self, person_id):
self.execute('DELETE FROM TopSet WHERE WorkoutId IN (SELECT WorkoutId FROM Workout WHERE PersonId=%s)', [
@@ -76,9 +77,13 @@ class DataBase():
self.execute('DELETE FROM Person WHERE PersonId=%s',
[person_id], commit=True)
def update_person_name(self, person_id, name):
self.execute('UPDATE Person SET Name=%s WHERE PersonId=%s', [
name, person_id], commit=True)
def is_valid_workout(self, person_id, workout_id):
workout = self.execute('SELECT W.WorkoutId AS "WorkoutId" FROM Person P, Workout W WHERE P.PersonId=W.PersonId AND P.PersonId=%s AND W.WorkoutId=%s LIMIT 1', [
person_id, workout_id], one=True)
person_id, workout_id], one=True)
return workout
def is_valid_topset(self, person_id, workout_id, topset_id):
@@ -121,12 +126,12 @@ class DataBase():
return self.execute("""
SELECT
P.PersonId AS "PersonId",
P.Name AS "Name",
COUNT(W.WorkoutId) AS "NumberOfWorkouts",
CASE P.PersonId
WHEN %s
THEN 1
ELSE 0
P.Name AS "Name",
COUNT(W.WorkoutId) AS "NumberOfWorkouts",
CASE P.PersonId
WHEN %s
THEN 1
ELSE 0
END "IsActive"
FROM
Person P LEFT JOIN Workout W ON P.PersonId = W.PersonId
@@ -141,15 +146,15 @@ class DataBase():
def get_person(self, person_id):
topsets = self.execute("""
SELECT
P.PersonId AS "PersonId",
P.Name AS "PersonName",
W.WorkoutId AS "WorkoutId",
W.StartDate AS "StartDate",
T.TopSetId AS "TopSetId",
SELECT
P.PersonId AS "PersonId",
P.Name AS "PersonName",
W.WorkoutId AS "WorkoutId",
W.StartDate AS "StartDate",
T.TopSetId AS "TopSetId",
E.ExerciseId AS "ExerciseId",
E.Name AS "ExerciseName",
T.Repetitions AS "Repetitions",
E.Name AS "ExerciseName",
T.Repetitions AS "Repetitions",
T.Weight AS "Weight"
FROM Person P
LEFT JOIN Workout W ON P.PersonId=W.PersonId
@@ -167,15 +172,15 @@ class DataBase():
def get_workout(self, person_id, workout_id):
topsets = self.execute("""
SELECT
P.PersonId AS "PersonId",
P.Name AS "PersonName",
W.WorkoutId AS "WorkoutId",
W.StartDate AS "StartDate",
T.TopSetId AS "TopSetId",
E.ExerciseId AS "ExerciseId",
E.Name AS "ExerciseName",
T.Repetitions AS "Repetitions",
SELECT
P.PersonId AS "PersonId",
P.Name AS "PersonName",
W.WorkoutId AS "WorkoutId",
W.StartDate AS "StartDate",
T.TopSetId AS "TopSetId",
E.ExerciseId AS "ExerciseId",
E.Name AS "ExerciseName",
T.Repetitions AS "Repetitions",
T.Weight AS "Weight"
FROM Person P
LEFT JOIN Workout W ON P.PersonId=W.PersonId
@@ -195,15 +200,15 @@ class DataBase():
def get_topset(self, person_id, workout_id, topset_id):
topset = self.execute("""
SELECT
P.PersonId AS "PersonId",
P.Name AS "PersonName",
W.WorkoutId AS "WorkoutId",
W.StartDate AS "StartDate",
T.TopSetId AS "TopSetId",
E.ExerciseId AS "ExerciseId",
E.Name AS "ExerciseName",
T.Repetitions AS "Repetitions",
SELECT
P.PersonId AS "PersonId",
P.Name AS "PersonName",
W.WorkoutId AS "WorkoutId",
W.StartDate AS "StartDate",
T.TopSetId AS "TopSetId",
E.ExerciseId AS "ExerciseId",
E.Name AS "ExerciseName",
T.Repetitions AS "Repetitions",
T.Weight AS "Weight"
FROM Person P
INNER JOIN Workout W ON P.PersonId=W.PersonId
@@ -228,15 +233,15 @@ class DataBase():
def get_all_topsets(self):
all_topsets = self.execute("""
SELECT
P.PersonId AS "PersonId",
P.Name AS "PersonName",
W.WorkoutId AS "WorkoutId",
W.StartDate AS "StartDate",
SELECT
P.PersonId AS "PersonId",
P.Name AS "PersonName",
W.WorkoutId AS "WorkoutId",
W.StartDate AS "StartDate",
T.TopSetId AS "TopSetId",
E.ExerciseId AS "ExerciseId",
E.Name AS "ExerciseName",
T.Repetitions AS "Repetitions",
T.Repetitions AS "Repetitions",
T.Weight AS "Weight",
round((100 * T.Weight)/(101.3-2.67123 * T.Repetitions),0)::numeric::integer AS "Estimated1RM"
FROM Person P

View File

@@ -85,25 +85,10 @@
<span class="ml-3">Dashboard</span>
</a>
</div>
<ul class="space-y-2 pb-2">
{% for p in get_list_of_people_and_workout_count() %}
<li>
<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 %}">
<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">
<path fill-rule="evenodd"
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd">
</path>
</svg>
<span class="ml-3 flex-1 whitespace-nowrap">{{ p['Name']}}</span>
<span
class="bg-gray-200 text-gray-800 ml-3 text-sm font-medium inline-flex items-center justify-center px-2 rounded-full">{{
p['NumberOfWorkouts'] }}</span>
</a>
</li>
{% endfor %}
<ul class="space-y-2 pb-2" hx-get="{{ url_for('get_person_list') }}"
hx-trigger="updatedPeople from:body">
{{ render_partial('partials/people_link.html',
people=get_list_of_people_and_workout_count()) }}
</ul>
<div class="space-y-2 pt-2">

View File

@@ -0,0 +1,16 @@
{% for p in people %}
<li>
<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 %}">
<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">
<path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd">
</path>
</svg>
<span class="ml-3 flex-1 whitespace-nowrap">{{ p['Name']}}</span>
<span
class="bg-gray-200 text-gray-800 ml-3 text-sm font-medium inline-flex items-center justify-center px-2 rounded-full">{{
p['NumberOfWorkouts'] }}</span>
</a>
</li>
{% endfor %}

View File

@@ -17,10 +17,6 @@
<table class="table-fixed min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col"
class="p-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-0.5">
#
</th>
<th scope="col"
class="p-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-3/5">
Name
@@ -30,17 +26,19 @@
</th>
</tr>
</thead>
<tbody class="bg-white">
<tbody class="bg-white" id="new-person" hx-target="closest tr"
hx-swap="outerHTML swap:0.5s">
{% for p in people %}
<tr>
<td class="p-4 whitespace-nowrap text-sm font-normal text-gray-500">
{{ loop.index }}
</td>
<td class="p-4 whitespace-nowrap text-sm font-semibold text-gray-900">
{{ p['Name'] }}
</td>
<td class="p-4 whitespace-nowrap text-sm font-semibold text-gray-900">
<a href="{{ url_for('delete_person', person_id=p['PersonId']) }}"
<a hx-get="{{ url_for('get_person_edit_form', person_id=p['PersonId']) }}"
class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg inline-flex items-center p-2 cursor-pointer">
Edit
</a>
<a hx-delete="{{ url_for('delete_person', person_id=p['PersonId']) }}"
class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg inline-flex items-center p-2 cursor-pointer">
Remove
</a>
@@ -54,7 +52,7 @@
</div>
</div>
<form class="w-full mt-3" action="{{ url_for('create_person') }}" method="post">
<form class="w-full mt-3" hx-post="{{ url_for('create_person') }}" hx-swap="beforeend" hx-target="#new-person">
<div class="flex flex-wrap -mx-3 mb-2">
<div class="grow px-3">
<label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" for="grid-city">