mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 02:11:08 -05:00
234 lines
8.7 KiB
Python
234 lines
8.7 KiB
Python
"""
|
|
Media API views for ThrillWiki API v1.
|
|
|
|
This module provides API endpoints for media management including
|
|
photo uploads, captions, and media operations.
|
|
Consolidated from apps.media.views
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
from typing import Any, Dict
|
|
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.core.exceptions import PermissionDenied
|
|
from django.http import Http404
|
|
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter
|
|
from drf_spectacular.types import OpenApiTypes
|
|
from rest_framework import status
|
|
from rest_framework.decorators import action
|
|
from rest_framework.permissions import IsAuthenticated, AllowAny
|
|
from rest_framework.request import Request
|
|
from rest_framework.response import Response
|
|
from rest_framework.views import APIView
|
|
from rest_framework.viewsets import ModelViewSet
|
|
from rest_framework.parsers import MultiPartParser, FormParser
|
|
|
|
# Import domain-specific models and services instead of generic Photo model
|
|
from apps.parks.models import ParkPhoto
|
|
from apps.rides.models import RidePhoto
|
|
from apps.parks.services import ParkMediaService
|
|
from apps.rides.services import RideMediaService
|
|
from apps.core.services.media_service import MediaService
|
|
from .serializers import (
|
|
PhotoUploadInputSerializer,
|
|
PhotoUploadOutputSerializer,
|
|
PhotoDetailOutputSerializer,
|
|
PhotoUpdateInputSerializer,
|
|
PhotoListOutputSerializer,
|
|
)
|
|
from ..parks.serializers import ParkPhotoSerializer
|
|
from ..rides.serializers import RidePhotoSerializer
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@extend_schema_view(
|
|
post=extend_schema(
|
|
summary="Upload photo",
|
|
description="Upload a photo and associate it with a content object (park, ride, etc.)",
|
|
request=PhotoUploadInputSerializer,
|
|
responses={
|
|
201: PhotoUploadOutputSerializer,
|
|
400: OpenApiTypes.OBJECT,
|
|
403: OpenApiTypes.OBJECT,
|
|
},
|
|
tags=["Media"],
|
|
),
|
|
)
|
|
class PhotoUploadAPIView(APIView):
|
|
"""API endpoint for photo uploads."""
|
|
|
|
permission_classes = [IsAuthenticated]
|
|
parser_classes = [MultiPartParser, FormParser]
|
|
|
|
def post(self, request: Request) -> Response:
|
|
"""Upload a photo and associate it with a content object."""
|
|
try:
|
|
serializer = PhotoUploadInputSerializer(data=request.data)
|
|
if not serializer.is_valid():
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
validated_data = serializer.validated_data
|
|
|
|
# Get content object
|
|
try:
|
|
content_type = ContentType.objects.get(
|
|
app_label=validated_data["app_label"], model=validated_data["model"]
|
|
)
|
|
content_object = content_type.get_object_for_this_type(
|
|
pk=validated_data["object_id"]
|
|
)
|
|
except ContentType.DoesNotExist:
|
|
return Response(
|
|
{
|
|
"error": f"Invalid content type: {validated_data['app_label']}.{validated_data['model']}"
|
|
},
|
|
status=status.HTTP_400_BAD_REQUEST,
|
|
)
|
|
except content_type.model_class().DoesNotExist:
|
|
return Response(
|
|
{"error": "Content object not found"},
|
|
status=status.HTTP_404_NOT_FOUND,
|
|
)
|
|
|
|
# Determine which domain service to use based on content object
|
|
if hasattr(content_object, '_meta') and content_object._meta.app_label == 'parks':
|
|
# Check permissions for park photos
|
|
if not request.user.has_perm("parks.add_parkphoto"):
|
|
return Response(
|
|
{"error": "You do not have permission to upload park photos"},
|
|
status=status.HTTP_403_FORBIDDEN,
|
|
)
|
|
|
|
# Create park photo using park media service
|
|
photo = ParkMediaService.upload_photo(
|
|
park=content_object,
|
|
image_file=validated_data["photo"],
|
|
user=request.user,
|
|
caption=validated_data.get("caption", ""),
|
|
alt_text=validated_data.get("alt_text", ""),
|
|
is_primary=validated_data.get("is_primary", False),
|
|
)
|
|
elif hasattr(content_object, '_meta') and content_object._meta.app_label == 'rides':
|
|
# Check permissions for ride photos
|
|
if not request.user.has_perm("rides.add_ridephoto"):
|
|
return Response(
|
|
{"error": "You do not have permission to upload ride photos"},
|
|
status=status.HTTP_403_FORBIDDEN,
|
|
)
|
|
|
|
# Create ride photo using ride media service
|
|
photo = RideMediaService.upload_photo(
|
|
ride=content_object,
|
|
image_file=validated_data["photo"],
|
|
user=request.user,
|
|
caption=validated_data.get("caption", ""),
|
|
alt_text=validated_data.get("alt_text", ""),
|
|
is_primary=validated_data.get("is_primary", False),
|
|
photo_type=validated_data.get("photo_type", "general"),
|
|
)
|
|
else:
|
|
return Response(
|
|
{"error": f"Unsupported content type for media upload: {content_object._meta.label}"},
|
|
status=status.HTTP_400_BAD_REQUEST,
|
|
)
|
|
|
|
response_serializer = PhotoUploadOutputSerializer(
|
|
{
|
|
"id": photo.id,
|
|
"url": photo.image.url,
|
|
"caption": photo.caption,
|
|
"alt_text": photo.alt_text,
|
|
"is_primary": photo.is_primary,
|
|
"message": "Photo uploaded successfully",
|
|
}
|
|
)
|
|
|
|
return Response(response_serializer.data, status=status.HTTP_201_CREATED)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in photo upload: {str(e)}", exc_info=True)
|
|
return Response(
|
|
{"error": f"An error occurred while uploading the photo: {str(e)}"},
|
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
)
|
|
|
|
|
|
@extend_schema_view(
|
|
list=extend_schema(
|
|
summary="List photos",
|
|
description="Retrieve a list of photos with optional filtering",
|
|
parameters=[
|
|
OpenApiParameter(
|
|
name="content_type",
|
|
type=OpenApiTypes.STR,
|
|
location=OpenApiParameter.QUERY,
|
|
description="Filter by content type (e.g., 'parks.park')",
|
|
),
|
|
OpenApiParameter(
|
|
name="object_id",
|
|
type=OpenApiTypes.INT,
|
|
location=OpenApiParameter.QUERY,
|
|
description="Filter by object ID",
|
|
),
|
|
],
|
|
responses={200: PhotoListOutputSerializer(many=True)},
|
|
tags=["Media"],
|
|
),
|
|
retrieve=extend_schema(
|
|
summary="Get photo details",
|
|
description="Retrieve detailed information about a specific photo",
|
|
responses={
|
|
200: PhotoDetailOutputSerializer,
|
|
404: OpenApiTypes.OBJECT,
|
|
},
|
|
tags=["Media"],
|
|
),
|
|
update=extend_schema(
|
|
summary="Update photo",
|
|
description="Update photo information (caption, alt text, etc.)",
|
|
request=PhotoUpdateInputSerializer,
|
|
responses={
|
|
200: PhotoDetailOutputSerializer,
|
|
400: OpenApiTypes.OBJECT,
|
|
403: OpenApiTypes.OBJECT,
|
|
404: OpenApiTypes.OBJECT,
|
|
},
|
|
tags=["Media"],
|
|
),
|
|
destroy=extend_schema(
|
|
summary="Delete photo",
|
|
description="Delete a photo (only by owner or admin)",
|
|
responses={
|
|
204: None,
|
|
403: OpenApiTypes.OBJECT,
|
|
404: OpenApiTypes.OBJECT,
|
|
},
|
|
tags=["Media"],
|
|
),
|
|
set_primary=extend_schema(
|
|
summary="Set photo as primary",
|
|
description="Set this photo as the primary photo for its content object",
|
|
responses={
|
|
200: OpenApiTypes.OBJECT,
|
|
403: OpenApiTypes.OBJECT,
|
|
404: OpenApiTypes.OBJECT,
|
|
},
|
|
tags=["Media"],
|
|
),
|
|
)
|
|
class PhotoViewSet(ModelViewSet):
|
|
"""ViewSet for managing photos."""
|
|
|
|
permission_classes = [IsAuthenticated]
|
|
lookup_field = "id"
|
|
|
|
def get_serializer_class(self):
|
|
"""Return appropriate serializer based on action."""
|
|
if self.action == "list":
|
|
return PhotoListOutputSerializer
|
|
elif self.action in ["update", "partial_update"]:
|
|
return PhotoUpdateInputSerializer
|
|
return PhotoDetailOutputSerializer
|