from rest_framework import serializers # Cross-app import (photos → tools). String FK in the model avoids DB circular # import; here we need the class directly for the serializer queryset. from apps.tools.models import ShotGroup from .models import GroupPhoto, GroupPhotoAnalysis, Photo, PointOfImpact class PhotoMetaSerializer(serializers.ModelSerializer): """Photo metadata only — the binary `data` field is never exposed in JSON.""" class Meta: model = Photo fields = ['id', 'content_type', 'size', 'width', 'height', 'uploaded_by', 'uploaded_at', 'description'] read_only_fields = ['id', 'size', 'width', 'height', 'uploaded_by', 'uploaded_at'] class GroupPhotoAnalysisSerializer(serializers.ModelSerializer): class Meta: model = GroupPhotoAnalysis fields = [ 'group_size_mm', 'group_size_moa', 'elevation_offset_mm', 'elevation_offset_moa', 'windage_offset_mm', 'windage_offset_moa', 'mean_radius_mm', 'mean_radius_moa', 'notes', ] def validate(self, attrs): instance = GroupPhotoAnalysis(**attrs) instance.clean() return attrs class PointOfImpactSerializer(serializers.ModelSerializer): # Write: accept Shot PK; Read: compact inline summary shot_detail = serializers.SerializerMethodField() class Meta: model = PointOfImpact fields = [ 'id', 'order', 'shot', 'shot_detail', 'x_px', 'y_px', 'x_mm', 'y_mm', 'radius_mm', 'notes', ] def get_shot_detail(self, obj): if not obj.shot_id: return None return { 'id': obj.shot.pk, 'shot_number': obj.shot.shot_number, 'velocity_fps': str(obj.shot.velocity_fps), } class GroupPhotoSerializer(serializers.ModelSerializer): photo = PhotoMetaSerializer(read_only=True) photo_id = serializers.PrimaryKeyRelatedField( source='photo', queryset=Photo.objects.all(), write_only=True, ) # shot_group is optional — photos can exist independently of any group shot_group = serializers.PrimaryKeyRelatedField( queryset=ShotGroup.objects.all(), required=False, allow_null=True, ) shot_group_detail = serializers.SerializerMethodField() analysis = GroupPhotoAnalysisSerializer(read_only=True) points_of_impact = PointOfImpactSerializer(many=True, read_only=True) class Meta: model = GroupPhoto fields = [ 'id', 'photo_id', 'photo', 'shot_group', 'shot_group_detail', 'caption', 'order', 'is_public', 'analysis', 'points_of_impact', ] def get_shot_group_detail(self, obj): if not obj.shot_group_id: return None sg = obj.shot_group return { 'id': sg.pk, 'label': sg.label, 'distance_m': str(sg.distance_m) if sg.distance_m else None, }