""" Park-specific media service for ThrillWiki. This module provides media management functionality specific to parks. """ import logging from typing import Any from django.contrib.auth import get_user_model from django.core.files.uploadedfile import UploadedFile from django.db import transaction 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) -> ParkPhoto | None: """ 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