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>
170 lines
7.7 KiB
Python
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 |