mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 08:51:09 -05:00
394 lines
12 KiB
Python
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",
|
|
)
|