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,5 +1,7 @@
import logging
from django.db import transaction
from workout.models import Workout
from superset.models import Superset, SupersetExercise
@@ -55,88 +57,87 @@ class PlanBuilder:
Workout
The fully-persisted Workout instance with all child objects.
"""
# ---- 1. Create the Workout ----
workout = Workout.objects.create(
name=workout_spec.get('name', 'Generated Workout'),
description=workout_spec.get('description', ''),
registered_user=self.registered_user,
)
workout.save()
workout_total_time = 0
superset_order = 1
# ---- 2. Create each Superset ----
for ss_spec in workout_spec.get('supersets', []):
ss_name = ss_spec.get('name', f'Set {superset_order}')
rounds = ss_spec.get('rounds', 1)
exercises = ss_spec.get('exercises', [])
superset = Superset.objects.create(
workout=workout,
name=ss_name,
rounds=rounds,
order=superset_order,
rest_between_rounds=ss_spec.get('rest_between_rounds', 45),
with transaction.atomic():
# ---- 1. Create the Workout ----
workout = Workout.objects.create(
name=workout_spec.get('name', 'Generated Workout'),
description=workout_spec.get('description', ''),
registered_user=self.registered_user,
)
superset.save()
superset_total_time = 0
workout_total_time = 0
superset_order = 1
# ---- 3. Create each SupersetExercise ----
for ex_spec in exercises:
exercise_obj = ex_spec.get('exercise')
if exercise_obj is None:
logger.warning(
"Skipping exercise entry with no exercise object in "
"superset '%s'", ss_name,
)
continue
# ---- 2. Create each Superset ----
for ss_spec in workout_spec.get('supersets', []):
ss_name = ss_spec.get('name', f'Set {superset_order}')
rounds = ss_spec.get('rounds', 1)
exercises = ss_spec.get('exercises', [])
order = ex_spec.get('order', 1)
superset_exercise = SupersetExercise.objects.create(
superset=superset,
exercise=exercise_obj,
order=order,
superset = Superset.objects.create(
workout=workout,
name=ss_name,
rounds=rounds,
order=superset_order,
rest_between_rounds=ss_spec.get('rest_between_rounds', 45),
)
# Assign optional fields exactly like add_workout does
if ex_spec.get('weight') is not None:
superset_exercise.weight = ex_spec['weight']
superset_total_time = 0
if ex_spec.get('reps') is not None:
superset_exercise.reps = ex_spec['reps']
rep_duration = exercise_obj.estimated_rep_duration or 3.0
superset_total_time += ex_spec['reps'] * rep_duration
# ---- 3. Create each SupersetExercise ----
for ex_spec in exercises:
exercise_obj = ex_spec.get('exercise')
if exercise_obj is None:
logger.warning(
"Skipping exercise entry with no exercise object in "
"superset '%s'", ss_name,
)
continue
if ex_spec.get('duration') is not None:
superset_exercise.duration = ex_spec['duration']
superset_total_time += ex_spec['duration']
order = ex_spec.get('order', 1)
superset_exercise.save()
superset_exercise = SupersetExercise.objects.create(
superset=superset,
exercise=exercise_obj,
order=order,
)
# ---- 4. Update superset estimated_time ----
# Store total time including all rounds and rest between rounds
rest_between_rounds = ss_spec.get('rest_between_rounds', 45)
rest_time = rest_between_rounds * max(0, rounds - 1)
superset.estimated_time = (superset_total_time * rounds) + rest_time
superset.save()
# Assign optional fields exactly like add_workout does
if ex_spec.get('weight') is not None:
superset_exercise.weight = ex_spec['weight']
# Accumulate into workout total (use the already-calculated superset time)
workout_total_time += superset.estimated_time
superset_order += 1
if ex_spec.get('reps') is not None:
superset_exercise.reps = ex_spec['reps']
rep_duration = exercise_obj.estimated_rep_duration or 3.0
superset_total_time += ex_spec['reps'] * rep_duration
# Add transition time between supersets
# (matches GENERATION_RULES['rest_between_supersets'] in workout_generator)
superset_count = superset_order - 1
if superset_count > 1:
rest_between_supersets = 30
workout_total_time += rest_between_supersets * (superset_count - 1)
if ex_spec.get('duration') is not None:
superset_exercise.duration = ex_spec['duration']
superset_total_time += ex_spec['duration']
# ---- 5. Update workout estimated_time ----
workout.estimated_time = workout_total_time
workout.save()
superset_exercise.save()
# ---- 4. Update superset estimated_time ----
# Store total time including all rounds and rest between rounds
rest_between_rounds = ss_spec.get('rest_between_rounds', 45)
rest_time = rest_between_rounds * max(0, rounds - 1)
superset.estimated_time = (superset_total_time * rounds) + rest_time
superset.save()
# Accumulate into workout total (use the already-calculated superset time)
workout_total_time += superset.estimated_time
superset_order += 1
# Add transition time between supersets
# (matches GENERATION_RULES['rest_between_supersets'] in workout_generator)
superset_count = superset_order - 1
if superset_count > 1:
rest_between_supersets = 30
workout_total_time += rest_between_supersets * (superset_count - 1)
# ---- 5. Update workout estimated_time ----
workout.estimated_time = workout_total_time
workout.save()
logger.info(
"Created workout '%s' (id=%s) with %d supersets, est. %ds",