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

@@ -81,23 +81,15 @@ class RideListOutputSerializer(serializers.Serializer):
id = serializers.IntegerField()
name = serializers.CharField()
slug = serializers.CharField()
category = RichChoiceFieldSerializer(
choice_group="categories",
domain="rides"
)
status = RichChoiceFieldSerializer(
choice_group="statuses",
domain="rides"
)
category = RichChoiceFieldSerializer(choice_group="categories", domain="rides")
status = RichChoiceFieldSerializer(choice_group="statuses", domain="rides")
description = serializers.CharField()
# Park info
park = RideParkOutputSerializer()
# Statistics
average_rating = serializers.DecimalField(
max_digits=3, decimal_places=2, allow_null=True
)
average_rating = serializers.DecimalField(max_digits=3, decimal_places=2, allow_null=True)
capacity_per_hour = serializers.IntegerField(allow_null=True)
# Dates
@@ -178,18 +170,10 @@ class RideDetailOutputSerializer(serializers.Serializer):
id = serializers.IntegerField()
name = serializers.CharField()
slug = serializers.CharField()
category = RichChoiceFieldSerializer(
choice_group="categories",
domain="rides"
)
status = RichChoiceFieldSerializer(
choice_group="statuses",
domain="rides"
)
category = RichChoiceFieldSerializer(choice_group="categories", domain="rides")
status = RichChoiceFieldSerializer(choice_group="statuses", domain="rides")
post_closing_status = RichChoiceFieldSerializer(
choice_group="post_closing_statuses",
domain="rides",
allow_null=True
choice_group="post_closing_statuses", domain="rides", allow_null=True
)
description = serializers.CharField()
@@ -209,9 +193,7 @@ class RideDetailOutputSerializer(serializers.Serializer):
ride_duration_seconds = serializers.IntegerField(allow_null=True)
# Statistics
average_rating = serializers.DecimalField(
max_digits=3, decimal_places=2, allow_null=True
)
average_rating = serializers.DecimalField(max_digits=3, decimal_places=2, allow_null=True)
# Companies
manufacturer = serializers.SerializerMethodField()
@@ -273,9 +255,7 @@ class RideDetailOutputSerializer(serializers.Serializer):
"""Get all approved photos for this ride."""
from apps.rides.models import RidePhoto
photos = RidePhoto.objects.filter(ride=obj, is_approved=True).order_by(
"-is_primary", "-created_at"
)[
photos = RidePhoto.objects.filter(ride=obj, is_approved=True).order_by("-is_primary", "-created_at")[
:10
] # Limit to 10 photos
@@ -285,9 +265,7 @@ class RideDetailOutputSerializer(serializers.Serializer):
"image_url": photo.image.url if photo.image else None,
"image_variants": (
{
"thumbnail": (
f"{photo.image.url}/thumbnail" if photo.image else None
),
"thumbnail": (f"{photo.image.url}/thumbnail" if photo.image else None),
"medium": f"{photo.image.url}/medium" if photo.image else None,
"large": f"{photo.image.url}/large" if photo.image else None,
"public": f"{photo.image.url}/public" if photo.image else None,
@@ -309,9 +287,7 @@ class RideDetailOutputSerializer(serializers.Serializer):
from apps.rides.models import RidePhoto
try:
photo = RidePhoto.objects.filter(
ride=obj, is_primary=True, is_approved=True
).first()
photo = RidePhoto.objects.filter(ride=obj, is_primary=True, is_approved=True).first()
if photo and photo.image:
return {
@@ -356,9 +332,7 @@ class RideDetailOutputSerializer(serializers.Serializer):
try:
latest_photo = (
RidePhoto.objects.filter(
ride=obj, is_approved=True, image__isnull=False
)
RidePhoto.objects.filter(ride=obj, is_approved=True, image__isnull=False)
.order_by("-created_at")
.first()
)
@@ -407,9 +381,7 @@ class RideDetailOutputSerializer(serializers.Serializer):
try:
latest_photo = (
RidePhoto.objects.filter(
ride=obj, is_approved=True, image__isnull=False
)
RidePhoto.objects.filter(ride=obj, is_approved=True, image__isnull=False)
.order_by("-created_at")
.first()
)
@@ -451,7 +423,7 @@ class RideImageSettingsInputSerializer(serializers.Serializer):
# The ride will be validated in the view
return value
except RidePhoto.DoesNotExist:
raise serializers.ValidationError("Photo not found")
raise serializers.ValidationError("Photo not found") from None
return value
def validate_card_image_id(self, value):
@@ -464,7 +436,7 @@ class RideImageSettingsInputSerializer(serializers.Serializer):
# The ride will be validated in the view
return value
except RidePhoto.DoesNotExist:
raise serializers.ValidationError("Photo not found")
raise serializers.ValidationError("Photo not found") from None
return value
@@ -474,9 +446,7 @@ class RideCreateInputSerializer(serializers.Serializer):
name = serializers.CharField(max_length=255)
description = serializers.CharField(allow_blank=True, default="")
category = serializers.ChoiceField(choices=ModelChoices.get_ride_category_choices())
status = serializers.ChoiceField(
choices=ModelChoices.get_ride_status_choices(), default="OPERATING"
)
status = serializers.ChoiceField(choices=ModelChoices.get_ride_status_choices(), default="OPERATING")
# Required park
park_id = serializers.IntegerField()
@@ -490,18 +460,10 @@ class RideCreateInputSerializer(serializers.Serializer):
status_since = serializers.DateField(required=False, allow_null=True)
# Optional specs
min_height_in = serializers.IntegerField(
required=False, allow_null=True, min_value=30, max_value=90
)
max_height_in = serializers.IntegerField(
required=False, allow_null=True, min_value=30, max_value=90
)
capacity_per_hour = serializers.IntegerField(
required=False, allow_null=True, min_value=1
)
ride_duration_seconds = serializers.IntegerField(
required=False, allow_null=True, min_value=1
)
min_height_in = serializers.IntegerField(required=False, allow_null=True, min_value=30, max_value=90)
max_height_in = serializers.IntegerField(required=False, allow_null=True, min_value=30, max_value=90)
capacity_per_hour = serializers.IntegerField(required=False, allow_null=True, min_value=1)
ride_duration_seconds = serializers.IntegerField(required=False, allow_null=True, min_value=1)
# Optional companies
manufacturer_id = serializers.IntegerField(required=False, allow_null=True)
@@ -517,18 +479,14 @@ class RideCreateInputSerializer(serializers.Serializer):
closing_date = attrs.get("closing_date")
if opening_date and closing_date and closing_date < opening_date:
raise serializers.ValidationError(
"Closing date cannot be before opening date"
)
raise serializers.ValidationError("Closing date cannot be before opening date")
# Height validation
min_height = attrs.get("min_height_in")
max_height = attrs.get("max_height_in")
if min_height and max_height and min_height > max_height:
raise serializers.ValidationError(
"Minimum height cannot be greater than maximum height"
)
raise serializers.ValidationError("Minimum height cannot be greater than maximum height")
# Park area validation when park changes
park_id = attrs.get("park_id")
@@ -537,6 +495,7 @@ class RideCreateInputSerializer(serializers.Serializer):
if park_id and park_area_id:
try:
from apps.parks.models import ParkArea
park_area = ParkArea.objects.get(id=park_area_id)
if park_area.park_id != park_id:
raise serializers.ValidationError(
@@ -554,12 +513,8 @@ class RideUpdateInputSerializer(serializers.Serializer):
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(), required=False
)
status = serializers.ChoiceField(
choices=ModelChoices.get_ride_status_choices(), required=False
)
category = serializers.ChoiceField(choices=ModelChoices.get_ride_category_choices(), required=False)
status = serializers.ChoiceField(choices=ModelChoices.get_ride_status_choices(), required=False)
post_closing_status = serializers.ChoiceField(
choices=ModelChoices.get_ride_post_closing_choices(),
required=False,
@@ -576,18 +531,10 @@ class RideUpdateInputSerializer(serializers.Serializer):
status_since = serializers.DateField(required=False, allow_null=True)
# Specs
min_height_in = serializers.IntegerField(
required=False, allow_null=True, min_value=30, max_value=90
)
max_height_in = serializers.IntegerField(
required=False, allow_null=True, min_value=30, max_value=90
)
capacity_per_hour = serializers.IntegerField(
required=False, allow_null=True, min_value=1
)
ride_duration_seconds = serializers.IntegerField(
required=False, allow_null=True, min_value=1
)
min_height_in = serializers.IntegerField(required=False, allow_null=True, min_value=30, max_value=90)
max_height_in = serializers.IntegerField(required=False, allow_null=True, min_value=30, max_value=90)
capacity_per_hour = serializers.IntegerField(required=False, allow_null=True, min_value=1)
ride_duration_seconds = serializers.IntegerField(required=False, allow_null=True, min_value=1)
# Companies
manufacturer_id = serializers.IntegerField(required=False, allow_null=True)
@@ -603,18 +550,14 @@ class RideUpdateInputSerializer(serializers.Serializer):
closing_date = attrs.get("closing_date")
if opening_date and closing_date and closing_date < opening_date:
raise serializers.ValidationError(
"Closing date cannot be before opening date"
)
raise serializers.ValidationError("Closing date cannot be before opening date")
# Height validation
min_height = attrs.get("min_height_in")
max_height = attrs.get("max_height_in")
if min_height and max_height and min_height > max_height:
raise serializers.ValidationError(
"Minimum height cannot be greater than maximum height"
)
raise serializers.ValidationError("Minimum height cannot be greater than maximum height")
return attrs
@@ -626,9 +569,7 @@ class RideFilterInputSerializer(serializers.Serializer):
search = serializers.CharField(required=False, allow_blank=True)
# Category filter
category = serializers.MultipleChoiceField(
choices=ModelChoices.get_ride_category_choices(), required=False
)
category = serializers.MultipleChoiceField(choices=ModelChoices.get_ride_category_choices(), required=False)
# Status filter
status = serializers.MultipleChoiceField(
@@ -707,33 +648,16 @@ class RollerCoasterStatsOutputSerializer(serializers.Serializer):
"""Output serializer for roller coaster statistics."""
id = serializers.IntegerField()
height_ft = serializers.DecimalField(
max_digits=6, decimal_places=2, allow_null=True
)
length_ft = serializers.DecimalField(
max_digits=7, decimal_places=2, allow_null=True
)
speed_mph = serializers.DecimalField(
max_digits=5, decimal_places=2, allow_null=True
)
height_ft = serializers.DecimalField(max_digits=6, decimal_places=2, allow_null=True)
length_ft = serializers.DecimalField(max_digits=7, decimal_places=2, allow_null=True)
speed_mph = serializers.DecimalField(max_digits=5, decimal_places=2, allow_null=True)
inversions = serializers.IntegerField()
ride_time_seconds = serializers.IntegerField(allow_null=True)
track_type = serializers.CharField()
track_material = RichChoiceFieldSerializer(
choice_group="track_materials",
domain="rides"
)
roller_coaster_type = RichChoiceFieldSerializer(
choice_group="coaster_types",
domain="rides"
)
max_drop_height_ft = serializers.DecimalField(
max_digits=6, decimal_places=2, allow_null=True
)
propulsion_system = RichChoiceFieldSerializer(
choice_group="propulsion_systems",
domain="rides"
)
track_material = RichChoiceFieldSerializer(choice_group="track_materials", domain="rides")
roller_coaster_type = RichChoiceFieldSerializer(choice_group="coaster_types", domain="rides")
max_drop_height_ft = serializers.DecimalField(max_digits=6, decimal_places=2, allow_null=True)
propulsion_system = RichChoiceFieldSerializer(choice_group="propulsion_systems", domain="rides")
train_style = serializers.CharField()
trains_count = serializers.IntegerField(allow_null=True)
cars_per_train = serializers.IntegerField(allow_null=True)
@@ -755,30 +679,16 @@ class RollerCoasterStatsCreateInputSerializer(serializers.Serializer):
"""Input serializer for creating roller coaster statistics."""
ride_id = serializers.IntegerField()
height_ft = serializers.DecimalField(
max_digits=6, decimal_places=2, required=False, allow_null=True
)
length_ft = serializers.DecimalField(
max_digits=7, decimal_places=2, required=False, allow_null=True
)
speed_mph = serializers.DecimalField(
max_digits=5, decimal_places=2, required=False, allow_null=True
)
height_ft = serializers.DecimalField(max_digits=6, decimal_places=2, required=False, allow_null=True)
length_ft = serializers.DecimalField(max_digits=7, decimal_places=2, required=False, allow_null=True)
speed_mph = serializers.DecimalField(max_digits=5, decimal_places=2, required=False, allow_null=True)
inversions = serializers.IntegerField(default=0)
ride_time_seconds = serializers.IntegerField(required=False, allow_null=True)
track_type = serializers.CharField(max_length=255, allow_blank=True, default="")
track_material = serializers.ChoiceField(
choices=ModelChoices.get_coaster_track_choices(), default="STEEL"
)
roller_coaster_type = serializers.ChoiceField(
choices=ModelChoices.get_coaster_type_choices(), default="SITDOWN"
)
max_drop_height_ft = serializers.DecimalField(
max_digits=6, decimal_places=2, required=False, allow_null=True
)
propulsion_system = serializers.ChoiceField(
choices=ModelChoices.get_propulsion_system_choices(), default="CHAIN"
)
track_material = serializers.ChoiceField(choices=ModelChoices.get_coaster_track_choices(), default="STEEL")
roller_coaster_type = serializers.ChoiceField(choices=ModelChoices.get_coaster_type_choices(), default="SITDOWN")
max_drop_height_ft = serializers.DecimalField(max_digits=6, decimal_places=2, required=False, allow_null=True)
propulsion_system = serializers.ChoiceField(choices=ModelChoices.get_propulsion_system_choices(), default="CHAIN")
train_style = serializers.CharField(max_length=255, allow_blank=True, default="")
trains_count = serializers.IntegerField(required=False, allow_null=True)
cars_per_train = serializers.IntegerField(required=False, allow_null=True)
@@ -788,33 +698,17 @@ class RollerCoasterStatsCreateInputSerializer(serializers.Serializer):
class RollerCoasterStatsUpdateInputSerializer(serializers.Serializer):
"""Input serializer for updating roller coaster statistics."""
height_ft = serializers.DecimalField(
max_digits=6, decimal_places=2, required=False, allow_null=True
)
length_ft = serializers.DecimalField(
max_digits=7, decimal_places=2, required=False, allow_null=True
)
speed_mph = serializers.DecimalField(
max_digits=5, decimal_places=2, required=False, allow_null=True
)
height_ft = serializers.DecimalField(max_digits=6, decimal_places=2, required=False, allow_null=True)
length_ft = serializers.DecimalField(max_digits=7, decimal_places=2, required=False, allow_null=True)
speed_mph = serializers.DecimalField(max_digits=5, decimal_places=2, required=False, allow_null=True)
inversions = serializers.IntegerField(required=False)
ride_time_seconds = serializers.IntegerField(required=False, allow_null=True)
track_type = serializers.CharField(max_length=255, allow_blank=True, required=False)
track_material = serializers.ChoiceField(
choices=ModelChoices.get_coaster_track_choices(), required=False
)
roller_coaster_type = serializers.ChoiceField(
choices=ModelChoices.get_coaster_type_choices(), required=False
)
max_drop_height_ft = serializers.DecimalField(
max_digits=6, decimal_places=2, required=False, allow_null=True
)
propulsion_system = serializers.ChoiceField(
choices=ModelChoices.get_propulsion_system_choices(), required=False
)
train_style = serializers.CharField(
max_length=255, allow_blank=True, required=False
)
track_material = serializers.ChoiceField(choices=ModelChoices.get_coaster_track_choices(), required=False)
roller_coaster_type = serializers.ChoiceField(choices=ModelChoices.get_coaster_type_choices(), required=False)
max_drop_height_ft = serializers.DecimalField(max_digits=6, decimal_places=2, required=False, allow_null=True)
propulsion_system = serializers.ChoiceField(choices=ModelChoices.get_propulsion_system_choices(), required=False)
train_style = serializers.CharField(max_length=255, allow_blank=True, required=False)
trains_count = serializers.IntegerField(required=False, allow_null=True)
cars_per_train = serializers.IntegerField(required=False, allow_null=True)
seats_per_car = serializers.IntegerField(required=False, allow_null=True)