""" Park API views for ThrillWiki API v1. This module contains consolidated park photo viewset for the centralized API structure. """ import logging from django.core.exceptions import PermissionDenied from drf_spectacular.utils import extend_schema_view, extend_schema from drf_spectacular.types import OpenApiTypes from rest_framework import status from rest_framework.decorators import action from rest_framework.exceptions import ValidationError from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet from apps.parks.models import ParkPhoto from apps.parks.services import ParkMediaService from .serializers import ( ParkPhotoOutputSerializer, ParkPhotoCreateInputSerializer, ParkPhotoUpdateInputSerializer, ParkPhotoListOutputSerializer, ParkPhotoApprovalInputSerializer, ParkPhotoStatsOutputSerializer, ) logger = logging.getLogger(__name__) @extend_schema_view( list=extend_schema( summary="List park photos", description="Retrieve a paginated list of park photos with filtering capabilities.", responses={200: ParkPhotoListOutputSerializer(many=True)}, tags=["Park Media"], ), create=extend_schema( summary="Upload park photo", description="Upload a new photo for a park. Requires authentication.", request=ParkPhotoCreateInputSerializer, responses={ 201: ParkPhotoOutputSerializer, 400: OpenApiTypes.OBJECT, 401: OpenApiTypes.OBJECT, }, tags=["Park Media"], ), retrieve=extend_schema( summary="Get park photo details", description="Retrieve detailed information about a specific park photo.", responses={ 200: ParkPhotoOutputSerializer, 404: OpenApiTypes.OBJECT, }, tags=["Park Media"], ), update=extend_schema( summary="Update park photo", description="Update park photo information. Requires authentication and ownership or admin privileges.", request=ParkPhotoUpdateInputSerializer, responses={ 200: ParkPhotoOutputSerializer, 400: OpenApiTypes.OBJECT, 401: OpenApiTypes.OBJECT, 403: OpenApiTypes.OBJECT, 404: OpenApiTypes.OBJECT, }, tags=["Park Media"], ), partial_update=extend_schema( summary="Partially update park photo", description="Partially update park photo information. Requires authentication and ownership or admin privileges.", request=ParkPhotoUpdateInputSerializer, responses={ 200: ParkPhotoOutputSerializer, 400: OpenApiTypes.OBJECT, 401: OpenApiTypes.OBJECT, 403: OpenApiTypes.OBJECT, 404: OpenApiTypes.OBJECT, }, tags=["Park Media"], ), destroy=extend_schema( summary="Delete park photo", description="Delete a park photo. Requires authentication and ownership or admin privileges.", responses={ 204: None, 401: OpenApiTypes.OBJECT, 403: OpenApiTypes.OBJECT, 404: OpenApiTypes.OBJECT, }, tags=["Park Media"], ), ) class ParkPhotoViewSet(ModelViewSet): """ ViewSet for managing park photos. Provides CRUD operations for park photos with proper permission checking. Uses ParkMediaService for business logic operations. """ permission_classes = [IsAuthenticated] lookup_field = "id" def get_queryset(self): """Get photos for the current park with optimized queries.""" return ( ParkPhoto.objects.select_related("park", "park__operator", "uploaded_by") .filter(park_id=self.kwargs.get("park_pk")) .order_by("-created_at") ) def get_serializer_class(self): """Return appropriate serializer based on action.""" if self.action == "list": return ParkPhotoListOutputSerializer elif self.action == "create": return ParkPhotoCreateInputSerializer elif self.action in ["update", "partial_update"]: return ParkPhotoUpdateInputSerializer else: return ParkPhotoOutputSerializer def perform_create(self, serializer): """Create a new park photo using ParkMediaService.""" park_id = self.kwargs.get("park_pk") if not park_id: raise ValidationError("Park ID is required") try: # Use the service to create the photo with proper business logic photo = ParkMediaService.create_photo( park_id=park_id, uploaded_by=self.request.user, **serializer.validated_data, ) # Set the instance for the serializer response serializer.instance = photo except Exception as e: logger.error(f"Error creating park photo: {e}") raise ValidationError(f"Failed to create photo: {str(e)}") def perform_update(self, serializer): """Update park photo with permission checking.""" instance = self.get_object() # Check permissions if not ( self.request.user == instance.uploaded_by or self.request.user.is_staff ): raise PermissionDenied("You can only edit your own photos or be an admin.") # Handle primary photo logic using service if serializer.validated_data.get("is_primary", False): try: ParkMediaService.set_primary_photo( park_id=instance.park_id, photo_id=instance.id ) # Remove is_primary from validated_data since service handles it if "is_primary" in serializer.validated_data: del serializer.validated_data["is_primary"] except Exception as e: logger.error(f"Error setting primary photo: {e}") raise ValidationError(f"Failed to set primary photo: {str(e)}") serializer.save() def perform_destroy(self, instance): """Delete park photo with permission checking.""" # Check permissions if not ( self.request.user == instance.uploaded_by or self.request.user.is_staff ): raise PermissionDenied( "You can only delete your own photos or be an admin." ) try: ParkMediaService.delete_photo(instance.id) except Exception as e: logger.error(f"Error deleting park photo: {e}") raise ValidationError(f"Failed to delete photo: {str(e)}") @action(detail=True, methods=["post"]) def set_primary(self, request, **kwargs): """Set this photo as the primary photo for the park.""" photo = self.get_object() # Check permissions if not (request.user == photo.uploaded_by or request.user.is_staff): raise PermissionDenied( "You can only modify your own photos or be an admin." ) try: ParkMediaService.set_primary_photo(park_id=photo.park_id, photo_id=photo.id) # Refresh the photo instance photo.refresh_from_db() serializer = self.get_serializer(photo) return Response( { "message": "Photo set as primary successfully", "photo": serializer.data, }, status=status.HTTP_200_OK, ) except Exception as e: logger.error(f"Error setting primary photo: {e}") return Response( {"error": f"Failed to set primary photo: {str(e)}"}, status=status.HTTP_400_BAD_REQUEST, ) @action(detail=False, methods=["post"], permission_classes=[IsAuthenticated]) def bulk_approve(self, request, **kwargs): """Bulk approve or reject multiple photos (admin only).""" if not request.user.is_staff: raise PermissionDenied("Only administrators can approve photos.") serializer = ParkPhotoApprovalInputSerializer(data=request.data) serializer.is_valid(raise_exception=True) photo_ids = serializer.validated_data["photo_ids"] approve = serializer.validated_data["approve"] park_id = self.kwargs.get("park_pk") try: # Filter photos to only those belonging to this park photos = ParkPhoto.objects.filter(id__in=photo_ids, park_id=park_id) updated_count = photos.update(is_approved=approve) return Response( { "message": f"Successfully {'approved' if approve else 'rejected'} {updated_count} photos", "updated_count": updated_count, }, status=status.HTTP_200_OK, ) except Exception as e: logger.error(f"Error in bulk photo approval: {e}") return Response( {"error": f"Failed to update photos: {str(e)}"}, status=status.HTTP_400_BAD_REQUEST, ) @action(detail=False, methods=["get"]) def stats(self, request, **kwargs): """Get photo statistics for the park.""" park_id = self.kwargs.get("park_pk") try: stats = ParkMediaService.get_photo_stats(park_id=park_id) serializer = ParkPhotoStatsOutputSerializer(stats) return Response(serializer.data, status=status.HTTP_200_OK) except Exception as e: logger.error(f"Error getting park photo stats: {e}") return Response( {"error": f"Failed to get photo statistics: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR, )