Move workout tag logic into tags blueprint

This commit is contained in:
Peter Stockings
2025-04-21 20:13:30 +10:00
parent c88d28b47c
commit e7d125d57b
4 changed files with 163 additions and 98 deletions

82
db.py
View File

@@ -341,88 +341,6 @@ class DataBase():
def delete_tag_for_dashboard(self, tag_id):
self.execute('DELETE FROM Tag WHERE tag_id=%s', [tag_id], commit=True)
# Note update logic moved to routes/notes.py
def add_tag_for_workout(self, workout_id, tags_id):
# If tags_id is not empty, delete tags that are not in the new selection
if tags_id:
self.execute(
"""
DELETE FROM workout_tag
WHERE workout_id = %s AND tag_id NOT IN %s
""",
[workout_id, tuple(tags_id)], commit=True
)
else:
# If tags_id is empty, delete all tags for this workout
self.execute(
"""
DELETE FROM workout_tag
WHERE workout_id = %s
""",
[workout_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",
TRUE AS "is_selected"
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

View File

@@ -100,4 +100,164 @@ def delete_tag(tag_id):
# Fetch updated tags and render the partial
tags = _get_tags_data(person_id)
return render_template('partials/tags.html', tags=tags, person_id=person_id)
return render_template('partials/tags.html', tags=tags, person_id=person_id)
# --- Workout Specific Tag Routes ---
@tags_bp.route("/workout/<int:workout_id>/add", methods=['POST'])
def add_tag_to_workout(workout_id):
"""Adds existing tags to a specific workout."""
# Note: Authorization (checking if the current user can modify this workout) might be needed here.
tags_id = [int(i) for i in request.form.getlist('tag_id')]
# --- Start: DB logic from db.add_tag_for_workout ---
# If tags_id is not empty, delete tags that are not in the new selection
if tags_id:
db.execute(
"""
DELETE FROM workout_tag
WHERE workout_id = %s AND tag_id NOT IN %s
""",
[workout_id, tuple(tags_id)], commit=True
)
else:
# If tags_id is empty, delete all tags for this workout
db.execute(
"""
DELETE FROM workout_tag
WHERE workout_id = %s
""",
[workout_id], commit=True
)
# Then, attempt to insert the new tags
for tag_id in tags_id:
db.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 = db.execute("""
SELECT
T.tag_id AS "tag_id",
T.person_id AS "person_id",
T.name AS "tag_name",
T.filter AS "tag_filter",
TRUE AS "is_selected" -- Mark these as selected since they are now associated
FROM Workout_Tag WT
LEFT JOIN Tag T ON WT.tag_id=T.tag_id
WHERE WT.workout_id=%s
ORDER BY T.name ASC -- Keep consistent ordering
""", [workout_id])
# --- End: DB logic from db.add_tag_for_workout ---
# We need person_id for rendering the partial if it requires it, fetch it.
workout_info = db.execute("SELECT person_id FROM workout WHERE workout_id = %s", [workout_id], one=True)
person_id = workout_info['person_id'] if workout_info else None
# The partial likely needs the full tag list for the workout, including unselected ones.
# Let's fetch all relevant tags for the person and mark selected ones.
all_person_tags = db.execute("""
SELECT
tag.tag_id,
tag.name AS tag_name,
tag.filter as tag_filter,
EXISTS (
SELECT 1 FROM workout_tag wt_check
WHERE wt_check.workout_id = %s AND wt_check.tag_id = tag.tag_id
) AS is_selected
FROM tag
WHERE tag.person_id = %s OR tag.person_id IS NULL -- Include global tags
ORDER BY tag.name ASC;
""", [workout_id, person_id])
# Render the partial with the complete list of tags (selected and unselected)
return render_template('partials/workout_tags_list.html', tags=all_person_tags, person_id=person_id, workout_id=workout_id)
@tags_bp.route("/workout/<int:workout_id>/new", methods=['POST'])
def create_new_tag_for_workout(workout_id):
"""Creates a new tag and associates it with a specific workout."""
# Note: Authorization might be needed here.
tag_name = request.form.get('tag_name')
if not tag_name:
# Handle error: Tag name cannot be empty
# Consider returning an error message via HTMX
return "Tag name cannot be empty", 400
# Fetch person_id associated with the workout_id first
workout_info = db.execute("SELECT person_id FROM workout WHERE workout_id = %s", [workout_id], one=True)
if not workout_info:
# Handle error: Workout not found
return "Workout not found", 404
person_id = workout_info['person_id']
# --- Start: DB logic from db.create_tag_for_workout ---
# Determine tag filter based on exercises in the workout (if any)
workout_exercises = db.execute("""
SELECT DISTINCT E.exercise_id AS "exercise_id"
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 AND E.exercise_id IS NOT NULL""", [workout_id])
tag_filter = ""
if workout_exercises:
tag_filter = "?" + "&".join(
f"exercise_id={e['exercise_id']}" for e in workout_exercises)
# Create tag for person (or update if exists with same name for this person)
# Check if tag already exists for this person
existing_tag = db.execute(
"SELECT tag_id FROM Tag WHERE person_id = %s AND name = %s",
[person_id, tag_name], one=True
)
new_tag_id = None
if existing_tag:
new_tag_id = existing_tag['tag_id']
# Optionally update the filter if it's different? For now, just use existing tag.
# db.execute("UPDATE Tag SET filter = %s WHERE tag_id = %s", [tag_filter, new_tag_id], commit=True)
else:
# Create the new tag
row = db.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
)
if row:
new_tag_id = row['tag_id']
if not new_tag_id:
# Handle error: Failed to create or find tag
return "Error creating tag", 500
# Add tag to workout (handle conflict just in case)
db.execute(
'INSERT INTO Workout_Tag (workout_id, tag_id) VALUES (%s, %s) ON CONFLICT (workout_id, tag_id) DO NOTHING',
[workout_id, new_tag_id], commit=True
)
# Now fetch updated list of all workout tags (selected and unselected)
all_person_tags = db.execute("""
SELECT
tag.tag_id,
tag.name AS tag_name,
tag.filter as tag_filter,
EXISTS (
SELECT 1 FROM workout_tag wt_check
WHERE wt_check.workout_id = %s AND wt_check.tag_id = tag.tag_id
) AS is_selected
FROM tag
WHERE tag.person_id = %s OR tag.person_id IS NULL -- Include global tags
ORDER BY tag.name ASC;
""", [workout_id, person_id])
# --- End: DB logic from db.create_tag_for_workout ---
# Render the partial with the complete list of tags
# Use 'tags' as the variable name consistent with the other endpoint and likely the partial
return render_template('partials/workout_tags_list.html', tags=all_person_tags, person_id=person_id, workout_id=workout_id)

View File

@@ -208,18 +208,6 @@ def delete_topset(person_id, workout_id, topset_id):
db.delete_topset(topset_id)
return ""
@workout_bp.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) # Keep using db.py for complex tag logic for now
return render_template('partials/workout_tags_list.html', tags=tags)
@workout_bp.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) # Keep using db.py for complex tag logic for now
return render_template('partials/workout_tags_list.html', workout_tags=workout_tags)
@workout_bp.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)

View File

@@ -35,7 +35,7 @@
<div class="relative">
<div class="w-full">
<select multiple name="tag_id"
hx-post="{{ url_for('workout.add_tag_to_workout', person_id=person_id, workout_id=workout_id) }}"
hx-post="{{ url_for('tags.add_tag_to_workout', 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"
_="init js(me)
@@ -65,8 +65,7 @@
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('workout.create_new_tag_for_workout', person_id=person_id, workout_id=workout_id) }}"
<button type="submit" hx-post="{{ url_for('tags.create_new_tag_for_workout', 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"