mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 16:11:08 -05:00
- Add complete backend/ directory with full Django application - Add frontend/ directory with Vite + TypeScript setup ready for Next.js - Add comprehensive shared/ directory with: - Complete documentation and memory-bank archives - Media files and avatars (letters, park/ride images) - Deployment scripts and automation tools - Shared types and utilities - Add architecture/ directory with migration guides - Configure pnpm workspace for monorepo development - Update .gitignore to exclude .django_tailwind_cli/ build artifacts - Preserve all historical documentation in shared/docs/memory-bank/ - Set up proper structure for full-stack development with shared resources
324 lines
9.3 KiB
Python
324 lines
9.3 KiB
Python
"""
|
|
Services for park-related business logic.
|
|
Following Django styleguide pattern for business logic encapsulation.
|
|
"""
|
|
|
|
from typing import Optional, Dict, Any
|
|
from django.db import transaction
|
|
from django.db.models import Q
|
|
from django.contrib.auth import get_user_model
|
|
from django.contrib.auth.models import AbstractBaseUser
|
|
|
|
from .models import Park, ParkArea
|
|
from apps.location.models import Location
|
|
|
|
# Use AbstractBaseUser for type hinting
|
|
UserType = AbstractBaseUser
|
|
User = get_user_model()
|
|
|
|
|
|
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[UserType] = 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 .models import Company
|
|
|
|
park.operator = Company.objects.get(id=operator_id)
|
|
|
|
if property_owner_id:
|
|
from .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:
|
|
LocationService.create_park_location(park=park, **location_data)
|
|
|
|
return park
|
|
|
|
@staticmethod
|
|
def update_park(
|
|
*,
|
|
park_id: int,
|
|
updates: Dict[str, Any],
|
|
updated_by: Optional[UserType] = 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[UserType] = 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[UserType] = 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 .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
|
|
|
|
|
|
class LocationService:
|
|
"""Service for managing location operations."""
|
|
|
|
@staticmethod
|
|
def create_park_location(
|
|
*,
|
|
park: Park,
|
|
latitude: Optional[float] = None,
|
|
longitude: Optional[float] = None,
|
|
street_address: str = "",
|
|
city: str = "",
|
|
state: str = "",
|
|
country: str = "",
|
|
postal_code: str = "",
|
|
) -> Location:
|
|
"""
|
|
Create a location for a park.
|
|
|
|
Args:
|
|
park: Park instance
|
|
latitude: Latitude coordinate
|
|
longitude: Longitude coordinate
|
|
street_address: Street address
|
|
city: City name
|
|
state: State/region name
|
|
country: Country name
|
|
postal_code: Postal/ZIP code
|
|
|
|
Returns:
|
|
Created Location instance
|
|
|
|
Raises:
|
|
ValidationError: If location data is invalid
|
|
"""
|
|
location = Location(
|
|
content_object=park,
|
|
name=park.name,
|
|
location_type="park",
|
|
latitude=latitude,
|
|
longitude=longitude,
|
|
street_address=street_address,
|
|
city=city,
|
|
state=state,
|
|
country=country,
|
|
postal_code=postal_code,
|
|
)
|
|
|
|
# CRITICAL STYLEGUIDE FIX: Call full_clean before save
|
|
location.full_clean()
|
|
location.save()
|
|
|
|
return location
|
|
|
|
@staticmethod
|
|
def update_park_location(
|
|
*, park_id: int, location_updates: Dict[str, Any]
|
|
) -> Location:
|
|
"""
|
|
Update location information for a park.
|
|
|
|
Args:
|
|
park_id: ID of the park
|
|
location_updates: Dictionary of location field updates
|
|
|
|
Returns:
|
|
Updated Location instance
|
|
|
|
Raises:
|
|
Location.DoesNotExist: If location doesn't exist
|
|
ValidationError: If location data is invalid
|
|
"""
|
|
with transaction.atomic():
|
|
park = Park.objects.get(id=park_id)
|
|
|
|
try:
|
|
location = park.location
|
|
except Location.DoesNotExist:
|
|
# Create location if it doesn't exist
|
|
return LocationService.create_park_location(
|
|
park=park, **location_updates
|
|
)
|
|
|
|
# Apply updates
|
|
for field, value in location_updates.items():
|
|
if hasattr(location, field):
|
|
setattr(location, field, value)
|
|
|
|
# CRITICAL STYLEGUIDE FIX: Call full_clean before save
|
|
location.full_clean()
|
|
location.save()
|
|
|
|
return location
|