""" Ride API views for ThrillWiki API v1. This module contains consolidated ride 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.rides.models import RidePhoto from apps.rides.services import RideMediaService from .serializers import ( RidePhotoOutputSerializer, RidePhotoCreateInputSerializer, RidePhotoUpdateInputSerializer, RidePhotoListOutputSerializer, RidePhotoApprovalInputSerializer, RidePhotoStatsOutputSerializer, ) logger = logging.getLogger(__name__) @extend_schema_view( list=extend_schema( summary="List ride photos", description="Retrieve a paginated list of ride photos with filtering capabilities.", responses={200: RidePhotoListOutputSerializer(many=True)}, tags=["Ride Media"], ), create=extend_schema( summary="Upload ride photo", description="Upload a new photo for a ride. Requires authentication.", request=RidePhotoCreateInputSerializer, responses={ 201: RidePhotoOutputSerializer, 400: OpenApiTypes.OBJECT, 401: OpenApiTypes.OBJECT, }, tags=["Ride Media"], ), retrieve=extend_schema( summary="Get ride photo details", description="Retrieve detailed information about a specific ride photo.", responses={ 200: RidePhotoOutputSerializer, 404: OpenApiTypes.OBJECT, }, tags=["Ride Media"], ), update=extend_schema( summary="Update ride photo", description="Update ride photo information. Requires authentication and ownership or admin privileges.", request=RidePhotoUpdateInputSerializer, responses={ 200: RidePhotoOutputSerializer, 400: OpenApiTypes.OBJECT, 401: OpenApiTypes.OBJECT, 403: OpenApiTypes.OBJECT, 404: OpenApiTypes.OBJECT, }, tags=["Ride Media"], ), partial_update=extend_schema( summary="Partially update ride photo", description="Partially update ride photo information. Requires authentication and ownership or admin privileges.", request=RidePhotoUpdateInputSerializer, responses={ 200: RidePhotoOutputSerializer, 400: OpenApiTypes.OBJECT, 401: OpenApiTypes.OBJECT, 403: OpenApiTypes.OBJECT, 404: OpenApiTypes.OBJECT, }, tags=["Ride Media"], ), destroy=extend_schema( summary="Delete ride photo", description="Delete a ride photo. Requires authentication and ownership or admin privileges.", responses={ 204: None, 401: OpenApiTypes.OBJECT, 403: OpenApiTypes.OBJECT, 404: OpenApiTypes.OBJECT, }, tags=["Ride Media"], ), ) class RidePhotoViewSet(ModelViewSet): """ ViewSet for managing ride photos. Provides CRUD operations for ride photos with proper permission checking. Uses RideMediaService for business logic operations. """ permission_classes = [IsAuthenticated] lookup_field = "id" def get_queryset(self): """Get photos for the current ride with optimized queries.""" return ( RidePhoto.objects.select_related("ride", "ride__park", "uploaded_by") .filter(ride_id=self.kwargs.get("ride_pk")) .order_by("-created_at") ) def get_serializer_class(self): """Return appropriate serializer based on action.""" if self.action == "list": return RidePhotoListOutputSerializer elif self.action == "create": return RidePhotoCreateInputSerializer elif self.action in ["update", "partial_update"]: return RidePhotoUpdateInputSerializer else: return RidePhotoOutputSerializer def perform_create(self, serializer): """Create a new ride photo using RideMediaService.""" ride_id = self.kwargs.get("ride_pk") if not ride_id: raise ValidationError("Ride ID is required") try: # Use the service to create the photo with proper business logic photo = RideMediaService.create_photo( ride_id=ride_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 ride photo: {e}") raise ValidationError(f"Failed to create photo: {str(e)}") def perform_update(self, serializer): """Update ride 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: RideMediaService.set_primary_photo( ride_id=instance.ride_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 ride 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: RideMediaService.delete_photo(instance.id) except Exception as e: logger.error(f"Error deleting ride 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 ride.""" 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: RideMediaService.set_primary_photo(ride_id=photo.ride_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 = RidePhotoApprovalInputSerializer(data=request.data) serializer.is_valid(raise_exception=True) photo_ids = serializer.validated_data["photo_ids"] approve = serializer.validated_data["approve"] ride_id = self.kwargs.get("ride_pk") try: # Filter photos to only those belonging to this ride photos = RidePhoto.objects.filter(id__in=photo_ids, ride_id=ride_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 ride.""" ride_id = self.kwargs.get("ride_pk") try: stats = RideMediaService.get_photo_stats(ride_id=ride_id) serializer = RidePhotoStatsOutputSerializer(stats) return Response(serializer.data, status=status.HTTP_200_OK) except Exception as e: logger.error(f"Error getting ride photo stats: {e}") return Response( {"error": f"Failed to get photo statistics: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR, )