Files
WerkoutAPI/workout/serializers.py
Trey t c80c66c2e5 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>
2026-02-27 22:29:14 -06:00

170 lines
7.7 KiB
Python

from rest_framework import serializers
from .models import *
from exercise.serializers import ExerciseSerializer
from registered_user.serializers import GetRegisteredUserSerializer
from muscle.models import ExerciseMuscle
from equipment.models import WorkoutEquipment
from video.models import Video
from video.serializers import VideoSerializer
from superset.serializers import SupersetSerializer, SupersetExerciseSerializer
from superset.models import Superset, SupersetExercise
from superset.helpers import *
from .helpers import *
class WorkoutExerciseSerializer(serializers.ModelSerializer):
exercise = ExerciseSerializer(read_only=True)
duration_audio = serializers.ReadOnlyField()
weight_audio = serializers.ReadOnlyField()
class Meta:
model = WorkoutExercise
fields = ('workout', 'exercise','weight','reps','duration','duration_audio','weight_audio', 'created_at',)
class CompleteWorkoutSerializer(serializers.ModelSerializer):
class Meta:
model = CompletedWorkout
exclude = ['registered_user']
def create(self, validated_data):
registered_user_id = self.context.get("registered_user")
registered_user = RegisteredUser.objects.get(id=registered_user_id)
completed_workout = CompletedWorkout.objects.create(
registered_user=registered_user,
workout=validated_data['workout'],
difficulty=validated_data['difficulty'],
total_time=validated_data['total_time'],
workout_start_time=validated_data['workout_start_time'],
# Fix #5: KeyError 'notes' - use .get() with default
notes=validated_data.get('notes', '')
)
if "health_kit_workout_uuid" in validated_data:
# Fix #6: wrong attribute name - model field is health_kit_workout_uuid
completed_workout.health_kit_workout_uuid = validated_data['health_kit_workout_uuid']
completed_workout.save()
return completed_workout
class WorkoutSerializer(serializers.ModelSerializer):
# exercise = WorkoutExerciseSerializer(source='workout_exercise_workout', many=True)
registered_user = GetRegisteredUserSerializer(many=False, read_only=True)
muscles = serializers.SerializerMethodField()
equipment = serializers.SerializerMethodField()
exercise_count = serializers.SerializerMethodField()
class Meta:
model = Workout
fields = '__all__'
# depth = 1
def get_muscles(self, obj):
# Fix #16: Use prefetched data when available, fall back to query
if hasattr(obj, '_prefetched_objects_cache') and 'superset_set' in obj._prefetched_objects_cache:
exercise_ids = []
for superset in obj.superset_set.all():
for se in superset.supersetexercise_set.all():
exercise_ids.append(se.exercise_id)
if not exercise_ids:
return []
muscles_names = ExerciseMuscle.objects.filter(exercise__id__in=exercise_ids).values_list('muscle__name', flat=True)
return list(set(muscles_names))
superset_ids = Superset.objects.filter(workout=obj).values_list('id')
exercise_ids = SupersetExercise.objects.filter(superset__id__in=superset_ids).values_list('exercise__id')
muscles_names = ExerciseMuscle.objects.filter(exercise__id__in=exercise_ids).values_list('muscle__name', flat=True)
return list(set(muscles_names))
def get_equipment(self, obj):
# Fix #16: Use prefetched data when available, fall back to query
if hasattr(obj, '_prefetched_objects_cache') and 'superset_set' in obj._prefetched_objects_cache:
exercise_ids = []
for superset in obj.superset_set.all():
for se in superset.supersetexercise_set.all():
exercise_ids.append(se.exercise_id)
if not exercise_ids:
return []
equipment_names = WorkoutEquipment.objects.filter(exercise__id__in=exercise_ids).values_list('equipment__name', flat=True)
return list(set(equipment_names))
superset_ids = Superset.objects.filter(workout=obj).values_list('id')
exercise_ids = SupersetExercise.objects.filter(superset__id__in=superset_ids).values_list('exercise__id')
equipment_names = WorkoutEquipment.objects.filter(exercise__id__in=exercise_ids).values_list('equipment__name', flat=True)
return list(set(equipment_names))
def get_exercise_count(self, obj):
# Fix #16: Use prefetched data when available, fall back to query
returnValue = 0
if hasattr(obj, '_prefetched_objects_cache') and 'superset_set' in obj._prefetched_objects_cache:
for superset in obj.superset_set.all():
exercise_count = len(superset.supersetexercise_set.all())
returnValue += (superset.rounds * exercise_count)
return returnValue
supersets = Superset.objects.filter(workout=obj)
for superset in supersets:
exercise_count = SupersetExercise.objects.filter(superset=superset).count()
returnValue += (superset.rounds * exercise_count)
return returnValue
class WorkoutDetailSerializer(serializers.ModelSerializer):
supersets = serializers.SerializerMethodField()
registered_user = serializers.SerializerMethodField()
all_superset_exercise = serializers.SerializerMethodField()
class Meta:
model = Workout
fields = ('id', 'name', 'description', 'supersets', 'registered_user', 'estimated_time', 'all_superset_exercise', )
depth = 1
def get_all_superset_exercise(self, obj):
all_exercise_data = create_all_exercise_list_for_workout(obj)
return all_exercise_data
def get_supersets(self, obj):
objs = Superset.objects.filter(workout=obj).order_by('order')
data = SupersetSerializer(objs, many=True).data
# add a beginning workout superset
first_up_superset = get_first_up_superset(obj)
first_up_superset_exercise = get_first_up_superset_exercise(first_up_superset)
first_data = SupersetSerializer([first_up_superset], many=True).data
__data = SupersetExerciseSerializer([first_up_superset_exercise], many=True).data
first_data[0]["exercises"] = __data
data[:0] = first_data
return data
def get_registered_user(self, obj):
data = GetRegisteredUserSerializer(obj.registered_user, many=False).data
return data
class GetCompleteWorkoutSerializer(serializers.ModelSerializer):
workout = WorkoutSerializer(many=False, read_only=True)
class Meta:
model = CompletedWorkout
exclude = ['registered_user']
class POSTCompleteWorkoutSerializer(serializers.ModelSerializer):
class Meta:
model = Workout
exclude = ['registered_user']
class PlannedWorkoutSerializer(serializers.ModelSerializer):
workout = POSTCompleteWorkoutSerializer(many=False, read_only=True)
class Meta:
model = PlannedWorkout
exclude = ['registered_user']
depth = 1
class POSTPlannedWorkoutSerializer(serializers.ModelSerializer):
class Meta:
model = PlannedWorkout
exclude = ['registered_user']
def create(self, validated_data):
registered_user_id = self.context.get("registered_user")
registered_user = RegisteredUser.objects.get(id=registered_user_id)
planned_workout = PlannedWorkout.objects.create(
registered_user=registered_user,
workout=validated_data['workout'],
on_date=validated_data['on_date']
)
# Fix #18: removed redundant save() right after create()
return planned_workout