feat: Implement initial schema and add various API, service, and management command enhancements across the application.

This commit is contained in:
pacnpal
2026-01-01 15:13:01 -05:00
parent c95f99ca10
commit b243b17af7
413 changed files with 11164 additions and 17433 deletions

View File

@@ -52,18 +52,14 @@ from apps.rides.models import Ride, RidePhoto
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
)
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")
)
@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
@@ -81,11 +77,7 @@ class RidePhotoOutputSerializer(serializers.ModelSerializer):
"""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
)
)
@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:
@@ -186,9 +178,7 @@ class RidePhotoUpdateInputSerializer(serializers.ModelSerializer):
class RidePhotoListOutputSerializer(serializers.ModelSerializer):
"""Simplified output serializer for ride photo lists."""
uploaded_by_username = serializers.CharField(
source="uploaded_by.username", read_only=True
)
uploaded_by_username = serializers.CharField(source="uploaded_by.username", read_only=True)
class Meta:
model = RidePhoto
@@ -208,12 +198,8 @@ class RidePhotoListOutputSerializer(serializers.ModelSerializer):
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"
)
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):
@@ -224,9 +210,7 @@ class RidePhotoStatsOutputSerializer(serializers.Serializer):
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"
)
by_type = serializers.DictField(child=serializers.IntegerField(), help_text="Photo counts by type")
class RidePhotoTypeFilterSerializer(serializers.Serializer):
@@ -292,8 +276,12 @@ class HybridRideSerializer(serializers.ModelSerializer):
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)
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()
@@ -323,7 +311,7 @@ class HybridRideSerializer(serializers.ModelSerializer):
def get_park_city(self, obj):
"""Get city from park location."""
try:
if obj.park and hasattr(obj.park, 'location') and obj.park.location:
if obj.park and hasattr(obj.park, "location") and obj.park.location:
return obj.park.location.city
return None
except AttributeError:
@@ -333,7 +321,7 @@ class HybridRideSerializer(serializers.ModelSerializer):
def get_park_state(self, obj):
"""Get state from park location."""
try:
if obj.park and hasattr(obj.park, 'location') and obj.park.location:
if obj.park and hasattr(obj.park, "location") and obj.park.location:
return obj.park.location.state
return None
except AttributeError:
@@ -343,7 +331,7 @@ class HybridRideSerializer(serializers.ModelSerializer):
def get_park_country(self, obj):
"""Get country from park location."""
try:
if obj.park and hasattr(obj.park, 'location') and obj.park.location:
if obj.park and hasattr(obj.park, "location") and obj.park.location:
return obj.park.location.country
return None
except AttributeError:
@@ -353,7 +341,7 @@ class HybridRideSerializer(serializers.ModelSerializer):
def get_coaster_height_ft(self, obj):
"""Get roller coaster height."""
try:
if hasattr(obj, 'coaster_stats') and obj.coaster_stats:
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):
@@ -363,7 +351,7 @@ class HybridRideSerializer(serializers.ModelSerializer):
def get_coaster_length_ft(self, obj):
"""Get roller coaster length."""
try:
if hasattr(obj, 'coaster_stats') and obj.coaster_stats:
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):
@@ -373,7 +361,7 @@ class HybridRideSerializer(serializers.ModelSerializer):
def get_coaster_speed_mph(self, obj):
"""Get roller coaster speed."""
try:
if hasattr(obj, 'coaster_stats') and obj.coaster_stats:
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):
@@ -383,7 +371,7 @@ class HybridRideSerializer(serializers.ModelSerializer):
def get_coaster_inversions(self, obj):
"""Get roller coaster inversions."""
try:
if hasattr(obj, 'coaster_stats') and obj.coaster_stats:
if hasattr(obj, "coaster_stats") and obj.coaster_stats:
return obj.coaster_stats.inversions
return None
except AttributeError:
@@ -393,7 +381,7 @@ class HybridRideSerializer(serializers.ModelSerializer):
def get_coaster_ride_time_seconds(self, obj):
"""Get roller coaster ride time."""
try:
if hasattr(obj, 'coaster_stats') and obj.coaster_stats:
if hasattr(obj, "coaster_stats") and obj.coaster_stats:
return obj.coaster_stats.ride_time_seconds
return None
except AttributeError:
@@ -403,7 +391,7 @@ class HybridRideSerializer(serializers.ModelSerializer):
def get_coaster_track_type(self, obj):
"""Get roller coaster track type."""
try:
if hasattr(obj, 'coaster_stats') and obj.coaster_stats:
if hasattr(obj, "coaster_stats") and obj.coaster_stats:
return obj.coaster_stats.track_type
return None
except AttributeError:
@@ -413,7 +401,7 @@ class HybridRideSerializer(serializers.ModelSerializer):
def get_coaster_track_material(self, obj):
"""Get roller coaster track material."""
try:
if hasattr(obj, 'coaster_stats') and obj.coaster_stats:
if hasattr(obj, "coaster_stats") and obj.coaster_stats:
return obj.coaster_stats.track_material
return None
except AttributeError:
@@ -423,7 +411,7 @@ class HybridRideSerializer(serializers.ModelSerializer):
def get_coaster_roller_coaster_type(self, obj):
"""Get roller coaster type."""
try:
if hasattr(obj, 'coaster_stats') and obj.coaster_stats:
if hasattr(obj, "coaster_stats") and obj.coaster_stats:
return obj.coaster_stats.roller_coaster_type
return None
except AttributeError:
@@ -433,7 +421,7 @@ class HybridRideSerializer(serializers.ModelSerializer):
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:
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):
@@ -443,7 +431,7 @@ class HybridRideSerializer(serializers.ModelSerializer):
def get_coaster_propulsion_system(self, obj):
"""Get roller coaster propulsion system."""
try:
if hasattr(obj, 'coaster_stats') and obj.coaster_stats:
if hasattr(obj, "coaster_stats") and obj.coaster_stats:
return obj.coaster_stats.propulsion_system
return None
except AttributeError:
@@ -453,7 +441,7 @@ class HybridRideSerializer(serializers.ModelSerializer):
def get_coaster_train_style(self, obj):
"""Get roller coaster train style."""
try:
if hasattr(obj, 'coaster_stats') and obj.coaster_stats:
if hasattr(obj, "coaster_stats") and obj.coaster_stats:
return obj.coaster_stats.train_style
return None
except AttributeError:
@@ -463,7 +451,7 @@ class HybridRideSerializer(serializers.ModelSerializer):
def get_coaster_trains_count(self, obj):
"""Get roller coaster trains count."""
try:
if hasattr(obj, 'coaster_stats') and obj.coaster_stats:
if hasattr(obj, "coaster_stats") and obj.coaster_stats:
return obj.coaster_stats.trains_count
return None
except AttributeError:
@@ -473,7 +461,7 @@ class HybridRideSerializer(serializers.ModelSerializer):
def get_coaster_cars_per_train(self, obj):
"""Get roller coaster cars per train."""
try:
if hasattr(obj, 'coaster_stats') and obj.coaster_stats:
if hasattr(obj, "coaster_stats") and obj.coaster_stats:
return obj.coaster_stats.cars_per_train
return None
except AttributeError:
@@ -483,7 +471,7 @@ class HybridRideSerializer(serializers.ModelSerializer):
def get_coaster_seats_per_car(self, obj):
"""Get roller coaster seats per car."""
try:
if hasattr(obj, 'coaster_stats') and obj.coaster_stats:
if hasattr(obj, "coaster_stats") and obj.coaster_stats:
return obj.coaster_stats.seats_per_car
return None
except AttributeError:
@@ -514,44 +502,37 @@ class HybridRideSerializer(serializers.ModelSerializer):
"category",
"status",
"post_closing_status",
# Dates and computed fields
"opening_date",
"closing_date",
"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",
# Roller coaster stats
"coaster_height_ft",
"coaster_length_ft",
@@ -567,18 +548,14 @@ class HybridRideSerializer(serializers.ModelSerializer):
"coaster_trains_count",
"coaster_cars_per_train",
"coaster_seats_per_car",
# Images
"banner_image_url",
"card_image_url",
# URLs
"url",
"park_url",
# Computed fields for filtering
"search_text",
# Metadata
"created_at",
"updated_at",