mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 02:11:08 -05:00
- Created ParkPhoto and ParkPhotoEvent models in the parks app, including fields for image, caption, alt text, and relationships to the Park model. - Implemented triggers for insert and update operations on ParkPhoto to log changes in ParkPhotoEvent. - Created RidePhoto and RidePhotoEvent models in the rides app, with similar structure and functionality as ParkPhoto. - Added fields for photo type in RidePhoto and implemented corresponding triggers for logging changes. - Established necessary indexes and unique constraints for both models to ensure data integrity and optimize queries.
275 lines
9.7 KiB
Python
275 lines
9.7 KiB
Python
"""
|
|
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,
|
|
)
|