mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 13:11:08 -05:00
798 lines
28 KiB
Python
798 lines
28 KiB
Python
"""
|
|
RideModel serializers for ThrillWiki API v1.
|
|
|
|
This module contains all serializers related to ride models, variants,
|
|
technical specifications, and related functionality.
|
|
"""
|
|
|
|
from rest_framework import serializers
|
|
from drf_spectacular.utils import (
|
|
extend_schema_serializer,
|
|
extend_schema_field,
|
|
OpenApiExample,
|
|
)
|
|
from config.django import base as settings
|
|
|
|
from .shared import ModelChoices
|
|
from apps.core.choices.serializers import RichChoiceFieldSerializer
|
|
|
|
# Use dynamic imports to avoid circular import issues
|
|
|
|
|
|
def get_ride_model_classes():
|
|
"""Get ride model classes dynamically to avoid import issues."""
|
|
from apps.rides.models import (
|
|
RideModel,
|
|
RideModelVariant,
|
|
RideModelPhoto,
|
|
RideModelTechnicalSpec,
|
|
)
|
|
|
|
return RideModel, RideModelVariant, RideModelPhoto, RideModelTechnicalSpec
|
|
|
|
|
|
# === RIDE MODEL SERIALIZERS ===
|
|
|
|
|
|
class RideModelManufacturerOutputSerializer(serializers.Serializer):
|
|
"""Output serializer for ride model's manufacturer data."""
|
|
|
|
id = serializers.IntegerField()
|
|
name = serializers.CharField()
|
|
slug = serializers.CharField()
|
|
|
|
|
|
class RideModelPhotoOutputSerializer(serializers.Serializer):
|
|
"""Output serializer for ride model photos."""
|
|
|
|
id = serializers.IntegerField()
|
|
image_url = serializers.SerializerMethodField()
|
|
caption = serializers.CharField()
|
|
alt_text = serializers.CharField()
|
|
photo_type = serializers.CharField()
|
|
is_primary = serializers.BooleanField()
|
|
photographer = serializers.CharField()
|
|
source = serializers.CharField()
|
|
|
|
@extend_schema_field(serializers.URLField(allow_null=True))
|
|
def get_image_url(self, obj):
|
|
"""Get the image URL."""
|
|
if obj.image:
|
|
return obj.image.url
|
|
return None
|
|
|
|
|
|
class RideModelTechnicalSpecOutputSerializer(serializers.Serializer):
|
|
"""Output serializer for ride model technical specifications."""
|
|
|
|
id = serializers.IntegerField()
|
|
spec_category = serializers.CharField()
|
|
spec_name = serializers.CharField()
|
|
spec_value = serializers.CharField()
|
|
spec_unit = serializers.CharField()
|
|
notes = serializers.CharField()
|
|
|
|
|
|
class RideModelVariantOutputSerializer(serializers.Serializer):
|
|
"""Output serializer for ride model variants."""
|
|
|
|
id = serializers.IntegerField()
|
|
name = serializers.CharField()
|
|
description = serializers.CharField()
|
|
min_height_ft = serializers.DecimalField(
|
|
max_digits=6, decimal_places=2, allow_null=True
|
|
)
|
|
max_height_ft = serializers.DecimalField(
|
|
max_digits=6, decimal_places=2, allow_null=True
|
|
)
|
|
min_speed_mph = serializers.DecimalField(
|
|
max_digits=5, decimal_places=2, allow_null=True
|
|
)
|
|
max_speed_mph = serializers.DecimalField(
|
|
max_digits=5, decimal_places=2, allow_null=True
|
|
)
|
|
distinguishing_features = serializers.CharField()
|
|
|
|
|
|
@extend_schema_serializer(
|
|
examples=[
|
|
OpenApiExample(
|
|
"Ride Model List Example",
|
|
summary="Example ride model list response",
|
|
description="A typical ride model in the list view",
|
|
value={
|
|
"id": 1,
|
|
"name": "Hyper Coaster",
|
|
"slug": "bolliger-mabillard-hyper-coaster",
|
|
"category": "RC",
|
|
"description": "High-speed steel roller coaster with airtime hills",
|
|
"manufacturer": {
|
|
"id": 1,
|
|
"name": "Bolliger & Mabillard",
|
|
"slug": "bolliger-mabillard",
|
|
},
|
|
"target_market": "THRILL",
|
|
"is_discontinued": False,
|
|
"total_installations": 15,
|
|
"first_installation_year": 1999,
|
|
"height_range_display": "200-325 ft",
|
|
"speed_range_display": "70-95 mph",
|
|
"primary_image": {
|
|
"id": 123,
|
|
"image_url": "https://example.com/image.jpg",
|
|
"caption": "B&M Hyper Coaster",
|
|
"photo_type": "PROMOTIONAL",
|
|
},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
class RideModelListOutputSerializer(serializers.Serializer):
|
|
"""Output serializer for ride model list view."""
|
|
|
|
id = serializers.IntegerField()
|
|
name = serializers.CharField()
|
|
slug = serializers.CharField()
|
|
category = RichChoiceFieldSerializer(
|
|
choice_group="categories",
|
|
domain="rides"
|
|
)
|
|
description = serializers.CharField()
|
|
|
|
# Manufacturer info
|
|
manufacturer = RideModelManufacturerOutputSerializer(allow_null=True)
|
|
|
|
# Market info
|
|
target_market = RichChoiceFieldSerializer(
|
|
choice_group="target_markets",
|
|
domain="rides"
|
|
)
|
|
is_discontinued = serializers.BooleanField()
|
|
total_installations = serializers.IntegerField()
|
|
first_installation_year = serializers.IntegerField(allow_null=True)
|
|
last_installation_year = serializers.IntegerField(allow_null=True)
|
|
|
|
# Display properties
|
|
height_range_display = serializers.CharField()
|
|
speed_range_display = serializers.CharField()
|
|
installation_years_range = serializers.CharField()
|
|
|
|
# Primary image
|
|
primary_image = RideModelPhotoOutputSerializer(allow_null=True)
|
|
|
|
# URL
|
|
url = serializers.SerializerMethodField()
|
|
|
|
# Metadata
|
|
created_at = serializers.DateTimeField()
|
|
updated_at = serializers.DateTimeField()
|
|
|
|
@extend_schema_field(serializers.URLField())
|
|
def get_url(self, obj) -> str:
|
|
"""Generate the frontend URL for this ride model."""
|
|
return f"{settings.FRONTEND_DOMAIN}/rides/manufacturers/{obj.manufacturer.slug}/{obj.slug}/"
|
|
|
|
|
|
@extend_schema_serializer(
|
|
examples=[
|
|
OpenApiExample(
|
|
"Ride Model Detail Example",
|
|
summary="Example ride model detail response",
|
|
description="A complete ride model detail response",
|
|
value={
|
|
"id": 1,
|
|
"name": "Hyper Coaster",
|
|
"slug": "bolliger-mabillard-hyper-coaster",
|
|
"category": "RC",
|
|
"description": "High-speed steel roller coaster featuring airtime hills and smooth ride experience",
|
|
"manufacturer": {
|
|
"id": 1,
|
|
"name": "Bolliger & Mabillard",
|
|
"slug": "bolliger-mabillard",
|
|
},
|
|
"typical_height_range_min_ft": 200.0,
|
|
"typical_height_range_max_ft": 325.0,
|
|
"typical_speed_range_min_mph": 70.0,
|
|
"typical_speed_range_max_mph": 95.0,
|
|
"typical_capacity_range_min": 1200,
|
|
"typical_capacity_range_max": 1800,
|
|
"track_type": "Tubular Steel",
|
|
"support_structure": "Steel",
|
|
"train_configuration": "2-3 trains, 7-9 cars per train, 4 seats per car",
|
|
"restraint_system": "Clamshell lap bar",
|
|
"target_market": "THRILL",
|
|
"is_discontinued": False,
|
|
"total_installations": 15,
|
|
"first_installation_year": 1999,
|
|
"notable_features": "Airtime hills, smooth ride, high capacity",
|
|
"photos": [
|
|
{
|
|
"id": 123,
|
|
"image_url": "https://example.com/image.jpg",
|
|
"caption": "B&M Hyper Coaster",
|
|
"photo_type": "PROMOTIONAL",
|
|
"is_primary": True,
|
|
}
|
|
],
|
|
"variants": [
|
|
{
|
|
"id": 1,
|
|
"name": "Mega Coaster",
|
|
"description": "200-299 ft height variant",
|
|
"min_height_ft": 200.0,
|
|
"max_height_ft": 299.0,
|
|
}
|
|
],
|
|
"technical_specs": [
|
|
{
|
|
"id": 1,
|
|
"spec_category": "DIMENSIONS",
|
|
"spec_name": "Track Width",
|
|
"spec_value": "1435",
|
|
"spec_unit": "mm",
|
|
}
|
|
],
|
|
"installations": [
|
|
{
|
|
"id": 1,
|
|
"name": "Nitro",
|
|
"park_name": "Six Flags Great Adventure",
|
|
"opening_date": "2001-04-07",
|
|
}
|
|
],
|
|
},
|
|
)
|
|
]
|
|
)
|
|
class RideModelDetailOutputSerializer(serializers.Serializer):
|
|
"""Output serializer for ride model detail view."""
|
|
|
|
id = serializers.IntegerField()
|
|
name = serializers.CharField()
|
|
slug = serializers.CharField()
|
|
category = serializers.CharField()
|
|
description = serializers.CharField()
|
|
|
|
# Manufacturer info
|
|
manufacturer = RideModelManufacturerOutputSerializer(allow_null=True)
|
|
|
|
# Technical specifications
|
|
typical_height_range_min_ft = serializers.DecimalField(
|
|
max_digits=6, decimal_places=2, allow_null=True
|
|
)
|
|
typical_height_range_max_ft = serializers.DecimalField(
|
|
max_digits=6, decimal_places=2, allow_null=True
|
|
)
|
|
typical_speed_range_min_mph = serializers.DecimalField(
|
|
max_digits=5, decimal_places=2, allow_null=True
|
|
)
|
|
typical_speed_range_max_mph = serializers.DecimalField(
|
|
max_digits=5, decimal_places=2, allow_null=True
|
|
)
|
|
typical_capacity_range_min = serializers.IntegerField(allow_null=True)
|
|
typical_capacity_range_max = serializers.IntegerField(allow_null=True)
|
|
|
|
# Design characteristics
|
|
track_type = serializers.CharField()
|
|
support_structure = serializers.CharField()
|
|
train_configuration = serializers.CharField()
|
|
restraint_system = serializers.CharField()
|
|
|
|
# Market information
|
|
first_installation_year = serializers.IntegerField(allow_null=True)
|
|
last_installation_year = serializers.IntegerField(allow_null=True)
|
|
is_discontinued = serializers.BooleanField()
|
|
total_installations = serializers.IntegerField()
|
|
|
|
# Design features
|
|
notable_features = serializers.CharField()
|
|
target_market = serializers.CharField()
|
|
|
|
# Display properties
|
|
height_range_display = serializers.CharField()
|
|
speed_range_display = serializers.CharField()
|
|
installation_years_range = serializers.CharField()
|
|
|
|
# SEO metadata
|
|
meta_title = serializers.CharField()
|
|
meta_description = serializers.CharField()
|
|
|
|
# Related data
|
|
photos = RideModelPhotoOutputSerializer(many=True)
|
|
variants = RideModelVariantOutputSerializer(many=True)
|
|
technical_specs = RideModelTechnicalSpecOutputSerializer(many=True)
|
|
installations = serializers.SerializerMethodField()
|
|
|
|
# URL
|
|
url = serializers.SerializerMethodField()
|
|
|
|
# Metadata
|
|
created_at = serializers.DateTimeField()
|
|
updated_at = serializers.DateTimeField()
|
|
|
|
@extend_schema_field(serializers.URLField())
|
|
def get_url(self, obj) -> str:
|
|
"""Generate the frontend URL for this ride model."""
|
|
return f"{settings.FRONTEND_DOMAIN}/rides/manufacturers/{obj.manufacturer.slug}/{obj.slug}/"
|
|
|
|
@extend_schema_field(serializers.ListField(child=serializers.DictField()))
|
|
def get_installations(self, obj):
|
|
"""Get ride installations using this model."""
|
|
from django.apps import apps
|
|
|
|
Ride = apps.get_model("rides", "Ride")
|
|
|
|
installations = Ride.objects.filter(ride_model=obj).select_related("park")[:10]
|
|
return [
|
|
{
|
|
"id": ride.id,
|
|
"name": ride.name,
|
|
"slug": ride.slug,
|
|
"park_name": ride.park.name,
|
|
"park_slug": ride.park.slug,
|
|
"opening_date": ride.opening_date,
|
|
"status": ride.status,
|
|
}
|
|
for ride in installations
|
|
]
|
|
|
|
|
|
class RideModelCreateInputSerializer(serializers.Serializer):
|
|
"""Input serializer for creating ride models."""
|
|
|
|
name = serializers.CharField(max_length=255)
|
|
description = serializers.CharField(allow_blank=True, default="")
|
|
category = serializers.ChoiceField(
|
|
choices=ModelChoices.get_ride_category_choices(), allow_blank=True, default=""
|
|
)
|
|
|
|
# Required manufacturer
|
|
manufacturer_id = serializers.IntegerField()
|
|
|
|
# Technical specifications
|
|
typical_height_range_min_ft = serializers.DecimalField(
|
|
max_digits=6, decimal_places=2, required=False, allow_null=True
|
|
)
|
|
typical_height_range_max_ft = serializers.DecimalField(
|
|
max_digits=6, decimal_places=2, required=False, allow_null=True
|
|
)
|
|
typical_speed_range_min_mph = serializers.DecimalField(
|
|
max_digits=5, decimal_places=2, required=False, allow_null=True
|
|
)
|
|
typical_speed_range_max_mph = serializers.DecimalField(
|
|
max_digits=5, decimal_places=2, required=False, allow_null=True
|
|
)
|
|
typical_capacity_range_min = serializers.IntegerField(
|
|
required=False, allow_null=True, min_value=1
|
|
)
|
|
typical_capacity_range_max = serializers.IntegerField(
|
|
required=False, allow_null=True, min_value=1
|
|
)
|
|
|
|
# Design characteristics
|
|
track_type = serializers.CharField(max_length=100, allow_blank=True, default="")
|
|
support_structure = serializers.CharField(
|
|
max_length=100, allow_blank=True, default=""
|
|
)
|
|
train_configuration = serializers.CharField(
|
|
max_length=200, allow_blank=True, default=""
|
|
)
|
|
restraint_system = serializers.CharField(
|
|
max_length=100, allow_blank=True, default=""
|
|
)
|
|
|
|
# Market information
|
|
first_installation_year = serializers.IntegerField(
|
|
required=False, allow_null=True, min_value=1800, max_value=2100
|
|
)
|
|
last_installation_year = serializers.IntegerField(
|
|
required=False, allow_null=True, min_value=1800, max_value=2100
|
|
)
|
|
is_discontinued = serializers.BooleanField(default=False)
|
|
|
|
# Design features
|
|
notable_features = serializers.CharField(allow_blank=True, default="")
|
|
target_market = serializers.ChoiceField(
|
|
choices=ModelChoices.get_target_market_choices(),
|
|
required=False,
|
|
allow_blank=True,
|
|
)
|
|
|
|
def validate(self, attrs):
|
|
"""Cross-field validation."""
|
|
# Height range validation
|
|
min_height = attrs.get("typical_height_range_min_ft")
|
|
max_height = attrs.get("typical_height_range_max_ft")
|
|
|
|
if min_height and max_height and min_height > max_height:
|
|
raise serializers.ValidationError(
|
|
"Minimum height cannot be greater than maximum height"
|
|
)
|
|
|
|
# Speed range validation
|
|
min_speed = attrs.get("typical_speed_range_min_mph")
|
|
max_speed = attrs.get("typical_speed_range_max_mph")
|
|
|
|
if min_speed and max_speed and min_speed > max_speed:
|
|
raise serializers.ValidationError(
|
|
"Minimum speed cannot be greater than maximum speed"
|
|
)
|
|
|
|
# Capacity range validation
|
|
min_capacity = attrs.get("typical_capacity_range_min")
|
|
max_capacity = attrs.get("typical_capacity_range_max")
|
|
|
|
if min_capacity and max_capacity and min_capacity > max_capacity:
|
|
raise serializers.ValidationError(
|
|
"Minimum capacity cannot be greater than maximum capacity"
|
|
)
|
|
|
|
# Installation years validation
|
|
first_year = attrs.get("first_installation_year")
|
|
last_year = attrs.get("last_installation_year")
|
|
|
|
if first_year and last_year and first_year > last_year:
|
|
raise serializers.ValidationError(
|
|
"First installation year cannot be after last installation year"
|
|
)
|
|
|
|
return attrs
|
|
|
|
|
|
class RideModelUpdateInputSerializer(serializers.Serializer):
|
|
"""Input serializer for updating ride models."""
|
|
|
|
name = serializers.CharField(max_length=255, required=False)
|
|
description = serializers.CharField(allow_blank=True, required=False)
|
|
category = serializers.ChoiceField(
|
|
choices=ModelChoices.get_ride_category_choices(),
|
|
allow_blank=True,
|
|
required=False,
|
|
)
|
|
|
|
# Manufacturer
|
|
manufacturer_id = serializers.IntegerField(required=False)
|
|
|
|
# Technical specifications
|
|
typical_height_range_min_ft = serializers.DecimalField(
|
|
max_digits=6, decimal_places=2, required=False, allow_null=True
|
|
)
|
|
typical_height_range_max_ft = serializers.DecimalField(
|
|
max_digits=6, decimal_places=2, required=False, allow_null=True
|
|
)
|
|
typical_speed_range_min_mph = serializers.DecimalField(
|
|
max_digits=5, decimal_places=2, required=False, allow_null=True
|
|
)
|
|
typical_speed_range_max_mph = serializers.DecimalField(
|
|
max_digits=5, decimal_places=2, required=False, allow_null=True
|
|
)
|
|
typical_capacity_range_min = serializers.IntegerField(
|
|
required=False, allow_null=True, min_value=1
|
|
)
|
|
typical_capacity_range_max = serializers.IntegerField(
|
|
required=False, allow_null=True, min_value=1
|
|
)
|
|
|
|
# Design characteristics
|
|
track_type = serializers.CharField(max_length=100, allow_blank=True, required=False)
|
|
support_structure = serializers.CharField(
|
|
max_length=100, allow_blank=True, required=False
|
|
)
|
|
train_configuration = serializers.CharField(
|
|
max_length=200, allow_blank=True, required=False
|
|
)
|
|
restraint_system = serializers.CharField(
|
|
max_length=100, allow_blank=True, required=False
|
|
)
|
|
|
|
# Market information
|
|
first_installation_year = serializers.IntegerField(
|
|
required=False, allow_null=True, min_value=1800, max_value=2100
|
|
)
|
|
last_installation_year = serializers.IntegerField(
|
|
required=False, allow_null=True, min_value=1800, max_value=2100
|
|
)
|
|
is_discontinued = serializers.BooleanField(required=False)
|
|
|
|
# Design features
|
|
notable_features = serializers.CharField(allow_blank=True, required=False)
|
|
target_market = serializers.ChoiceField(
|
|
choices=ModelChoices.get_target_market_choices(),
|
|
allow_blank=True,
|
|
required=False,
|
|
)
|
|
|
|
def validate(self, attrs):
|
|
"""Cross-field validation."""
|
|
# Height range validation
|
|
min_height = attrs.get("typical_height_range_min_ft")
|
|
max_height = attrs.get("typical_height_range_max_ft")
|
|
|
|
if min_height and max_height and min_height > max_height:
|
|
raise serializers.ValidationError(
|
|
"Minimum height cannot be greater than maximum height"
|
|
)
|
|
|
|
# Speed range validation
|
|
min_speed = attrs.get("typical_speed_range_min_mph")
|
|
max_speed = attrs.get("typical_speed_range_max_mph")
|
|
|
|
if min_speed and max_speed and min_speed > max_speed:
|
|
raise serializers.ValidationError(
|
|
"Minimum speed cannot be greater than maximum speed"
|
|
)
|
|
|
|
# Capacity range validation
|
|
min_capacity = attrs.get("typical_capacity_range_min")
|
|
max_capacity = attrs.get("typical_capacity_range_max")
|
|
|
|
if min_capacity and max_capacity and min_capacity > max_capacity:
|
|
raise serializers.ValidationError(
|
|
"Minimum capacity cannot be greater than maximum capacity"
|
|
)
|
|
|
|
# Installation years validation
|
|
first_year = attrs.get("first_installation_year")
|
|
last_year = attrs.get("last_installation_year")
|
|
|
|
if first_year and last_year and first_year > last_year:
|
|
raise serializers.ValidationError(
|
|
"First installation year cannot be after last installation year"
|
|
)
|
|
|
|
return attrs
|
|
|
|
|
|
class RideModelFilterInputSerializer(serializers.Serializer):
|
|
"""Input serializer for ride model filtering and search."""
|
|
|
|
# Search
|
|
search = serializers.CharField(required=False, allow_blank=True)
|
|
|
|
# Category filter
|
|
category = serializers.MultipleChoiceField(
|
|
choices=ModelChoices.get_ride_category_choices(), required=False
|
|
)
|
|
|
|
# Manufacturer filter
|
|
manufacturer_id = serializers.IntegerField(required=False)
|
|
manufacturer_slug = serializers.CharField(required=False, allow_blank=True)
|
|
|
|
# Market filter
|
|
target_market = serializers.MultipleChoiceField(
|
|
choices=ModelChoices.get_target_market_choices(),
|
|
required=False,
|
|
)
|
|
|
|
# Status filter
|
|
is_discontinued = serializers.BooleanField(required=False)
|
|
|
|
# Year filters
|
|
first_installation_year_min = serializers.IntegerField(required=False)
|
|
first_installation_year_max = serializers.IntegerField(required=False)
|
|
|
|
# Installation count filter
|
|
min_installations = serializers.IntegerField(required=False, min_value=0)
|
|
|
|
# Height filters
|
|
min_height_ft = serializers.DecimalField(
|
|
max_digits=6, decimal_places=2, required=False
|
|
)
|
|
max_height_ft = serializers.DecimalField(
|
|
max_digits=6, decimal_places=2, required=False
|
|
)
|
|
|
|
# Speed filters
|
|
min_speed_mph = serializers.DecimalField(
|
|
max_digits=5, decimal_places=2, required=False
|
|
)
|
|
max_speed_mph = serializers.DecimalField(
|
|
max_digits=5, decimal_places=2, required=False
|
|
)
|
|
|
|
# Ordering
|
|
ordering = serializers.ChoiceField(
|
|
choices=[
|
|
"name",
|
|
"-name",
|
|
"manufacturer__name",
|
|
"-manufacturer__name",
|
|
"first_installation_year",
|
|
"-first_installation_year",
|
|
"total_installations",
|
|
"-total_installations",
|
|
"created_at",
|
|
"-created_at",
|
|
],
|
|
required=False,
|
|
default="manufacturer__name,name",
|
|
)
|
|
|
|
|
|
# === RIDE MODEL VARIANT SERIALIZERS ===
|
|
|
|
|
|
class RideModelVariantCreateInputSerializer(serializers.Serializer):
|
|
"""Input serializer for creating ride model variants."""
|
|
|
|
ride_model_id = serializers.IntegerField()
|
|
name = serializers.CharField(max_length=255)
|
|
description = serializers.CharField(allow_blank=True, default="")
|
|
|
|
# Variant-specific specifications
|
|
min_height_ft = serializers.DecimalField(
|
|
max_digits=6, decimal_places=2, required=False, allow_null=True
|
|
)
|
|
max_height_ft = serializers.DecimalField(
|
|
max_digits=6, decimal_places=2, required=False, allow_null=True
|
|
)
|
|
min_speed_mph = serializers.DecimalField(
|
|
max_digits=5, decimal_places=2, required=False, allow_null=True
|
|
)
|
|
max_speed_mph = serializers.DecimalField(
|
|
max_digits=5, decimal_places=2, required=False, allow_null=True
|
|
)
|
|
|
|
# Distinguishing features
|
|
distinguishing_features = serializers.CharField(allow_blank=True, default="")
|
|
|
|
def validate(self, attrs):
|
|
"""Cross-field validation."""
|
|
# Height range validation
|
|
min_height = attrs.get("min_height_ft")
|
|
max_height = attrs.get("max_height_ft")
|
|
|
|
if min_height and max_height and min_height > max_height:
|
|
raise serializers.ValidationError(
|
|
"Minimum height cannot be greater than maximum height"
|
|
)
|
|
|
|
# Speed range validation
|
|
min_speed = attrs.get("min_speed_mph")
|
|
max_speed = attrs.get("max_speed_mph")
|
|
|
|
if min_speed and max_speed and min_speed > max_speed:
|
|
raise serializers.ValidationError(
|
|
"Minimum speed cannot be greater than maximum speed"
|
|
)
|
|
|
|
return attrs
|
|
|
|
|
|
class RideModelVariantUpdateInputSerializer(serializers.Serializer):
|
|
"""Input serializer for updating ride model variants."""
|
|
|
|
name = serializers.CharField(max_length=255, required=False)
|
|
description = serializers.CharField(allow_blank=True, required=False)
|
|
|
|
# Variant-specific specifications
|
|
min_height_ft = serializers.DecimalField(
|
|
max_digits=6, decimal_places=2, required=False, allow_null=True
|
|
)
|
|
max_height_ft = serializers.DecimalField(
|
|
max_digits=6, decimal_places=2, required=False, allow_null=True
|
|
)
|
|
min_speed_mph = serializers.DecimalField(
|
|
max_digits=5, decimal_places=2, required=False, allow_null=True
|
|
)
|
|
max_speed_mph = serializers.DecimalField(
|
|
max_digits=5, decimal_places=2, required=False, allow_null=True
|
|
)
|
|
|
|
# Distinguishing features
|
|
distinguishing_features = serializers.CharField(allow_blank=True, required=False)
|
|
|
|
def validate(self, attrs):
|
|
"""Cross-field validation."""
|
|
# Height range validation
|
|
min_height = attrs.get("min_height_ft")
|
|
max_height = attrs.get("max_height_ft")
|
|
|
|
if min_height and max_height and min_height > max_height:
|
|
raise serializers.ValidationError(
|
|
"Minimum height cannot be greater than maximum height"
|
|
)
|
|
|
|
# Speed range validation
|
|
min_speed = attrs.get("min_speed_mph")
|
|
max_speed = attrs.get("max_speed_mph")
|
|
|
|
if min_speed and max_speed and min_speed > max_speed:
|
|
raise serializers.ValidationError(
|
|
"Minimum speed cannot be greater than maximum speed"
|
|
)
|
|
|
|
return attrs
|
|
|
|
|
|
# === RIDE MODEL TECHNICAL SPEC SERIALIZERS ===
|
|
|
|
|
|
class RideModelTechnicalSpecCreateInputSerializer(serializers.Serializer):
|
|
"""Input serializer for creating ride model technical specifications."""
|
|
|
|
ride_model_id = serializers.IntegerField()
|
|
spec_category = serializers.ChoiceField(
|
|
choices=ModelChoices.get_technical_spec_category_choices()
|
|
)
|
|
spec_name = serializers.CharField(max_length=100)
|
|
spec_value = serializers.CharField(max_length=255)
|
|
spec_unit = serializers.CharField(max_length=20, allow_blank=True, default="")
|
|
notes = serializers.CharField(allow_blank=True, default="")
|
|
|
|
|
|
class RideModelTechnicalSpecUpdateInputSerializer(serializers.Serializer):
|
|
"""Input serializer for updating ride model technical specifications."""
|
|
|
|
spec_category = serializers.ChoiceField(
|
|
choices=ModelChoices.get_technical_spec_category_choices(),
|
|
required=False,
|
|
)
|
|
spec_name = serializers.CharField(max_length=100, required=False)
|
|
spec_value = serializers.CharField(max_length=255, required=False)
|
|
spec_unit = serializers.CharField(max_length=20, allow_blank=True, required=False)
|
|
notes = serializers.CharField(allow_blank=True, required=False)
|
|
|
|
|
|
# === RIDE MODEL PHOTO SERIALIZERS ===
|
|
|
|
|
|
class RideModelPhotoCreateInputSerializer(serializers.Serializer):
|
|
"""Input serializer for creating ride model photos."""
|
|
|
|
ride_model_id = serializers.IntegerField()
|
|
image = serializers.ImageField()
|
|
caption = serializers.CharField(max_length=500, allow_blank=True, default="")
|
|
alt_text = serializers.CharField(max_length=255, allow_blank=True, default="")
|
|
photo_type = serializers.ChoiceField(
|
|
choices=ModelChoices.get_photo_type_choices(),
|
|
default="PROMOTIONAL",
|
|
)
|
|
is_primary = serializers.BooleanField(default=False)
|
|
photographer = serializers.CharField(max_length=255, allow_blank=True, default="")
|
|
source = serializers.CharField(max_length=255, allow_blank=True, default="")
|
|
copyright_info = serializers.CharField(max_length=255, allow_blank=True, default="")
|
|
|
|
|
|
class RideModelPhotoUpdateInputSerializer(serializers.Serializer):
|
|
"""Input serializer for updating ride model photos."""
|
|
|
|
caption = serializers.CharField(max_length=500, allow_blank=True, required=False)
|
|
alt_text = serializers.CharField(max_length=255, allow_blank=True, required=False)
|
|
photo_type = serializers.ChoiceField(
|
|
choices=ModelChoices.get_photo_type_choices(),
|
|
required=False,
|
|
)
|
|
is_primary = serializers.BooleanField(required=False)
|
|
photographer = serializers.CharField(
|
|
max_length=255, allow_blank=True, required=False
|
|
)
|
|
source = serializers.CharField(max_length=255, allow_blank=True, required=False)
|
|
copyright_info = serializers.CharField(
|
|
max_length=255, allow_blank=True, required=False
|
|
)
|
|
|
|
|
|
# === RIDE MODEL STATS SERIALIZERS ===
|
|
|
|
|
|
class RideModelStatsOutputSerializer(serializers.Serializer):
|
|
"""Output serializer for ride model statistics."""
|
|
|
|
total_models = serializers.IntegerField()
|
|
total_installations = serializers.IntegerField()
|
|
active_manufacturers = serializers.IntegerField()
|
|
discontinued_models = serializers.IntegerField()
|
|
by_category = serializers.DictField(
|
|
child=serializers.IntegerField(), help_text="Model counts by category"
|
|
)
|
|
by_target_market = serializers.DictField(
|
|
child=serializers.IntegerField(), help_text="Model counts by target market"
|
|
)
|
|
by_manufacturer = serializers.DictField(
|
|
child=serializers.IntegerField(), help_text="Model counts by manufacturer"
|
|
)
|
|
recent_models = serializers.IntegerField(
|
|
help_text="Models created in the last 30 days"
|
|
)
|