""" Ride media serializers for ThrillWiki API v1. This module contains serializers for ride-specific media functionality. """ from drf_spectacular.utils import ( OpenApiExample, extend_schema_field, extend_schema_serializer, ) from rest_framework import serializers from apps.rides.models import Ride, RidePhoto @extend_schema_serializer( examples=[ OpenApiExample( name="Ride Photo with Cloudflare Images", summary="Complete ride photo response", description="Example response showing all fields including Cloudflare Images URLs and variants", value={ "id": 123, "image": "https://imagedelivery.net/account-hash/abc123def456/public", "image_url": "https://imagedelivery.net/account-hash/abc123def456/public", "image_variants": { "thumbnail": "https://imagedelivery.net/account-hash/abc123def456/thumbnail", "medium": "https://imagedelivery.net/account-hash/abc123def456/medium", "large": "https://imagedelivery.net/account-hash/abc123def456/large", "public": "https://imagedelivery.net/account-hash/abc123def456/public", }, "caption": "Amazing roller coaster photo", "alt_text": "Steel roller coaster with multiple inversions", "is_primary": True, "is_approved": True, "photo_type": "exterior", "created_at": "2023-01-01T12:00:00Z", "updated_at": "2023-01-01T12:00:00Z", "date_taken": "2023-01-01T10:00:00Z", "uploaded_by_username": "photographer123", "file_size": 2048576, "dimensions": [1920, 1080], "ride_slug": "steel-vengeance", "ride_name": "Steel Vengeance", "park_slug": "cedar-point", "park_name": "Cedar Point", }, ) ] ) class RidePhotoOutputSerializer(serializers.ModelSerializer): """Output serializer for ride photos with Cloudflare Images support.""" uploaded_by_username = serializers.CharField(source="uploaded_by.username", read_only=True) file_size = serializers.SerializerMethodField() dimensions = serializers.SerializerMethodField() image_url = serializers.SerializerMethodField() image_variants = serializers.SerializerMethodField() @extend_schema_field(serializers.IntegerField(allow_null=True, help_text="File size in bytes")) def get_file_size(self, obj): """Get file size in bytes.""" return obj.file_size @extend_schema_field( serializers.ListField( child=serializers.IntegerField(), min_length=2, max_length=2, allow_null=True, help_text="Image dimensions as [width, height] in pixels", ) ) def get_dimensions(self, obj): """Get image dimensions as [width, height].""" return obj.dimensions @extend_schema_field(serializers.URLField(help_text="Full URL to the Cloudflare Images asset", allow_null=True)) def get_image_url(self, obj): """Get the full Cloudflare Images URL.""" if obj.image: return obj.image.public_url return None @extend_schema_field( serializers.DictField( child=serializers.URLField(), help_text="Available Cloudflare Images variants with their URLs", ) ) def get_image_variants(self, obj): """Get available image variants from Cloudflare Images.""" if not obj.image: return {} # Common variants for ride photos variants = { "thumbnail": f"{obj.image.public_url}/thumbnail", "medium": f"{obj.image.public_url}/medium", "large": f"{obj.image.public_url}/large", "public": f"{obj.image.public_url}/public", } return variants ride_slug = serializers.CharField(source="ride.slug", read_only=True) ride_name = serializers.CharField(source="ride.name", read_only=True) park_slug = serializers.CharField(source="ride.park.slug", read_only=True) park_name = serializers.CharField(source="ride.park.name", read_only=True) class Meta: model = RidePhoto fields = [ "id", "image", "image_url", "image_variants", "caption", "photographer", "alt_text", "is_primary", "is_approved", "photo_type", "created_at", "updated_at", "date_taken", "uploaded_by_username", "file_size", "dimensions", "ride_slug", "ride_name", "park_slug", "park_name", ] read_only_fields = [ "id", "image_url", "image_variants", "created_at", "updated_at", "uploaded_by_username", "file_size", "dimensions", "ride_slug", "ride_name", "park_slug", "park_name", ] class RidePhotoCreateInputSerializer(serializers.ModelSerializer): """Input serializer for creating ride photos.""" class Meta: model = RidePhoto fields = [ "image", "caption", "photographer", "alt_text", "photo_type", "is_primary", ] class RidePhotoUpdateInputSerializer(serializers.ModelSerializer): """Input serializer for updating ride photos.""" class Meta: model = RidePhoto fields = [ "caption", "photographer", "alt_text", "photo_type", "is_primary", ] class RidePhotoListOutputSerializer(serializers.ModelSerializer): """Simplified output serializer for ride photo lists.""" uploaded_by_username = serializers.CharField(source="uploaded_by.username", read_only=True) class Meta: model = RidePhoto fields = [ "id", "image", "caption", "photo_type", "is_primary", "is_approved", "created_at", "uploaded_by_username", ] read_only_fields = fields class RidePhotoApprovalInputSerializer(serializers.Serializer): """Input serializer for photo approval operations.""" photo_ids = serializers.ListField(child=serializers.IntegerField(), help_text="List of photo IDs to approve") approve = serializers.BooleanField(default=True, help_text="Whether to approve (True) or reject (False) the photos") class RidePhotoStatsOutputSerializer(serializers.Serializer): """Output serializer for ride photo statistics.""" total_photos = serializers.IntegerField() approved_photos = serializers.IntegerField() pending_photos = serializers.IntegerField() has_primary = serializers.BooleanField() recent_uploads = serializers.IntegerField() by_type = serializers.DictField(child=serializers.IntegerField(), help_text="Photo counts by type") class RidePhotoTypeFilterSerializer(serializers.Serializer): """Serializer for filtering photos by type.""" photo_type = serializers.ChoiceField( choices=[ ("exterior", "Exterior View"), ("queue", "Queue Area"), ("station", "Station"), ("onride", "On-Ride"), ("construction", "Construction"), ("other", "Other"), ], required=False, help_text="Filter photos by type", ) class RidePhotoSerializer(serializers.ModelSerializer): """Legacy serializer for backward compatibility.""" class Meta: model = RidePhoto fields = [ "id", "image", "caption", "alt_text", "is_primary", "photo_type", "uploaded_at", "uploaded_by", ] class HybridRideSerializer(serializers.ModelSerializer): """ Enhanced serializer for hybrid filtering strategy. Includes all filterable fields for client-side filtering. """ # Park fields park_name = serializers.CharField(source="park.name", read_only=True) park_slug = serializers.CharField(source="park.slug", read_only=True) # Park location fields park_city = serializers.SerializerMethodField() park_state = serializers.SerializerMethodField() park_country = serializers.SerializerMethodField() # Park area fields park_area_name = serializers.CharField(source="park_area.name", read_only=True, allow_null=True) park_area_slug = serializers.CharField(source="park_area.slug", read_only=True, allow_null=True) # Company fields manufacturer_name = serializers.CharField(source="manufacturer.name", read_only=True, allow_null=True) manufacturer_slug = serializers.CharField(source="manufacturer.slug", read_only=True, allow_null=True) designer_name = serializers.CharField(source="designer.name", read_only=True, allow_null=True) designer_slug = serializers.CharField(source="designer.slug", read_only=True, allow_null=True) # Ride model fields ride_model_name = serializers.CharField(source="ride_model.name", read_only=True, allow_null=True) ride_model_slug = serializers.CharField(source="ride_model.slug", read_only=True, allow_null=True) ride_model_category = serializers.CharField(source="ride_model.category", read_only=True, allow_null=True) ride_model_manufacturer_name = serializers.CharField( source="ride_model.manufacturer.name", read_only=True, allow_null=True ) ride_model_manufacturer_slug = serializers.CharField( source="ride_model.manufacturer.slug", read_only=True, allow_null=True ) # Roller coaster stats fields coaster_height_ft = serializers.SerializerMethodField() coaster_length_ft = serializers.SerializerMethodField() coaster_speed_mph = serializers.SerializerMethodField() coaster_inversions = serializers.SerializerMethodField() coaster_ride_time_seconds = serializers.SerializerMethodField() coaster_track_type = serializers.SerializerMethodField() coaster_track_material = serializers.SerializerMethodField() coaster_roller_coaster_type = serializers.SerializerMethodField() coaster_max_drop_height_ft = serializers.SerializerMethodField() coaster_propulsion_system = serializers.SerializerMethodField() coaster_train_style = serializers.SerializerMethodField() coaster_trains_count = serializers.SerializerMethodField() coaster_cars_per_train = serializers.SerializerMethodField() coaster_seats_per_car = serializers.SerializerMethodField() # Image URLs for display banner_image_url = serializers.SerializerMethodField() card_image_url = serializers.SerializerMethodField() # Computed fields for filtering opening_year = serializers.IntegerField(read_only=True) search_text = serializers.CharField(read_only=True) @extend_schema_field(serializers.CharField(allow_null=True)) def get_park_city(self, obj): """Get city from park location.""" try: if obj.park and hasattr(obj.park, "location") and obj.park.location: return obj.park.location.city return None except AttributeError: return None @extend_schema_field(serializers.CharField(allow_null=True)) def get_park_state(self, obj): """Get state from park location.""" try: if obj.park and hasattr(obj.park, "location") and obj.park.location: return obj.park.location.state return None except AttributeError: return None @extend_schema_field(serializers.CharField(allow_null=True)) def get_park_country(self, obj): """Get country from park location.""" try: if obj.park and hasattr(obj.park, "location") and obj.park.location: return obj.park.location.country return None except AttributeError: return None @extend_schema_field(serializers.FloatField(allow_null=True)) def get_coaster_height_ft(self, obj): """Get roller coaster height.""" try: if hasattr(obj, "coaster_stats") and obj.coaster_stats: return float(obj.coaster_stats.height_ft) if obj.coaster_stats.height_ft else None return None except (AttributeError, TypeError): return None @extend_schema_field(serializers.FloatField(allow_null=True)) def get_coaster_length_ft(self, obj): """Get roller coaster length.""" try: if hasattr(obj, "coaster_stats") and obj.coaster_stats: return float(obj.coaster_stats.length_ft) if obj.coaster_stats.length_ft else None return None except (AttributeError, TypeError): return None @extend_schema_field(serializers.FloatField(allow_null=True)) def get_coaster_speed_mph(self, obj): """Get roller coaster speed.""" try: if hasattr(obj, "coaster_stats") and obj.coaster_stats: return float(obj.coaster_stats.speed_mph) if obj.coaster_stats.speed_mph else None return None except (AttributeError, TypeError): return None @extend_schema_field(serializers.IntegerField(allow_null=True)) def get_coaster_inversions(self, obj): """Get roller coaster inversions.""" try: if hasattr(obj, "coaster_stats") and obj.coaster_stats: return obj.coaster_stats.inversions return None except AttributeError: return None @extend_schema_field(serializers.IntegerField(allow_null=True)) def get_coaster_ride_time_seconds(self, obj): """Get roller coaster ride time.""" try: if hasattr(obj, "coaster_stats") and obj.coaster_stats: return obj.coaster_stats.ride_time_seconds return None except AttributeError: return None @extend_schema_field(serializers.CharField(allow_null=True)) def get_coaster_track_type(self, obj): """Get roller coaster track type.""" try: if hasattr(obj, "coaster_stats") and obj.coaster_stats: return obj.coaster_stats.track_type return None except AttributeError: return None @extend_schema_field(serializers.CharField(allow_null=True)) def get_coaster_track_material(self, obj): """Get roller coaster track material.""" try: if hasattr(obj, "coaster_stats") and obj.coaster_stats: return obj.coaster_stats.track_material return None except AttributeError: return None @extend_schema_field(serializers.CharField(allow_null=True)) def get_coaster_roller_coaster_type(self, obj): """Get roller coaster type.""" try: if hasattr(obj, "coaster_stats") and obj.coaster_stats: return obj.coaster_stats.roller_coaster_type return None except AttributeError: return None @extend_schema_field(serializers.FloatField(allow_null=True)) def get_coaster_max_drop_height_ft(self, obj): """Get roller coaster max drop height.""" try: if hasattr(obj, "coaster_stats") and obj.coaster_stats: return float(obj.coaster_stats.max_drop_height_ft) if obj.coaster_stats.max_drop_height_ft else None return None except (AttributeError, TypeError): return None @extend_schema_field(serializers.CharField(allow_null=True)) def get_coaster_propulsion_system(self, obj): """Get roller coaster propulsion system.""" try: if hasattr(obj, "coaster_stats") and obj.coaster_stats: return obj.coaster_stats.propulsion_system return None except AttributeError: return None @extend_schema_field(serializers.CharField(allow_null=True)) def get_coaster_train_style(self, obj): """Get roller coaster train style.""" try: if hasattr(obj, "coaster_stats") and obj.coaster_stats: return obj.coaster_stats.train_style return None except AttributeError: return None @extend_schema_field(serializers.IntegerField(allow_null=True)) def get_coaster_trains_count(self, obj): """Get roller coaster trains count.""" try: if hasattr(obj, "coaster_stats") and obj.coaster_stats: return obj.coaster_stats.trains_count return None except AttributeError: return None @extend_schema_field(serializers.IntegerField(allow_null=True)) def get_coaster_cars_per_train(self, obj): """Get roller coaster cars per train.""" try: if hasattr(obj, "coaster_stats") and obj.coaster_stats: return obj.coaster_stats.cars_per_train return None except AttributeError: return None @extend_schema_field(serializers.IntegerField(allow_null=True)) def get_coaster_seats_per_car(self, obj): """Get roller coaster seats per car.""" try: if hasattr(obj, "coaster_stats") and obj.coaster_stats: return obj.coaster_stats.seats_per_car return None except AttributeError: return None @extend_schema_field(serializers.URLField(allow_null=True)) def get_banner_image_url(self, obj): """Get banner image URL.""" if obj.banner_image and obj.banner_image.image: return obj.banner_image.image.public_url return None @extend_schema_field(serializers.URLField(allow_null=True)) def get_card_image_url(self, obj): """Get card image URL.""" if obj.card_image and obj.card_image.image: return obj.card_image.image.public_url return None # Computed property is_closing = serializers.SerializerMethodField() @extend_schema_field(serializers.BooleanField()) def get_is_closing(self, obj): """Check if ride has an announced closing date in the future.""" return obj.is_closing # Water ride stats fields water_wetness_level = serializers.SerializerMethodField() water_splash_height_ft = serializers.SerializerMethodField() water_has_splash_zone = serializers.SerializerMethodField() water_boat_capacity = serializers.SerializerMethodField() water_uses_flume = serializers.SerializerMethodField() water_rapids_sections = serializers.SerializerMethodField() @extend_schema_field(serializers.CharField(allow_null=True)) def get_water_wetness_level(self, obj): try: if hasattr(obj, "water_stats") and obj.water_stats: return obj.water_stats.wetness_level return None except AttributeError: return None @extend_schema_field(serializers.FloatField(allow_null=True)) def get_water_splash_height_ft(self, obj): try: if hasattr(obj, "water_stats") and obj.water_stats: return float(obj.water_stats.splash_height_ft) if obj.water_stats.splash_height_ft else None return None except (AttributeError, TypeError): return None @extend_schema_field(serializers.BooleanField(allow_null=True)) def get_water_has_splash_zone(self, obj): try: if hasattr(obj, "water_stats") and obj.water_stats: return obj.water_stats.has_splash_zone return None except AttributeError: return None @extend_schema_field(serializers.IntegerField(allow_null=True)) def get_water_boat_capacity(self, obj): try: if hasattr(obj, "water_stats") and obj.water_stats: return obj.water_stats.boat_capacity return None except AttributeError: return None @extend_schema_field(serializers.BooleanField(allow_null=True)) def get_water_uses_flume(self, obj): try: if hasattr(obj, "water_stats") and obj.water_stats: return obj.water_stats.uses_flume return None except AttributeError: return None @extend_schema_field(serializers.IntegerField(allow_null=True)) def get_water_rapids_sections(self, obj): try: if hasattr(obj, "water_stats") and obj.water_stats: return obj.water_stats.rapids_sections return None except AttributeError: return None # Dark ride stats fields dark_scene_count = serializers.SerializerMethodField() dark_animatronic_count = serializers.SerializerMethodField() dark_has_projection_technology = serializers.SerializerMethodField() dark_is_interactive = serializers.SerializerMethodField() dark_ride_system = serializers.SerializerMethodField() dark_uses_practical_effects = serializers.SerializerMethodField() dark_uses_motion_base = serializers.SerializerMethodField() @extend_schema_field(serializers.IntegerField(allow_null=True)) def get_dark_scene_count(self, obj): try: if hasattr(obj, "dark_stats") and obj.dark_stats: return obj.dark_stats.scene_count return None except AttributeError: return None @extend_schema_field(serializers.IntegerField(allow_null=True)) def get_dark_animatronic_count(self, obj): try: if hasattr(obj, "dark_stats") and obj.dark_stats: return obj.dark_stats.animatronic_count return None except AttributeError: return None @extend_schema_field(serializers.BooleanField(allow_null=True)) def get_dark_has_projection_technology(self, obj): try: if hasattr(obj, "dark_stats") and obj.dark_stats: return obj.dark_stats.has_projection_technology return None except AttributeError: return None @extend_schema_field(serializers.BooleanField(allow_null=True)) def get_dark_is_interactive(self, obj): try: if hasattr(obj, "dark_stats") and obj.dark_stats: return obj.dark_stats.is_interactive return None except AttributeError: return None @extend_schema_field(serializers.CharField(allow_null=True)) def get_dark_ride_system(self, obj): try: if hasattr(obj, "dark_stats") and obj.dark_stats: return obj.dark_stats.ride_system return None except AttributeError: return None @extend_schema_field(serializers.BooleanField(allow_null=True)) def get_dark_uses_practical_effects(self, obj): try: if hasattr(obj, "dark_stats") and obj.dark_stats: return obj.dark_stats.uses_practical_effects return None except AttributeError: return None @extend_schema_field(serializers.BooleanField(allow_null=True)) def get_dark_uses_motion_base(self, obj): try: if hasattr(obj, "dark_stats") and obj.dark_stats: return obj.dark_stats.uses_motion_base return None except AttributeError: return None # Flat ride stats fields flat_max_height_ft = serializers.SerializerMethodField() flat_rotation_speed_rpm = serializers.SerializerMethodField() flat_swing_angle_degrees = serializers.SerializerMethodField() flat_motion_type = serializers.SerializerMethodField() flat_arm_count = serializers.SerializerMethodField() flat_seats_per_gondola = serializers.SerializerMethodField() flat_max_g_force = serializers.SerializerMethodField() @extend_schema_field(serializers.FloatField(allow_null=True)) def get_flat_max_height_ft(self, obj): try: if hasattr(obj, "flat_stats") and obj.flat_stats: return float(obj.flat_stats.max_height_ft) if obj.flat_stats.max_height_ft else None return None except (AttributeError, TypeError): return None @extend_schema_field(serializers.FloatField(allow_null=True)) def get_flat_rotation_speed_rpm(self, obj): try: if hasattr(obj, "flat_stats") and obj.flat_stats: return float(obj.flat_stats.rotation_speed_rpm) if obj.flat_stats.rotation_speed_rpm else None return None except (AttributeError, TypeError): return None @extend_schema_field(serializers.IntegerField(allow_null=True)) def get_flat_swing_angle_degrees(self, obj): try: if hasattr(obj, "flat_stats") and obj.flat_stats: return obj.flat_stats.swing_angle_degrees return None except AttributeError: return None @extend_schema_field(serializers.CharField(allow_null=True)) def get_flat_motion_type(self, obj): try: if hasattr(obj, "flat_stats") and obj.flat_stats: return obj.flat_stats.motion_type return None except AttributeError: return None @extend_schema_field(serializers.IntegerField(allow_null=True)) def get_flat_arm_count(self, obj): try: if hasattr(obj, "flat_stats") and obj.flat_stats: return obj.flat_stats.arm_count return None except AttributeError: return None @extend_schema_field(serializers.IntegerField(allow_null=True)) def get_flat_seats_per_gondola(self, obj): try: if hasattr(obj, "flat_stats") and obj.flat_stats: return obj.flat_stats.seats_per_gondola return None except AttributeError: return None @extend_schema_field(serializers.FloatField(allow_null=True)) def get_flat_max_g_force(self, obj): try: if hasattr(obj, "flat_stats") and obj.flat_stats: return float(obj.flat_stats.max_g_force) if obj.flat_stats.max_g_force else None return None except (AttributeError, TypeError): return None # Kiddie ride stats fields kiddie_min_age = serializers.SerializerMethodField() kiddie_max_age = serializers.SerializerMethodField() kiddie_educational_theme = serializers.SerializerMethodField() kiddie_character_theme = serializers.SerializerMethodField() kiddie_guardian_required = serializers.SerializerMethodField() kiddie_adult_ride_along = serializers.SerializerMethodField() kiddie_seats_per_vehicle = serializers.SerializerMethodField() @extend_schema_field(serializers.IntegerField(allow_null=True)) def get_kiddie_min_age(self, obj): try: if hasattr(obj, "kiddie_stats") and obj.kiddie_stats: return obj.kiddie_stats.min_age return None except AttributeError: return None @extend_schema_field(serializers.IntegerField(allow_null=True)) def get_kiddie_max_age(self, obj): try: if hasattr(obj, "kiddie_stats") and obj.kiddie_stats: return obj.kiddie_stats.max_age return None except AttributeError: return None @extend_schema_field(serializers.CharField(allow_null=True)) def get_kiddie_educational_theme(self, obj): try: if hasattr(obj, "kiddie_stats") and obj.kiddie_stats: return obj.kiddie_stats.educational_theme or None return None except AttributeError: return None @extend_schema_field(serializers.CharField(allow_null=True)) def get_kiddie_character_theme(self, obj): try: if hasattr(obj, "kiddie_stats") and obj.kiddie_stats: return obj.kiddie_stats.character_theme or None return None except AttributeError: return None @extend_schema_field(serializers.BooleanField(allow_null=True)) def get_kiddie_guardian_required(self, obj): try: if hasattr(obj, "kiddie_stats") and obj.kiddie_stats: return obj.kiddie_stats.guardian_required return None except AttributeError: return None @extend_schema_field(serializers.BooleanField(allow_null=True)) def get_kiddie_adult_ride_along(self, obj): try: if hasattr(obj, "kiddie_stats") and obj.kiddie_stats: return obj.kiddie_stats.adult_ride_along return None except AttributeError: return None @extend_schema_field(serializers.IntegerField(allow_null=True)) def get_kiddie_seats_per_vehicle(self, obj): try: if hasattr(obj, "kiddie_stats") and obj.kiddie_stats: return obj.kiddie_stats.seats_per_vehicle return None except AttributeError: return None # Transportation stats fields transport_type = serializers.SerializerMethodField() transport_route_length_ft = serializers.SerializerMethodField() transport_stations_count = serializers.SerializerMethodField() transport_vehicle_capacity = serializers.SerializerMethodField() transport_vehicles_count = serializers.SerializerMethodField() transport_round_trip_duration_minutes = serializers.SerializerMethodField() transport_scenic_highlights = serializers.SerializerMethodField() transport_is_one_way = serializers.SerializerMethodField() @extend_schema_field(serializers.CharField(allow_null=True)) def get_transport_type(self, obj): try: if hasattr(obj, "transport_stats") and obj.transport_stats: return obj.transport_stats.transport_type return None except AttributeError: return None @extend_schema_field(serializers.FloatField(allow_null=True)) def get_transport_route_length_ft(self, obj): try: if hasattr(obj, "transport_stats") and obj.transport_stats: return float(obj.transport_stats.route_length_ft) if obj.transport_stats.route_length_ft else None return None except (AttributeError, TypeError): return None @extend_schema_field(serializers.IntegerField(allow_null=True)) def get_transport_stations_count(self, obj): try: if hasattr(obj, "transport_stats") and obj.transport_stats: return obj.transport_stats.stations_count return None except AttributeError: return None @extend_schema_field(serializers.IntegerField(allow_null=True)) def get_transport_vehicle_capacity(self, obj): try: if hasattr(obj, "transport_stats") and obj.transport_stats: return obj.transport_stats.vehicle_capacity return None except AttributeError: return None @extend_schema_field(serializers.IntegerField(allow_null=True)) def get_transport_vehicles_count(self, obj): try: if hasattr(obj, "transport_stats") and obj.transport_stats: return obj.transport_stats.vehicles_count return None except AttributeError: return None @extend_schema_field(serializers.IntegerField(allow_null=True)) def get_transport_round_trip_duration_minutes(self, obj): try: if hasattr(obj, "transport_stats") and obj.transport_stats: return obj.transport_stats.round_trip_duration_minutes return None except AttributeError: return None @extend_schema_field(serializers.CharField(allow_null=True)) def get_transport_scenic_highlights(self, obj): try: if hasattr(obj, "transport_stats") and obj.transport_stats: return obj.transport_stats.scenic_highlights or None return None except AttributeError: return None @extend_schema_field(serializers.BooleanField(allow_null=True)) def get_transport_is_one_way(self, obj): try: if hasattr(obj, "transport_stats") and obj.transport_stats: return obj.transport_stats.is_one_way return None except AttributeError: return None class Meta: model = Ride fields = [ # Basic ride info "id", "name", "slug", "description", "category", "status", "post_closing_status", # Dates and computed fields "opening_date", "opening_date_precision", "closing_date", "closing_date_precision", "is_closing", "status_since", "opening_year", # Park fields "park_name", "park_slug", "park_city", "park_state", "park_country", # Park area fields "park_area_name", "park_area_slug", # Company fields "manufacturer_name", "manufacturer_slug", "designer_name", "designer_slug", # Ride model fields "ride_model_name", "ride_model_slug", "ride_model_category", "ride_model_manufacturer_name", "ride_model_manufacturer_slug", # Ride specifications "min_height_in", "max_height_in", "capacity_per_hour", "ride_duration_seconds", "average_rating", # Additional classification "ride_sub_type", "age_requirement", # Roller coaster stats "coaster_height_ft", "coaster_length_ft", "coaster_speed_mph", "coaster_inversions", "coaster_ride_time_seconds", "coaster_track_type", "coaster_track_material", "coaster_roller_coaster_type", "coaster_max_drop_height_ft", "coaster_propulsion_system", "coaster_train_style", "coaster_trains_count", "coaster_cars_per_train", "coaster_seats_per_car", # Water ride stats "water_wetness_level", "water_splash_height_ft", "water_has_splash_zone", "water_boat_capacity", "water_uses_flume", "water_rapids_sections", # Dark ride stats "dark_scene_count", "dark_animatronic_count", "dark_has_projection_technology", "dark_is_interactive", "dark_ride_system", "dark_uses_practical_effects", "dark_uses_motion_base", # Flat ride stats "flat_max_height_ft", "flat_rotation_speed_rpm", "flat_swing_angle_degrees", "flat_motion_type", "flat_arm_count", "flat_seats_per_gondola", "flat_max_g_force", # Kiddie ride stats "kiddie_min_age", "kiddie_max_age", "kiddie_educational_theme", "kiddie_character_theme", "kiddie_guardian_required", "kiddie_adult_ride_along", "kiddie_seats_per_vehicle", # Transportation stats "transport_type", "transport_route_length_ft", "transport_stations_count", "transport_vehicle_capacity", "transport_vehicles_count", "transport_round_trip_duration_minutes", "transport_scenic_highlights", "transport_is_one_way", # Images "banner_image_url", "card_image_url", # URLs "url", "park_url", # Computed fields for filtering "search_text", # Metadata "created_at", "updated_at", ] read_only_fields = fields class RideSerializer(serializers.ModelSerializer): """Serializer for the Ride model.""" class Meta: model = Ride fields = [ "id", "name", "slug", "park", "manufacturer", "designer", "category", "status", "opening_date", "closing_date", ]