Files
thrillwiki_django_no_react/backend/apps/rides/services.py
pacnpal 2e35f8c5d9 feat: Refactor rides app with unique constraints, mixins, and enhanced documentation
- Added migration to convert unique_together constraints to UniqueConstraint for RideModel.
- Introduced RideFormMixin for handling entity suggestions in ride forms.
- Created comprehensive code standards documentation outlining formatting, docstring requirements, complexity guidelines, and testing requirements.
- Established error handling guidelines with a structured exception hierarchy and best practices for API and view error handling.
- Documented view pattern guidelines, emphasizing the use of CBVs, FBVs, and ViewSets with examples.
- Implemented a benchmarking script for query performance analysis and optimization.
- Developed security documentation detailing measures, configurations, and a security checklist.
- Compiled a database optimization guide covering indexing strategies, query optimization patterns, and computed fields.
2025-12-22 11:17:31 -05:00

392 lines
12 KiB
Python

"""
Services for ride-related business logic.
Following Django styleguide pattern for business logic encapsulation.
"""
from typing import Optional, Dict, Any
from django.db import transaction
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AbstractBaseUser
from apps.rides.models import Ride
# Use AbstractBaseUser for type hinting
UserType = AbstractBaseUser
User = get_user_model()
class RideService:
"""Service for managing ride operations."""
@staticmethod
def create_ride(
*,
name: str,
park_id: int,
description: str = "",
status: str = "OPERATING",
category: str = "",
manufacturer_id: Optional[int] = None,
designer_id: Optional[int] = None,
ride_model_id: Optional[int] = None,
park_area_id: Optional[int] = None,
opening_date: Optional[str] = None,
closing_date: Optional[str] = None,
created_by: Optional[UserType] = None,
) -> Ride:
"""
Create a new ride with validation.
Args:
name: Ride name
park_id: ID of the park
description: Ride description
status: Operating status
category: Ride category
manufacturer_id: ID of manufacturer company
designer_id: ID of designer company
ride_model_id: ID of ride model
park_area_id: ID of park area
opening_date: Opening date
closing_date: Closing date
created_by: User creating the ride
Returns:
Created Ride instance
Raises:
ValidationError: If ride data is invalid
"""
with transaction.atomic():
from apps.parks.models import Park
# Get park
park = Park.objects.get(id=park_id)
# Create ride instance
ride = Ride(
name=name,
park=park,
description=description,
status=status,
category=category,
opening_date=opening_date,
closing_date=closing_date,
)
# Set foreign key relationships if provided
if park_area_id:
from apps.parks.models import ParkArea
ride.park_area = ParkArea.objects.get(id=park_area_id)
if manufacturer_id:
from apps.rides.models import Company
ride.manufacturer = Company.objects.get(id=manufacturer_id)
if designer_id:
from apps.rides.models import Company
ride.designer = Company.objects.get(id=designer_id)
if ride_model_id:
from apps.rides.models import RideModel
ride.ride_model = RideModel.objects.get(id=ride_model_id)
# CRITICAL STYLEGUIDE FIX: Call full_clean before save
ride.full_clean()
ride.save()
return ride
@staticmethod
def update_ride(
*,
ride_id: int,
updates: Dict[str, Any],
updated_by: Optional[UserType] = None,
) -> Ride:
"""
Update an existing ride with validation.
Args:
ride_id: ID of ride to update
updates: Dictionary of field updates
updated_by: User performing the update
Returns:
Updated Ride instance
Raises:
Ride.DoesNotExist: If ride doesn't exist
ValidationError: If update data is invalid
"""
with transaction.atomic():
ride = Ride.objects.select_for_update().get(id=ride_id)
# Apply updates
for field, value in updates.items():
if hasattr(ride, field):
setattr(ride, field, value)
# CRITICAL STYLEGUIDE FIX: Call full_clean before save
ride.full_clean()
ride.save()
return ride
@staticmethod
def close_ride_temporarily(
*, ride_id: int, user: Optional[UserType] = None
) -> Ride:
"""
Temporarily close a ride.
Args:
ride_id: ID of ride to close temporarily
user: User performing the action
Returns:
Updated Ride instance
Raises:
Ride.DoesNotExist: If ride doesn't exist
"""
with transaction.atomic():
ride = Ride.objects.select_for_update().get(id=ride_id)
ride.close_temporarily(user=user)
return ride
@staticmethod
def mark_ride_sbno(
*, ride_id: int, user: Optional[UserType] = None
) -> Ride:
"""
Mark a ride as SBNO (Standing But Not Operating).
Args:
ride_id: ID of ride to mark as SBNO
user: User performing the action
Returns:
Updated Ride instance
Raises:
Ride.DoesNotExist: If ride doesn't exist
"""
with transaction.atomic():
ride = Ride.objects.select_for_update().get(id=ride_id)
ride.mark_sbno(user=user)
return ride
@staticmethod
def schedule_ride_closing(
*,
ride_id: int,
closing_date,
post_closing_status: str,
user: Optional[UserType] = None,
) -> Ride:
"""
Schedule a ride to close on a specific date with a post-closing status.
Args:
ride_id: ID of ride to schedule for closing
closing_date: Date when ride will close
post_closing_status: Status to transition to after closing
user: User performing the action
Returns:
Updated Ride instance
Raises:
Ride.DoesNotExist: If ride doesn't exist
ValidationError: If post_closing_status is not set
"""
with transaction.atomic():
ride = Ride.objects.select_for_update().get(id=ride_id)
ride.mark_closing(
closing_date=closing_date,
post_closing_status=post_closing_status,
user=user,
)
return ride
@staticmethod
def close_ride_permanently(
*, ride_id: int, user: Optional[UserType] = None
) -> Ride:
"""
Permanently close a ride.
Args:
ride_id: ID of ride to close permanently
user: User performing the action
Returns:
Updated Ride instance
Raises:
Ride.DoesNotExist: If ride doesn't exist
"""
with transaction.atomic():
ride = Ride.objects.select_for_update().get(id=ride_id)
ride.close_permanently(user=user)
return ride
@staticmethod
def demolish_ride(*, ride_id: int, user: Optional[UserType] = None) -> Ride:
"""
Mark a ride as demolished.
Args:
ride_id: ID of ride to demolish
user: User performing the action
Returns:
Updated Ride instance
Raises:
Ride.DoesNotExist: If ride doesn't exist
"""
with transaction.atomic():
ride = Ride.objects.select_for_update().get(id=ride_id)
ride.demolish(user=user)
return ride
@staticmethod
def relocate_ride(
*, ride_id: int, new_park_id: int, user: Optional[UserType] = None
) -> Ride:
"""
Relocate a ride to a new park.
Args:
ride_id: ID of ride to relocate
new_park_id: ID of the new park
user: User performing the action
Returns:
Updated Ride instance
Raises:
Ride.DoesNotExist: If ride doesn't exist
"""
with transaction.atomic():
from apps.parks.models import Park
ride = Ride.objects.select_for_update().get(id=ride_id)
new_park = Park.objects.get(id=new_park_id)
# Mark as relocated first
ride.relocate(user=user)
# Move to new park
ride.move_to_park(new_park, clear_park_area=True)
return ride
@staticmethod
def reopen_ride(*, ride_id: int, user: Optional[UserType] = None) -> Ride:
"""
Reopen a ride for operation.
Args:
ride_id: ID of ride to reopen
user: User performing the action
Returns:
Updated Ride instance
Raises:
Ride.DoesNotExist: If ride doesn't exist
"""
with transaction.atomic():
ride = Ride.objects.select_for_update().get(id=ride_id)
ride.open(user=user)
return ride
@staticmethod
def handle_new_entity_suggestions(
*,
form_data: Dict[str, Any],
submitter: UserType,
) -> Dict[str, Any]:
"""
Handle suggestions for new manufacturers, designers, and ride models.
Creates moderation submissions for entities that don't exist in the system.
This extracts the business logic from RideCreateView and RideUpdateView.
Args:
form_data: Cleaned form data containing search fields and selections
submitter: User making the suggestions
Returns:
Dictionary with lists of created submission IDs by type:
{
'manufacturers': [...],
'designers': [...],
'ride_models': [...],
'total_submissions': int
}
"""
from apps.moderation.services import ModerationService
result = {
'manufacturers': [],
'designers': [],
'ride_models': [],
'total_submissions': 0
}
# Check for new manufacturer
manufacturer_name = form_data.get("manufacturer_search")
if manufacturer_name and not form_data.get("manufacturer"):
submission = ModerationService.create_edit_submission_with_queue(
content_object=None,
changes={"name": manufacturer_name, "roles": ["MANUFACTURER"]},
submitter=submitter,
submission_type="CREATE",
reason=f"New manufacturer suggested: {manufacturer_name}",
)
if submission:
result['manufacturers'].append(submission.id)
result['total_submissions'] += 1
# Check for new designer
designer_name = form_data.get("designer_search")
if designer_name and not form_data.get("designer"):
submission = ModerationService.create_edit_submission_with_queue(
content_object=None,
changes={"name": designer_name, "roles": ["DESIGNER"]},
submitter=submitter,
submission_type="CREATE",
reason=f"New designer suggested: {designer_name}",
)
if submission:
result['designers'].append(submission.id)
result['total_submissions'] += 1
# Check for new ride model
ride_model_name = form_data.get("ride_model_search")
manufacturer = form_data.get("manufacturer")
if ride_model_name and not form_data.get("ride_model") and manufacturer:
submission = ModerationService.create_edit_submission_with_queue(
content_object=None,
changes={
"name": ride_model_name,
"manufacturer": manufacturer.id,
},
submitter=submitter,
submission_type="CREATE",
reason=f"New ride model suggested: {ride_model_name}",
)
if submission:
result['ride_models'].append(submission.id)
result['total_submissions'] += 1
return result