392 lines
14 KiB
Python
392 lines
14 KiB
Python
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']
|