Files
thrillwiki_django_no_react/backend/apps/parks/signals.py
pacnpal b860e332cb feat(state-machine): add comprehensive callback system for transitions
Extend state machine module with callback infrastructure including:
- Pre/post/error transition callbacks with registry
- Signal-based transition notifications
- Callback configuration and monitoring support
- Helper functions for callback registration
- Improved park ride count updates with FSM integration
2025-12-21 19:20:49 -05:00

151 lines
4.8 KiB
Python

import logging
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from django.db.models import Q
from apps.rides.models import Ride
from .models import Park
logger = logging.getLogger(__name__)
# Status values that count as "active" rides for counting purposes
ACTIVE_STATUSES = {'OPERATING', 'SEASONAL', 'UNDER_CONSTRUCTION'}
# Status values that should decrement ride counts
INACTIVE_STATUSES = {'CLOSED_PERM', 'DEMOLISHED', 'RELOCATED', 'REMOVED'}
def update_park_ride_counts(park, old_status=None, new_status=None):
"""
Update ride_count and coaster_count for a park.
Args:
park: The Park instance or park ID to update.
old_status: The previous status of the ride (for FSM transitions).
new_status: The new status of the ride (for FSM transitions).
"""
if park is None:
logger.warning("Cannot update counts: park is None")
return
# Get park ID
park_id = park.pk if hasattr(park, 'pk') else park
try:
# Fetch the park if we only have an ID
if not hasattr(park, 'rides'):
park = Park.objects.get(id=park_id)
# Build the query for active rides
active_statuses = list(ACTIVE_STATUSES)
operating_rides = Q(status__in=active_statuses)
# Count total operating rides
ride_count = park.rides.filter(operating_rides).count()
# Count total operating roller coasters
coaster_count = park.rides.filter(operating_rides, category="RC").count()
# Update park counts
Park.objects.filter(id=park_id).update(
ride_count=ride_count, coaster_count=coaster_count
)
logger.debug(
f"Updated park {park_id} counts: "
f"ride_count={ride_count}, coaster_count={coaster_count}"
)
except Park.DoesNotExist:
logger.warning(f"Park {park_id} does not exist, cannot update counts")
except Exception as e:
logger.exception(f"Failed to update park counts for {park_id}: {e}")
def should_update_counts(old_status, new_status):
"""
Determine if a status change should trigger count updates.
Args:
old_status: The previous status value.
new_status: The new status value.
Returns:
True if counts should be updated, False otherwise.
"""
if old_status == new_status:
return False
# Check if either status is in active or inactive sets
old_active = old_status in ACTIVE_STATUSES if old_status else False
new_active = new_status in ACTIVE_STATUSES if new_status else False
old_inactive = old_status in INACTIVE_STATUSES if old_status else False
new_inactive = new_status in INACTIVE_STATUSES if new_status else False
# Update if transitioning to/from active status
return old_active != new_active or old_inactive != new_inactive
@receiver(post_save, sender=Ride)
def ride_saved(sender, instance, created, **kwargs):
"""
Update park counts when a ride is saved.
Integrates with FSM transitions by checking for status changes.
"""
# For new rides, always update counts
if created:
update_park_ride_counts(instance.park)
return
# Check if status changed using model's tracker if available
if hasattr(instance, 'tracker') and hasattr(instance.tracker, 'has_changed'):
if instance.tracker.has_changed('status'):
old_status = instance.tracker.previous('status')
new_status = instance.status
if should_update_counts(old_status, new_status):
logger.info(
f"Ride {instance.pk} status changed: {old_status}{new_status}"
)
update_park_ride_counts(instance.park, old_status, new_status)
else:
# Fallback: always update counts on save
update_park_ride_counts(instance.park)
@receiver(post_delete, sender=Ride)
def ride_deleted(sender, instance, **kwargs):
"""
Update park counts when a ride is deleted.
Logs the deletion for audit purposes.
"""
logger.info(f"Ride {instance.pk} deleted from park {instance.park_id}")
update_park_ride_counts(instance.park)
# FSM transition signal handlers
def handle_ride_status_transition(instance, source, target, user, **kwargs):
"""
Handle ride status FSM transitions.
This function is called by the FSM callback system when a ride
status transition occurs.
Args:
instance: The Ride instance.
source: The source state.
target: The target state.
user: The user who initiated the transition.
"""
if should_update_counts(source, target):
logger.info(
f"FSM transition: Ride {instance.pk} {source}{target} "
f"by {user if user else 'system'}"
)
update_park_ride_counts(instance.park, source, target)