Codebase hardening: 102 fixes across 35+ files

Deep audit identified 106 findings; 102 fixed, 4 deferred. Covers 8 areas:

- Settings & deploy: env-gated DEBUG/SECRET_KEY, HTTPS headers, gunicorn, celery worker
- Auth (registered_user): password write_only, request.data fixes, transaction safety, proper HTTP status codes
- Workout app: IDOR protection, get_object_or_404, prefetch_related N+1 fixes, transaction.atomic
- Video/scripts: path traversal sanitization, HLS trigger guard, auth on cache wipe
- Models (exercise/equipment/muscle/superset): null-safe __str__, stable IDs, prefetch support
- Generator views: helper for registered_user lookup, logger.exception, bulk_update, transaction wrapping
- Generator core (rules/selector/generator): push-pull ratio, type affinity normalization, modality checks, side-pair exact match, word-boundary regex, equipment cache clearing
- Generator services (plan_builder/analyzer/normalizer): transaction.atomic, muscle cache, bulk_update, glutes classification fix

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-27 22:29:14 -06:00
parent 63b57a83ab
commit c80c66c2e5
58 changed files with 3363 additions and 1049 deletions

View File

@@ -1,6 +1,8 @@
import logging
import time
from datetime import datetime, timedelta
from django.db import transaction
from django.shortcuts import get_object_or_404
from rest_framework.decorators import (
api_view,
@@ -44,6 +46,19 @@ from .serializers import (
)
from exercise.serializers import ExerciseSerializer
logger = logging.getLogger(__name__)
def get_registered_user(request):
"""Get RegisteredUser for the authenticated user, or 404.
Caches the result on the request object to avoid repeated DB hits
when called multiple times in the same request cycle.
"""
if not hasattr(request, '_registered_user'):
request._registered_user = get_object_or_404(RegisteredUser, user=request.user)
return request._registered_user
# ============================================================
# Generation Rules
@@ -67,7 +82,7 @@ def generation_rules(request):
@permission_classes([IsAuthenticated])
def get_preferences(request):
"""Get (or auto-create) the UserPreference for the logged-in user."""
registered_user = RegisteredUser.objects.get(user=request.user)
registered_user = get_registered_user(request)
preference, _created = UserPreference.objects.get_or_create(
registered_user=registered_user,
)
@@ -80,7 +95,7 @@ def get_preferences(request):
@permission_classes([IsAuthenticated])
def update_preferences(request):
"""Update the logged-in user's preferences. Accepts equipment_ids, muscle_ids, workout_type_ids."""
registered_user = RegisteredUser.objects.get(user=request.user)
registered_user = get_registered_user(request)
preference, _created = UserPreference.objects.get_or_create(
registered_user=registered_user,
)
@@ -109,7 +124,7 @@ def generate_plan(request):
Generate a weekly workout plan.
Body: {"week_start_date": "YYYY-MM-DD"}
"""
registered_user = RegisteredUser.objects.get(user=request.user)
registered_user = get_registered_user(request)
week_start_date_str = request.data.get('week_start_date')
if not week_start_date_str:
@@ -191,8 +206,9 @@ def generate_plan(request):
generation_warnings = generator.warnings
except Exception as e:
logger.exception("Unexpected error in generate_plan")
return Response(
{'error': f'Plan generation failed: {str(e)}'},
{"error": "An unexpected error occurred. Please try again."},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
@@ -212,9 +228,11 @@ def generate_plan(request):
@permission_classes([IsAuthenticated])
def list_plans(request):
"""List all generated plans for the logged-in user, newest first."""
registered_user = RegisteredUser.objects.get(user=request.user)
registered_user = get_registered_user(request)
plans = GeneratedWeeklyPlan.objects.filter(
registered_user=registered_user,
).select_related(
'registered_user',
).prefetch_related(
'generated_workouts__workout_type',
'generated_workouts__workout',
@@ -228,9 +246,11 @@ def list_plans(request):
@permission_classes([IsAuthenticated])
def plan_detail(request, plan_id):
"""Get a single plan with all its generated workouts."""
registered_user = RegisteredUser.objects.get(user=request.user)
registered_user = get_registered_user(request)
plan = get_object_or_404(
GeneratedWeeklyPlan.objects.prefetch_related(
GeneratedWeeklyPlan.objects.select_related(
'registered_user',
).prefetch_related(
'generated_workouts__workout_type',
'generated_workouts__workout',
),
@@ -253,9 +273,9 @@ def accept_workout(request, workout_id):
Accept a generated workout.
Sets status to 'accepted' and creates a PlannedWorkout for the scheduled_date.
"""
registered_user = RegisteredUser.objects.get(user=request.user)
registered_user = get_registered_user(request)
generated_workout = get_object_or_404(
GeneratedWorkout,
GeneratedWorkout.objects.select_related('workout', 'workout_type'),
pk=workout_id,
plan__registered_user=registered_user,
)
@@ -298,9 +318,9 @@ def reject_workout(request, workout_id):
Reject a generated workout with optional feedback.
Body: {"feedback": "..."}
"""
registered_user = RegisteredUser.objects.get(user=request.user)
registered_user = get_registered_user(request)
generated_workout = get_object_or_404(
GeneratedWorkout,
GeneratedWorkout.objects.select_related('workout', 'workout_type'),
pk=workout_id,
plan__registered_user=registered_user,
)
@@ -328,9 +348,9 @@ def rate_workout(request, workout_id):
Rate a generated workout 1-5 with optional feedback.
Body: {"rating": 5, "feedback": "..."}
"""
registered_user = RegisteredUser.objects.get(user=request.user)
registered_user = get_registered_user(request)
generated_workout = get_object_or_404(
GeneratedWorkout,
GeneratedWorkout.objects.select_related('workout', 'workout_type'),
pk=workout_id,
plan__registered_user=registered_user,
)
@@ -379,9 +399,9 @@ def regenerate_workout(request, workout_id):
Regenerate a single workout within an existing plan.
Deletes the old linked Workout (if any) and generates a fresh one for the same day/type.
"""
registered_user = RegisteredUser.objects.get(user=request.user)
registered_user = get_registered_user(request)
generated_workout = get_object_or_404(
GeneratedWorkout,
GeneratedWorkout.objects.select_related('workout', 'workout_type', 'plan'),
pk=workout_id,
plan__registered_user=registered_user,
)
@@ -413,20 +433,15 @@ def regenerate_workout(request, workout_id):
generator = WorkoutGenerator(preference)
# Exclude exercises from sibling workouts in the same plan (Item #9)
sibling_workouts = GeneratedWorkout.objects.filter(
plan=generated_workout.plan,
is_rest_day=False,
workout__isnull=False,
).exclude(pk=generated_workout.pk)
sibling_exercise_ids = set()
for sibling in sibling_workouts:
if sibling.workout:
sibling_exercise_ids.update(
SupersetExercise.objects.filter(
superset__workout=sibling.workout
).values_list('exercise_id', flat=True)
)
# Exclude exercises from sibling workouts in the same plan (single query)
sibling_exercise_ids = set(
SupersetExercise.objects.filter(
superset__workout__generated_from__plan=generated_workout.plan,
superset__workout__generated_from__is_rest_day=False,
).exclude(
superset__workout__generated_from=generated_workout,
).values_list('exercise_id', flat=True)
)
if sibling_exercise_ids:
generator.exercise_selector.hard_exclude_ids.update(sibling_exercise_ids)
@@ -489,8 +504,9 @@ def regenerate_workout(request, workout_id):
cache.delete(f"plan{generated_workout.plan_id}")
except Exception as e:
logger.exception("Unexpected error in regenerate_workout")
return Response(
{'error': f'Regeneration failed: {str(e)}'},
{"error": "An unexpected error occurred. Please try again."},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
@@ -510,9 +526,9 @@ def delete_workout_day(request, workout_id):
Delete a generated workout day (converts it to a rest day).
Deletes the linked Workout object (cascading to supersets/exercises).
"""
registered_user = RegisteredUser.objects.get(user=request.user)
registered_user = get_registered_user(request)
generated_workout = get_object_or_404(
GeneratedWorkout,
GeneratedWorkout.objects.select_related('workout'),
pk=workout_id,
plan__registered_user=registered_user,
)
@@ -545,7 +561,7 @@ def delete_workout_day(request, workout_id):
@permission_classes([IsAuthenticated])
def delete_superset(request, superset_id):
"""Delete a superset from a workout. Re-orders remaining supersets."""
registered_user = RegisteredUser.objects.get(user=request.user)
registered_user = get_registered_user(request)
superset = get_object_or_404(Superset, pk=superset_id)
# Verify ownership through the workout
@@ -565,11 +581,14 @@ def delete_superset(request, superset_id):
# Invalidate workout detail cache
cache.delete(f"wk{workout.id}")
# Re-order remaining supersets
remaining = Superset.objects.filter(workout=workout, order__gt=deleted_order).order_by('order')
# Re-order remaining supersets with bulk_update
remaining = list(
Superset.objects.filter(workout=workout, order__gt=deleted_order).order_by('order')
)
for ss in remaining:
ss.order -= 1
ss.save()
if remaining:
Superset.objects.bulk_update(remaining, ['order'])
return Response({'status': 'deleted'}, status=status.HTTP_200_OK)
@@ -579,7 +598,7 @@ def delete_superset(request, superset_id):
@permission_classes([IsAuthenticated])
def delete_superset_exercise(request, exercise_id):
"""Delete an exercise from a superset. Re-orders remaining exercises."""
registered_user = RegisteredUser.objects.get(user=request.user)
registered_user = get_registered_user(request)
superset_exercise = get_object_or_404(SupersetExercise, pk=exercise_id)
# Verify ownership
@@ -600,11 +619,14 @@ def delete_superset_exercise(request, exercise_id):
# Invalidate workout detail cache
cache.delete(f"wk{workout.id}")
# Re-order remaining exercises
remaining = SupersetExercise.objects.filter(superset=superset, order__gt=deleted_order).order_by('order')
# Re-order remaining exercises with bulk_update
remaining = list(
SupersetExercise.objects.filter(superset=superset, order__gt=deleted_order).order_by('order')
)
for se in remaining:
se.order -= 1
se.save()
if remaining:
SupersetExercise.objects.bulk_update(remaining, ['order'])
# If the superset is now empty, delete it too
if SupersetExercise.objects.filter(superset=superset).count() == 0:
@@ -653,7 +675,7 @@ def swap_exercise(request, exercise_id):
Swap a SupersetExercise's exercise for a new one.
Body: {"new_exercise_id": 123}
"""
registered_user = RegisteredUser.objects.get(user=request.user)
registered_user = get_registered_user(request)
superset_exercise = get_object_or_404(SupersetExercise, pk=exercise_id)
# Verify ownership
@@ -734,7 +756,7 @@ def analysis_stats(request):
"""
muscle_splits = MuscleGroupSplit.objects.all()
weekly_patterns = WeeklySplitPattern.objects.all()
structure_rules = WorkoutStructureRule.objects.all()
structure_rules = WorkoutStructureRule.objects.select_related('workout_type').all()
movement_orders = MovementPatternOrder.objects.all()
data = {
@@ -778,29 +800,35 @@ def confirm_plan(request, plan_id):
"""
Batch-accept all workouts in a plan and create PlannedWorkout entries.
"""
registered_user = RegisteredUser.objects.get(user=request.user)
registered_user = get_registered_user(request)
plan = get_object_or_404(
GeneratedWeeklyPlan,
pk=plan_id,
registered_user=registered_user,
)
workouts = GeneratedWorkout.objects.filter(plan=plan)
for gw in workouts:
if gw.is_rest_day or not gw.workout:
continue
gw.status = 'accepted'
gw.save()
workouts = GeneratedWorkout.objects.filter(plan=plan).select_related('workout')
PlannedWorkout.objects.filter(
registered_user=registered_user,
on_date=gw.scheduled_date,
).delete()
PlannedWorkout.objects.create(
workout=gw.workout,
registered_user=registered_user,
on_date=gw.scheduled_date,
)
with transaction.atomic():
workouts_to_update = []
for gw in workouts:
if gw.is_rest_day or not gw.workout:
continue
gw.status = 'accepted'
workouts_to_update.append(gw)
PlannedWorkout.objects.filter(
registered_user=registered_user,
on_date=gw.scheduled_date,
).delete()
PlannedWorkout.objects.create(
workout=gw.workout,
registered_user=registered_user,
on_date=gw.scheduled_date,
)
if workouts_to_update:
GeneratedWorkout.objects.bulk_update(workouts_to_update, ['status'])
serializer = GeneratedWeeklyPlanSerializer(plan)
return Response(serializer.data, status=status.HTTP_200_OK)
@@ -815,10 +843,10 @@ def confirm_plan(request, plan_id):
@permission_classes([IsAuthenticated])
def preview_plan(request):
"""
Generate a weekly plan preview. Returns JSON nothing is saved to DB.
Generate a weekly plan preview. Returns JSON -- nothing is saved to DB.
Body: {"week_start_date": "YYYY-MM-DD"}
"""
registered_user = RegisteredUser.objects.get(user=request.user)
registered_user = get_registered_user(request)
week_start_date_str = request.data.get('week_start_date')
if not week_start_date_str:
@@ -872,8 +900,9 @@ def preview_plan(request):
)
preview = generator.generate_weekly_preview(week_start_date)
except Exception as e:
logger.exception("Unexpected error in preview_plan")
return Response(
{'error': f'Preview generation failed: {str(e)}'},
{"error": "An unexpected error occurred. Please try again."},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
@@ -885,7 +914,7 @@ def preview_plan(request):
@permission_classes([IsAuthenticated])
def preview_day(request):
"""
Generate a single day preview. Returns JSON nothing is saved to DB.
Generate a single day preview. Returns JSON -- nothing is saved to DB.
Body: {
"target_muscles": ["chest", "shoulders"],
"focus_area": "Upper Push",
@@ -893,7 +922,7 @@ def preview_day(request):
"date": "2026-02-09"
}
"""
registered_user = RegisteredUser.objects.get(user=request.user)
registered_user = get_registered_user(request)
date_str = request.data.get('date')
if not date_str:
@@ -954,26 +983,19 @@ def preview_day(request):
generator = WorkoutGenerator(preference)
# If plan_id is provided, exclude sibling workout exercises
# If plan_id is provided, exclude sibling workout exercises (single query)
if plan_id is not None:
try:
plan = GeneratedWeeklyPlan.objects.get(
pk=plan_id,
registered_user=registered_user,
)
sibling_workouts = GeneratedWorkout.objects.filter(
plan=plan,
is_rest_day=False,
workout__isnull=False,
sibling_exercise_ids = set(
SupersetExercise.objects.filter(
superset__workout__generated_from__plan=plan,
superset__workout__generated_from__is_rest_day=False,
).values_list('exercise_id', flat=True)
)
sibling_exercise_ids = set()
for sibling in sibling_workouts:
if sibling.workout:
sibling_exercise_ids.update(
SupersetExercise.objects.filter(
superset__workout=sibling.workout
).values_list('exercise_id', flat=True)
)
if sibling_exercise_ids:
generator.exercise_selector.hard_exclude_ids.update(sibling_exercise_ids)
except GeneratedWeeklyPlan.DoesNotExist:
@@ -987,8 +1009,9 @@ def preview_day(request):
if plan_id is not None:
day_preview['plan_id'] = plan_id
except Exception as e:
logger.exception("Unexpected error in preview_day")
return Response(
{'error': f'Day preview generation failed: {str(e)}'},
{"error": "An unexpected error occurred. Please try again."},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
@@ -1003,7 +1026,7 @@ def save_plan(request):
Save a preview plan to the database.
Body: the full preview JSON (same shape as preview_plan response).
"""
registered_user = RegisteredUser.objects.get(user=request.user)
registered_user = get_registered_user(request)
week_start_date_str = request.data.get('week_start_date')
days = request.data.get('days', [])
@@ -1057,105 +1080,130 @@ def save_plan(request):
),
}
plan = GeneratedWeeklyPlan.objects.create(
registered_user=registered_user,
week_start_date=week_start_date,
week_end_date=week_end_date,
status='completed',
preferences_snapshot=prefs_snapshot,
)
# Prefetch all exercise IDs referenced in the plan to avoid N+1 queries
all_exercise_ids = []
for day_data in days:
day_date_str = day_data.get('date')
scheduled_date = datetime.strptime(day_date_str, '%Y-%m-%d').date()
day_of_week = scheduled_date.weekday()
is_rest_day = day_data.get('is_rest_day', False)
if is_rest_day:
GeneratedWorkout.objects.create(
plan=plan,
workout=None,
workout_type=None,
scheduled_date=scheduled_date,
day_of_week=day_of_week,
is_rest_day=True,
status='accepted',
focus_area='Rest Day',
target_muscles=[],
)
if day_data.get('is_rest_day', False):
continue
workout_spec_data = day_data.get('workout_spec', {})
focus_area = day_data.get('focus_area', 'Workout')
target_muscles = day_data.get('target_muscles', [])
workout_type_id = day_data.get('workout_type_id')
workout_type = None
if workout_type_id:
workout_type = WorkoutType.objects.filter(pk=workout_type_id).first()
supersets_data = workout_spec_data.get('supersets', [])
orm_supersets = []
for ss_data in supersets_data:
exercises = []
for ss_data in workout_spec_data.get('supersets', []):
for ex_data in ss_data.get('exercises', []):
exercise_id = ex_data.get('exercise_id')
if not exercise_id:
continue
try:
exercise_obj = Exercise.objects.get(pk=exercise_id)
except Exercise.DoesNotExist:
continue
if exercise_id:
all_exercise_ids.append(exercise_id)
exercises.append({
'exercise': exercise_obj,
'reps': ex_data.get('reps'),
'duration': ex_data.get('duration'),
'weight': ex_data.get('weight'),
'order': ex_data.get('order', 1),
exercises_map = {
e.id: e for e in Exercise.objects.filter(id__in=all_exercise_ids)
}
# Prefetch all workout type IDs referenced in the plan
all_workout_type_ids = []
for day_data in days:
wt_id = day_data.get('workout_type_id')
if wt_id:
all_workout_type_ids.append(wt_id)
workout_types_map = {
wt.id: wt for wt in WorkoutType.objects.filter(id__in=all_workout_type_ids)
}
with transaction.atomic():
plan = GeneratedWeeklyPlan.objects.create(
registered_user=registered_user,
week_start_date=week_start_date,
week_end_date=week_end_date,
status='completed',
preferences_snapshot=prefs_snapshot,
)
for day_data in days:
day_date_str = day_data.get('date')
scheduled_date = datetime.strptime(day_date_str, '%Y-%m-%d').date()
day_of_week = scheduled_date.weekday()
is_rest_day = day_data.get('is_rest_day', False)
if is_rest_day:
GeneratedWorkout.objects.create(
plan=plan,
workout=None,
workout_type=None,
scheduled_date=scheduled_date,
day_of_week=day_of_week,
is_rest_day=True,
status='accepted',
focus_area='Rest Day',
target_muscles=[],
)
continue
workout_spec_data = day_data.get('workout_spec', {})
focus_area = day_data.get('focus_area', 'Workout')
target_muscles = day_data.get('target_muscles', [])
workout_type_id = day_data.get('workout_type_id')
workout_type = workout_types_map.get(workout_type_id) if workout_type_id else None
supersets_data = workout_spec_data.get('supersets', [])
orm_supersets = []
for ss_data in supersets_data:
exercises = []
for ex_data in ss_data.get('exercises', []):
exercise_id = ex_data.get('exercise_id')
if not exercise_id:
continue
exercise_obj = exercises_map.get(exercise_id)
if not exercise_obj:
continue
exercises.append({
'exercise': exercise_obj,
'reps': ex_data.get('reps'),
'duration': ex_data.get('duration'),
'weight': ex_data.get('weight'),
'order': ex_data.get('order', 1),
})
orm_supersets.append({
'name': ss_data.get('name', 'Set'),
'rounds': ss_data.get('rounds', 1),
'rest_between_rounds': ss_data.get('rest_between_rounds', 0),
'exercises': exercises,
})
orm_supersets.append({
'name': ss_data.get('name', 'Set'),
'rounds': ss_data.get('rounds', 1),
'rest_between_rounds': ss_data.get('rest_between_rounds', 0),
'exercises': exercises,
})
workout_spec = {
'name': workout_spec_data.get('name', f'{focus_area} Workout'),
'description': workout_spec_data.get('description', ''),
'supersets': orm_supersets,
}
workout_spec = {
'name': workout_spec_data.get('name', f'{focus_area} Workout'),
'description': workout_spec_data.get('description', ''),
'supersets': orm_supersets,
}
workout_obj = plan_builder.create_workout_from_spec(workout_spec)
workout_obj = plan_builder.create_workout_from_spec(workout_spec)
GeneratedWorkout.objects.create(
plan=plan,
workout=workout_obj,
workout_type=workout_type,
scheduled_date=scheduled_date,
day_of_week=day_of_week,
is_rest_day=False,
status='accepted',
focus_area=focus_area,
target_muscles=target_muscles,
)
GeneratedWorkout.objects.create(
plan=plan,
workout=workout_obj,
workout_type=workout_type,
scheduled_date=scheduled_date,
day_of_week=day_of_week,
is_rest_day=False,
status='accepted',
focus_area=focus_area,
target_muscles=target_muscles,
)
# Create/replace PlannedWorkout for this date
PlannedWorkout.objects.filter(
registered_user=registered_user,
on_date=scheduled_date,
).delete()
PlannedWorkout.objects.create(
workout=workout_obj,
registered_user=registered_user,
on_date=scheduled_date,
)
# Create/replace PlannedWorkout for this date
PlannedWorkout.objects.filter(
registered_user=registered_user,
on_date=scheduled_date,
).delete()
PlannedWorkout.objects.create(
workout=workout_obj,
registered_user=registered_user,
on_date=scheduled_date,
)
except Exception as e:
logger.exception("Unexpected error in save_plan")
return Response(
{'error': f'Save failed: {str(e)}'},
{"error": "An unexpected error occurred. Please try again."},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)