""" Park-specific media service for ThrillWiki. This module provides media management functionality specific to parks. """ import logging from typing import List, Optional, Dict, Any from django.core.files.uploadedfile import UploadedFile from django.db import transaction from django.contrib.auth import get_user_model from apps.core.services.media_service import MediaService from ..models import Park, ParkPhoto User = get_user_model() logger = logging.getLogger(__name__) class ParkMediaService: """Service for managing park-specific media operations.""" @staticmethod def upload_photo( park: Park, image_file: UploadedFile, user: User, caption: str = "", alt_text: str = "", is_primary: bool = False, auto_approve: bool = False ) -> ParkPhoto: """ Upload a photo for a park. Args: park: Park instance image_file: Uploaded image file user: User uploading the photo caption: Photo caption alt_text: Alt text for accessibility is_primary: Whether this should be the primary photo auto_approve: Whether to auto-approve the photo Returns: Created ParkPhoto instance Raises: ValueError: If image validation fails """ # Validate image file is_valid, error_message = MediaService.validate_image_file(image_file) if not is_valid: raise ValueError(error_message) # Process image processed_image = MediaService.process_image(image_file) with transaction.atomic(): # Create photo instance photo = ParkPhoto( park=park, image=processed_image, caption=caption or MediaService.generate_default_caption(user.username), alt_text=alt_text, is_primary=is_primary, is_approved=auto_approve, uploaded_by=user ) # Extract EXIF date photo.date_taken = MediaService.extract_exif_date(processed_image) photo.save() logger.info(f"Photo uploaded for park {park.slug} by user {user.username}") return photo @staticmethod def get_park_photos( park: Park, approved_only: bool = True, primary_first: bool = True ) -> List[ParkPhoto]: """ Get photos for a park. Args: park: Park instance approved_only: Whether to only return approved photos primary_first: Whether to order primary photos first Returns: List of ParkPhoto instances """ queryset = park.photos.all() if approved_only: queryset = queryset.filter(is_approved=True) if primary_first: queryset = queryset.order_by('-is_primary', '-created_at') else: queryset = queryset.order_by('-created_at') return list(queryset) @staticmethod def get_primary_photo(park: Park) -> Optional[ParkPhoto]: """ Get the primary photo for a park. Args: park: Park instance Returns: Primary ParkPhoto instance or None """ try: return park.photos.filter(is_primary=True, is_approved=True).first() except ParkPhoto.DoesNotExist: return None @staticmethod def set_primary_photo(park: Park, photo: ParkPhoto) -> bool: """ Set a photo as the primary photo for a park. Args: park: Park instance photo: ParkPhoto to set as primary Returns: True if successful, False otherwise """ if photo.park != park: return False with transaction.atomic(): # Unset current primary park.photos.filter(is_primary=True).update(is_primary=False) # Set new primary photo.is_primary = True photo.save() logger.info(f"Set photo {photo.pk} as primary for park {park.slug}") return True @staticmethod def approve_photo(photo: ParkPhoto, approved_by: User) -> bool: """ Approve a park photo. Args: photo: ParkPhoto to approve approved_by: User approving the photo Returns: True if successful, False otherwise """ try: photo.is_approved = True photo.save() logger.info(f"Photo {photo.pk} approved by user {approved_by.username}") return True except Exception as e: logger.error(f"Failed to approve photo {photo.pk}: {str(e)}") return False @staticmethod def delete_photo(photo: ParkPhoto, deleted_by: User) -> bool: """ Delete a park photo. Args: photo: ParkPhoto to delete deleted_by: User deleting the photo Returns: True if successful, False otherwise """ try: park_slug = photo.park.slug photo_id = photo.pk # Delete the file and database record if photo.image: photo.image.delete(save=False) photo.delete() logger.info( f"Photo {photo_id} deleted from park {park_slug} by user {deleted_by.username}") return True except Exception as e: logger.error(f"Failed to delete photo {photo.pk}: {str(e)}") return False @staticmethod def get_photo_stats(park: Park) -> Dict[str, Any]: """ Get photo statistics for a park. Args: park: Park instance Returns: Dictionary with photo statistics """ photos = park.photos.all() return { "total_photos": photos.count(), "approved_photos": photos.filter(is_approved=True).count(), "pending_photos": photos.filter(is_approved=False).count(), "has_primary": photos.filter(is_primary=True).exists(), "recent_uploads": photos.order_by('-created_at')[:5].count() } @staticmethod def bulk_approve_photos(photos: List[ParkPhoto], approved_by: User) -> int: """ Bulk approve multiple photos. Args: photos: List of ParkPhoto instances to approve approved_by: User approving the photos Returns: Number of photos successfully approved """ approved_count = 0 with transaction.atomic(): for photo in photos: if ParkMediaService.approve_photo(photo, approved_by): approved_count += 1 logger.info( f"Bulk approved {approved_count} photos by user {approved_by.username}") return approved_count