Files
thrillwiki_django_no_react/apps/api/v1/parks/serializers.py
pacnpal 75cc618c2b update
2025-09-21 20:04:42 -04:00

394 lines
12 KiB
Python

"""
Park media serializers for ThrillWiki API v1.
This module contains serializers for park-specific media functionality.
Enhanced from rogue implementation to maintain full feature parity.
"""
from rest_framework import serializers
from drf_spectacular.utils import (
extend_schema_field,
extend_schema_serializer,
OpenApiExample,
)
from apps.parks.models import Park, ParkPhoto
@extend_schema_serializer(
examples=[
OpenApiExample(
name="Park Photo with Cloudflare Images",
summary="Complete park photo response",
description="Example response showing all fields including Cloudflare Images URLs and variants",
value={
"id": 456,
"image": "https://imagedelivery.net/account-hash/def456ghi789/public",
"image_url": "https://imagedelivery.net/account-hash/def456ghi789/public",
"image_variants": {
"thumbnail": "https://imagedelivery.net/account-hash/def456ghi789/thumbnail",
"medium": "https://imagedelivery.net/account-hash/def456ghi789/medium",
"large": "https://imagedelivery.net/account-hash/def456ghi789/large",
"public": "https://imagedelivery.net/account-hash/def456ghi789/public",
},
"caption": "Beautiful park entrance",
"alt_text": "Main entrance gate with decorative archway",
"is_primary": True,
"is_approved": True,
"created_at": "2023-01-01T12:00:00Z",
"updated_at": "2023-01-01T12:00:00Z",
"date_taken": "2023-01-01T11:00:00Z",
"uploaded_by_username": "parkfan456",
"file_size": 1536000,
"dimensions": [1600, 900],
"park_slug": "cedar-point",
"park_name": "Cedar Point",
},
)
]
)
class ParkPhotoOutputSerializer(serializers.ModelSerializer):
"""Enhanced output serializer for park 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.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 park photos
variants = {
"thumbnail": f"{obj.image.url}/thumbnail",
"medium": f"{obj.image.url}/medium",
"large": f"{obj.image.url}/large",
"public": f"{obj.image.url}/public",
}
return variants
park_slug = serializers.CharField(source="park.slug", read_only=True)
park_name = serializers.CharField(source="park.name", read_only=True)
class Meta:
model = ParkPhoto
fields = [
"id",
"image",
"image_url",
"image_variants",
"caption",
"alt_text",
"is_primary",
"is_approved",
"created_at",
"updated_at",
"date_taken",
"uploaded_by_username",
"file_size",
"dimensions",
"park_slug",
"park_name",
]
read_only_fields = [
"id",
"image_url",
"image_variants",
"created_at",
"updated_at",
"uploaded_by_username",
"file_size",
"dimensions",
"park_slug",
"park_name",
]
class ParkPhotoCreateInputSerializer(serializers.ModelSerializer):
"""Input serializer for creating park photos."""
class Meta:
model = ParkPhoto
fields = [
"image",
"caption",
"alt_text",
"is_primary",
]
class ParkPhotoUpdateInputSerializer(serializers.ModelSerializer):
"""Input serializer for updating park photos."""
class Meta:
model = ParkPhoto
fields = [
"caption",
"alt_text",
"is_primary",
]
class ParkPhotoListOutputSerializer(serializers.ModelSerializer):
"""Optimized output serializer for park photo lists."""
uploaded_by_username = serializers.CharField(
source="uploaded_by.username", read_only=True
)
class Meta:
model = ParkPhoto
fields = [
"id",
"image",
"caption",
"is_primary",
"is_approved",
"created_at",
"uploaded_by_username",
]
read_only_fields = fields
class ParkPhotoApprovalInputSerializer(serializers.Serializer):
"""Input serializer for bulk 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 ParkPhotoStatsOutputSerializer(serializers.Serializer):
"""Output serializer for park photo statistics."""
total_photos = serializers.IntegerField()
approved_photos = serializers.IntegerField()
pending_photos = serializers.IntegerField()
has_primary = serializers.BooleanField()
recent_uploads = serializers.IntegerField()
# Legacy serializers for backwards compatibility
class ParkPhotoSerializer(serializers.ModelSerializer):
"""Legacy serializer for the ParkPhoto model - maintained for compatibility."""
class Meta:
model = ParkPhoto
fields = (
"id",
"image",
"caption",
"alt_text",
"is_primary",
"uploaded_at",
"uploaded_by",
)
class HybridParkSerializer(serializers.ModelSerializer):
"""
Enhanced serializer for hybrid filtering strategy.
Includes all filterable fields for client-side filtering.
"""
# Location fields from related ParkLocation
city = serializers.SerializerMethodField()
state = serializers.SerializerMethodField()
country = serializers.SerializerMethodField()
continent = serializers.SerializerMethodField()
latitude = serializers.SerializerMethodField()
longitude = serializers.SerializerMethodField()
# Company fields
operator_name = serializers.CharField(source="operator.name", read_only=True)
property_owner_name = serializers.CharField(source="property_owner.name", read_only=True, allow_null=True)
# 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_city(self, obj):
"""Get city from related location."""
try:
return obj.location.city if hasattr(obj, 'location') and obj.location else None
except AttributeError:
return None
@extend_schema_field(serializers.CharField(allow_null=True))
def get_state(self, obj):
"""Get state from related location."""
try:
return obj.location.state if hasattr(obj, 'location') and obj.location else None
except AttributeError:
return None
@extend_schema_field(serializers.CharField(allow_null=True))
def get_country(self, obj):
"""Get country from related location."""
try:
return obj.location.country if hasattr(obj, 'location') and obj.location else None
except AttributeError:
return None
@extend_schema_field(serializers.CharField(allow_null=True))
def get_continent(self, obj):
"""Get continent from related location."""
try:
return obj.location.continent if hasattr(obj, 'location') and obj.location else None
except AttributeError:
return None
@extend_schema_field(serializers.FloatField(allow_null=True))
def get_latitude(self, obj):
"""Get latitude from related location."""
try:
if hasattr(obj, 'location') and obj.location and obj.location.coordinates:
return obj.location.coordinates[1] # PostGIS returns [lon, lat]
return None
except (AttributeError, IndexError, TypeError):
return None
@extend_schema_field(serializers.FloatField(allow_null=True))
def get_longitude(self, obj):
"""Get longitude from related location."""
try:
if hasattr(obj, 'location') and obj.location and obj.location.coordinates:
return obj.location.coordinates[0] # PostGIS returns [lon, lat]
return None
except (AttributeError, IndexError, TypeError):
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.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.url
return None
class Meta:
model = Park
fields = [
# Basic park info
"id",
"name",
"slug",
"description",
"status",
"park_type",
# Dates and computed fields
"opening_date",
"closing_date",
"opening_year",
"operating_season",
# Location fields
"city",
"state",
"country",
"continent",
"latitude",
"longitude",
# Company relationships
"operator_name",
"property_owner_name",
# Statistics
"size_acres",
"average_rating",
"ride_count",
"coaster_count",
# Images
"banner_image_url",
"card_image_url",
# URLs
"website",
"url",
# Computed fields for filtering
"search_text",
# Metadata
"created_at",
"updated_at",
]
read_only_fields = fields
class ParkSerializer(serializers.ModelSerializer):
"""Serializer for the Park model."""
class Meta:
model = Park
fields = (
"id",
"name",
"slug",
"country",
"continent",
"latitude",
"longitude",
"website",
"status",
)