from typing import List, Dict, Any, Optional from django.contrib.auth.models import AnonymousUser from django.db.models import QuerySet from django.shortcuts import get_object_or_404 from django_unicorn.components import UnicornView from apps.parks.models import Park from apps.rides.models import Ride from apps.media.models import Photo class ParkDetailView(UnicornView): """ Django Unicorn component for park detail page. Handles park information display, photo management, ride listings, location mapping, and history tracking with reactive updates. """ # Core park data park: Optional[Park] = None park_slug: str = "" # Section data (converted to lists for caching compatibility) rides: List[Dict[str, Any]] = [] photos: List[Dict[str, Any]] = [] history_records: List[Dict[str, Any]] = [] # UI state management show_photo_modal: bool = False show_all_rides: bool = False loading_photos: bool = False loading_rides: bool = False loading_history: bool = False # Photo upload state uploading_photo: bool = False upload_error: str = "" upload_success: str = "" # Map state show_map: bool = False map_latitude: Optional[float] = None map_longitude: Optional[float] = None def mount(self): """Initialize component with park data.""" if self.park_slug: self.load_park_data() def load_park_data(self): """Load park and related data.""" try: # Get park with related data park_queryset = Park.objects.select_related( 'operator', 'property_owner' ).prefetch_related( 'photos', 'rides__ride_model__manufacturer', 'location' ) self.park = get_object_or_404(park_queryset, slug=self.park_slug) # Load sections self.load_rides() self.load_photos() self.load_history() self.load_map_data() except Exception as e: # Handle park not found or other errors self.park = None def load_rides(self): """Load park rides data.""" if not self.park: self.rides = [] return try: self.loading_rides = True # Get rides with related data rides_queryset = self.park.rides.select_related( 'ride_model__manufacturer', 'park' ).prefetch_related( 'photos' ).order_by('name') # Convert to list for caching compatibility self.rides = [] for ride in rides_queryset: ride_data = { 'id': ride.id, 'name': ride.name, 'slug': ride.slug, 'category': ride.category, 'category_display': ride.get_category_display(), 'status': ride.status, 'status_display': ride.get_status_display(), 'average_rating': ride.average_rating, 'url': ride.get_absolute_url(), 'has_photos': ride.photos.exists(), 'ride_model': { 'name': ride.ride_model.name if ride.ride_model else None, 'manufacturer': ride.ride_model.manufacturer.name if ride.ride_model and ride.ride_model.manufacturer else None, } if ride.ride_model else None } self.rides.append(ride_data) except Exception as e: self.rides = [] finally: self.loading_rides = False def load_photos(self): """Load park photos data.""" if not self.park: self.photos = [] return try: self.loading_photos = True # Get photos with related data photos_queryset = self.park.photos.select_related( 'uploaded_by' ).order_by('-created_at') # Convert to list for caching compatibility self.photos = [] for photo in photos_queryset: photo_data = { 'id': photo.id, 'image_url': photo.image.url if photo.image else None, 'image_variants': getattr(photo.image, 'variants', []) if photo.image else [], 'caption': photo.caption or '', 'uploaded_by': photo.uploaded_by.username if photo.uploaded_by else 'Anonymous', 'created_at': photo.created_at, 'is_primary': getattr(photo, 'is_primary', False), } self.photos.append(photo_data) except Exception as e: self.photos = [] finally: self.loading_photos = False def load_history(self): """Load park history records.""" if not self.park: self.history_records = [] return try: self.loading_history = True # Get history records (using pghistory) history_queryset = self.park.history.select_related( 'history_user' ).order_by('-history_date')[:10] # Last 10 changes # Convert to list for caching compatibility self.history_records = [] for record in history_queryset: # Get changes from previous record changes = {} try: if hasattr(record, 'diff_against_previous'): diff = record.diff_against_previous() if diff: changes = { field: { 'old': str(change.old) if change.old is not None else 'None', 'new': str(change.new) if change.new is not None else 'None' } for field, change in diff.items() if field != 'updated_at' # Skip timestamp changes } except: changes = {} history_data = { 'id': record.history_id, 'date': record.history_date, 'user': record.history_user.username if record.history_user else 'System', 'changes': changes, 'has_changes': bool(changes) } self.history_records.append(history_data) except Exception as e: self.history_records = [] finally: self.loading_history = False def load_map_data(self): """Load map coordinates if location exists.""" if not self.park: self.show_map = False return try: location = self.park.location.first() if location and location.point: self.map_latitude = location.point.y self.map_longitude = location.point.x self.show_map = True else: self.show_map = False except: self.show_map = False # UI Event Handlers def toggle_photo_modal(self): """Toggle photo upload modal.""" self.show_photo_modal = not self.show_photo_modal if self.show_photo_modal: self.upload_error = "" self.upload_success = "" def close_photo_modal(self): """Close photo upload modal.""" self.show_photo_modal = False self.upload_error = "" self.upload_success = "" def toggle_all_rides(self): """Toggle between showing limited rides vs all rides.""" self.show_all_rides = not self.show_all_rides def refresh_photos(self): """Refresh photos after upload.""" self.load_photos() self.upload_success = "Photo uploaded successfully!" # Auto-hide success message after 3 seconds # Note: In a real implementation, you might use JavaScript for this def refresh_data(self): """Refresh all park data.""" self.load_park_data() # Computed Properties @property def visible_rides(self) -> List[Dict[str, Any]]: """Get rides to display (limited or all).""" if self.show_all_rides: return self.rides return self.rides[:6] # Show first 6 rides @property def has_more_rides(self) -> bool: """Check if there are more rides to show.""" return len(self.rides) > 6 @property def park_stats(self) -> Dict[str, Any]: """Get park statistics for display.""" if not self.park: return {} return { 'total_rides': self.park.ride_count or len(self.rides), 'coaster_count': self.park.coaster_count or 0, 'average_rating': self.park.average_rating, 'status': self.park.get_status_display() if self.park else '', 'opening_date': self.park.opening_date if self.park else None, 'website': self.park.website if self.park else None, 'operator': { 'name': self.park.operator.name if self.park and self.park.operator else None, 'slug': self.park.operator.slug if self.park and self.park.operator else None, }, 'property_owner': { 'name': self.park.property_owner.name if self.park and self.park.property_owner else None, 'slug': self.park.property_owner.slug if self.park and self.park.property_owner else None, } if self.park and self.park.property_owner and self.park.property_owner != self.park.operator else None } @property def can_upload_photos(self) -> bool: """Check if user can upload photos.""" if isinstance(self.request.user, AnonymousUser): return False return self.request.user.has_perm('media.add_photo') @property def formatted_location(self) -> str: """Get formatted location string.""" if not self.park: return "" try: location = self.park.location.first() if location: parts = [] if location.city: parts.append(location.city) if location.state: parts.append(location.state) if location.country: parts.append(location.country) return ", ".join(parts) except: pass return ""