Files
thrilltrack-explorer/django/api/v1/endpoints/rides.py
pacnpal d6ff4cc3a3 Add email templates for user notifications and account management
- Created a base email template (base.html) for consistent styling across all emails.
- Added moderation approval email template (moderation_approved.html) to notify users of approved submissions.
- Added moderation rejection email template (moderation_rejected.html) to inform users of required changes for their submissions.
- Created password reset email template (password_reset.html) for users requesting to reset their passwords.
- Developed a welcome email template (welcome.html) to greet new users and provide account details and tips for using ThrillWiki.
2025-11-08 15:34:04 -05:00

361 lines
11 KiB
Python

"""
Ride endpoints for API v1.
Provides CRUD operations for Ride entities with filtering and search.
"""
from typing import List, Optional
from uuid import UUID
from django.shortcuts import get_object_or_404
from django.db.models import Q
from ninja import Router, Query
from ninja.pagination import paginate, PageNumberPagination
from apps.entities.models import Ride, Park, Company, RideModel
from ..schemas import (
RideCreate,
RideUpdate,
RideOut,
RideListOut,
ErrorResponse
)
router = Router(tags=["Rides"])
class RidePagination(PageNumberPagination):
"""Custom pagination for rides."""
page_size = 50
@router.get(
"/",
response={200: List[RideOut]},
summary="List rides",
description="Get a paginated list of rides with optional filtering"
)
@paginate(RidePagination)
def list_rides(
request,
search: Optional[str] = Query(None, description="Search by ride name"),
park_id: Optional[UUID] = Query(None, description="Filter by park"),
ride_category: Optional[str] = Query(None, description="Filter by ride category"),
status: Optional[str] = Query(None, description="Filter by status"),
is_coaster: Optional[bool] = Query(None, description="Filter for roller coasters only"),
manufacturer_id: Optional[UUID] = Query(None, description="Filter by manufacturer"),
ordering: Optional[str] = Query("-created", description="Sort by field (prefix with - for descending)")
):
"""
List all rides with optional filters.
**Filters:**
- search: Search ride names (case-insensitive partial match)
- park_id: Filter by park
- ride_category: Filter by ride category
- status: Filter by operational status
- is_coaster: Filter for roller coasters (true/false)
- manufacturer_id: Filter by manufacturer
- ordering: Sort results (default: -created)
**Returns:** Paginated list of rides
"""
queryset = Ride.objects.select_related('park', 'manufacturer', 'model').all()
# Apply search filter
if search:
queryset = queryset.filter(
Q(name__icontains=search) | Q(description__icontains=search)
)
# Apply park filter
if park_id:
queryset = queryset.filter(park_id=park_id)
# Apply ride category filter
if ride_category:
queryset = queryset.filter(ride_category=ride_category)
# Apply status filter
if status:
queryset = queryset.filter(status=status)
# Apply coaster filter
if is_coaster is not None:
queryset = queryset.filter(is_coaster=is_coaster)
# Apply manufacturer filter
if manufacturer_id:
queryset = queryset.filter(manufacturer_id=manufacturer_id)
# Apply ordering
valid_order_fields = ['name', 'created', 'modified', 'opening_date', 'height', 'speed', 'length']
order_field = ordering.lstrip('-')
if order_field in valid_order_fields:
queryset = queryset.order_by(ordering)
else:
queryset = queryset.order_by('-created')
# Annotate with related names
for ride in queryset:
ride.park_name = ride.park.name if ride.park else None
ride.manufacturer_name = ride.manufacturer.name if ride.manufacturer else None
ride.model_name = ride.model.name if ride.model else None
return queryset
@router.get(
"/{ride_id}",
response={200: RideOut, 404: ErrorResponse},
summary="Get ride",
description="Retrieve a single ride by ID"
)
def get_ride(request, ride_id: UUID):
"""
Get a ride by ID.
**Parameters:**
- ride_id: UUID of the ride
**Returns:** Ride details
"""
ride = get_object_or_404(
Ride.objects.select_related('park', 'manufacturer', 'model'),
id=ride_id
)
ride.park_name = ride.park.name if ride.park else None
ride.manufacturer_name = ride.manufacturer.name if ride.manufacturer else None
ride.model_name = ride.model.name if ride.model else None
return ride
@router.post(
"/",
response={201: RideOut, 400: ErrorResponse, 404: ErrorResponse},
summary="Create ride",
description="Create a new ride (requires authentication)"
)
def create_ride(request, payload: RideCreate):
"""
Create a new ride.
**Authentication:** Required
**Parameters:**
- payload: Ride data
**Returns:** Created ride
"""
# TODO: Add authentication check
# if not request.auth:
# return 401, {"detail": "Authentication required"}
# Verify park exists
park = get_object_or_404(Park, id=payload.park_id)
# Verify manufacturer if provided
if payload.manufacturer_id:
get_object_or_404(Company, id=payload.manufacturer_id)
# Verify model if provided
if payload.model_id:
get_object_or_404(RideModel, id=payload.model_id)
ride = Ride.objects.create(**payload.dict())
# Reload with related objects
ride = Ride.objects.select_related('park', 'manufacturer', 'model').get(id=ride.id)
ride.park_name = ride.park.name if ride.park else None
ride.manufacturer_name = ride.manufacturer.name if ride.manufacturer else None
ride.model_name = ride.model.name if ride.model else None
return 201, ride
@router.put(
"/{ride_id}",
response={200: RideOut, 404: ErrorResponse, 400: ErrorResponse},
summary="Update ride",
description="Update an existing ride (requires authentication)"
)
def update_ride(request, ride_id: UUID, payload: RideUpdate):
"""
Update a ride.
**Authentication:** Required
**Parameters:**
- ride_id: UUID of the ride
- payload: Updated ride data
**Returns:** Updated ride
"""
# TODO: Add authentication check
# if not request.auth:
# return 401, {"detail": "Authentication required"}
ride = get_object_or_404(
Ride.objects.select_related('park', 'manufacturer', 'model'),
id=ride_id
)
# Update only provided fields
for key, value in payload.dict(exclude_unset=True).items():
setattr(ride, key, value)
ride.save()
# Reload to get updated relationships
ride = Ride.objects.select_related('park', 'manufacturer', 'model').get(id=ride.id)
ride.park_name = ride.park.name if ride.park else None
ride.manufacturer_name = ride.manufacturer.name if ride.manufacturer else None
ride.model_name = ride.model.name if ride.model else None
return ride
@router.patch(
"/{ride_id}",
response={200: RideOut, 404: ErrorResponse, 400: ErrorResponse},
summary="Partial update ride",
description="Partially update an existing ride (requires authentication)"
)
def partial_update_ride(request, ride_id: UUID, payload: RideUpdate):
"""
Partially update a ride.
**Authentication:** Required
**Parameters:**
- ride_id: UUID of the ride
- payload: Fields to update
**Returns:** Updated ride
"""
# TODO: Add authentication check
# if not request.auth:
# return 401, {"detail": "Authentication required"}
ride = get_object_or_404(
Ride.objects.select_related('park', 'manufacturer', 'model'),
id=ride_id
)
# Update only provided fields
for key, value in payload.dict(exclude_unset=True).items():
setattr(ride, key, value)
ride.save()
# Reload to get updated relationships
ride = Ride.objects.select_related('park', 'manufacturer', 'model').get(id=ride.id)
ride.park_name = ride.park.name if ride.park else None
ride.manufacturer_name = ride.manufacturer.name if ride.manufacturer else None
ride.model_name = ride.model.name if ride.model else None
return ride
@router.delete(
"/{ride_id}",
response={204: None, 404: ErrorResponse},
summary="Delete ride",
description="Delete a ride (requires authentication)"
)
def delete_ride(request, ride_id: UUID):
"""
Delete a ride.
**Authentication:** Required
**Parameters:**
- ride_id: UUID of the ride
**Returns:** No content (204)
"""
# TODO: Add authentication check
# if not request.auth:
# return 401, {"detail": "Authentication required"}
ride = get_object_or_404(Ride, id=ride_id)
ride.delete()
return 204, None
@router.get(
"/coasters/",
response={200: List[RideOut]},
summary="List roller coasters",
description="Get a paginated list of roller coasters only"
)
@paginate(RidePagination)
def list_coasters(
request,
search: Optional[str] = Query(None, description="Search by ride name"),
park_id: Optional[UUID] = Query(None, description="Filter by park"),
status: Optional[str] = Query(None, description="Filter by status"),
manufacturer_id: Optional[UUID] = Query(None, description="Filter by manufacturer"),
min_height: Optional[float] = Query(None, description="Minimum height in feet"),
min_speed: Optional[float] = Query(None, description="Minimum speed in mph"),
ordering: Optional[str] = Query("-height", description="Sort by field (prefix with - for descending)")
):
"""
List only roller coasters with optional filters.
**Filters:**
- search: Search coaster names
- park_id: Filter by park
- status: Filter by operational status
- manufacturer_id: Filter by manufacturer
- min_height: Minimum height filter
- min_speed: Minimum speed filter
- ordering: Sort results (default: -height)
**Returns:** Paginated list of roller coasters
"""
queryset = Ride.objects.filter(is_coaster=True).select_related(
'park', 'manufacturer', 'model'
)
# Apply search filter
if search:
queryset = queryset.filter(
Q(name__icontains=search) | Q(description__icontains=search)
)
# Apply park filter
if park_id:
queryset = queryset.filter(park_id=park_id)
# Apply status filter
if status:
queryset = queryset.filter(status=status)
# Apply manufacturer filter
if manufacturer_id:
queryset = queryset.filter(manufacturer_id=manufacturer_id)
# Apply height filter
if min_height is not None:
queryset = queryset.filter(height__gte=min_height)
# Apply speed filter
if min_speed is not None:
queryset = queryset.filter(speed__gte=min_speed)
# Apply ordering
valid_order_fields = ['name', 'height', 'speed', 'length', 'opening_date', 'inversions']
order_field = ordering.lstrip('-')
if order_field in valid_order_fields:
queryset = queryset.order_by(ordering)
else:
queryset = queryset.order_by('-height')
# Annotate with related names
for ride in queryset:
ride.park_name = ride.park.name if ride.park else None
ride.manufacturer_name = ride.manufacturer.name if ride.manufacturer else None
ride.model_name = ride.model.name if ride.model else None
return queryset