mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-23 12:51:09 -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.
214 lines
7.1 KiB
Python
214 lines
7.1 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__)
|
|
|
|
|
|
# =============================================================================
|
|
# Computed Field Maintenance Signals
|
|
# =============================================================================
|
|
|
|
def update_park_search_text(park):
|
|
"""
|
|
Update park's search_text computed field.
|
|
|
|
This is called when related objects (location, operator, property_owner)
|
|
change and might affect the park's search text.
|
|
"""
|
|
if park is None:
|
|
return
|
|
|
|
try:
|
|
park._populate_computed_fields()
|
|
park.save(update_fields=['search_text'])
|
|
logger.debug(f"Updated search_text for park {park.pk}")
|
|
except Exception as e:
|
|
logger.exception(f"Failed to update search_text for park {park.pk}: {e}")
|
|
|
|
|
|
# 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 (including water coasters)
|
|
coaster_count = park.rides.filter(operating_rides, category__in=["RC", "WC"]).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)
|
|
|
|
|
|
# =============================================================================
|
|
# Computed Field Maintenance Signal Handlers
|
|
# =============================================================================
|
|
|
|
@receiver(post_save, sender='parks.ParkLocation')
|
|
def update_park_search_text_on_location_change(sender, instance, **kwargs):
|
|
"""
|
|
Update park search_text when location changes.
|
|
|
|
When a park's location is updated (city, state, country changes),
|
|
the park's search_text needs to be regenerated to include the new
|
|
location information.
|
|
"""
|
|
try:
|
|
if hasattr(instance, 'park') and instance.park:
|
|
update_park_search_text(instance.park)
|
|
except Exception as e:
|
|
logger.exception(f"Failed to update park search_text on location change: {e}")
|
|
|
|
|
|
@receiver(post_save, sender='parks.Company')
|
|
def update_park_search_text_on_company_change(sender, instance, **kwargs):
|
|
"""
|
|
Update park search_text when operator/owner name changes.
|
|
|
|
When a company's name changes, all parks operated or owned by that
|
|
company need their search_text regenerated.
|
|
"""
|
|
try:
|
|
# Update all parks operated by this company
|
|
for park in instance.operated_parks.all():
|
|
update_park_search_text(park)
|
|
|
|
# Update all parks owned by this company
|
|
for park in instance.owned_parks.all():
|
|
update_park_search_text(park)
|
|
|
|
except Exception as e:
|
|
logger.exception(f"Failed to update park search_text on company change: {e}")
|