""" 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 )