mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2026-01-01 22:07:03 -05:00
238 lines
8.4 KiB
Python
238 lines
8.4 KiB
Python
import logging
|
|
|
|
from django.db.models.signals import post_save, pre_save
|
|
from django.dispatch import receiver
|
|
from django.utils import timezone
|
|
|
|
from .models import Ride
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# =============================================================================
|
|
# Computed Field Maintenance
|
|
# =============================================================================
|
|
|
|
|
|
def update_ride_search_text(ride):
|
|
"""
|
|
Update ride's search_text computed field.
|
|
|
|
This is called when related objects (park, manufacturer, ride_model)
|
|
change and might affect the ride's search text.
|
|
"""
|
|
if ride is None:
|
|
return
|
|
|
|
try:
|
|
ride._populate_computed_fields()
|
|
ride.save(update_fields=["search_text"])
|
|
logger.debug(f"Updated search_text for ride {ride.pk}")
|
|
except Exception as e:
|
|
logger.exception(f"Failed to update search_text for ride {ride.pk}: {e}")
|
|
|
|
|
|
@receiver(pre_save, sender=Ride)
|
|
def handle_ride_status(sender, instance, **kwargs):
|
|
"""
|
|
Handle ride status changes based on closing date.
|
|
|
|
Integrates with FSM transitions by using transition methods when available.
|
|
"""
|
|
if not instance.closing_date:
|
|
return
|
|
|
|
today = timezone.now().date()
|
|
|
|
# If we've reached the closing date and status is "CLOSING"
|
|
if today >= instance.closing_date and instance.status == "CLOSING":
|
|
target_status = instance.post_closing_status or "SBNO"
|
|
|
|
logger.info(f"Ride {instance.pk} closing date reached, " f"transitioning to {target_status}")
|
|
|
|
# Try to use FSM transition method if available
|
|
transition_method_name = f"transition_to_{target_status.lower()}"
|
|
if hasattr(instance, transition_method_name):
|
|
# Check if transition is allowed before attempting
|
|
if hasattr(instance, "can_proceed"):
|
|
can_proceed = getattr(instance, f"can_transition_to_{target_status.lower()}", None)
|
|
if can_proceed and callable(can_proceed) and not can_proceed():
|
|
logger.warning(f"FSM transition to {target_status} not allowed " f"for ride {instance.pk}")
|
|
# Fall back to direct status change
|
|
instance.status = target_status
|
|
instance.status_since = instance.closing_date
|
|
return
|
|
|
|
try:
|
|
method = getattr(instance, transition_method_name)
|
|
method()
|
|
instance.status_since = instance.closing_date
|
|
logger.info(f"Applied FSM transition to {target_status} for ride {instance.pk}")
|
|
except Exception as e:
|
|
logger.exception(f"Failed to apply FSM transition for ride {instance.pk}: {e}")
|
|
# Fall back to direct status change
|
|
instance.status = target_status
|
|
instance.status_since = instance.closing_date
|
|
else:
|
|
# No FSM transition method, use direct assignment
|
|
instance.status = target_status
|
|
instance.status_since = instance.closing_date
|
|
|
|
|
|
@receiver(pre_save, sender=Ride)
|
|
def validate_closing_status(sender, instance, **kwargs):
|
|
"""
|
|
Validate that post_closing_status is set when entering CLOSING state.
|
|
"""
|
|
# Only validate if this is an existing ride being updated
|
|
if not instance.pk:
|
|
return
|
|
|
|
# Check if we're transitioning to CLOSING
|
|
if instance.status == "CLOSING":
|
|
# Ensure post_closing_status is set
|
|
if not instance.post_closing_status:
|
|
logger.warning(f"Ride {instance.pk} entering CLOSING without post_closing_status set")
|
|
# Default to SBNO if not set
|
|
instance.post_closing_status = "SBNO"
|
|
|
|
# Ensure closing_date is set
|
|
if not instance.closing_date:
|
|
logger.warning(f"Ride {instance.pk} entering CLOSING without closing_date set")
|
|
# Default to today's date
|
|
instance.closing_date = timezone.now().date()
|
|
|
|
|
|
# FSM transition signal handlers
|
|
|
|
|
|
def handle_ride_transition_to_closing(instance, source, target, user, **kwargs):
|
|
"""
|
|
Validate transition to CLOSING status.
|
|
|
|
This function is called by the FSM callback system before a ride
|
|
transitions to CLOSING status.
|
|
|
|
Args:
|
|
instance: The Ride instance.
|
|
source: The source state.
|
|
target: The target state.
|
|
user: The user who initiated the transition.
|
|
|
|
Returns:
|
|
True if transition should proceed, False to abort.
|
|
"""
|
|
if target != "CLOSING":
|
|
return True
|
|
|
|
if not instance.post_closing_status:
|
|
logger.error(f"Cannot transition ride {instance.pk} to CLOSING: " "post_closing_status not set")
|
|
return False
|
|
|
|
if not instance.closing_date:
|
|
logger.warning(f"Ride {instance.pk} transitioning to CLOSING without closing_date")
|
|
|
|
return True
|
|
|
|
|
|
def apply_post_closing_status(instance, user=None):
|
|
"""
|
|
Apply the post_closing_status to a ride in CLOSING state.
|
|
|
|
This function can be called by the FSM callback system or directly
|
|
when a ride's closing date is reached.
|
|
|
|
Args:
|
|
instance: The Ride instance in CLOSING state.
|
|
user: The user initiating the change (optional).
|
|
|
|
Returns:
|
|
True if status was applied, False otherwise.
|
|
"""
|
|
if instance.status != "CLOSING":
|
|
logger.debug(f"Ride {instance.pk} not in CLOSING state, skipping")
|
|
return False
|
|
|
|
target_status = instance.post_closing_status
|
|
if not target_status:
|
|
logger.warning(f"Ride {instance.pk} in CLOSING but no post_closing_status set")
|
|
return False
|
|
|
|
# Try to use FSM transition
|
|
transition_method_name = f"transition_to_{target_status.lower()}"
|
|
if hasattr(instance, transition_method_name):
|
|
try:
|
|
method = getattr(instance, transition_method_name)
|
|
method(user=user)
|
|
instance.post_closing_status = None
|
|
instance.save(update_fields=["post_closing_status"])
|
|
logger.info(f"Applied post_closing_status {target_status} to ride {instance.pk}")
|
|
return True
|
|
except Exception as e:
|
|
logger.exception(f"Failed to apply post_closing_status for ride {instance.pk}: {e}")
|
|
return False
|
|
else:
|
|
# Direct status change
|
|
instance.status = target_status
|
|
instance.post_closing_status = None
|
|
instance.status_since = timezone.now().date()
|
|
instance.save(update_fields=["status", "post_closing_status", "status_since"])
|
|
logger.info(f"Applied post_closing_status {target_status} to ride {instance.pk} (direct)")
|
|
return True
|
|
|
|
|
|
# =============================================================================
|
|
# Computed Field Maintenance Signal Handlers
|
|
# =============================================================================
|
|
|
|
|
|
@receiver(post_save, sender="parks.Park")
|
|
def update_ride_search_text_on_park_change(sender, instance, **kwargs):
|
|
"""
|
|
Update ride search_text when park name or location changes.
|
|
|
|
When a park's name changes, all rides at that park need their
|
|
search_text regenerated.
|
|
"""
|
|
try:
|
|
for ride in instance.rides.all():
|
|
update_ride_search_text(ride)
|
|
except Exception as e:
|
|
logger.exception(f"Failed to update ride search_text on park change: {e}")
|
|
|
|
|
|
@receiver(post_save, sender="parks.Company")
|
|
def update_ride_search_text_on_company_change(sender, instance, **kwargs):
|
|
"""
|
|
Update ride search_text when manufacturer/designer name changes.
|
|
|
|
When a company's name changes, all rides manufactured or designed
|
|
by that company need their search_text regenerated.
|
|
"""
|
|
try:
|
|
# Update all rides manufactured by this company
|
|
for ride in instance.manufactured_rides.all():
|
|
update_ride_search_text(ride)
|
|
|
|
# Update all rides designed by this company
|
|
for ride in instance.designed_rides.all():
|
|
update_ride_search_text(ride)
|
|
|
|
except Exception as e:
|
|
logger.exception(f"Failed to update ride search_text on company change: {e}")
|
|
|
|
|
|
@receiver(post_save, sender="rides.RideModel")
|
|
def update_ride_search_text_on_ride_model_change(sender, instance, **kwargs):
|
|
"""
|
|
Update ride search_text when ride model name changes.
|
|
|
|
When a ride model's name changes, all rides using that model need
|
|
their search_text regenerated.
|
|
"""
|
|
try:
|
|
for ride in instance.rides.all():
|
|
update_ride_search_text(ride)
|
|
except Exception as e:
|
|
logger.exception(f"Failed to update ride search_text on ride model change: {e}")
|