Files
ShooterHub/apps/sessions/serializers.py
2026-04-02 11:24:30 +02:00

302 lines
11 KiB
Python

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',
]