mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-21 09:31:09 -05:00
Refactor code structure and remove redundant changes
This commit is contained in:
241
backend/apps/parks/services/media_service.py
Normal file
241
backend/apps/parks/services/media_service.py
Normal file
@@ -0,0 +1,241 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user