""" 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