from rest_framework import serializers from apps.calibers.models import Caliber from .models import ( Ammo, Bipod, Brass, Bullet, Firearm, Gear, GearStatus, Magazine, Powder, Primer, ReloadedAmmoBatch, ReloadRecipe, Rig, RigItem, Scope, Suppressor, UserGear, get_concrete_gear, ) # ── Caliber helper serializer ───────────────────────────────────────────────── class CaliberMinSerializer(serializers.ModelSerializer): class Meta: model = Caliber fields = ['id', 'name', 'short_name'] # ── Gear catalog serializers ────────────────────────────────────────────────── class GearBaseSerializer(serializers.ModelSerializer): """Common read-only fields for every gear type (used in list views).""" class Meta: model = Gear fields = [ 'id', 'brand', 'model_name', 'description', 'gear_type', 'status', 'created_at', ] read_only_fields = ['gear_type', 'status', 'created_at'] class FirearmSerializer(serializers.ModelSerializer): caliber = serializers.PrimaryKeyRelatedField( queryset=Caliber.objects.filter(status='VERIFIED'), required=False, allow_null=True, ) caliber_detail = serializers.SerializerMethodField() def get_caliber_detail(self, obj): if obj.caliber_id: return CaliberMinSerializer(obj.caliber).data return None class Meta: model = Firearm fields = [ 'id', 'brand', 'model_name', 'description', 'gear_type', 'status', 'firearm_type', 'caliber', 'caliber_detail', 'barrel_length_mm', 'magazine_capacity', 'submitted_by', 'reviewed_by', 'reviewed_at', 'created_at', ] read_only_fields = ['gear_type', 'status', 'submitted_by', 'reviewed_by', 'reviewed_at', 'created_at', 'caliber_detail'] class ScopeSerializer(serializers.ModelSerializer): class Meta: model = Scope fields = [ 'id', 'brand', 'model_name', 'description', 'gear_type', 'status', 'magnification_min', 'magnification_max', 'objective_diameter_mm', 'tube_diameter_mm', 'reticle_type', 'adjustment_unit', 'focal_plane', 'submitted_by', 'reviewed_by', 'reviewed_at', 'created_at', ] read_only_fields = ['gear_type', 'status', 'submitted_by', 'reviewed_by', 'reviewed_at', 'created_at'] class SuppressorSerializer(serializers.ModelSerializer): max_caliber = serializers.PrimaryKeyRelatedField( queryset=Caliber.objects.filter(status='VERIFIED'), required=False, allow_null=True, ) max_caliber_detail = serializers.SerializerMethodField() def get_max_caliber_detail(self, obj): if obj.max_caliber_id: return CaliberMinSerializer(obj.max_caliber).data return None class Meta: model = Suppressor fields = [ 'id', 'brand', 'model_name', 'description', 'gear_type', 'status', 'max_caliber', 'max_caliber_detail', 'thread_pitch', 'length_mm', 'weight_g', 'submitted_by', 'reviewed_by', 'reviewed_at', 'created_at', ] read_only_fields = ['gear_type', 'status', 'submitted_by', 'reviewed_by', 'reviewed_at', 'created_at', 'max_caliber_detail'] class BipodSerializer(serializers.ModelSerializer): class Meta: model = Bipod fields = [ 'id', 'brand', 'model_name', 'description', 'gear_type', 'status', 'min_height_mm', 'max_height_mm', 'attachment_type', 'submitted_by', 'reviewed_by', 'reviewed_at', 'created_at', ] read_only_fields = ['gear_type', 'status', 'submitted_by', 'reviewed_by', 'reviewed_at', 'created_at'] class MagazineSerializer(serializers.ModelSerializer): caliber = serializers.PrimaryKeyRelatedField( queryset=Caliber.objects.filter(status='VERIFIED'), required=False, allow_null=True, ) caliber_detail = serializers.SerializerMethodField() def get_caliber_detail(self, obj): if obj.caliber_id: return CaliberMinSerializer(obj.caliber).data return None class Meta: model = Magazine fields = [ 'id', 'brand', 'model_name', 'description', 'gear_type', 'status', 'caliber', 'caliber_detail', 'capacity', 'submitted_by', 'reviewed_by', 'reviewed_at', 'created_at', ] read_only_fields = ['gear_type', 'status', 'submitted_by', 'reviewed_by', 'reviewed_at', 'created_at', 'caliber_detail'] # Maps gear_type discriminator → serializer class _GEAR_SERIALIZER_MAP = { 'FIREARM': FirearmSerializer, 'SCOPE': ScopeSerializer, 'SUPPRESSOR': SuppressorSerializer, 'BIPOD': BipodSerializer, 'MAGAZINE': MagazineSerializer, } class PolymorphicGearSerializer(serializers.BaseSerializer): """ Read-only serializer that dispatches to the correct typed serializer based on gear_type. Used when embedding gear details in nested responses. """ def to_representation(self, instance): concrete = get_concrete_gear(instance) serializer_cls = _GEAR_SERIALIZER_MAP.get(instance.gear_type, GearBaseSerializer) return serializer_cls(concrete, context=self.context).data # ── User inventory serializers ──────────────────────────────────────────────── class UserGearSerializer(serializers.ModelSerializer): # Write: accept a gear FK (VERIFIED or user's own PENDING) gear = serializers.PrimaryKeyRelatedField( queryset=Gear.objects.none(), # narrowed in __init__ write_only=True, ) # Read: return full typed gear details gear_detail = PolymorphicGearSerializer(source='gear', read_only=True) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) from django.db.models import Q request = self.context.get('request') if request and request.user.is_authenticated: self.fields['gear'].queryset = Gear.objects.filter( Q(status=GearStatus.VERIFIED) | Q(status=GearStatus.PENDING, submitted_by=request.user) ) else: self.fields['gear'].queryset = Gear.objects.filter(status=GearStatus.VERIFIED) class Meta: model = UserGear fields = [ 'id', 'gear', # write 'gear_detail', # read 'nickname', 'serial_number', 'purchase_date', 'notes', 'added_at', ] read_only_fields = ['added_at'] # ── Rig serializers ─────────────────────────────────────────────────────────── class RigItemReadSerializer(serializers.ModelSerializer): user_gear = UserGearSerializer(read_only=True) class Meta: model = RigItem fields = ['id', 'user_gear', 'role'] class RigItemCreateSerializer(serializers.ModelSerializer): """Used when adding an item to a rig (POST /rigs/{id}/items/).""" user_gear = serializers.PrimaryKeyRelatedField( queryset=UserGear.objects.none() # narrowed to request.user in __init__ ) class Meta: model = RigItem fields = ['id', 'user_gear', 'role'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) request = self.context.get('request') if request and request.user.is_authenticated: self.fields['user_gear'].queryset = UserGear.objects.filter( user=request.user ) def validate(self, attrs): rig = self.context['rig'] # Build a temporary instance for clean() validation instance = RigItem(rig=rig, **attrs) instance.clean() return attrs def create(self, validated_data): rig = self.context['rig'] return RigItem.objects.create(rig=rig, **validated_data) class RigSerializer(serializers.ModelSerializer): rig_items = RigItemReadSerializer(many=True, read_only=True) primary_caliber = serializers.SerializerMethodField() def get_primary_caliber(self, obj): for item in obj.rig_items.all(): if item.role == 'PRIMARY': gear = item.user_gear.gear # Firearm caliber lives on the MTI child table try: firearm = gear.firearm if firearm.caliber_id: return {'id': firearm.caliber_id, 'name': firearm.caliber.name} return None except Exception: return None return None class Meta: model = Rig fields = ['id', 'name', 'description', 'is_public', 'primary_caliber', 'rig_items', 'created_at', 'updated_at'] read_only_fields = ['created_at', 'updated_at'] # ── Ammo catalog serializer ─────────────────────────────────────────────────── class AmmoSerializer(serializers.ModelSerializer): caliber = serializers.PrimaryKeyRelatedField( queryset=Caliber.objects.filter(status='VERIFIED'), required=False, allow_null=True, ) caliber_detail = serializers.SerializerMethodField() def get_caliber_detail(self, obj): if obj.caliber_id: return CaliberMinSerializer(obj.caliber).data return None class Meta: model = Ammo fields = [ 'id', 'brand', 'name', 'caliber', 'caliber_detail', 'bullet_weight_gr', 'bullet_type', 'primer_size', 'case_material', 'muzzle_velocity_fps', 'muzzle_energy_ftlb', 'box_count', 'notes', 'status', 'submitted_by', 'reviewed_by', 'reviewed_at', 'created_at', ] read_only_fields = ['status', 'submitted_by', 'reviewed_by', 'reviewed_at', 'created_at', 'caliber_detail'] # ── Reloading component serializers ────────────────────────────────────────── class PrimerSerializer(serializers.ModelSerializer): class Meta: model = Primer fields = ['id', 'brand', 'name', 'size', 'notes', 'status', 'submitted_by'] read_only_fields = ['status', 'submitted_by'] class BrassSerializer(serializers.ModelSerializer): caliber = serializers.PrimaryKeyRelatedField( queryset=Caliber.objects.filter(status='VERIFIED'), required=False, allow_null=True, ) caliber_detail = serializers.SerializerMethodField() def get_caliber_detail(self, obj): if obj.caliber_id: return CaliberMinSerializer(obj.caliber).data return None class Meta: model = Brass fields = ['id', 'brand', 'caliber', 'caliber_detail', 'primer_pocket', 'trim_length_mm', 'notes', 'status', 'submitted_by'] read_only_fields = ['status', 'submitted_by', 'caliber_detail'] class BulletSerializer(serializers.ModelSerializer): class Meta: model = Bullet fields = [ 'id', 'brand', 'model_name', 'weight_gr', 'bullet_type', 'diameter_mm', 'length_mm', 'bc_g1', 'bc_g7', 'status', 'submitted_by', ] read_only_fields = ['status', 'submitted_by'] class PowderSerializer(serializers.ModelSerializer): class Meta: model = Powder fields = ['id', 'brand', 'name', 'powder_type', 'burn_rate_index', 'notes', 'status', 'submitted_by'] read_only_fields = ['status', 'submitted_by'] # ── Reload development serializers ──────────────────────────────────────────── class ReloadedAmmoBatchSerializer(serializers.ModelSerializer): powder_detail = PowderSerializer(source='powder', read_only=True) class Meta: model = ReloadedAmmoBatch fields = [ 'id', 'recipe', 'powder', # write (PK) 'powder_detail', # read (nested) 'powder_charge_gr', 'quantity', 'oal_mm', 'coal_mm', 'crimp', 'case_prep_notes', 'notes', 'loaded_at', 'created_at', 'updated_at', ] read_only_fields = ['created_at', 'updated_at'] class ReloadRecipeSerializer(serializers.ModelSerializer): caliber = serializers.PrimaryKeyRelatedField( queryset=Caliber.objects.filter(status='VERIFIED'), required=False, allow_null=True, ) caliber_detail = serializers.SerializerMethodField() primer_detail = PrimerSerializer(source='primer', read_only=True) brass_detail = BrassSerializer(source='brass', read_only=True) bullet_detail = BulletSerializer(source='bullet', read_only=True) batches = ReloadedAmmoBatchSerializer(many=True, read_only=True) def get_caliber_detail(self, obj): if obj.caliber_id: return CaliberMinSerializer(obj.caliber).data return None class Meta: model = ReloadRecipe fields = [ 'id', 'name', 'caliber', 'caliber_detail', 'primer', # write 'primer_detail', # read 'brass', 'brass_detail', 'bullet', 'bullet_detail', 'notes', 'is_public', 'batches', 'created_at', 'updated_at', ] read_only_fields = ['created_at', 'updated_at', 'caliber_detail']