mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-22 19:11:08 -05:00
- 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.
392 lines
12 KiB
Python
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
|