Add logic to handle adding/creating new tags for workouts
This commit is contained in:
18
app.py
18
app.py
@@ -167,7 +167,9 @@ def get_calendar(person_id):
|
|||||||
@ validate_workout
|
@ validate_workout
|
||||||
def get_workout_modal(person_id, workout_id):
|
def get_workout_modal(person_id, workout_id):
|
||||||
workout = db.get_workout(person_id, workout_id)
|
workout = db.get_workout(person_id, workout_id)
|
||||||
return render_template('partials/workout_modal.html', workout=workout)
|
(person_tags, workout_tags, selected_workout_tag_ids) = db.get_workout_tags(
|
||||||
|
person_id, workout_id)
|
||||||
|
return render_template('partials/workout_modal.html', workout=workout, person_tags=person_tags, workout_tags=workout_tags, selected_workout_tag_ids=selected_workout_tag_ids)
|
||||||
|
|
||||||
|
|
||||||
@ app.route("/person/<int:person_id>/workout", methods=['POST'])
|
@ app.route("/person/<int:person_id>/workout", methods=['POST'])
|
||||||
@@ -384,6 +386,20 @@ def get_workout_note(person_id, workout_id):
|
|||||||
return render_template('partials/workout_note.html', person_id=person_id, workout_id=workout_id, note=workout['Note'])
|
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')]
|
||||||
|
workout_tags = db.add_tag_for_workout(workout_id, tags_id)
|
||||||
|
return render_template('partials/workout_tags_list.html', workout_tags=workout_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.teardown_appcontext
|
@app.teardown_appcontext
|
||||||
def closeConnection(exception):
|
def closeConnection(exception):
|
||||||
db.close_connection()
|
db.close_connection()
|
||||||
|
|||||||
92
db.py
92
db.py
@@ -347,3 +347,95 @@ class DataBase():
|
|||||||
def update_workout_note_for_person(self, person_id, workout_id, note):
|
def update_workout_note_for_person(self, person_id, workout_id, note):
|
||||||
self.execute('UPDATE workout SET note=%s WHERE person_id=%s AND workout_id=%s', [
|
self.execute('UPDATE workout SET note=%s WHERE person_id=%s AND workout_id=%s', [
|
||||||
note, person_id, workout_id], commit=True)
|
note, person_id, workout_id], commit=True)
|
||||||
|
|
||||||
|
def add_tag_for_workout(self, workout_id, tags_id):
|
||||||
|
# First, delete tags that are not in the new selection
|
||||||
|
self.execute(
|
||||||
|
"""
|
||||||
|
DELETE FROM workout_tag
|
||||||
|
WHERE workout_id = %s AND tag_id NOT IN %s
|
||||||
|
""",
|
||||||
|
[workout_id, tuple(tags_id)], commit=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Then, attempt to insert the new tags
|
||||||
|
for tag_id in tags_id:
|
||||||
|
self.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO workout_tag (workout_id, tag_id)
|
||||||
|
VALUES (%s, %s)
|
||||||
|
ON CONFLICT (workout_id, tag_id) DO NOTHING
|
||||||
|
""",
|
||||||
|
[workout_id, tag_id], commit=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Now fetch updated list of workout tags
|
||||||
|
workout_tags = self.execute("""
|
||||||
|
SELECT
|
||||||
|
T.tag_id AS "tag_id",
|
||||||
|
T.person_id AS "person_id",
|
||||||
|
T.name AS "tag_name",
|
||||||
|
T.filter AS "tag_filter"
|
||||||
|
FROM Workout_Tag WT
|
||||||
|
LEFT JOIN Tag T ON WT.tag_id=T.tag_id
|
||||||
|
WHERE WT.workout_id=%s""", [workout_id])
|
||||||
|
|
||||||
|
return workout_tags
|
||||||
|
|
||||||
|
def create_tag_for_workout(self, person_id, workout_id, tag_name):
|
||||||
|
workout_exercises = self.execute("""
|
||||||
|
SELECT
|
||||||
|
E.exercise_id AS "exercise_id",
|
||||||
|
E.name AS "exercise_name"
|
||||||
|
FROM Workout W
|
||||||
|
LEFT JOIN TopSet T ON W.workout_id=T.workout_id
|
||||||
|
LEFT JOIN Exercise E ON T.exercise_id=E.exercise_id
|
||||||
|
WHERE W.workout_id=%s""", [workout_id])
|
||||||
|
|
||||||
|
tag_filter = "?" + \
|
||||||
|
"&".join(
|
||||||
|
f"exercise_id={e['exercise_id']}" for e in workout_exercises)
|
||||||
|
|
||||||
|
# create tag for person
|
||||||
|
row = self.execute('INSERT INTO Tag (person_id, name, filter) VALUES (%s, %s, %s) RETURNING tag_id AS "tag_id"', [
|
||||||
|
person_id, tag_name, tag_filter], commit=True, one=True)
|
||||||
|
|
||||||
|
# add tag to workout
|
||||||
|
self.execute('INSERT INTO Workout_Tag (workout_id, tag_id) VALUES (%s, %s)', [
|
||||||
|
workout_id, row['tag_id']], commit=True)
|
||||||
|
|
||||||
|
# Now fetch updated list of workout tags
|
||||||
|
workout_tags = self.execute("""
|
||||||
|
SELECT
|
||||||
|
T.tag_id AS "tag_id",
|
||||||
|
T.person_id AS "person_id",
|
||||||
|
T.name AS "tag_name",
|
||||||
|
T.filter AS "tag_filter"
|
||||||
|
FROM Workout_Tag WT
|
||||||
|
LEFT JOIN Tag T ON WT.tag_id=T.tag_id
|
||||||
|
WHERE WT.workout_id=%s""", [workout_id])
|
||||||
|
|
||||||
|
return workout_tags
|
||||||
|
|
||||||
|
def get_workout_tags(self, person_id, workout_id):
|
||||||
|
person_tags = self.execute("""
|
||||||
|
SELECT
|
||||||
|
T.tag_id AS "tag_id",
|
||||||
|
T.name AS "tag_name",
|
||||||
|
T.filter AS "tag_filter"
|
||||||
|
FROM Tag T
|
||||||
|
WHERE T.person_id=%s""", [person_id])
|
||||||
|
|
||||||
|
workout_tags = self.execute("""
|
||||||
|
SELECT
|
||||||
|
T.tag_id AS "tag_id",
|
||||||
|
T.person_id AS "person_id",
|
||||||
|
T.name AS "tag_name",
|
||||||
|
T.filter AS "tag_filter"
|
||||||
|
FROM Workout_Tag WT
|
||||||
|
LEFT JOIN Tag T ON WT.tag_id=T.tag_id
|
||||||
|
WHERE WT.workout_id=%s""", [workout_id])
|
||||||
|
|
||||||
|
selected_workout_tag_ids = [wt['tag_id'] for wt in workout_tags]
|
||||||
|
|
||||||
|
return (person_tags, workout_tags, selected_workout_tag_ids)
|
||||||
|
|||||||
@@ -16,109 +16,10 @@
|
|||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<h3 class="text-xl font-bold text-gray-900">{{ workout['PersonName'] }}</h3>
|
<h3 class="text-xl font-bold text-gray-900">{{ workout['PersonName'] }}</h3>
|
||||||
|
|
||||||
<div class="flex">
|
{{ render_partial('partials/workout_tags.html', person_id=workout['PersonId'],
|
||||||
<div id="tag-wrapper-w-{{ workout['WorkoutId'] }}">
|
workout_id=workout['WorkoutId'],
|
||||||
<span
|
person_tags=person_tags, workout_tags=workout_tags,
|
||||||
class="text-xs font-semibold inline-block py-1 px-2 uppercase rounded text-pink-600 bg-pink-200 uppercase last:mr-0 mr-1 max-h-fit">Push
|
selected_workout_tag_ids=selected_workout_tag_ids) }}
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="text-xs font-semibold inline-block py-1 px-2 uppercase rounded text-pink-600 bg-pink-200 uppercase last:mr-0 mr-1 max-h-fit">Accessories
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<span id="tag-form-show-w-{{ workout['WorkoutId'] }}" class="" _="on click
|
|
||||||
toggle .hidden on #tag-form-w-{{ workout['WorkoutId'] }}
|
|
||||||
then toggle .hidden on #tag-form-hide-w-{{ workout['WorkoutId'] }}
|
|
||||||
then toggle .hidden on #tag-wrapper-w-{{ workout['WorkoutId'] }}
|
|
||||||
then toggle .hidden on me">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5" stroke="currentColor" class="w-6 h-6"
|
|
||||||
data-darkreader-inline-stroke=""
|
|
||||||
style="--darkreader-inline-stroke: currentColor;">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round"
|
|
||||||
d="M12 4.5v15m7.5-7.5h-15"></path>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<span id="tag-form-hide-w-{{ workout['WorkoutId'] }}" _="on click
|
|
||||||
toggle .hidden on #tag-form-w-{{ workout['WorkoutId'] }}
|
|
||||||
then toggle .hidden on #tag-form-show-w-{{ workout['WorkoutId'] }}
|
|
||||||
then toggle .hidden on #tag-wrapper-w-{{ workout['WorkoutId'] }}
|
|
||||||
then toggle .hidden on me" class="hidden">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 12h-15" />
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div id="tag-form-w-{{workout['WorkoutId']}}" class="hidden pt-2 pb-3">
|
|
||||||
|
|
||||||
<span class="text-base font-normal text-gray-500">Add tag to workout</span>
|
|
||||||
|
|
||||||
<div class="flex pt-2 flex-col sm:flex-row pb-2">
|
|
||||||
|
|
||||||
<div class="w-full md:w-1/2 px-3 mb-6 md:mb-0">
|
|
||||||
<label
|
|
||||||
class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
|
|
||||||
for="grid-state">
|
|
||||||
Tag
|
|
||||||
</label>
|
|
||||||
<div class="relative">
|
|
||||||
<div class="w-full">
|
|
||||||
<select id="workout-tag-select-{{ workout['WorkoutId'] }}"
|
|
||||||
data-te-select-init data-te-select-filter="true"
|
|
||||||
data-te-select-size="lg" name="exercise_id"
|
|
||||||
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">
|
|
||||||
{% for e in workout['Exercises'] %}
|
|
||||||
<option value="{{ e['ExerciseId'] }}">{{
|
|
||||||
e['Name']
|
|
||||||
}}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
te.Select.getOrCreateInstance(document.querySelector("#workout-tag-select-{{ workout['WorkoutId'] }}"));
|
|
||||||
</script>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="w-full md:w-1/2 px-3 mb-6 md:mb-0">
|
|
||||||
<label
|
|
||||||
class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2">
|
|
||||||
New tag name
|
|
||||||
</label>
|
|
||||||
<div class="flex">
|
|
||||||
<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="repetitions">
|
|
||||||
|
|
||||||
<button type="submit"
|
|
||||||
class="p-2.5 ml-2 text-sm font-medium text-white bg-blue-700 rounded-lg border border-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none"
|
|
||||||
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
|
||||||
class="w-6 h-6" data-darkreader-inline-stroke=""
|
|
||||||
style="--darkreader-inline-stroke: currentColor;">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round"
|
|
||||||
d="M12 4.5v15m7.5-7.5h-15"></path>
|
|
||||||
</svg>
|
|
||||||
<span class="sr-only">Search</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<span class="text-base font-normal text-gray-500 pt-2">Remove tag from
|
|
||||||
workout</span>
|
|
||||||
<div>
|
|
||||||
<span
|
|
||||||
class="text-xs font-semibold inline-block py-1 px-2 uppercase rounded text-pink-600 bg-pink-100 uppercase last:mr-0 mr-1 max-h-fit">Push
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="text-xs font-semibold inline-block py-1 px-2 uppercase rounded text-pink-600 bg-pink-100 uppercase last:mr-0 mr-1 max-h-fit">Accessories
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
80
templates/partials/workout_tags.html
Normal file
80
templates/partials/workout_tags.html
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<div class="flex">
|
||||||
|
<div id="tag-wrapper-w-{{ workout_id }}">
|
||||||
|
{{ render_partial('partials/workout_tags_list.html', workout_tags=workout_tags)
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
<span id="tag-form-show-w-{{ workout_id }}" class="" _="on click
|
||||||
|
toggle .hidden on #tag-form-w-{{ workout_id }}
|
||||||
|
then toggle .hidden on #tag-form-hide-w-{{ workout_id }}
|
||||||
|
then toggle .hidden on me">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
||||||
|
class="w-6 h-6" data-darkreader-inline-stroke="" style="--darkreader-inline-stroke: currentColor;">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"></path>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span id="tag-form-hide-w-{{ workout_id }}" _="on click
|
||||||
|
toggle .hidden on #tag-form-w-{{ workout_id }}
|
||||||
|
then toggle .hidden on #tag-form-show-w-{{ workout_id }}
|
||||||
|
then toggle .hidden on me" class="hidden">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
||||||
|
class="w-6 h-6">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 12h-15" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div id="tag-form-w-{{ workout_id }}" class="hidden pt-2 pb-3">
|
||||||
|
|
||||||
|
<span class="text-base font-normal text-gray-500">Add tag to workout</span>
|
||||||
|
|
||||||
|
<div class="flex pt-2 flex-col sm:flex-row pb-2">
|
||||||
|
|
||||||
|
<div class="w-full md:w-1/2 px-3 mb-6 md:mb-0">
|
||||||
|
<label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" for="grid-state">
|
||||||
|
Tag
|
||||||
|
</label>
|
||||||
|
<div class="relative">
|
||||||
|
<div class="w-full">
|
||||||
|
<select id="workout-tag-select-{{ workout_id }}" data-te-select-init data-te-select-filter="true"
|
||||||
|
data-te-select-size="lg" multiple name="tag_id"
|
||||||
|
hx-post="{{ url_for('add_tag_to_workout', person_id=person_id, workout_id=workout_id) }}"
|
||||||
|
hx-target="#tag-wrapper-w-{{ workout_id }}"
|
||||||
|
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">
|
||||||
|
{% for p in person_tags %}
|
||||||
|
<option value="{{ p.tag_id }}">{{
|
||||||
|
p.tag_name
|
||||||
|
}}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
te.Select.getOrCreateInstance(document.querySelector("#workout-tag-select-{{ workout_id }}")).setValue({{ selected_workout_tag_ids| list_to_string | safe }});
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full md:w-1/2 px-3 mb-6 md:mb-0">
|
||||||
|
<label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2">
|
||||||
|
New tag name
|
||||||
|
</label>
|
||||||
|
<div class="flex">
|
||||||
|
<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="tag_name">
|
||||||
|
|
||||||
|
<button type="submit"
|
||||||
|
hx-post="{{ url_for('create_new_tag_for_workout', person_id=person_id, workout_id=workout_id) }}"
|
||||||
|
hx-include="[name='tag_name']" hx-target="#tag-wrapper-w-{{ workout_id }}"
|
||||||
|
class="p-2.5 ml-2 text-sm font-medium text-white bg-blue-700 rounded-lg border border-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||||
|
stroke="currentColor" class="w-6 h-6" data-darkreader-inline-stroke=""
|
||||||
|
style="--darkreader-inline-stroke: currentColor;">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"></path>
|
||||||
|
</svg>
|
||||||
|
<span class="sr-only">Search</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
8
templates/partials/workout_tags_list.html
Normal file
8
templates/partials/workout_tags_list.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{% for tag in workout_tags %}
|
||||||
|
<span
|
||||||
|
class="text-xs font-semibold inline-block py-1 px-2 uppercase rounded text-pink-600 bg-pink-200 uppercase last:mr-0 mr-1 max-h-fit cursor-pointer"
|
||||||
|
hx-get="{{ url_for('goto_tag') }}" hx-vals='{"filter": "{{ tag.tag_filter }}", "person_id": "{{ tag.person_id }}"}'
|
||||||
|
hx-target="#container" hx-push-url="true" _='on click trigger closeModal'>{{
|
||||||
|
tag.tag_name }}
|
||||||
|
</span>
|
||||||
|
{% endfor%}
|
||||||
Reference in New Issue
Block a user