from django.db.models import Q from rest_framework import serializers from apps.common.serializer_helpers import ammo_detail, batch_detail from apps.gears.models import Ammo, GearStatus, ReloadedAmmoBatch, Rig from apps.tools.models import ChronographAnalysis, ShotGroup from .models import ( CorrectionUnit, FreePracticeSession, PRSSession, PRSStage, SpeedShootingSession, ) # ── Shared helpers ──────────────────────────────────────────────────────────── def _rig_detail(rig): if rig is None: return None return {'id': rig.id, 'name': rig.name} # ── Abstract write mixin ────────────────────────────────────────────────────── def _analysis_detail(analysis): if analysis is None: return None return {'id': analysis.id, 'name': analysis.name, 'date': str(analysis.date) if analysis.date else None} class AbstractSessionWriteMixin: """ Shared __init__ for all session write serializers: narrows FK querysets to the current user. """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) request = self.context.get('request') if request and request.user.is_authenticated: self.fields['rig'].queryset = Rig.objects.filter(user=request.user) self.fields['reloaded_batch'].queryset = ReloadedAmmoBatch.objects.filter( recipe__user=request.user ) self.fields['analysis'].queryset = ChronographAnalysis.objects.filter( Q(user=request.user) | Q(user__isnull=True) ) def validate(self, attrs): user = self.context['request'].user instance = self.Meta.model(user=user, **attrs) instance.clean() return attrs # ── PRS session ─────────────────────────────────────────────────────────────── class PRSStageSerializer(serializers.ModelSerializer): shot_group = serializers.PrimaryKeyRelatedField( queryset=ShotGroup.objects.none(), required=False, allow_null=True, ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) request = self.context.get('request') if request and request.user.is_authenticated: self.fields['shot_group'].queryset = ShotGroup.objects.filter( analysis__user=request.user ) class Meta: model = PRSStage fields = [ 'id', 'order', 'position', 'distance_m', 'target_width_cm', 'target_height_cm', 'max_time_s', 'shots_count', 'notes_prep', 'computed_elevation', 'computed_windage', 'correction_unit', 'actual_elevation', 'actual_windage', 'hits', 'score', 'time_taken_s', 'shot_group', 'notes_post', ] read_only_fields = ['computed_elevation', 'computed_windage', 'correction_unit'] class PRSSessionListSerializer(serializers.ModelSerializer): class Meta: model = PRSSession fields = [ 'id', 'name', 'competition_name', 'category', 'date', 'location', 'rig', 'is_public', 'created_at', ] class PRSSessionWriteSerializer(AbstractSessionWriteMixin, serializers.ModelSerializer): rig = serializers.PrimaryKeyRelatedField( queryset=Rig.objects.none(), required=False, allow_null=True, ) ammo = serializers.PrimaryKeyRelatedField( queryset=Ammo.objects.filter(status=GearStatus.VERIFIED), required=False, allow_null=True, ) reloaded_batch = serializers.PrimaryKeyRelatedField( queryset=ReloadedAmmoBatch.objects.none(), required=False, allow_null=True, ) analysis = serializers.PrimaryKeyRelatedField( queryset=ChronographAnalysis.objects.none(), required=False, allow_null=True, ) class Meta: model = PRSSession fields = [ 'id', 'name', 'competition_name', 'category', 'date', 'location', 'rig', 'ammo', 'reloaded_batch', 'analysis', 'temperature_c', 'wind_speed_ms', 'wind_direction_deg', 'humidity_pct', 'pressure_hpa', 'weather_notes', 'notes', 'is_public', ] class PRSSessionDetailSerializer(serializers.ModelSerializer): rig_detail = serializers.SerializerMethodField() ammo_detail = serializers.SerializerMethodField() reloaded_batch_detail = serializers.SerializerMethodField() analysis_detail = serializers.SerializerMethodField() stages = PRSStageSerializer(many=True, read_only=True) def get_rig_detail(self, obj): return _rig_detail(obj.rig) def get_ammo_detail(self, obj): return ammo_detail(obj.ammo) def get_reloaded_batch_detail(self, obj): return batch_detail(obj.reloaded_batch) def get_analysis_detail(self, obj): return _analysis_detail(obj.analysis) class Meta: model = PRSSession fields = [ 'id', 'name', 'competition_name', 'category', 'date', 'location', 'rig', 'rig_detail', 'ammo', 'ammo_detail', 'reloaded_batch', 'reloaded_batch_detail', 'analysis', 'analysis_detail', 'temperature_c', 'wind_speed_ms', 'wind_direction_deg', 'humidity_pct', 'pressure_hpa', 'weather_notes', 'notes', 'is_public', 'stages', 'created_at', 'updated_at', ] # ── Free Practice session ───────────────────────────────────────────────────── class FreePracticeSessionListSerializer(serializers.ModelSerializer): class Meta: model = FreePracticeSession fields = ['id', 'name', 'date', 'location', 'distance_m', 'rounds_fired', 'rig', 'is_public', 'created_at'] class FreePracticeSessionWriteSerializer(AbstractSessionWriteMixin, serializers.ModelSerializer): rig = serializers.PrimaryKeyRelatedField( queryset=Rig.objects.none(), required=False, allow_null=True, ) ammo = serializers.PrimaryKeyRelatedField( queryset=Ammo.objects.filter(status=GearStatus.VERIFIED), required=False, allow_null=True, ) reloaded_batch = serializers.PrimaryKeyRelatedField( queryset=ReloadedAmmoBatch.objects.none(), required=False, allow_null=True, ) analysis = serializers.PrimaryKeyRelatedField( queryset=ChronographAnalysis.objects.none(), required=False, allow_null=True, ) class Meta: model = FreePracticeSession fields = [ 'id', 'name', 'date', 'location', 'rig', 'ammo', 'reloaded_batch', 'analysis', 'distance_m', 'target_description', 'rounds_fired', 'temperature_c', 'wind_speed_ms', 'wind_direction_deg', 'humidity_pct', 'pressure_hpa', 'weather_notes', 'notes', 'is_public', ] class FreePracticeSessionDetailSerializer(serializers.ModelSerializer): rig_detail = serializers.SerializerMethodField() ammo_detail = serializers.SerializerMethodField() reloaded_batch_detail = serializers.SerializerMethodField() analysis_detail = serializers.SerializerMethodField() def get_rig_detail(self, obj): return _rig_detail(obj.rig) def get_ammo_detail(self, obj): return ammo_detail(obj.ammo) def get_reloaded_batch_detail(self, obj): return batch_detail(obj.reloaded_batch) def get_analysis_detail(self, obj): return _analysis_detail(obj.analysis) class Meta: model = FreePracticeSession fields = [ 'id', 'name', 'date', 'location', 'rig', 'rig_detail', 'ammo', 'ammo_detail', 'reloaded_batch', 'reloaded_batch_detail', 'analysis', 'analysis_detail', 'distance_m', 'target_description', 'rounds_fired', 'temperature_c', 'wind_speed_ms', 'wind_direction_deg', 'humidity_pct', 'pressure_hpa', 'weather_notes', 'notes', 'is_public', 'created_at', 'updated_at', ] # ── Speed Shooting session ──────────────────────────────────────────────────── class SpeedShootingSessionListSerializer(serializers.ModelSerializer): class Meta: model = SpeedShootingSession fields = ['id', 'name', 'format', 'date', 'location', 'rounds_fired', 'rig', 'is_public', 'created_at'] class SpeedShootingSessionWriteSerializer(AbstractSessionWriteMixin, serializers.ModelSerializer): rig = serializers.PrimaryKeyRelatedField( queryset=Rig.objects.none(), required=False, allow_null=True, ) ammo = serializers.PrimaryKeyRelatedField( queryset=Ammo.objects.filter(status=GearStatus.VERIFIED), required=False, allow_null=True, ) reloaded_batch = serializers.PrimaryKeyRelatedField( queryset=ReloadedAmmoBatch.objects.none(), required=False, allow_null=True, ) analysis = serializers.PrimaryKeyRelatedField( queryset=ChronographAnalysis.objects.none(), required=False, allow_null=True, ) class Meta: model = SpeedShootingSession fields = [ 'id', 'name', 'format', 'date', 'location', 'rig', 'ammo', 'reloaded_batch', 'analysis', 'rounds_fired', 'temperature_c', 'wind_speed_ms', 'wind_direction_deg', 'humidity_pct', 'pressure_hpa', 'weather_notes', 'notes', 'is_public', ] class SpeedShootingSessionDetailSerializer(serializers.ModelSerializer): rig_detail = serializers.SerializerMethodField() ammo_detail = serializers.SerializerMethodField() reloaded_batch_detail = serializers.SerializerMethodField() analysis_detail = serializers.SerializerMethodField() def get_rig_detail(self, obj): return _rig_detail(obj.rig) def get_ammo_detail(self, obj): return ammo_detail(obj.ammo) def get_reloaded_batch_detail(self, obj): return batch_detail(obj.reloaded_batch) def get_analysis_detail(self, obj): return _analysis_detail(obj.analysis) class Meta: model = SpeedShootingSession fields = [ 'id', 'name', 'format', 'date', 'location', 'rig', 'rig_detail', 'ammo', 'ammo_detail', 'reloaded_batch', 'reloaded_batch_detail', 'analysis', 'analysis_detail', 'rounds_fired', 'temperature_c', 'wind_speed_ms', 'wind_direction_deg', 'humidity_pct', 'pressure_hpa', 'weather_notes', 'notes', 'is_public', 'created_at', 'updated_at', ]