Move workout tag logic into tags blueprint
This commit is contained in:
82
db.py
82
db.py
@@ -341,88 +341,6 @@ class DataBase():
|
|||||||
def delete_tag_for_dashboard(self, tag_id):
|
def delete_tag_for_dashboard(self, tag_id):
|
||||||
self.execute('DELETE FROM Tag WHERE tag_id=%s', [tag_id], commit=True)
|
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):
|
def get_workout_tags(self, person_id, workout_id):
|
||||||
person_tags = self.execute("""
|
person_tags = self.execute("""
|
||||||
SELECT
|
SELECT
|
||||||
|
|||||||
162
routes/tags.py
162
routes/tags.py
@@ -100,4 +100,164 @@ def delete_tag(tag_id):
|
|||||||
|
|
||||||
# Fetch updated tags and render the partial
|
# Fetch updated tags and render the partial
|
||||||
tags = _get_tags_data(person_id)
|
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)
|
||||||
@@ -208,18 +208,6 @@ def delete_topset(person_id, workout_id, topset_id):
|
|||||||
db.delete_topset(topset_id)
|
db.delete_topset(topset_id)
|
||||||
return ""
|
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'])
|
@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):
|
def get_most_recent_topset_for_exercise(person_id, workout_id):
|
||||||
exercise_id = request.args.get('exercise_id', type=int)
|
exercise_id = request.args.get('exercise_id', type=int)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<select multiple name="tag_id"
|
<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 }}"
|
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"
|
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)
|
_="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"
|
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">
|
type="text" name="tag_name">
|
||||||
|
|
||||||
<button type="submit"
|
<button type="submit" hx-post="{{ url_for('tags.create_new_tag_for_workout', workout_id=workout_id) }}"
|
||||||
hx-post="{{ url_for('workout.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 }}"
|
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">
|
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"
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||||
|
|||||||
Reference in New Issue
Block a user