""" Services for park-related business logic. Following Django styleguide pattern for business logic encapsulation. """ from typing import Optional, Dict, Any, TYPE_CHECKING from django.db import transaction from django.db.models import Q if TYPE_CHECKING: from django.contrib.auth.models import AbstractUser from ..models import Park, ParkArea from .location_service import ParkLocationService class ParkService: """Service for managing park operations.""" @staticmethod def create_park( *, name: str, description: str = "", status: str = "OPERATING", operator_id: Optional[int] = None, property_owner_id: Optional[int] = None, opening_date: Optional[str] = None, closing_date: Optional[str] = None, operating_season: str = "", size_acres: Optional[float] = None, website: str = "", location_data: Optional[Dict[str, Any]] = None, created_by: Optional["AbstractUser"] = None, ) -> Park: """ Create a new park with validation and location handling. Args: name: Park name description: Park description status: Operating status operator_id: ID of operating company property_owner_id: ID of property owner company opening_date: Opening date closing_date: Closing date operating_season: Operating season description size_acres: Park size in acres website: Park website URL location_data: Dictionary containing location information created_by: User creating the park Returns: Created Park instance Raises: ValidationError: If park data is invalid """ with transaction.atomic(): # Create park instance park = Park( name=name, description=description, status=status, opening_date=opening_date, closing_date=closing_date, operating_season=operating_season, size_acres=size_acres, website=website, ) # Set foreign key relationships if provided if operator_id: from apps.parks.models import Company park.operator = Company.objects.get(id=operator_id) if property_owner_id: from apps.parks.models import Company park.property_owner = Company.objects.get(id=property_owner_id) # CRITICAL STYLEGUIDE FIX: Call full_clean before save park.full_clean() park.save() # Handle location if provided if location_data: ParkLocationService.create_park_location(park=park, **location_data) return park @staticmethod def update_park( *, park_id: int, updates: Dict[str, Any], updated_by: Optional["AbstractUser"] = None, ) -> Park: """ Update an existing park with validation. Args: park_id: ID of park to update updates: Dictionary of field updates updated_by: User performing the update Returns: Updated Park instance Raises: Park.DoesNotExist: If park doesn't exist ValidationError: If update data is invalid """ with transaction.atomic(): park = Park.objects.select_for_update().get(id=park_id) # Apply updates for field, value in updates.items(): if hasattr(park, field): setattr(park, field, value) # CRITICAL STYLEGUIDE FIX: Call full_clean before save park.full_clean() park.save() return park @staticmethod def delete_park( *, park_id: int, deleted_by: Optional["AbstractUser"] = None ) -> bool: """ Soft delete a park by setting status to DEMOLISHED. Args: park_id: ID of park to delete deleted_by: User performing the deletion Returns: True if successfully deleted Raises: Park.DoesNotExist: If park doesn't exist """ with transaction.atomic(): park = Park.objects.select_for_update().get(id=park_id) park.status = "DEMOLISHED" # CRITICAL STYLEGUIDE FIX: Call full_clean before save park.full_clean() park.save() return True @staticmethod def create_park_area( *, park_id: int, name: str, description: str = "", created_by: Optional["AbstractUser"] = None, ) -> ParkArea: """ Create a new area within a park. Args: park_id: ID of the parent park name: Area name description: Area description created_by: User creating the area Returns: Created ParkArea instance Raises: Park.DoesNotExist: If park doesn't exist ValidationError: If area data is invalid """ park = Park.objects.get(id=park_id) area = ParkArea(park=park, name=name, description=description) # CRITICAL STYLEGUIDE FIX: Call full_clean before save area.full_clean() area.save() return area @staticmethod def update_park_statistics(*, park_id: int) -> Park: """ Recalculate and update park statistics (ride counts, ratings). Args: park_id: ID of park to update statistics for Returns: Updated Park instance with fresh statistics """ from apps.rides.models import Ride from apps.parks.models import ParkReview from django.db.models import Count, Avg with transaction.atomic(): park = Park.objects.select_for_update().get(id=park_id) # Calculate ride counts ride_stats = Ride.objects.filter(park=park).aggregate( total_rides=Count("id"), coaster_count=Count("id", filter=Q(category__in=["RC", "WC"])), ) # Calculate average rating avg_rating = ParkReview.objects.filter( park=park, is_published=True ).aggregate(avg_rating=Avg("rating"))["avg_rating"] # Update park fields park.ride_count = ride_stats["total_rides"] or 0 park.coaster_count = ride_stats["coaster_count"] or 0 park.average_rating = avg_rating # CRITICAL STYLEGUIDE FIX: Call full_clean before save park.full_clean() park.save() return park