mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2026-03-31 11:28:25 -04:00
feat: Implement initial schema and add various API, service, and management command enhancements across the application.
This commit is contained in:
@@ -90,7 +90,6 @@ _ACCOUNTS_SYMBOLS: list[str] = [
|
||||
"UserProfileOutputSerializer",
|
||||
"UserProfileCreateInputSerializer",
|
||||
"UserProfileUpdateInputSerializer",
|
||||
|
||||
"UserOutputSerializer",
|
||||
"LoginInputSerializer",
|
||||
"LoginOutputSerializer",
|
||||
|
||||
@@ -187,6 +187,7 @@ class PublicUserSerializer(serializers.ModelSerializer):
|
||||
Public user serializer for viewing other users' profiles.
|
||||
Only exposes public information.
|
||||
"""
|
||||
|
||||
profile = UserProfileSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
@@ -228,37 +229,21 @@ class UserPreferencesSerializer(serializers.Serializer):
|
||||
"""Serializer for user preferences and settings."""
|
||||
|
||||
theme_preference = RichChoiceFieldSerializer(
|
||||
choice_group="theme_preferences",
|
||||
domain="accounts",
|
||||
help_text="User's theme preference"
|
||||
)
|
||||
email_notifications = serializers.BooleanField(
|
||||
default=True, help_text="Whether to receive email notifications"
|
||||
)
|
||||
push_notifications = serializers.BooleanField(
|
||||
default=False, help_text="Whether to receive push notifications"
|
||||
choice_group="theme_preferences", domain="accounts", help_text="User's theme preference"
|
||||
)
|
||||
email_notifications = serializers.BooleanField(default=True, help_text="Whether to receive email notifications")
|
||||
push_notifications = serializers.BooleanField(default=False, help_text="Whether to receive push notifications")
|
||||
privacy_level = RichChoiceFieldSerializer(
|
||||
choice_group="privacy_levels",
|
||||
domain="accounts",
|
||||
default="public",
|
||||
help_text="Profile visibility level",
|
||||
)
|
||||
show_email = serializers.BooleanField(
|
||||
default=False, help_text="Whether to show email on profile"
|
||||
)
|
||||
show_real_name = serializers.BooleanField(
|
||||
default=True, help_text="Whether to show real name on profile"
|
||||
)
|
||||
show_statistics = serializers.BooleanField(
|
||||
default=True, help_text="Whether to show ride statistics on profile"
|
||||
)
|
||||
allow_friend_requests = serializers.BooleanField(
|
||||
default=True, help_text="Whether to allow friend requests"
|
||||
)
|
||||
allow_messages = serializers.BooleanField(
|
||||
default=True, help_text="Whether to allow direct messages"
|
||||
)
|
||||
show_email = serializers.BooleanField(default=False, help_text="Whether to show email on profile")
|
||||
show_real_name = serializers.BooleanField(default=True, help_text="Whether to show real name on profile")
|
||||
show_statistics = serializers.BooleanField(default=True, help_text="Whether to show ride statistics on profile")
|
||||
allow_friend_requests = serializers.BooleanField(default=True, help_text="Whether to allow friend requests")
|
||||
allow_messages = serializers.BooleanField(default=True, help_text="Whether to allow direct messages")
|
||||
|
||||
|
||||
# === NOTIFICATION SETTINGS SERIALIZERS ===
|
||||
@@ -363,39 +348,17 @@ class PrivacySettingsSerializer(serializers.Serializer):
|
||||
default="public",
|
||||
help_text="Overall profile visibility",
|
||||
)
|
||||
show_email = serializers.BooleanField(
|
||||
default=False, help_text="Show email address on profile"
|
||||
)
|
||||
show_real_name = serializers.BooleanField(
|
||||
default=True, help_text="Show real name on profile"
|
||||
)
|
||||
show_join_date = serializers.BooleanField(
|
||||
default=True, help_text="Show join date on profile"
|
||||
)
|
||||
show_statistics = serializers.BooleanField(
|
||||
default=True, help_text="Show ride statistics on profile"
|
||||
)
|
||||
show_reviews = serializers.BooleanField(
|
||||
default=True, help_text="Show reviews on profile"
|
||||
)
|
||||
show_photos = serializers.BooleanField(
|
||||
default=True, help_text="Show uploaded photos on profile"
|
||||
)
|
||||
show_top_lists = serializers.BooleanField(
|
||||
default=True, help_text="Show top lists on profile"
|
||||
)
|
||||
allow_friend_requests = serializers.BooleanField(
|
||||
default=True, help_text="Allow others to send friend requests"
|
||||
)
|
||||
allow_messages = serializers.BooleanField(
|
||||
default=True, help_text="Allow others to send direct messages"
|
||||
)
|
||||
allow_profile_comments = serializers.BooleanField(
|
||||
default=False, help_text="Allow others to comment on profile"
|
||||
)
|
||||
search_visibility = serializers.BooleanField(
|
||||
default=True, help_text="Allow profile to appear in search results"
|
||||
)
|
||||
show_email = serializers.BooleanField(default=False, help_text="Show email address on profile")
|
||||
show_real_name = serializers.BooleanField(default=True, help_text="Show real name on profile")
|
||||
show_join_date = serializers.BooleanField(default=True, help_text="Show join date on profile")
|
||||
show_statistics = serializers.BooleanField(default=True, help_text="Show ride statistics on profile")
|
||||
show_reviews = serializers.BooleanField(default=True, help_text="Show reviews on profile")
|
||||
show_photos = serializers.BooleanField(default=True, help_text="Show uploaded photos on profile")
|
||||
show_top_lists = serializers.BooleanField(default=True, help_text="Show top lists on profile")
|
||||
allow_friend_requests = serializers.BooleanField(default=True, help_text="Allow others to send friend requests")
|
||||
allow_messages = serializers.BooleanField(default=True, help_text="Allow others to send direct messages")
|
||||
allow_profile_comments = serializers.BooleanField(default=False, help_text="Allow others to comment on profile")
|
||||
search_visibility = serializers.BooleanField(default=True, help_text="Allow profile to appear in search results")
|
||||
activity_visibility = RichChoiceFieldSerializer(
|
||||
choice_group="privacy_levels",
|
||||
domain="accounts",
|
||||
@@ -431,21 +394,13 @@ class SecuritySettingsSerializer(serializers.Serializer):
|
||||
two_factor_enabled = serializers.BooleanField(
|
||||
default=False, help_text="Whether two-factor authentication is enabled"
|
||||
)
|
||||
login_notifications = serializers.BooleanField(
|
||||
default=True, help_text="Send notifications for new logins"
|
||||
)
|
||||
login_notifications = serializers.BooleanField(default=True, help_text="Send notifications for new logins")
|
||||
session_timeout = serializers.IntegerField(
|
||||
default=30, min_value=5, max_value=180, help_text="Session timeout in days"
|
||||
)
|
||||
require_password_change = serializers.BooleanField(
|
||||
default=False, help_text="Whether password change is required"
|
||||
)
|
||||
last_password_change = serializers.DateTimeField(
|
||||
read_only=True, help_text="When password was last changed"
|
||||
)
|
||||
active_sessions = serializers.IntegerField(
|
||||
read_only=True, help_text="Number of active sessions"
|
||||
)
|
||||
require_password_change = serializers.BooleanField(default=False, help_text="Whether password change is required")
|
||||
last_password_change = serializers.DateTimeField(read_only=True, help_text="When password was last changed")
|
||||
active_sessions = serializers.IntegerField(read_only=True, help_text="Number of active sessions")
|
||||
login_history_retention = serializers.IntegerField(
|
||||
default=90,
|
||||
min_value=30,
|
||||
@@ -699,7 +654,7 @@ class ThemePreferenceSerializer(serializers.ModelSerializer):
|
||||
"id": 1,
|
||||
"notification_type": "submission_approved",
|
||||
"title": "Your submission has been approved!",
|
||||
"message": "Your photo submission for Cedar Point has been approved and is now live on the site.",
|
||||
"detail": "Your photo submission for Cedar Point has been approved and is now live on the site.",
|
||||
"priority": "normal",
|
||||
"is_read": False,
|
||||
"read_at": None,
|
||||
@@ -866,15 +821,11 @@ class MarkNotificationsReadSerializer(serializers.Serializer):
|
||||
def validate_notification_ids(self, value):
|
||||
"""Validate that all notification IDs belong to the requesting user."""
|
||||
user = self.context["request"].user
|
||||
valid_ids = UserNotification.objects.filter(
|
||||
id__in=value, user=user
|
||||
).values_list("id", flat=True)
|
||||
valid_ids = UserNotification.objects.filter(id__in=value, user=user).values_list("id", flat=True)
|
||||
|
||||
invalid_ids = set(value) - set(valid_ids)
|
||||
if invalid_ids:
|
||||
raise serializers.ValidationError(
|
||||
f"Invalid notification IDs: {list(invalid_ids)}"
|
||||
)
|
||||
raise serializers.ValidationError(f"Invalid notification IDs: {list(invalid_ids)}")
|
||||
|
||||
return value
|
||||
|
||||
@@ -901,9 +852,8 @@ class AvatarUploadSerializer(serializers.Serializer):
|
||||
raise serializers.ValidationError("No file provided")
|
||||
|
||||
# Check file size constraints (max 10MB for Cloudflare Images)
|
||||
if hasattr(value, 'size') and value.size > 10 * 1024 * 1024:
|
||||
raise serializers.ValidationError(
|
||||
"Image file too large. Maximum size is 10MB.")
|
||||
if hasattr(value, "size") and value.size > 10 * 1024 * 1024:
|
||||
raise serializers.ValidationError("Image file too large. Maximum size is 10MB.")
|
||||
|
||||
# Try to validate with PIL
|
||||
try:
|
||||
@@ -926,13 +876,13 @@ class AvatarUploadSerializer(serializers.Serializer):
|
||||
|
||||
# Check image dimensions (max 12,000x12,000 for Cloudflare Images)
|
||||
if image.size[0] > 12000 or image.size[1] > 12000:
|
||||
raise serializers.ValidationError(
|
||||
"Image dimensions too large. Maximum is 12,000x12,000 pixels.")
|
||||
raise serializers.ValidationError("Image dimensions too large. Maximum is 12,000x12,000 pixels.")
|
||||
|
||||
# Check if it's a supported format
|
||||
if image.format not in ['JPEG', 'PNG', 'GIF', 'WEBP']:
|
||||
if image.format not in ["JPEG", "PNG", "GIF", "WEBP"]:
|
||||
raise serializers.ValidationError(
|
||||
f"Unsupported image format: {image.format}. Supported formats: JPEG, PNG, GIF, WebP.")
|
||||
f"Unsupported image format: {image.format}. Supported formats: JPEG, PNG, GIF, WebP."
|
||||
)
|
||||
|
||||
except serializers.ValidationError:
|
||||
raise # Re-raise validation errors
|
||||
|
||||
@@ -97,7 +97,7 @@ class LoginInputSerializer(serializers.Serializer):
|
||||
password=password,
|
||||
)
|
||||
|
||||
if not user:
|
||||
if not user: # noqa: SIM102
|
||||
# Try email-based authentication if username failed
|
||||
if "@" in username:
|
||||
try:
|
||||
@@ -138,7 +138,7 @@ class LoginInputSerializer(serializers.Serializer):
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
},
|
||||
"message": "Login successful",
|
||||
"detail": "Login successful",
|
||||
},
|
||||
)
|
||||
]
|
||||
@@ -213,7 +213,7 @@ class SignupInputSerializer(serializers.ModelSerializer):
|
||||
try:
|
||||
validate_password(value)
|
||||
except DjangoValidationError as e:
|
||||
raise serializers.ValidationError(list(e.messages))
|
||||
raise serializers.ValidationError(list(e.messages)) from None
|
||||
return value
|
||||
|
||||
def validate(self, attrs):
|
||||
@@ -253,7 +253,7 @@ class SignupInputSerializer(serializers.ModelSerializer):
|
||||
"first_name": "Jane",
|
||||
"last_name": "Smith",
|
||||
},
|
||||
"message": "Registration successful",
|
||||
"detail": "Registration successful",
|
||||
},
|
||||
)
|
||||
]
|
||||
@@ -276,7 +276,7 @@ class SignupOutputSerializer(serializers.Serializer):
|
||||
summary="Example logout response",
|
||||
description="Successful logout response",
|
||||
value={
|
||||
"message": "Logout successful",
|
||||
"detail": "Logout successful",
|
||||
},
|
||||
)
|
||||
]
|
||||
@@ -318,9 +318,9 @@ class PasswordResetInputSerializer(serializers.Serializer):
|
||||
"""Send password reset email."""
|
||||
email = self.validated_data["email"] # type: ignore[index]
|
||||
try:
|
||||
_user = UserModel.objects.get(email=email)
|
||||
# Check if email exists (but don't reveal the result for security)
|
||||
UserModel.objects.get(email=email)
|
||||
# Here you would typically send a password reset email
|
||||
# For now, we'll just pass
|
||||
pass
|
||||
except UserModel.DoesNotExist:
|
||||
# Don't reveal if email exists for security
|
||||
@@ -393,7 +393,7 @@ class PasswordChangeInputSerializer(serializers.Serializer):
|
||||
try:
|
||||
validate_password(value, user=self.context["request"].user)
|
||||
except DjangoValidationError as e:
|
||||
raise serializers.ValidationError(list(e.messages))
|
||||
raise serializers.ValidationError(list(e.messages)) from None
|
||||
return value
|
||||
|
||||
def validate(self, attrs):
|
||||
@@ -492,6 +492,4 @@ class AuthStatusOutputSerializer(serializers.Serializer):
|
||||
"""Output serializer for authentication status."""
|
||||
|
||||
authenticated = serializers.BooleanField(help_text="Whether user is authenticated")
|
||||
user = UserOutputSerializer(
|
||||
allow_null=True, help_text="User information if authenticated"
|
||||
)
|
||||
user = UserOutputSerializer(allow_null=True, help_text="User information if authenticated")
|
||||
|
||||
@@ -112,10 +112,7 @@ class RideModelDetailOutputSerializer(serializers.Serializer):
|
||||
id = serializers.IntegerField()
|
||||
name = serializers.CharField()
|
||||
description = serializers.CharField()
|
||||
category = RichChoiceFieldSerializer(
|
||||
choice_group="categories",
|
||||
domain="rides"
|
||||
)
|
||||
category = RichChoiceFieldSerializer(choice_group="categories", domain="rides")
|
||||
|
||||
# Manufacturer info
|
||||
manufacturer = serializers.SerializerMethodField()
|
||||
|
||||
@@ -99,9 +99,7 @@ class ParkHistoryOutputSerializer(serializers.Serializer):
|
||||
"slug": park.slug,
|
||||
"status": park.status,
|
||||
"opening_date": (
|
||||
park.opening_date.isoformat()
|
||||
if hasattr(park, "opening_date") and park.opening_date
|
||||
else None
|
||||
park.opening_date.isoformat() if hasattr(park, "opening_date") and park.opening_date else None
|
||||
),
|
||||
"coaster_count": getattr(park, "coaster_count", 0),
|
||||
"ride_count": getattr(park, "ride_count", 0),
|
||||
@@ -143,9 +141,7 @@ class RideHistoryOutputSerializer(serializers.Serializer):
|
||||
"park_name": ride.park.name if hasattr(ride, "park") else None,
|
||||
"status": getattr(ride, "status", "UNKNOWN"),
|
||||
"opening_date": (
|
||||
ride.opening_date.isoformat()
|
||||
if hasattr(ride, "opening_date") and ride.opening_date
|
||||
else None
|
||||
ride.opening_date.isoformat() if hasattr(ride, "opening_date") and ride.opening_date else None
|
||||
),
|
||||
"ride_type": getattr(ride, "ride_type", "Unknown"),
|
||||
}
|
||||
|
||||
@@ -79,16 +79,12 @@ class MapLocationSerializer(serializers.Serializer):
|
||||
return {
|
||||
"coaster_count": obj.coaster_count or 0,
|
||||
"ride_count": obj.ride_count or 0,
|
||||
"average_rating": (
|
||||
float(obj.average_rating) if obj.average_rating else None
|
||||
),
|
||||
"average_rating": (float(obj.average_rating) if obj.average_rating else None),
|
||||
}
|
||||
elif obj._meta.model_name == "ride":
|
||||
return {
|
||||
"category": obj.get_category_display() if obj.category else None,
|
||||
"average_rating": (
|
||||
float(obj.average_rating) if obj.average_rating else None
|
||||
),
|
||||
"average_rating": (float(obj.average_rating) if obj.average_rating else None),
|
||||
"park_name": obj.park.name if obj.park else None,
|
||||
}
|
||||
return {}
|
||||
@@ -339,24 +335,16 @@ class MapLocationDetailSerializer(serializers.Serializer):
|
||||
return {
|
||||
"coaster_count": obj.coaster_count or 0,
|
||||
"ride_count": obj.ride_count or 0,
|
||||
"average_rating": (
|
||||
float(obj.average_rating) if obj.average_rating else None
|
||||
),
|
||||
"average_rating": (float(obj.average_rating) if obj.average_rating else None),
|
||||
"size_acres": float(obj.size_acres) if obj.size_acres else None,
|
||||
"opening_date": (
|
||||
obj.opening_date.isoformat() if obj.opening_date else None
|
||||
),
|
||||
"opening_date": (obj.opening_date.isoformat() if obj.opening_date else None),
|
||||
}
|
||||
elif obj._meta.model_name == "ride":
|
||||
return {
|
||||
"category": obj.get_category_display() if obj.category else None,
|
||||
"average_rating": (
|
||||
float(obj.average_rating) if obj.average_rating else None
|
||||
),
|
||||
"average_rating": (float(obj.average_rating) if obj.average_rating else None),
|
||||
"park_name": obj.park.name if obj.park else None,
|
||||
"opening_date": (
|
||||
obj.opening_date.isoformat() if obj.opening_date else None
|
||||
),
|
||||
"opening_date": (obj.opening_date.isoformat() if obj.opening_date else None),
|
||||
"manufacturer": obj.manufacturer.name if obj.manufacturer else None,
|
||||
}
|
||||
return {}
|
||||
@@ -382,9 +370,7 @@ class MapBoundsInputSerializer(serializers.Serializer):
|
||||
def validate(self, attrs):
|
||||
"""Validate that bounds make geographic sense."""
|
||||
if attrs["north"] <= attrs["south"]:
|
||||
raise serializers.ValidationError(
|
||||
"North bound must be greater than south bound"
|
||||
)
|
||||
raise serializers.ValidationError("North bound must be greater than south bound")
|
||||
|
||||
# Handle longitude wraparound (e.g., crossing the international date line)
|
||||
# For now, we'll require west < east for simplicity
|
||||
|
||||
@@ -31,9 +31,7 @@ class PhotoUploadInputSerializer(serializers.Serializer):
|
||||
allow_blank=True,
|
||||
help_text="Alt text for accessibility",
|
||||
)
|
||||
is_primary = serializers.BooleanField(
|
||||
default=False, help_text="Whether this should be the primary photo"
|
||||
)
|
||||
is_primary = serializers.BooleanField(default=False, help_text="Whether this should be the primary photo")
|
||||
|
||||
|
||||
@extend_schema_serializer(
|
||||
@@ -89,9 +87,7 @@ class PhotoDetailOutputSerializer(serializers.Serializer):
|
||||
return {
|
||||
"id": obj.uploaded_by.id,
|
||||
"username": obj.uploaded_by.username,
|
||||
"display_name": getattr(
|
||||
obj.uploaded_by, "get_display_name", lambda: obj.uploaded_by.username
|
||||
)(),
|
||||
"display_name": getattr(obj.uploaded_by, "get_display_name", lambda: obj.uploaded_by.username)(),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -24,12 +24,8 @@ class ParkStatsOutputSerializer(serializers.Serializer):
|
||||
under_construction = serializers.IntegerField()
|
||||
|
||||
# Averages
|
||||
average_rating = serializers.DecimalField(
|
||||
max_digits=3, decimal_places=2, allow_null=True
|
||||
)
|
||||
average_coaster_count = serializers.DecimalField(
|
||||
max_digits=5, decimal_places=2, allow_null=True
|
||||
)
|
||||
average_rating = serializers.DecimalField(max_digits=3, decimal_places=2, allow_null=True)
|
||||
average_coaster_count = serializers.DecimalField(max_digits=5, decimal_places=2, allow_null=True)
|
||||
|
||||
# Top countries
|
||||
top_countries = serializers.ListField(child=serializers.DictField())
|
||||
@@ -50,12 +46,8 @@ class RideStatsOutputSerializer(serializers.Serializer):
|
||||
rides_by_category = serializers.DictField()
|
||||
|
||||
# Averages
|
||||
average_rating = serializers.DecimalField(
|
||||
max_digits=3, decimal_places=2, allow_null=True
|
||||
)
|
||||
average_capacity = serializers.DecimalField(
|
||||
max_digits=8, decimal_places=2, allow_null=True
|
||||
)
|
||||
average_rating = serializers.DecimalField(max_digits=3, decimal_places=2, allow_null=True)
|
||||
average_capacity = serializers.DecimalField(max_digits=8, decimal_places=2, allow_null=True)
|
||||
|
||||
# Top manufacturers
|
||||
top_manufacturers = serializers.ListField(child=serializers.DictField())
|
||||
@@ -91,10 +83,7 @@ class ParkReviewOutputSerializer(serializers.Serializer):
|
||||
class HealthCheckOutputSerializer(serializers.Serializer):
|
||||
"""Output serializer for health check responses."""
|
||||
|
||||
status = RichChoiceFieldSerializer(
|
||||
choice_group="health_statuses",
|
||||
domain="core"
|
||||
)
|
||||
status = RichChoiceFieldSerializer(choice_group="health_statuses", domain="core")
|
||||
timestamp = serializers.DateTimeField()
|
||||
version = serializers.CharField()
|
||||
environment = serializers.CharField()
|
||||
@@ -115,9 +104,6 @@ class PerformanceMetricsOutputSerializer(serializers.Serializer):
|
||||
class SimpleHealthOutputSerializer(serializers.Serializer):
|
||||
"""Output serializer for simple health check."""
|
||||
|
||||
status = RichChoiceFieldSerializer(
|
||||
choice_group="simple_health_statuses",
|
||||
domain="core"
|
||||
)
|
||||
status = RichChoiceFieldSerializer(choice_group="simple_health_statuses", domain="core")
|
||||
timestamp = serializers.DateTimeField()
|
||||
error = serializers.CharField(required=False)
|
||||
|
||||
@@ -29,14 +29,10 @@ from apps.parks.models.reviews import ParkReview
|
||||
"user": {
|
||||
"username": "park_fan",
|
||||
"display_name": "Park Fan",
|
||||
"avatar_url": "https://example.com/avatar.jpg"
|
||||
"avatar_url": "https://example.com/avatar.jpg",
|
||||
},
|
||||
"park": {
|
||||
"id": 101,
|
||||
"name": "Cedar Point",
|
||||
"slug": "cedar-point"
|
||||
}
|
||||
}
|
||||
"park": {"id": 101, "name": "Cedar Point", "slug": "cedar-point"},
|
||||
},
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -145,8 +141,7 @@ class ParkReviewStatsOutputSerializer(serializers.Serializer):
|
||||
pending_reviews = serializers.IntegerField()
|
||||
average_rating = serializers.FloatField(allow_null=True)
|
||||
rating_distribution = serializers.DictField(
|
||||
child=serializers.IntegerField(),
|
||||
help_text="Count of reviews by rating (1-10)"
|
||||
child=serializers.IntegerField(), help_text="Count of reviews by rating (1-10)"
|
||||
)
|
||||
recent_reviews = serializers.IntegerField()
|
||||
|
||||
@@ -154,20 +149,15 @@ class ParkReviewStatsOutputSerializer(serializers.Serializer):
|
||||
class ParkReviewModerationInputSerializer(serializers.Serializer):
|
||||
"""Input serializer for review moderation operations."""
|
||||
|
||||
review_ids = serializers.ListField(
|
||||
child=serializers.IntegerField(),
|
||||
help_text="List of review IDs to moderate"
|
||||
)
|
||||
review_ids = serializers.ListField(child=serializers.IntegerField(), help_text="List of review IDs to moderate")
|
||||
action = serializers.ChoiceField(
|
||||
choices=[
|
||||
("publish", "Publish"),
|
||||
("unpublish", "Unpublish"),
|
||||
("delete", "Delete"),
|
||||
],
|
||||
help_text="Moderation action to perform"
|
||||
help_text="Moderation action to perform",
|
||||
)
|
||||
moderation_notes = serializers.CharField(
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
help_text="Optional notes about the moderation action"
|
||||
required=False, allow_blank=True, help_text="Optional notes about the moderation action"
|
||||
)
|
||||
|
||||
@@ -52,16 +52,11 @@ class ParkListOutputSerializer(serializers.Serializer):
|
||||
id = serializers.IntegerField()
|
||||
name = serializers.CharField()
|
||||
slug = serializers.CharField()
|
||||
status = RichChoiceFieldSerializer(
|
||||
choice_group="statuses",
|
||||
domain="parks"
|
||||
)
|
||||
status = RichChoiceFieldSerializer(choice_group="statuses", domain="parks")
|
||||
description = serializers.CharField()
|
||||
|
||||
# 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)
|
||||
coaster_count = serializers.IntegerField(allow_null=True)
|
||||
ride_count = serializers.IntegerField(allow_null=True)
|
||||
|
||||
@@ -145,25 +140,18 @@ class ParkDetailOutputSerializer(serializers.Serializer):
|
||||
id = serializers.IntegerField()
|
||||
name = serializers.CharField()
|
||||
slug = serializers.CharField()
|
||||
status = RichChoiceFieldSerializer(
|
||||
choice_group="statuses",
|
||||
domain="parks"
|
||||
)
|
||||
status = RichChoiceFieldSerializer(choice_group="statuses", domain="parks")
|
||||
description = serializers.CharField()
|
||||
|
||||
# Details
|
||||
opening_date = serializers.DateField(allow_null=True)
|
||||
closing_date = serializers.DateField(allow_null=True)
|
||||
operating_season = serializers.CharField()
|
||||
size_acres = serializers.DecimalField(
|
||||
max_digits=10, decimal_places=2, allow_null=True
|
||||
)
|
||||
size_acres = serializers.DecimalField(max_digits=10, decimal_places=2, allow_null=True)
|
||||
website = serializers.URLField()
|
||||
|
||||
# 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)
|
||||
coaster_count = serializers.IntegerField(allow_null=True)
|
||||
ride_count = serializers.IntegerField(allow_null=True)
|
||||
|
||||
@@ -211,9 +199,7 @@ class ParkDetailOutputSerializer(serializers.Serializer):
|
||||
"""Get all approved photos for this park."""
|
||||
from apps.parks.models import ParkPhoto
|
||||
|
||||
photos = ParkPhoto.objects.filter(park=obj, is_approved=True).order_by(
|
||||
"-is_primary", "-created_at"
|
||||
)[
|
||||
photos = ParkPhoto.objects.filter(park=obj, is_approved=True).order_by("-is_primary", "-created_at")[
|
||||
:10
|
||||
] # Limit to 10 photos
|
||||
|
||||
@@ -228,7 +214,9 @@ class ParkDetailOutputSerializer(serializers.Serializer):
|
||||
"public": MediaURLService.get_cloudflare_url_with_fallback(photo.image, "public"),
|
||||
},
|
||||
"friendly_urls": {
|
||||
"thumbnail": MediaURLService.generate_park_photo_url(obj.slug, photo.caption, photo.pk, "thumbnail"),
|
||||
"thumbnail": MediaURLService.generate_park_photo_url(
|
||||
obj.slug, photo.caption, photo.pk, "thumbnail"
|
||||
),
|
||||
"medium": MediaURLService.generate_park_photo_url(obj.slug, photo.caption, photo.pk, "medium"),
|
||||
"large": MediaURLService.generate_park_photo_url(obj.slug, photo.caption, photo.pk, "large"),
|
||||
"public": MediaURLService.generate_park_photo_url(obj.slug, photo.caption, photo.pk, "public"),
|
||||
@@ -246,9 +234,7 @@ class ParkDetailOutputSerializer(serializers.Serializer):
|
||||
from apps.parks.models import ParkPhoto
|
||||
|
||||
try:
|
||||
photo = ParkPhoto.objects.filter(
|
||||
park=obj, is_primary=True, is_approved=True
|
||||
).first()
|
||||
photo = ParkPhoto.objects.filter(park=obj, is_primary=True, is_approved=True).first()
|
||||
|
||||
if photo and photo.image:
|
||||
return {
|
||||
@@ -261,7 +247,9 @@ class ParkDetailOutputSerializer(serializers.Serializer):
|
||||
"public": MediaURLService.get_cloudflare_url_with_fallback(photo.image, "public"),
|
||||
},
|
||||
"friendly_urls": {
|
||||
"thumbnail": MediaURLService.generate_park_photo_url(obj.slug, photo.caption, photo.pk, "thumbnail"),
|
||||
"thumbnail": MediaURLService.generate_park_photo_url(
|
||||
obj.slug, photo.caption, photo.pk, "thumbnail"
|
||||
),
|
||||
"medium": MediaURLService.generate_park_photo_url(obj.slug, photo.caption, photo.pk, "medium"),
|
||||
"large": MediaURLService.generate_park_photo_url(obj.slug, photo.caption, photo.pk, "large"),
|
||||
"public": MediaURLService.generate_park_photo_url(obj.slug, photo.caption, photo.pk, "public"),
|
||||
@@ -289,10 +277,18 @@ class ParkDetailOutputSerializer(serializers.Serializer):
|
||||
"public": MediaURLService.get_cloudflare_url_with_fallback(obj.banner_image.image, "public"),
|
||||
},
|
||||
"friendly_urls": {
|
||||
"thumbnail": MediaURLService.generate_park_photo_url(obj.slug, obj.banner_image.caption, obj.banner_image.pk, "thumbnail"),
|
||||
"medium": MediaURLService.generate_park_photo_url(obj.slug, obj.banner_image.caption, obj.banner_image.pk, "medium"),
|
||||
"large": MediaURLService.generate_park_photo_url(obj.slug, obj.banner_image.caption, obj.banner_image.pk, "large"),
|
||||
"public": MediaURLService.generate_park_photo_url(obj.slug, obj.banner_image.caption, obj.banner_image.pk, "public"),
|
||||
"thumbnail": MediaURLService.generate_park_photo_url(
|
||||
obj.slug, obj.banner_image.caption, obj.banner_image.pk, "thumbnail"
|
||||
),
|
||||
"medium": MediaURLService.generate_park_photo_url(
|
||||
obj.slug, obj.banner_image.caption, obj.banner_image.pk, "medium"
|
||||
),
|
||||
"large": MediaURLService.generate_park_photo_url(
|
||||
obj.slug, obj.banner_image.caption, obj.banner_image.pk, "large"
|
||||
),
|
||||
"public": MediaURLService.generate_park_photo_url(
|
||||
obj.slug, obj.banner_image.caption, obj.banner_image.pk, "public"
|
||||
),
|
||||
},
|
||||
"caption": obj.banner_image.caption,
|
||||
"alt_text": obj.banner_image.alt_text,
|
||||
@@ -303,9 +299,7 @@ class ParkDetailOutputSerializer(serializers.Serializer):
|
||||
|
||||
try:
|
||||
latest_photo = (
|
||||
ParkPhoto.objects.filter(
|
||||
park=obj, is_approved=True, image__isnull=False
|
||||
)
|
||||
ParkPhoto.objects.filter(park=obj, is_approved=True, image__isnull=False)
|
||||
.order_by("-created_at")
|
||||
.first()
|
||||
)
|
||||
@@ -321,10 +315,18 @@ class ParkDetailOutputSerializer(serializers.Serializer):
|
||||
"public": MediaURLService.get_cloudflare_url_with_fallback(latest_photo.image, "public"),
|
||||
},
|
||||
"friendly_urls": {
|
||||
"thumbnail": MediaURLService.generate_park_photo_url(obj.slug, latest_photo.caption, latest_photo.pk, "thumbnail"),
|
||||
"medium": MediaURLService.generate_park_photo_url(obj.slug, latest_photo.caption, latest_photo.pk, "medium"),
|
||||
"large": MediaURLService.generate_park_photo_url(obj.slug, latest_photo.caption, latest_photo.pk, "large"),
|
||||
"public": MediaURLService.generate_park_photo_url(obj.slug, latest_photo.caption, latest_photo.pk, "public"),
|
||||
"thumbnail": MediaURLService.generate_park_photo_url(
|
||||
obj.slug, latest_photo.caption, latest_photo.pk, "thumbnail"
|
||||
),
|
||||
"medium": MediaURLService.generate_park_photo_url(
|
||||
obj.slug, latest_photo.caption, latest_photo.pk, "medium"
|
||||
),
|
||||
"large": MediaURLService.generate_park_photo_url(
|
||||
obj.slug, latest_photo.caption, latest_photo.pk, "large"
|
||||
),
|
||||
"public": MediaURLService.generate_park_photo_url(
|
||||
obj.slug, latest_photo.caption, latest_photo.pk, "public"
|
||||
),
|
||||
},
|
||||
"caption": latest_photo.caption,
|
||||
"alt_text": latest_photo.alt_text,
|
||||
@@ -350,10 +352,18 @@ class ParkDetailOutputSerializer(serializers.Serializer):
|
||||
"public": MediaURLService.get_cloudflare_url_with_fallback(obj.card_image.image, "public"),
|
||||
},
|
||||
"friendly_urls": {
|
||||
"thumbnail": MediaURLService.generate_park_photo_url(obj.slug, obj.card_image.caption, obj.card_image.pk, "thumbnail"),
|
||||
"medium": MediaURLService.generate_park_photo_url(obj.slug, obj.card_image.caption, obj.card_image.pk, "medium"),
|
||||
"large": MediaURLService.generate_park_photo_url(obj.slug, obj.card_image.caption, obj.card_image.pk, "large"),
|
||||
"public": MediaURLService.generate_park_photo_url(obj.slug, obj.card_image.caption, obj.card_image.pk, "public"),
|
||||
"thumbnail": MediaURLService.generate_park_photo_url(
|
||||
obj.slug, obj.card_image.caption, obj.card_image.pk, "thumbnail"
|
||||
),
|
||||
"medium": MediaURLService.generate_park_photo_url(
|
||||
obj.slug, obj.card_image.caption, obj.card_image.pk, "medium"
|
||||
),
|
||||
"large": MediaURLService.generate_park_photo_url(
|
||||
obj.slug, obj.card_image.caption, obj.card_image.pk, "large"
|
||||
),
|
||||
"public": MediaURLService.generate_park_photo_url(
|
||||
obj.slug, obj.card_image.caption, obj.card_image.pk, "public"
|
||||
),
|
||||
},
|
||||
"caption": obj.card_image.caption,
|
||||
"alt_text": obj.card_image.alt_text,
|
||||
@@ -364,9 +374,7 @@ class ParkDetailOutputSerializer(serializers.Serializer):
|
||||
|
||||
try:
|
||||
latest_photo = (
|
||||
ParkPhoto.objects.filter(
|
||||
park=obj, is_approved=True, image__isnull=False
|
||||
)
|
||||
ParkPhoto.objects.filter(park=obj, is_approved=True, image__isnull=False)
|
||||
.order_by("-created_at")
|
||||
.first()
|
||||
)
|
||||
@@ -382,10 +390,18 @@ class ParkDetailOutputSerializer(serializers.Serializer):
|
||||
"public": MediaURLService.get_cloudflare_url_with_fallback(latest_photo.image, "public"),
|
||||
},
|
||||
"friendly_urls": {
|
||||
"thumbnail": MediaURLService.generate_park_photo_url(obj.slug, latest_photo.caption, latest_photo.pk, "thumbnail"),
|
||||
"medium": MediaURLService.generate_park_photo_url(obj.slug, latest_photo.caption, latest_photo.pk, "medium"),
|
||||
"large": MediaURLService.generate_park_photo_url(obj.slug, latest_photo.caption, latest_photo.pk, "large"),
|
||||
"public": MediaURLService.generate_park_photo_url(obj.slug, latest_photo.caption, latest_photo.pk, "public"),
|
||||
"thumbnail": MediaURLService.generate_park_photo_url(
|
||||
obj.slug, latest_photo.caption, latest_photo.pk, "thumbnail"
|
||||
),
|
||||
"medium": MediaURLService.generate_park_photo_url(
|
||||
obj.slug, latest_photo.caption, latest_photo.pk, "medium"
|
||||
),
|
||||
"large": MediaURLService.generate_park_photo_url(
|
||||
obj.slug, latest_photo.caption, latest_photo.pk, "large"
|
||||
),
|
||||
"public": MediaURLService.generate_park_photo_url(
|
||||
obj.slug, latest_photo.caption, latest_photo.pk, "public"
|
||||
),
|
||||
},
|
||||
"caption": latest_photo.caption,
|
||||
"alt_text": latest_photo.alt_text,
|
||||
@@ -417,7 +433,7 @@ class ParkImageSettingsInputSerializer(serializers.Serializer):
|
||||
# The park will be validated in the view
|
||||
return value
|
||||
except ParkPhoto.DoesNotExist:
|
||||
raise serializers.ValidationError("Photo not found")
|
||||
raise serializers.ValidationError("Photo not found") from None
|
||||
return value
|
||||
|
||||
def validate_card_image_id(self, value):
|
||||
@@ -430,7 +446,7 @@ class ParkImageSettingsInputSerializer(serializers.Serializer):
|
||||
# The park will be validated in the view
|
||||
return value
|
||||
except ParkPhoto.DoesNotExist:
|
||||
raise serializers.ValidationError("Photo not found")
|
||||
raise serializers.ValidationError("Photo not found") from None
|
||||
return value
|
||||
|
||||
|
||||
@@ -439,19 +455,13 @@ class ParkCreateInputSerializer(serializers.Serializer):
|
||||
|
||||
name = serializers.CharField(max_length=255)
|
||||
description = serializers.CharField(allow_blank=True, default="")
|
||||
status = serializers.ChoiceField(
|
||||
choices=ModelChoices.get_park_status_choices(), default="OPERATING"
|
||||
)
|
||||
status = serializers.ChoiceField(choices=ModelChoices.get_park_status_choices(), default="OPERATING")
|
||||
|
||||
# Optional details
|
||||
opening_date = serializers.DateField(required=False, allow_null=True)
|
||||
closing_date = serializers.DateField(required=False, allow_null=True)
|
||||
operating_season = serializers.CharField(
|
||||
max_length=255, required=False, allow_blank=True
|
||||
)
|
||||
size_acres = serializers.DecimalField(
|
||||
max_digits=10, decimal_places=2, required=False, allow_null=True
|
||||
)
|
||||
operating_season = serializers.CharField(max_length=255, required=False, allow_blank=True)
|
||||
size_acres = serializers.DecimalField(max_digits=10, decimal_places=2, required=False, allow_null=True)
|
||||
website = serializers.URLField(required=False, allow_blank=True)
|
||||
|
||||
# Required operator
|
||||
@@ -466,9 +476,7 @@ class ParkCreateInputSerializer(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")
|
||||
|
||||
return attrs
|
||||
|
||||
@@ -478,19 +486,13 @@ class ParkUpdateInputSerializer(serializers.Serializer):
|
||||
|
||||
name = serializers.CharField(max_length=255, required=False)
|
||||
description = serializers.CharField(allow_blank=True, required=False)
|
||||
status = serializers.ChoiceField(
|
||||
choices=ModelChoices.get_park_status_choices(), required=False
|
||||
)
|
||||
status = serializers.ChoiceField(choices=ModelChoices.get_park_status_choices(), required=False)
|
||||
|
||||
# Optional details
|
||||
opening_date = serializers.DateField(required=False, allow_null=True)
|
||||
closing_date = serializers.DateField(required=False, allow_null=True)
|
||||
operating_season = serializers.CharField(
|
||||
max_length=255, required=False, allow_blank=True
|
||||
)
|
||||
size_acres = serializers.DecimalField(
|
||||
max_digits=10, decimal_places=2, required=False, allow_null=True
|
||||
)
|
||||
operating_season = serializers.CharField(max_length=255, required=False, allow_blank=True)
|
||||
size_acres = serializers.DecimalField(max_digits=10, decimal_places=2, required=False, allow_null=True)
|
||||
website = serializers.URLField(required=False, allow_blank=True)
|
||||
|
||||
# Companies
|
||||
@@ -503,9 +505,7 @@ class ParkUpdateInputSerializer(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")
|
||||
|
||||
return attrs
|
||||
|
||||
@@ -537,12 +537,8 @@ class ParkFilterInputSerializer(serializers.Serializer):
|
||||
)
|
||||
|
||||
# Size filter
|
||||
min_size_acres = serializers.DecimalField(
|
||||
max_digits=10, decimal_places=2, required=False, min_value=0
|
||||
)
|
||||
max_size_acres = serializers.DecimalField(
|
||||
max_digits=10, decimal_places=2, required=False, min_value=0
|
||||
)
|
||||
min_size_acres = serializers.DecimalField(max_digits=10, decimal_places=2, required=False, min_value=0)
|
||||
max_size_acres = serializers.DecimalField(max_digits=10, decimal_places=2, required=False, min_value=0)
|
||||
|
||||
# Company filters
|
||||
operator_id = serializers.IntegerField(required=False)
|
||||
@@ -625,9 +621,7 @@ class ParkAreaCreateInputSerializer(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")
|
||||
|
||||
return attrs
|
||||
|
||||
@@ -646,9 +640,7 @@ class ParkAreaUpdateInputSerializer(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")
|
||||
|
||||
return attrs
|
||||
|
||||
|
||||
@@ -12,9 +12,7 @@ from apps.parks.models import ParkPhoto
|
||||
class ParkPhotoOutputSerializer(serializers.ModelSerializer):
|
||||
"""Output serializer for park photos."""
|
||||
|
||||
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.ReadOnlyField()
|
||||
dimensions = serializers.ReadOnlyField()
|
||||
park_slug = serializers.CharField(source="park.slug", read_only=True)
|
||||
@@ -78,9 +76,7 @@ class ParkPhotoUpdateInputSerializer(serializers.ModelSerializer):
|
||||
class ParkPhotoListOutputSerializer(serializers.ModelSerializer):
|
||||
"""Simplified output serializer for park 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 = ParkPhoto
|
||||
@@ -99,12 +95,8 @@ class ParkPhotoListOutputSerializer(serializers.ModelSerializer):
|
||||
class ParkPhotoApprovalInputSerializer(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 ParkPhotoStatsOutputSerializer(serializers.Serializer):
|
||||
|
||||
@@ -8,35 +8,33 @@ from apps.rides.models.credits import RideCredit
|
||||
class RideCreditSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for user ride credits."""
|
||||
|
||||
ride_id = serializers.PrimaryKeyRelatedField(
|
||||
queryset=Ride.objects.all(), source='ride', write_only=True
|
||||
)
|
||||
ride_id = serializers.PrimaryKeyRelatedField(queryset=Ride.objects.all(), source="ride", write_only=True)
|
||||
ride = RideListOutputSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = RideCredit
|
||||
fields = [
|
||||
'id',
|
||||
'ride',
|
||||
'ride_id',
|
||||
'count',
|
||||
'rating',
|
||||
'first_ridden_at',
|
||||
'last_ridden_at',
|
||||
'notes',
|
||||
'display_order',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
"id",
|
||||
"ride",
|
||||
"ride_id",
|
||||
"count",
|
||||
"rating",
|
||||
"first_ridden_at",
|
||||
"last_ridden_at",
|
||||
"notes",
|
||||
"display_order",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
read_only_fields = ['id', 'created_at', 'updated_at']
|
||||
read_only_fields = ["id", "created_at", "updated_at"]
|
||||
|
||||
def validate(self, attrs):
|
||||
"""
|
||||
Validate data.
|
||||
"""
|
||||
# Ensure dates make sense
|
||||
first = attrs.get('first_ridden_at')
|
||||
last = attrs.get('last_ridden_at')
|
||||
first = attrs.get("first_ridden_at")
|
||||
last = attrs.get("last_ridden_at")
|
||||
if first and last and last < first:
|
||||
raise serializers.ValidationError("Last ridden date cannot be before first ridden date.")
|
||||
|
||||
@@ -44,6 +42,6 @@ class RideCreditSerializer(serializers.ModelSerializer):
|
||||
|
||||
def create(self, validated_data):
|
||||
"""Create a new ride credit."""
|
||||
user = self.context['request'].user
|
||||
validated_data['user'] = user
|
||||
user = self.context["request"].user
|
||||
validated_data["user"] = user
|
||||
return super().create(validated_data)
|
||||
|
||||
@@ -80,18 +80,10 @@ class RideModelVariantOutputSerializer(serializers.Serializer):
|
||||
id = serializers.IntegerField()
|
||||
name = serializers.CharField()
|
||||
description = serializers.CharField()
|
||||
min_height_ft = serializers.DecimalField(
|
||||
max_digits=6, decimal_places=2, allow_null=True
|
||||
)
|
||||
max_height_ft = serializers.DecimalField(
|
||||
max_digits=6, decimal_places=2, allow_null=True
|
||||
)
|
||||
min_speed_mph = serializers.DecimalField(
|
||||
max_digits=5, decimal_places=2, allow_null=True
|
||||
)
|
||||
max_speed_mph = serializers.DecimalField(
|
||||
max_digits=5, decimal_places=2, allow_null=True
|
||||
)
|
||||
min_height_ft = serializers.DecimalField(max_digits=6, decimal_places=2, allow_null=True)
|
||||
max_height_ft = serializers.DecimalField(max_digits=6, decimal_places=2, allow_null=True)
|
||||
min_speed_mph = serializers.DecimalField(max_digits=5, decimal_places=2, allow_null=True)
|
||||
max_speed_mph = serializers.DecimalField(max_digits=5, decimal_places=2, allow_null=True)
|
||||
distinguishing_features = serializers.CharField()
|
||||
|
||||
|
||||
@@ -134,20 +126,14 @@ class RideModelListOutputSerializer(serializers.Serializer):
|
||||
id = serializers.IntegerField()
|
||||
name = serializers.CharField()
|
||||
slug = serializers.CharField()
|
||||
category = RichChoiceFieldSerializer(
|
||||
choice_group="categories",
|
||||
domain="rides"
|
||||
)
|
||||
category = RichChoiceFieldSerializer(choice_group="categories", domain="rides")
|
||||
description = serializers.CharField()
|
||||
|
||||
# Manufacturer info
|
||||
manufacturer = RideModelManufacturerOutputSerializer(allow_null=True)
|
||||
|
||||
# Market info
|
||||
target_market = RichChoiceFieldSerializer(
|
||||
choice_group="target_markets",
|
||||
domain="rides"
|
||||
)
|
||||
target_market = RichChoiceFieldSerializer(choice_group="target_markets", domain="rides")
|
||||
is_discontinued = serializers.BooleanField()
|
||||
total_installations = serializers.IntegerField()
|
||||
first_installation_year = serializers.IntegerField(allow_null=True)
|
||||
@@ -258,18 +244,10 @@ class RideModelDetailOutputSerializer(serializers.Serializer):
|
||||
manufacturer = RideModelManufacturerOutputSerializer(allow_null=True)
|
||||
|
||||
# Technical specifications
|
||||
typical_height_range_min_ft = serializers.DecimalField(
|
||||
max_digits=6, decimal_places=2, allow_null=True
|
||||
)
|
||||
typical_height_range_max_ft = serializers.DecimalField(
|
||||
max_digits=6, decimal_places=2, allow_null=True
|
||||
)
|
||||
typical_speed_range_min_mph = serializers.DecimalField(
|
||||
max_digits=5, decimal_places=2, allow_null=True
|
||||
)
|
||||
typical_speed_range_max_mph = serializers.DecimalField(
|
||||
max_digits=5, decimal_places=2, allow_null=True
|
||||
)
|
||||
typical_height_range_min_ft = serializers.DecimalField(max_digits=6, decimal_places=2, allow_null=True)
|
||||
typical_height_range_max_ft = serializers.DecimalField(max_digits=6, decimal_places=2, allow_null=True)
|
||||
typical_speed_range_min_mph = serializers.DecimalField(max_digits=5, decimal_places=2, allow_null=True)
|
||||
typical_speed_range_max_mph = serializers.DecimalField(max_digits=5, decimal_places=2, allow_null=True)
|
||||
typical_capacity_range_min = serializers.IntegerField(allow_null=True)
|
||||
typical_capacity_range_max = serializers.IntegerField(allow_null=True)
|
||||
|
||||
@@ -343,9 +321,7 @@ class RideModelCreateInputSerializer(serializers.Serializer):
|
||||
|
||||
name = serializers.CharField(max_length=255)
|
||||
description = serializers.CharField(allow_blank=True, default="")
|
||||
category = serializers.ChoiceField(
|
||||
choices=ModelChoices.get_ride_category_choices(), allow_blank=True, default=""
|
||||
)
|
||||
category = serializers.ChoiceField(choices=ModelChoices.get_ride_category_choices(), allow_blank=True, default="")
|
||||
|
||||
# Required manufacturer
|
||||
manufacturer_id = serializers.IntegerField()
|
||||
@@ -363,32 +339,18 @@ class RideModelCreateInputSerializer(serializers.Serializer):
|
||||
typical_speed_range_max_mph = serializers.DecimalField(
|
||||
max_digits=5, decimal_places=2, required=False, allow_null=True
|
||||
)
|
||||
typical_capacity_range_min = serializers.IntegerField(
|
||||
required=False, allow_null=True, min_value=1
|
||||
)
|
||||
typical_capacity_range_max = serializers.IntegerField(
|
||||
required=False, allow_null=True, min_value=1
|
||||
)
|
||||
typical_capacity_range_min = serializers.IntegerField(required=False, allow_null=True, min_value=1)
|
||||
typical_capacity_range_max = serializers.IntegerField(required=False, allow_null=True, min_value=1)
|
||||
|
||||
# Design characteristics
|
||||
track_type = serializers.CharField(max_length=100, allow_blank=True, default="")
|
||||
support_structure = serializers.CharField(
|
||||
max_length=100, allow_blank=True, default=""
|
||||
)
|
||||
train_configuration = serializers.CharField(
|
||||
max_length=200, allow_blank=True, default=""
|
||||
)
|
||||
restraint_system = serializers.CharField(
|
||||
max_length=100, allow_blank=True, default=""
|
||||
)
|
||||
support_structure = serializers.CharField(max_length=100, allow_blank=True, default="")
|
||||
train_configuration = serializers.CharField(max_length=200, allow_blank=True, default="")
|
||||
restraint_system = serializers.CharField(max_length=100, allow_blank=True, default="")
|
||||
|
||||
# Market information
|
||||
first_installation_year = serializers.IntegerField(
|
||||
required=False, allow_null=True, min_value=1800, max_value=2100
|
||||
)
|
||||
last_installation_year = serializers.IntegerField(
|
||||
required=False, allow_null=True, min_value=1800, max_value=2100
|
||||
)
|
||||
first_installation_year = serializers.IntegerField(required=False, allow_null=True, min_value=1800, max_value=2100)
|
||||
last_installation_year = serializers.IntegerField(required=False, allow_null=True, min_value=1800, max_value=2100)
|
||||
is_discontinued = serializers.BooleanField(default=False)
|
||||
|
||||
# Design features
|
||||
@@ -406,36 +368,28 @@ class RideModelCreateInputSerializer(serializers.Serializer):
|
||||
max_height = attrs.get("typical_height_range_max_ft")
|
||||
|
||||
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")
|
||||
|
||||
# Speed range validation
|
||||
min_speed = attrs.get("typical_speed_range_min_mph")
|
||||
max_speed = attrs.get("typical_speed_range_max_mph")
|
||||
|
||||
if min_speed and max_speed and min_speed > max_speed:
|
||||
raise serializers.ValidationError(
|
||||
"Minimum speed cannot be greater than maximum speed"
|
||||
)
|
||||
raise serializers.ValidationError("Minimum speed cannot be greater than maximum speed")
|
||||
|
||||
# Capacity range validation
|
||||
min_capacity = attrs.get("typical_capacity_range_min")
|
||||
max_capacity = attrs.get("typical_capacity_range_max")
|
||||
|
||||
if min_capacity and max_capacity and min_capacity > max_capacity:
|
||||
raise serializers.ValidationError(
|
||||
"Minimum capacity cannot be greater than maximum capacity"
|
||||
)
|
||||
raise serializers.ValidationError("Minimum capacity cannot be greater than maximum capacity")
|
||||
|
||||
# Installation years validation
|
||||
first_year = attrs.get("first_installation_year")
|
||||
last_year = attrs.get("last_installation_year")
|
||||
|
||||
if first_year and last_year and first_year > last_year:
|
||||
raise serializers.ValidationError(
|
||||
"First installation year cannot be after last installation year"
|
||||
)
|
||||
raise serializers.ValidationError("First installation year cannot be after last installation year")
|
||||
|
||||
return attrs
|
||||
|
||||
@@ -467,32 +421,18 @@ class RideModelUpdateInputSerializer(serializers.Serializer):
|
||||
typical_speed_range_max_mph = serializers.DecimalField(
|
||||
max_digits=5, decimal_places=2, required=False, allow_null=True
|
||||
)
|
||||
typical_capacity_range_min = serializers.IntegerField(
|
||||
required=False, allow_null=True, min_value=1
|
||||
)
|
||||
typical_capacity_range_max = serializers.IntegerField(
|
||||
required=False, allow_null=True, min_value=1
|
||||
)
|
||||
typical_capacity_range_min = serializers.IntegerField(required=False, allow_null=True, min_value=1)
|
||||
typical_capacity_range_max = serializers.IntegerField(required=False, allow_null=True, min_value=1)
|
||||
|
||||
# Design characteristics
|
||||
track_type = serializers.CharField(max_length=100, allow_blank=True, required=False)
|
||||
support_structure = serializers.CharField(
|
||||
max_length=100, allow_blank=True, required=False
|
||||
)
|
||||
train_configuration = serializers.CharField(
|
||||
max_length=200, allow_blank=True, required=False
|
||||
)
|
||||
restraint_system = serializers.CharField(
|
||||
max_length=100, allow_blank=True, required=False
|
||||
)
|
||||
support_structure = serializers.CharField(max_length=100, allow_blank=True, required=False)
|
||||
train_configuration = serializers.CharField(max_length=200, allow_blank=True, required=False)
|
||||
restraint_system = serializers.CharField(max_length=100, allow_blank=True, required=False)
|
||||
|
||||
# Market information
|
||||
first_installation_year = serializers.IntegerField(
|
||||
required=False, allow_null=True, min_value=1800, max_value=2100
|
||||
)
|
||||
last_installation_year = serializers.IntegerField(
|
||||
required=False, allow_null=True, min_value=1800, max_value=2100
|
||||
)
|
||||
first_installation_year = serializers.IntegerField(required=False, allow_null=True, min_value=1800, max_value=2100)
|
||||
last_installation_year = serializers.IntegerField(required=False, allow_null=True, min_value=1800, max_value=2100)
|
||||
is_discontinued = serializers.BooleanField(required=False)
|
||||
|
||||
# Design features
|
||||
@@ -510,36 +450,28 @@ class RideModelUpdateInputSerializer(serializers.Serializer):
|
||||
max_height = attrs.get("typical_height_range_max_ft")
|
||||
|
||||
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")
|
||||
|
||||
# Speed range validation
|
||||
min_speed = attrs.get("typical_speed_range_min_mph")
|
||||
max_speed = attrs.get("typical_speed_range_max_mph")
|
||||
|
||||
if min_speed and max_speed and min_speed > max_speed:
|
||||
raise serializers.ValidationError(
|
||||
"Minimum speed cannot be greater than maximum speed"
|
||||
)
|
||||
raise serializers.ValidationError("Minimum speed cannot be greater than maximum speed")
|
||||
|
||||
# Capacity range validation
|
||||
min_capacity = attrs.get("typical_capacity_range_min")
|
||||
max_capacity = attrs.get("typical_capacity_range_max")
|
||||
|
||||
if min_capacity and max_capacity and min_capacity > max_capacity:
|
||||
raise serializers.ValidationError(
|
||||
"Minimum capacity cannot be greater than maximum capacity"
|
||||
)
|
||||
raise serializers.ValidationError("Minimum capacity cannot be greater than maximum capacity")
|
||||
|
||||
# Installation years validation
|
||||
first_year = attrs.get("first_installation_year")
|
||||
last_year = attrs.get("last_installation_year")
|
||||
|
||||
if first_year and last_year and first_year > last_year:
|
||||
raise serializers.ValidationError(
|
||||
"First installation year cannot be after last installation year"
|
||||
)
|
||||
raise serializers.ValidationError("First installation year cannot be after last installation year")
|
||||
|
||||
return attrs
|
||||
|
||||
@@ -551,9 +483,7 @@ class RideModelFilterInputSerializer(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)
|
||||
|
||||
# Manufacturer filter
|
||||
manufacturer_id = serializers.IntegerField(required=False)
|
||||
@@ -576,20 +506,12 @@ class RideModelFilterInputSerializer(serializers.Serializer):
|
||||
min_installations = serializers.IntegerField(required=False, min_value=0)
|
||||
|
||||
# Height filters
|
||||
min_height_ft = serializers.DecimalField(
|
||||
max_digits=6, decimal_places=2, required=False
|
||||
)
|
||||
max_height_ft = serializers.DecimalField(
|
||||
max_digits=6, decimal_places=2, required=False
|
||||
)
|
||||
min_height_ft = serializers.DecimalField(max_digits=6, decimal_places=2, required=False)
|
||||
max_height_ft = serializers.DecimalField(max_digits=6, decimal_places=2, required=False)
|
||||
|
||||
# Speed filters
|
||||
min_speed_mph = serializers.DecimalField(
|
||||
max_digits=5, decimal_places=2, required=False
|
||||
)
|
||||
max_speed_mph = serializers.DecimalField(
|
||||
max_digits=5, decimal_places=2, required=False
|
||||
)
|
||||
min_speed_mph = serializers.DecimalField(max_digits=5, decimal_places=2, required=False)
|
||||
max_speed_mph = serializers.DecimalField(max_digits=5, decimal_places=2, required=False)
|
||||
|
||||
# Ordering
|
||||
ordering = serializers.ChoiceField(
|
||||
@@ -621,18 +543,10 @@ class RideModelVariantCreateInputSerializer(serializers.Serializer):
|
||||
description = serializers.CharField(allow_blank=True, default="")
|
||||
|
||||
# Variant-specific specifications
|
||||
min_height_ft = serializers.DecimalField(
|
||||
max_digits=6, decimal_places=2, required=False, allow_null=True
|
||||
)
|
||||
max_height_ft = serializers.DecimalField(
|
||||
max_digits=6, decimal_places=2, required=False, allow_null=True
|
||||
)
|
||||
min_speed_mph = serializers.DecimalField(
|
||||
max_digits=5, decimal_places=2, required=False, allow_null=True
|
||||
)
|
||||
max_speed_mph = serializers.DecimalField(
|
||||
max_digits=5, decimal_places=2, required=False, allow_null=True
|
||||
)
|
||||
min_height_ft = serializers.DecimalField(max_digits=6, decimal_places=2, required=False, allow_null=True)
|
||||
max_height_ft = serializers.DecimalField(max_digits=6, decimal_places=2, required=False, allow_null=True)
|
||||
min_speed_mph = serializers.DecimalField(max_digits=5, decimal_places=2, required=False, allow_null=True)
|
||||
max_speed_mph = serializers.DecimalField(max_digits=5, decimal_places=2, required=False, allow_null=True)
|
||||
|
||||
# Distinguishing features
|
||||
distinguishing_features = serializers.CharField(allow_blank=True, default="")
|
||||
@@ -644,18 +558,14 @@ class RideModelVariantCreateInputSerializer(serializers.Serializer):
|
||||
max_height = attrs.get("max_height_ft")
|
||||
|
||||
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")
|
||||
|
||||
# Speed range validation
|
||||
min_speed = attrs.get("min_speed_mph")
|
||||
max_speed = attrs.get("max_speed_mph")
|
||||
|
||||
if min_speed and max_speed and min_speed > max_speed:
|
||||
raise serializers.ValidationError(
|
||||
"Minimum speed cannot be greater than maximum speed"
|
||||
)
|
||||
raise serializers.ValidationError("Minimum speed cannot be greater than maximum speed")
|
||||
|
||||
return attrs
|
||||
|
||||
@@ -667,18 +577,10 @@ class RideModelVariantUpdateInputSerializer(serializers.Serializer):
|
||||
description = serializers.CharField(allow_blank=True, required=False)
|
||||
|
||||
# Variant-specific specifications
|
||||
min_height_ft = serializers.DecimalField(
|
||||
max_digits=6, decimal_places=2, required=False, allow_null=True
|
||||
)
|
||||
max_height_ft = serializers.DecimalField(
|
||||
max_digits=6, decimal_places=2, required=False, allow_null=True
|
||||
)
|
||||
min_speed_mph = serializers.DecimalField(
|
||||
max_digits=5, decimal_places=2, required=False, allow_null=True
|
||||
)
|
||||
max_speed_mph = serializers.DecimalField(
|
||||
max_digits=5, decimal_places=2, required=False, allow_null=True
|
||||
)
|
||||
min_height_ft = serializers.DecimalField(max_digits=6, decimal_places=2, required=False, allow_null=True)
|
||||
max_height_ft = serializers.DecimalField(max_digits=6, decimal_places=2, required=False, allow_null=True)
|
||||
min_speed_mph = serializers.DecimalField(max_digits=5, decimal_places=2, required=False, allow_null=True)
|
||||
max_speed_mph = serializers.DecimalField(max_digits=5, decimal_places=2, required=False, allow_null=True)
|
||||
|
||||
# Distinguishing features
|
||||
distinguishing_features = serializers.CharField(allow_blank=True, required=False)
|
||||
@@ -690,18 +592,14 @@ class RideModelVariantUpdateInputSerializer(serializers.Serializer):
|
||||
max_height = attrs.get("max_height_ft")
|
||||
|
||||
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")
|
||||
|
||||
# Speed range validation
|
||||
min_speed = attrs.get("min_speed_mph")
|
||||
max_speed = attrs.get("max_speed_mph")
|
||||
|
||||
if min_speed and max_speed and min_speed > max_speed:
|
||||
raise serializers.ValidationError(
|
||||
"Minimum speed cannot be greater than maximum speed"
|
||||
)
|
||||
raise serializers.ValidationError("Minimum speed cannot be greater than maximum speed")
|
||||
|
||||
return attrs
|
||||
|
||||
@@ -713,9 +611,7 @@ class RideModelTechnicalSpecCreateInputSerializer(serializers.Serializer):
|
||||
"""Input serializer for creating ride model technical specifications."""
|
||||
|
||||
ride_model_id = serializers.IntegerField()
|
||||
spec_category = serializers.ChoiceField(
|
||||
choices=ModelChoices.get_technical_spec_category_choices()
|
||||
)
|
||||
spec_category = serializers.ChoiceField(choices=ModelChoices.get_technical_spec_category_choices())
|
||||
spec_name = serializers.CharField(max_length=100)
|
||||
spec_value = serializers.CharField(max_length=255)
|
||||
spec_unit = serializers.CharField(max_length=20, allow_blank=True, default="")
|
||||
@@ -765,13 +661,9 @@ class RideModelPhotoUpdateInputSerializer(serializers.Serializer):
|
||||
required=False,
|
||||
)
|
||||
is_primary = serializers.BooleanField(required=False)
|
||||
photographer = serializers.CharField(
|
||||
max_length=255, allow_blank=True, required=False
|
||||
)
|
||||
photographer = serializers.CharField(max_length=255, allow_blank=True, required=False)
|
||||
source = serializers.CharField(max_length=255, allow_blank=True, required=False)
|
||||
copyright_info = serializers.CharField(
|
||||
max_length=255, allow_blank=True, required=False
|
||||
)
|
||||
copyright_info = serializers.CharField(max_length=255, allow_blank=True, required=False)
|
||||
|
||||
|
||||
# === RIDE MODEL STATS SERIALIZERS ===
|
||||
@@ -784,15 +676,9 @@ class RideModelStatsOutputSerializer(serializers.Serializer):
|
||||
total_installations = serializers.IntegerField()
|
||||
active_manufacturers = serializers.IntegerField()
|
||||
discontinued_models = serializers.IntegerField()
|
||||
by_category = serializers.DictField(
|
||||
child=serializers.IntegerField(), help_text="Model counts by category"
|
||||
)
|
||||
by_category = serializers.DictField(child=serializers.IntegerField(), help_text="Model counts by category")
|
||||
by_target_market = serializers.DictField(
|
||||
child=serializers.IntegerField(), help_text="Model counts by target market"
|
||||
)
|
||||
by_manufacturer = serializers.DictField(
|
||||
child=serializers.IntegerField(), help_text="Model counts by manufacturer"
|
||||
)
|
||||
recent_models = serializers.IntegerField(
|
||||
help_text="Models created in the last 30 days"
|
||||
)
|
||||
by_manufacturer = serializers.DictField(child=serializers.IntegerField(), help_text="Model counts by manufacturer")
|
||||
recent_models = serializers.IntegerField(help_text="Models created in the last 30 days")
|
||||
|
||||
@@ -54,19 +54,11 @@ class ReviewUserSerializer(serializers.ModelSerializer):
|
||||
"id": 456,
|
||||
"username": "coaster_fan",
|
||||
"display_name": "Coaster Fan",
|
||||
"avatar_url": "https://example.com/avatar.jpg"
|
||||
"avatar_url": "https://example.com/avatar.jpg",
|
||||
},
|
||||
"ride": {
|
||||
"id": 789,
|
||||
"name": "Steel Vengeance",
|
||||
"slug": "steel-vengeance"
|
||||
},
|
||||
"park": {
|
||||
"id": 101,
|
||||
"name": "Cedar Point",
|
||||
"slug": "cedar-point"
|
||||
}
|
||||
}
|
||||
"ride": {"id": 789, "name": "Steel Vengeance", "slug": "steel-vengeance"},
|
||||
"park": {"id": 101, "name": "Cedar Point", "slug": "cedar-point"},
|
||||
},
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -191,8 +183,7 @@ class RideReviewStatsOutputSerializer(serializers.Serializer):
|
||||
pending_reviews = serializers.IntegerField()
|
||||
average_rating = serializers.FloatField(allow_null=True)
|
||||
rating_distribution = serializers.DictField(
|
||||
child=serializers.IntegerField(),
|
||||
help_text="Count of reviews by rating (1-10)"
|
||||
child=serializers.IntegerField(), help_text="Count of reviews by rating (1-10)"
|
||||
)
|
||||
recent_reviews = serializers.IntegerField()
|
||||
|
||||
@@ -200,20 +191,15 @@ class RideReviewStatsOutputSerializer(serializers.Serializer):
|
||||
class RideReviewModerationInputSerializer(serializers.Serializer):
|
||||
"""Input serializer for review moderation operations."""
|
||||
|
||||
review_ids = serializers.ListField(
|
||||
child=serializers.IntegerField(),
|
||||
help_text="List of review IDs to moderate"
|
||||
)
|
||||
review_ids = serializers.ListField(child=serializers.IntegerField(), help_text="List of review IDs to moderate")
|
||||
action = serializers.ChoiceField(
|
||||
choices=[
|
||||
("publish", "Publish"),
|
||||
("unpublish", "Unpublish"),
|
||||
("delete", "Delete"),
|
||||
],
|
||||
help_text="Moderation action to perform"
|
||||
help_text="Moderation action to perform",
|
||||
)
|
||||
moderation_notes = serializers.CharField(
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
help_text="Optional notes about the moderation action"
|
||||
required=False, allow_blank=True, help_text="Optional notes about the moderation action"
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -12,9 +12,7 @@ from apps.rides.models import RidePhoto
|
||||
class RidePhotoOutputSerializer(serializers.ModelSerializer):
|
||||
"""Output serializer for ride photos."""
|
||||
|
||||
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.ReadOnlyField()
|
||||
dimensions = serializers.ReadOnlyField()
|
||||
ride_slug = serializers.CharField(source="ride.slug", read_only=True)
|
||||
@@ -87,9 +85,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
|
||||
@@ -109,12 +105,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):
|
||||
@@ -125,9 +117,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):
|
||||
|
||||
@@ -19,9 +19,7 @@ class EntitySearchInputSerializer(serializers.Serializer):
|
||||
|
||||
query = serializers.CharField(max_length=255, help_text="Search query string")
|
||||
entity_types = serializers.ListField(
|
||||
child=serializers.ChoiceField(
|
||||
choices=ModelChoices.get_entity_type_choices()
|
||||
),
|
||||
child=serializers.ChoiceField(choices=ModelChoices.get_entity_type_choices()),
|
||||
required=False,
|
||||
help_text="Types of entities to search for",
|
||||
)
|
||||
@@ -39,17 +37,12 @@ class EntitySearchResultSerializer(serializers.Serializer):
|
||||
id = serializers.IntegerField()
|
||||
name = serializers.CharField()
|
||||
slug = serializers.CharField()
|
||||
type = RichChoiceFieldSerializer(
|
||||
choice_group="entity_types",
|
||||
domain="core"
|
||||
)
|
||||
type = RichChoiceFieldSerializer(choice_group="entity_types", domain="core")
|
||||
description = serializers.CharField()
|
||||
relevance_score = serializers.FloatField()
|
||||
|
||||
# Context-specific info — renamed to avoid overriding Serializer.context
|
||||
context_info = serializers.JSONField(
|
||||
help_text="Additional context based on entity type"
|
||||
)
|
||||
context_info = serializers.JSONField(help_text="Additional context based on entity type")
|
||||
|
||||
|
||||
class EntitySearchOutputSerializer(serializers.Serializer):
|
||||
|
||||
@@ -39,9 +39,7 @@ class SimpleHealthOutputSerializer(serializers.Serializer):
|
||||
|
||||
status = serializers.CharField(help_text="Simple health status")
|
||||
timestamp = serializers.DateTimeField(help_text="Timestamp of health check")
|
||||
error = serializers.CharField(
|
||||
required=False, help_text="Error message if unhealthy"
|
||||
)
|
||||
error = serializers.CharField(required=False, help_text="Error message if unhealthy")
|
||||
|
||||
|
||||
# === EMAIL SERVICE SERIALIZERS ===
|
||||
@@ -151,7 +149,7 @@ class ModerationSubmissionSerializer(serializers.Serializer):
|
||||
("PHOTO", "Photo Submission"),
|
||||
("REVIEW", "Review Submission"),
|
||||
],
|
||||
help_text="Type of submission"
|
||||
help_text="Type of submission",
|
||||
)
|
||||
content_type = serializers.CharField(help_text="Content type being modified")
|
||||
object_id = serializers.IntegerField(help_text="ID of object being modified")
|
||||
@@ -221,9 +219,7 @@ class RoadtripOutputSerializer(serializers.Serializer):
|
||||
parks = RoadtripParkSerializer(many=True)
|
||||
total_distance_miles = serializers.FloatField()
|
||||
estimated_drive_time_hours = serializers.FloatField()
|
||||
route_coordinates = serializers.ListField(
|
||||
child=serializers.ListField(child=serializers.FloatField())
|
||||
)
|
||||
route_coordinates = serializers.ListField(child=serializers.ListField(child=serializers.FloatField()))
|
||||
created_at = serializers.DateTimeField()
|
||||
|
||||
|
||||
|
||||
@@ -25,21 +25,13 @@ class FilterOptionSerializer(serializers.Serializer):
|
||||
selected?: boolean;
|
||||
}
|
||||
"""
|
||||
value = serializers.CharField(
|
||||
help_text="The actual value used for filtering"
|
||||
)
|
||||
label = serializers.CharField(
|
||||
help_text="Human-readable display label"
|
||||
)
|
||||
|
||||
value = serializers.CharField(help_text="The actual value used for filtering")
|
||||
label = serializers.CharField(help_text="Human-readable display label")
|
||||
count = serializers.IntegerField(
|
||||
required=False,
|
||||
allow_null=True,
|
||||
help_text="Number of items matching this filter option"
|
||||
)
|
||||
selected = serializers.BooleanField(
|
||||
default=False,
|
||||
help_text="Whether this option is currently selected"
|
||||
required=False, allow_null=True, help_text="Number of items matching this filter option"
|
||||
)
|
||||
selected = serializers.BooleanField(default=False, help_text="Whether this option is currently selected")
|
||||
|
||||
|
||||
class FilterRangeSerializer(serializers.Serializer):
|
||||
@@ -54,22 +46,12 @@ class FilterRangeSerializer(serializers.Serializer):
|
||||
unit?: string;
|
||||
}
|
||||
"""
|
||||
min = serializers.FloatField(
|
||||
allow_null=True,
|
||||
help_text="Minimum value for the range"
|
||||
)
|
||||
max = serializers.FloatField(
|
||||
allow_null=True,
|
||||
help_text="Maximum value for the range"
|
||||
)
|
||||
step = serializers.FloatField(
|
||||
default=1.0,
|
||||
help_text="Step size for range inputs"
|
||||
)
|
||||
|
||||
min = serializers.FloatField(allow_null=True, help_text="Minimum value for the range")
|
||||
max = serializers.FloatField(allow_null=True, help_text="Maximum value for the range")
|
||||
step = serializers.FloatField(default=1.0, help_text="Step size for range inputs")
|
||||
unit = serializers.CharField(
|
||||
required=False,
|
||||
allow_null=True,
|
||||
help_text="Unit of measurement (e.g., 'feet', 'mph', 'stars')"
|
||||
required=False, allow_null=True, help_text="Unit of measurement (e.g., 'feet', 'mph', 'stars')"
|
||||
)
|
||||
|
||||
|
||||
@@ -84,15 +66,10 @@ class BooleanFilterSerializer(serializers.Serializer):
|
||||
description: string;
|
||||
}
|
||||
"""
|
||||
key = serializers.CharField(
|
||||
help_text="The filter parameter key"
|
||||
)
|
||||
label = serializers.CharField(
|
||||
help_text="Human-readable label for the filter"
|
||||
)
|
||||
description = serializers.CharField(
|
||||
help_text="Description of what this filter does"
|
||||
)
|
||||
|
||||
key = serializers.CharField(help_text="The filter parameter key")
|
||||
label = serializers.CharField(help_text="Human-readable label for the filter")
|
||||
description = serializers.CharField(help_text="Description of what this filter does")
|
||||
|
||||
|
||||
class OrderingOptionSerializer(serializers.Serializer):
|
||||
@@ -105,12 +82,9 @@ class OrderingOptionSerializer(serializers.Serializer):
|
||||
label: string;
|
||||
}
|
||||
"""
|
||||
value = serializers.CharField(
|
||||
help_text="The ordering parameter value"
|
||||
)
|
||||
label = serializers.CharField(
|
||||
help_text="Human-readable label for the ordering option"
|
||||
)
|
||||
|
||||
value = serializers.CharField(help_text="The ordering parameter value")
|
||||
label = serializers.CharField(help_text="Human-readable label for the ordering option")
|
||||
|
||||
|
||||
class StandardizedFilterMetadataSerializer(serializers.Serializer):
|
||||
@@ -120,27 +94,16 @@ class StandardizedFilterMetadataSerializer(serializers.Serializer):
|
||||
This serializer ensures all filter metadata responses follow the same structure
|
||||
that the frontend expects, preventing runtime type errors.
|
||||
"""
|
||||
|
||||
categorical = serializers.DictField(
|
||||
child=FilterOptionSerializer(many=True),
|
||||
help_text="Categorical filter options with value/label/count structure"
|
||||
child=FilterOptionSerializer(many=True), help_text="Categorical filter options with value/label/count structure"
|
||||
)
|
||||
ranges = serializers.DictField(
|
||||
child=FilterRangeSerializer(),
|
||||
help_text="Range filter metadata with min/max/step/unit"
|
||||
)
|
||||
total_count = serializers.IntegerField(
|
||||
help_text="Total number of items in the filtered dataset"
|
||||
)
|
||||
ordering_options = FilterOptionSerializer(
|
||||
many=True,
|
||||
required=False,
|
||||
help_text="Available ordering options"
|
||||
)
|
||||
boolean_filters = BooleanFilterSerializer(
|
||||
many=True,
|
||||
required=False,
|
||||
help_text="Available boolean filter options"
|
||||
child=FilterRangeSerializer(), help_text="Range filter metadata with min/max/step/unit"
|
||||
)
|
||||
total_count = serializers.IntegerField(help_text="Total number of items in the filtered dataset")
|
||||
ordering_options = FilterOptionSerializer(many=True, required=False, help_text="Available ordering options")
|
||||
boolean_filters = BooleanFilterSerializer(many=True, required=False, help_text="Available boolean filter options")
|
||||
|
||||
|
||||
class PaginationMetadataSerializer(serializers.Serializer):
|
||||
@@ -157,28 +120,13 @@ class PaginationMetadataSerializer(serializers.Serializer):
|
||||
total_pages: number;
|
||||
}
|
||||
"""
|
||||
count = serializers.IntegerField(
|
||||
help_text="Total number of items across all pages"
|
||||
)
|
||||
next = serializers.URLField(
|
||||
allow_null=True,
|
||||
required=False,
|
||||
help_text="URL for the next page of results"
|
||||
)
|
||||
previous = serializers.URLField(
|
||||
allow_null=True,
|
||||
required=False,
|
||||
help_text="URL for the previous page of results"
|
||||
)
|
||||
page_size = serializers.IntegerField(
|
||||
help_text="Number of items per page"
|
||||
)
|
||||
current_page = serializers.IntegerField(
|
||||
help_text="Current page number (1-indexed)"
|
||||
)
|
||||
total_pages = serializers.IntegerField(
|
||||
help_text="Total number of pages"
|
||||
)
|
||||
|
||||
count = serializers.IntegerField(help_text="Total number of items across all pages")
|
||||
next = serializers.URLField(allow_null=True, required=False, help_text="URL for the next page of results")
|
||||
previous = serializers.URLField(allow_null=True, required=False, help_text="URL for the previous page of results")
|
||||
page_size = serializers.IntegerField(help_text="Number of items per page")
|
||||
current_page = serializers.IntegerField(help_text="Current page number (1-indexed)")
|
||||
total_pages = serializers.IntegerField(help_text="Total number of pages")
|
||||
|
||||
|
||||
class ApiResponseSerializer(serializers.Serializer):
|
||||
@@ -193,22 +141,14 @@ class ApiResponseSerializer(serializers.Serializer):
|
||||
errors?: ValidationError;
|
||||
}
|
||||
"""
|
||||
success = serializers.BooleanField(
|
||||
help_text="Whether the request was successful"
|
||||
)
|
||||
|
||||
success = serializers.BooleanField(help_text="Whether the request was successful")
|
||||
response_data = serializers.JSONField(
|
||||
required=False,
|
||||
help_text="Response data (structure varies by endpoint)",
|
||||
source='data'
|
||||
)
|
||||
message = serializers.CharField(
|
||||
required=False,
|
||||
help_text="Human-readable message about the operation"
|
||||
required=False, help_text="Response data (structure varies by endpoint)", source="data"
|
||||
)
|
||||
message = serializers.CharField(required=False, help_text="Human-readable message about the operation")
|
||||
response_errors = serializers.DictField(
|
||||
required=False,
|
||||
help_text="Validation errors (field -> error messages)",
|
||||
source='errors'
|
||||
required=False, help_text="Validation errors (field -> error messages)", source="errors"
|
||||
)
|
||||
|
||||
|
||||
@@ -228,18 +168,11 @@ class ErrorResponseSerializer(serializers.Serializer):
|
||||
data: null;
|
||||
}
|
||||
"""
|
||||
status = serializers.CharField(
|
||||
default="error",
|
||||
help_text="Response status indicator"
|
||||
)
|
||||
error = serializers.DictField(
|
||||
help_text="Error details"
|
||||
)
|
||||
|
||||
status = serializers.CharField(default="error", help_text="Response status indicator")
|
||||
error = serializers.DictField(help_text="Error details")
|
||||
response_data = serializers.JSONField(
|
||||
default=None,
|
||||
allow_null=True,
|
||||
help_text="Always null for error responses",
|
||||
source='data'
|
||||
default=None, allow_null=True, help_text="Always null for error responses", source="data"
|
||||
)
|
||||
|
||||
|
||||
@@ -257,32 +190,13 @@ class LocationSerializer(serializers.Serializer):
|
||||
longitude?: number;
|
||||
}
|
||||
"""
|
||||
city = serializers.CharField(
|
||||
help_text="City name"
|
||||
)
|
||||
state = serializers.CharField(
|
||||
required=False,
|
||||
allow_null=True,
|
||||
help_text="State/province name"
|
||||
)
|
||||
country = serializers.CharField(
|
||||
help_text="Country name"
|
||||
)
|
||||
address = serializers.CharField(
|
||||
required=False,
|
||||
allow_null=True,
|
||||
help_text="Street address"
|
||||
)
|
||||
latitude = serializers.FloatField(
|
||||
required=False,
|
||||
allow_null=True,
|
||||
help_text="Latitude coordinate"
|
||||
)
|
||||
longitude = serializers.FloatField(
|
||||
required=False,
|
||||
allow_null=True,
|
||||
help_text="Longitude coordinate"
|
||||
)
|
||||
|
||||
city = serializers.CharField(help_text="City name")
|
||||
state = serializers.CharField(required=False, allow_null=True, help_text="State/province name")
|
||||
country = serializers.CharField(help_text="Country name")
|
||||
address = serializers.CharField(required=False, allow_null=True, help_text="Street address")
|
||||
latitude = serializers.FloatField(required=False, allow_null=True, help_text="Latitude coordinate")
|
||||
longitude = serializers.FloatField(required=False, allow_null=True, help_text="Longitude coordinate")
|
||||
|
||||
|
||||
# Alias for backward compatibility
|
||||
@@ -301,24 +215,15 @@ class CompanyOutputSerializer(serializers.Serializer):
|
||||
roles?: string[];
|
||||
}
|
||||
"""
|
||||
id = serializers.IntegerField(
|
||||
help_text="Company ID"
|
||||
)
|
||||
name = serializers.CharField(
|
||||
help_text="Company name"
|
||||
)
|
||||
slug = serializers.SlugField(
|
||||
help_text="URL-friendly identifier"
|
||||
)
|
||||
|
||||
id = serializers.IntegerField(help_text="Company ID")
|
||||
name = serializers.CharField(help_text="Company name")
|
||||
slug = serializers.SlugField(help_text="URL-friendly identifier")
|
||||
roles = serializers.ListField(
|
||||
child=serializers.CharField(),
|
||||
required=False,
|
||||
help_text="Company roles (manufacturer, operator, etc.)"
|
||||
child=serializers.CharField(), required=False, help_text="Company roles (manufacturer, operator, etc.)"
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
class ModelChoices:
|
||||
"""
|
||||
Utility class to provide model choices for serializers using Rich Choice Objects.
|
||||
@@ -331,6 +236,7 @@ class ModelChoices:
|
||||
def get_park_status_choices():
|
||||
"""Get park status choices from Rich Choice registry."""
|
||||
from apps.core.choices.registry import get_choices
|
||||
|
||||
choices = get_choices("statuses", "parks")
|
||||
return [(choice.value, choice.label) for choice in choices]
|
||||
|
||||
@@ -338,6 +244,7 @@ class ModelChoices:
|
||||
def get_ride_status_choices():
|
||||
"""Get ride status choices from Rich Choice registry."""
|
||||
from apps.core.choices.registry import get_choices
|
||||
|
||||
choices = get_choices("statuses", "rides")
|
||||
return [(choice.value, choice.label) for choice in choices]
|
||||
|
||||
@@ -345,6 +252,7 @@ class ModelChoices:
|
||||
def get_company_role_choices():
|
||||
"""Get company role choices from Rich Choice registry."""
|
||||
from apps.core.choices.registry import get_choices
|
||||
|
||||
# Get rides domain company roles (MANUFACTURER, DESIGNER)
|
||||
rides_choices = get_choices("company_roles", "rides")
|
||||
# Get parks domain company roles (OPERATOR, PROPERTY_OWNER)
|
||||
@@ -356,6 +264,7 @@ class ModelChoices:
|
||||
def get_ride_category_choices():
|
||||
"""Get ride category choices from Rich Choice registry."""
|
||||
from apps.core.choices.registry import get_choices
|
||||
|
||||
choices = get_choices("categories", "rides")
|
||||
return [(choice.value, choice.label) for choice in choices]
|
||||
|
||||
@@ -363,6 +272,7 @@ class ModelChoices:
|
||||
def get_ride_post_closing_choices():
|
||||
"""Get ride post-closing status choices from Rich Choice registry."""
|
||||
from apps.core.choices.registry import get_choices
|
||||
|
||||
choices = get_choices("post_closing_statuses", "rides")
|
||||
return [(choice.value, choice.label) for choice in choices]
|
||||
|
||||
@@ -370,6 +280,7 @@ class ModelChoices:
|
||||
def get_coaster_track_choices():
|
||||
"""Get coaster track material choices from Rich Choice registry."""
|
||||
from apps.core.choices.registry import get_choices
|
||||
|
||||
choices = get_choices("track_materials", "rides")
|
||||
return [(choice.value, choice.label) for choice in choices]
|
||||
|
||||
@@ -377,6 +288,7 @@ class ModelChoices:
|
||||
def get_coaster_type_choices():
|
||||
"""Get coaster type choices from Rich Choice registry."""
|
||||
from apps.core.choices.registry import get_choices
|
||||
|
||||
choices = get_choices("coaster_types", "rides")
|
||||
return [(choice.value, choice.label) for choice in choices]
|
||||
|
||||
@@ -384,6 +296,7 @@ class ModelChoices:
|
||||
def get_launch_choices():
|
||||
"""Get launch system choices from Rich Choice registry (legacy method)."""
|
||||
from apps.core.choices.registry import get_choices
|
||||
|
||||
choices = get_choices("propulsion_systems", "rides")
|
||||
return [(choice.value, choice.label) for choice in choices]
|
||||
|
||||
@@ -391,6 +304,7 @@ class ModelChoices:
|
||||
def get_propulsion_system_choices():
|
||||
"""Get propulsion system choices from Rich Choice registry."""
|
||||
from apps.core.choices.registry import get_choices
|
||||
|
||||
choices = get_choices("propulsion_systems", "rides")
|
||||
return [(choice.value, choice.label) for choice in choices]
|
||||
|
||||
@@ -398,6 +312,7 @@ class ModelChoices:
|
||||
def get_photo_type_choices():
|
||||
"""Get photo type choices from Rich Choice registry."""
|
||||
from apps.core.choices.registry import get_choices
|
||||
|
||||
choices = get_choices("photo_types", "rides")
|
||||
return [(choice.value, choice.label) for choice in choices]
|
||||
|
||||
@@ -405,6 +320,7 @@ class ModelChoices:
|
||||
def get_spec_category_choices():
|
||||
"""Get technical specification category choices from Rich Choice registry."""
|
||||
from apps.core.choices.registry import get_choices
|
||||
|
||||
choices = get_choices("spec_categories", "rides")
|
||||
return [(choice.value, choice.label) for choice in choices]
|
||||
|
||||
@@ -412,6 +328,7 @@ class ModelChoices:
|
||||
def get_technical_spec_category_choices():
|
||||
"""Get technical specification category choices from Rich Choice registry."""
|
||||
from apps.core.choices.registry import get_choices
|
||||
|
||||
choices = get_choices("spec_categories", "rides")
|
||||
return [(choice.value, choice.label) for choice in choices]
|
||||
|
||||
@@ -419,6 +336,7 @@ class ModelChoices:
|
||||
def get_target_market_choices():
|
||||
"""Get target market choices from Rich Choice registry."""
|
||||
from apps.core.choices.registry import get_choices
|
||||
|
||||
choices = get_choices("target_markets", "rides")
|
||||
return [(choice.value, choice.label) for choice in choices]
|
||||
|
||||
@@ -426,6 +344,7 @@ class ModelChoices:
|
||||
def get_entity_type_choices():
|
||||
"""Get entity type choices for search functionality."""
|
||||
from apps.core.choices.registry import get_choices
|
||||
|
||||
choices = get_choices("entity_types", "core")
|
||||
return [(choice.value, choice.label) for choice in choices]
|
||||
|
||||
@@ -433,6 +352,7 @@ class ModelChoices:
|
||||
def get_health_status_choices():
|
||||
"""Get health check status choices from Rich Choice registry."""
|
||||
from apps.core.choices.registry import get_choices
|
||||
|
||||
choices = get_choices("health_statuses", "core")
|
||||
return [(choice.value, choice.label) for choice in choices]
|
||||
|
||||
@@ -440,6 +360,7 @@ class ModelChoices:
|
||||
def get_simple_health_status_choices():
|
||||
"""Get simple health check status choices from Rich Choice registry."""
|
||||
from apps.core.choices.registry import get_choices
|
||||
|
||||
choices = get_choices("simple_health_statuses", "core")
|
||||
return [(choice.value, choice.label) for choice in choices]
|
||||
|
||||
@@ -455,15 +376,10 @@ class EntityReferenceSerializer(serializers.Serializer):
|
||||
slug: string;
|
||||
}
|
||||
"""
|
||||
id = serializers.IntegerField(
|
||||
help_text="Unique identifier"
|
||||
)
|
||||
name = serializers.CharField(
|
||||
help_text="Display name"
|
||||
)
|
||||
slug = serializers.SlugField(
|
||||
help_text="URL-friendly identifier"
|
||||
)
|
||||
|
||||
id = serializers.IntegerField(help_text="Unique identifier")
|
||||
name = serializers.CharField(help_text="Display name")
|
||||
slug = serializers.SlugField(help_text="URL-friendly identifier")
|
||||
|
||||
|
||||
class ImageVariantsSerializer(serializers.Serializer):
|
||||
@@ -478,19 +394,11 @@ class ImageVariantsSerializer(serializers.Serializer):
|
||||
avatar?: string;
|
||||
}
|
||||
"""
|
||||
thumbnail = serializers.URLField(
|
||||
help_text="Thumbnail size image URL"
|
||||
)
|
||||
medium = serializers.URLField(
|
||||
help_text="Medium size image URL"
|
||||
)
|
||||
large = serializers.URLField(
|
||||
help_text="Large size image URL"
|
||||
)
|
||||
avatar = serializers.URLField(
|
||||
required=False,
|
||||
help_text="Avatar size image URL (for user avatars)"
|
||||
)
|
||||
|
||||
thumbnail = serializers.URLField(help_text="Thumbnail size image URL")
|
||||
medium = serializers.URLField(help_text="Medium size image URL")
|
||||
large = serializers.URLField(help_text="Large size image URL")
|
||||
avatar = serializers.URLField(required=False, help_text="Avatar size image URL (for user avatars)")
|
||||
|
||||
|
||||
class PhotoSerializer(serializers.Serializer):
|
||||
@@ -509,39 +417,15 @@ class PhotoSerializer(serializers.Serializer):
|
||||
uploaded_at?: string;
|
||||
}
|
||||
"""
|
||||
id = serializers.IntegerField(
|
||||
help_text="Photo ID"
|
||||
)
|
||||
image_variants = ImageVariantsSerializer(
|
||||
help_text="Available image size variants"
|
||||
)
|
||||
alt_text = serializers.CharField(
|
||||
required=False,
|
||||
allow_null=True,
|
||||
help_text="Alternative text for accessibility"
|
||||
)
|
||||
image_url = serializers.URLField(
|
||||
required=False,
|
||||
help_text="Primary image URL (for compatibility)"
|
||||
)
|
||||
caption = serializers.CharField(
|
||||
required=False,
|
||||
allow_null=True,
|
||||
help_text="Photo caption"
|
||||
)
|
||||
photo_type = serializers.CharField(
|
||||
required=False,
|
||||
allow_null=True,
|
||||
help_text="Type/category of photo"
|
||||
)
|
||||
uploaded_by = EntityReferenceSerializer(
|
||||
required=False,
|
||||
help_text="User who uploaded the photo"
|
||||
)
|
||||
uploaded_at = serializers.DateTimeField(
|
||||
required=False,
|
||||
help_text="When the photo was uploaded"
|
||||
)
|
||||
|
||||
id = serializers.IntegerField(help_text="Photo ID")
|
||||
image_variants = ImageVariantsSerializer(help_text="Available image size variants")
|
||||
alt_text = serializers.CharField(required=False, allow_null=True, help_text="Alternative text for accessibility")
|
||||
image_url = serializers.URLField(required=False, help_text="Primary image URL (for compatibility)")
|
||||
caption = serializers.CharField(required=False, allow_null=True, help_text="Photo caption")
|
||||
photo_type = serializers.CharField(required=False, allow_null=True, help_text="Type/category of photo")
|
||||
uploaded_by = EntityReferenceSerializer(required=False, help_text="User who uploaded the photo")
|
||||
uploaded_at = serializers.DateTimeField(required=False, help_text="When the photo was uploaded")
|
||||
|
||||
|
||||
class UserInfoSerializer(serializers.Serializer):
|
||||
@@ -556,20 +440,11 @@ class UserInfoSerializer(serializers.Serializer):
|
||||
avatar_url?: string;
|
||||
}
|
||||
"""
|
||||
id = serializers.IntegerField(
|
||||
help_text="User ID"
|
||||
)
|
||||
username = serializers.CharField(
|
||||
help_text="Username"
|
||||
)
|
||||
display_name = serializers.CharField(
|
||||
help_text="Display name"
|
||||
)
|
||||
avatar_url = serializers.URLField(
|
||||
required=False,
|
||||
allow_null=True,
|
||||
help_text="User avatar URL"
|
||||
)
|
||||
|
||||
id = serializers.IntegerField(help_text="User ID")
|
||||
username = serializers.CharField(help_text="Username")
|
||||
display_name = serializers.CharField(help_text="Display name")
|
||||
avatar_url = serializers.URLField(required=False, allow_null=True, help_text="User avatar URL")
|
||||
|
||||
|
||||
def validate_filter_metadata_contract(data: dict[str, Any]) -> dict[str, Any]:
|
||||
@@ -613,27 +488,22 @@ def ensure_filter_option_format(options: list[Any]) -> list[dict[str, Any]]:
|
||||
if isinstance(option, dict):
|
||||
# Already in correct format or close to it
|
||||
standardized_option = {
|
||||
'value': str(option.get('value', option.get('id', ''))),
|
||||
'label': option.get('label', option.get('name', str(option.get('value', '')))),
|
||||
'count': option.get('count'),
|
||||
'selected': option.get('selected', False)
|
||||
"value": str(option.get("value", option.get("id", ""))),
|
||||
"label": option.get("label", option.get("name", str(option.get("value", "")))),
|
||||
"count": option.get("count"),
|
||||
"selected": option.get("selected", False),
|
||||
}
|
||||
elif hasattr(option, 'value') and hasattr(option, 'label'):
|
||||
elif hasattr(option, "value") and hasattr(option, "label"):
|
||||
# RichChoice object format
|
||||
standardized_option = {
|
||||
'value': str(option.value),
|
||||
'label': str(option.label),
|
||||
'count': None,
|
||||
'selected': False
|
||||
"value": str(option.value),
|
||||
"label": str(option.label),
|
||||
"count": None,
|
||||
"selected": False,
|
||||
}
|
||||
else:
|
||||
# Simple value - use as both value and label
|
||||
standardized_option = {
|
||||
'value': str(option),
|
||||
'label': str(option),
|
||||
'count': None,
|
||||
'selected': False
|
||||
}
|
||||
standardized_option = {"value": str(option), "label": str(option), "count": None, "selected": False}
|
||||
|
||||
standardized.append(standardized_option)
|
||||
|
||||
@@ -651,8 +521,8 @@ def ensure_range_format(range_data: dict[str, Any]) -> dict[str, Any]:
|
||||
Range data in standard format
|
||||
"""
|
||||
return {
|
||||
'min': range_data.get('min'),
|
||||
'max': range_data.get('max'),
|
||||
'step': range_data.get('step', 1.0),
|
||||
'unit': range_data.get('unit')
|
||||
"min": range_data.get("min"),
|
||||
"max": range_data.get("max"),
|
||||
"step": range_data.get("step", 1.0),
|
||||
"unit": range_data.get("unit"),
|
||||
}
|
||||
|
||||
@@ -16,120 +16,56 @@ class StatsSerializer(serializers.Serializer):
|
||||
"""
|
||||
|
||||
# Core entity counts
|
||||
total_parks = serializers.IntegerField(
|
||||
help_text="Total number of parks in the database"
|
||||
)
|
||||
total_rides = serializers.IntegerField(
|
||||
help_text="Total number of rides in the database"
|
||||
)
|
||||
total_manufacturers = serializers.IntegerField(
|
||||
help_text="Total number of ride manufacturers"
|
||||
)
|
||||
total_operators = serializers.IntegerField(
|
||||
help_text="Total number of park operators"
|
||||
)
|
||||
total_designers = serializers.IntegerField(
|
||||
help_text="Total number of ride designers"
|
||||
)
|
||||
total_property_owners = serializers.IntegerField(
|
||||
help_text="Total number of property owners"
|
||||
)
|
||||
total_roller_coasters = serializers.IntegerField(
|
||||
help_text="Total number of roller coasters with detailed stats"
|
||||
)
|
||||
total_parks = serializers.IntegerField(help_text="Total number of parks in the database")
|
||||
total_rides = serializers.IntegerField(help_text="Total number of rides in the database")
|
||||
total_manufacturers = serializers.IntegerField(help_text="Total number of ride manufacturers")
|
||||
total_operators = serializers.IntegerField(help_text="Total number of park operators")
|
||||
total_designers = serializers.IntegerField(help_text="Total number of ride designers")
|
||||
total_property_owners = serializers.IntegerField(help_text="Total number of property owners")
|
||||
total_roller_coasters = serializers.IntegerField(help_text="Total number of roller coasters with detailed stats")
|
||||
|
||||
# Photo counts
|
||||
total_photos = serializers.IntegerField(
|
||||
help_text="Total number of photos (parks + rides combined)"
|
||||
)
|
||||
total_park_photos = serializers.IntegerField(
|
||||
help_text="Total number of park photos"
|
||||
)
|
||||
total_ride_photos = serializers.IntegerField(
|
||||
help_text="Total number of ride photos"
|
||||
)
|
||||
total_photos = serializers.IntegerField(help_text="Total number of photos (parks + rides combined)")
|
||||
total_park_photos = serializers.IntegerField(help_text="Total number of park photos")
|
||||
total_ride_photos = serializers.IntegerField(help_text="Total number of ride photos")
|
||||
|
||||
# Review counts
|
||||
total_reviews = serializers.IntegerField(
|
||||
help_text="Total number of reviews (parks + rides)"
|
||||
)
|
||||
total_park_reviews = serializers.IntegerField(
|
||||
help_text="Total number of park reviews"
|
||||
)
|
||||
total_ride_reviews = serializers.IntegerField(
|
||||
help_text="Total number of ride reviews"
|
||||
)
|
||||
total_reviews = serializers.IntegerField(help_text="Total number of reviews (parks + rides)")
|
||||
total_park_reviews = serializers.IntegerField(help_text="Total number of park reviews")
|
||||
total_ride_reviews = serializers.IntegerField(help_text="Total number of ride reviews")
|
||||
|
||||
# Ride category counts (optional fields since they depend on data)
|
||||
roller_coasters = serializers.IntegerField(
|
||||
required=False, help_text="Number of rides categorized as roller coasters"
|
||||
)
|
||||
dark_rides = serializers.IntegerField(
|
||||
required=False, help_text="Number of rides categorized as dark rides"
|
||||
)
|
||||
flat_rides = serializers.IntegerField(
|
||||
required=False, help_text="Number of rides categorized as flat rides"
|
||||
)
|
||||
water_rides = serializers.IntegerField(
|
||||
required=False, help_text="Number of rides categorized as water rides"
|
||||
)
|
||||
dark_rides = serializers.IntegerField(required=False, help_text="Number of rides categorized as dark rides")
|
||||
flat_rides = serializers.IntegerField(required=False, help_text="Number of rides categorized as flat rides")
|
||||
water_rides = serializers.IntegerField(required=False, help_text="Number of rides categorized as water rides")
|
||||
transport_rides = serializers.IntegerField(
|
||||
required=False, help_text="Number of rides categorized as transport rides"
|
||||
)
|
||||
other_rides = serializers.IntegerField(
|
||||
required=False, help_text="Number of rides categorized as other"
|
||||
)
|
||||
other_rides = serializers.IntegerField(required=False, help_text="Number of rides categorized as other")
|
||||
|
||||
# Park status counts (optional fields since they depend on data)
|
||||
operating_parks = serializers.IntegerField(
|
||||
required=False, help_text="Number of currently operating parks"
|
||||
)
|
||||
temporarily_closed_parks = serializers.IntegerField(
|
||||
required=False, help_text="Number of temporarily closed parks"
|
||||
)
|
||||
permanently_closed_parks = serializers.IntegerField(
|
||||
required=False, help_text="Number of permanently closed parks"
|
||||
)
|
||||
under_construction_parks = serializers.IntegerField(
|
||||
required=False, help_text="Number of parks under construction"
|
||||
)
|
||||
demolished_parks = serializers.IntegerField(
|
||||
required=False, help_text="Number of demolished parks"
|
||||
)
|
||||
relocated_parks = serializers.IntegerField(
|
||||
required=False, help_text="Number of relocated parks"
|
||||
)
|
||||
operating_parks = serializers.IntegerField(required=False, help_text="Number of currently operating parks")
|
||||
temporarily_closed_parks = serializers.IntegerField(required=False, help_text="Number of temporarily closed parks")
|
||||
permanently_closed_parks = serializers.IntegerField(required=False, help_text="Number of permanently closed parks")
|
||||
under_construction_parks = serializers.IntegerField(required=False, help_text="Number of parks under construction")
|
||||
demolished_parks = serializers.IntegerField(required=False, help_text="Number of demolished parks")
|
||||
relocated_parks = serializers.IntegerField(required=False, help_text="Number of relocated parks")
|
||||
|
||||
# Ride status counts (optional fields since they depend on data)
|
||||
operating_rides = serializers.IntegerField(
|
||||
required=False, help_text="Number of currently operating rides"
|
||||
)
|
||||
temporarily_closed_rides = serializers.IntegerField(
|
||||
required=False, help_text="Number of temporarily closed rides"
|
||||
)
|
||||
sbno_rides = serializers.IntegerField(
|
||||
required=False, help_text="Number of rides standing but not operating"
|
||||
)
|
||||
closing_rides = serializers.IntegerField(
|
||||
required=False, help_text="Number of rides in the process of closing"
|
||||
)
|
||||
permanently_closed_rides = serializers.IntegerField(
|
||||
required=False, help_text="Number of permanently closed rides"
|
||||
)
|
||||
under_construction_rides = serializers.IntegerField(
|
||||
required=False, help_text="Number of rides under construction"
|
||||
)
|
||||
demolished_rides = serializers.IntegerField(
|
||||
required=False, help_text="Number of demolished rides"
|
||||
)
|
||||
relocated_rides = serializers.IntegerField(
|
||||
required=False, help_text="Number of relocated rides"
|
||||
)
|
||||
operating_rides = serializers.IntegerField(required=False, help_text="Number of currently operating rides")
|
||||
temporarily_closed_rides = serializers.IntegerField(required=False, help_text="Number of temporarily closed rides")
|
||||
sbno_rides = serializers.IntegerField(required=False, help_text="Number of rides standing but not operating")
|
||||
closing_rides = serializers.IntegerField(required=False, help_text="Number of rides in the process of closing")
|
||||
permanently_closed_rides = serializers.IntegerField(required=False, help_text="Number of permanently closed rides")
|
||||
under_construction_rides = serializers.IntegerField(required=False, help_text="Number of rides under construction")
|
||||
demolished_rides = serializers.IntegerField(required=False, help_text="Number of demolished rides")
|
||||
relocated_rides = serializers.IntegerField(required=False, help_text="Number of relocated rides")
|
||||
|
||||
# Metadata
|
||||
last_updated = serializers.CharField(
|
||||
help_text="ISO timestamp when these statistics were last calculated"
|
||||
)
|
||||
last_updated = serializers.CharField(help_text="ISO timestamp when these statistics were last calculated")
|
||||
relative_last_updated = serializers.CharField(
|
||||
help_text="Human-readable relative time since last update (e.g., '2 minutes ago')"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user