""" Parks API views following Django styleguide patterns. Uses ClassNameApi naming convention and proper Input/Output serializers. """ from rest_framework.decorators import action from rest_framework.request import Request from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet from rest_framework.permissions import ( IsAuthenticated, IsAuthenticatedOrReadOnly, ) from django_filters.rest_framework import DjangoFilterBackend from rest_framework.filters import SearchFilter, OrderingFilter from apps.core.api.mixins import ( CreateApiMixin, UpdateApiMixin, ListApiMixin, RetrieveApiMixin, DestroyApiMixin, ) from ..selectors import ( park_list_with_stats, park_detail_optimized, park_reviews_for_park, park_statistics, ) from ..services import ParkService from .serializers import ( ParkListOutputSerializer, ParkDetailOutputSerializer, ParkCreateInputSerializer, ParkUpdateInputSerializer, ParkFilterInputSerializer, ParkReviewOutputSerializer, ParkStatsOutputSerializer, ) class ParkListApi(ListApiMixin, GenericViewSet): """ API endpoint for listing parks with filtering and search. GET /api/v1/parks/ """ permission_classes = [IsAuthenticatedOrReadOnly] filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter] search_fields = ["name", "description"] ordering_fields = [ "name", "opening_date", "average_rating", "coaster_count", "created_at", ] ordering = ["name"] OutputSerializer = ParkListOutputSerializer FilterSerializer = ParkFilterInputSerializer def get_queryset(self): """Use selector to get optimized queryset.""" # Parse filter parameters filter_serializer = self.FilterSerializer(data=self.request.query_params) filter_serializer.is_valid(raise_exception=True) filters = filter_serializer.validated_data return park_list_with_stats(filters=filters) @action(detail=False, methods=["get"]) def stats(self, request: Request) -> Response: """ Get park statistics. GET /api/v1/parks/stats/ """ stats = park_statistics() serializer = ParkStatsOutputSerializer(stats) return self.create_response( data=serializer.data, metadata={"cache_duration": 3600}, # 1 hour cache hint ) class ParkDetailApi(RetrieveApiMixin, GenericViewSet): """ API endpoint for retrieving individual park details. GET /api/v1/parks/{id}/ """ permission_classes = [IsAuthenticatedOrReadOnly] lookup_field = "slug" OutputSerializer = ParkDetailOutputSerializer def get_object(self): """Use selector for optimized detail query.""" slug = self.kwargs.get("slug") return park_detail_optimized(slug=slug) @action(detail=True, methods=["get"]) def reviews(self, request: Request, slug: str = None) -> Response: """ Get reviews for a specific park. GET /api/v1/parks/{slug}/reviews/ """ park = self.get_object() reviews = park_reviews_for_park(park_id=park.id, limit=50) serializer = ParkReviewOutputSerializer(reviews, many=True) return self.create_response( data=serializer.data, metadata={"total_reviews": len(reviews), "park_name": park.name}, ) class ParkCreateApi(CreateApiMixin, GenericViewSet): """ API endpoint for creating parks. POST /api/v1/parks/create/ """ permission_classes = [IsAuthenticated] InputSerializer = ParkCreateInputSerializer OutputSerializer = ParkDetailOutputSerializer def perform_create(self, **validated_data): """Create park using service layer.""" return ParkService.create_park(**validated_data) class ParkUpdateApi(UpdateApiMixin, RetrieveApiMixin, GenericViewSet): """ API endpoint for updating parks. PUT /api/v1/parks/{slug}/update/ PATCH /api/v1/parks/{slug}/update/ """ permission_classes = [IsAuthenticated] lookup_field = "slug" InputSerializer = ParkUpdateInputSerializer OutputSerializer = ParkDetailOutputSerializer def get_object(self): """Use selector for optimized detail query.""" slug = self.kwargs.get("slug") return park_detail_optimized(slug=slug) def perform_update(self, instance, **validated_data): """Update park using service layer.""" return ParkService.update_park(park_id=instance.id, **validated_data) class ParkDeleteApi(DestroyApiMixin, RetrieveApiMixin, GenericViewSet): """ API endpoint for deleting parks. DELETE /api/v1/parks/{slug}/delete/ """ permission_classes = [IsAuthenticated] # TODO: Add staff/admin permission lookup_field = "slug" def get_object(self): """Use selector for optimized detail query.""" slug = self.kwargs.get("slug") return park_detail_optimized(slug=slug) def perform_destroy(self, instance): """Delete park using service layer.""" ParkService.delete_park(park_id=instance.id) # Unified API ViewSet (alternative approach) class ParkApi( CreateApiMixin, UpdateApiMixin, ListApiMixin, RetrieveApiMixin, DestroyApiMixin, GenericViewSet, ): """ Unified API endpoint for parks with all CRUD operations. GET /api/v1/parks/ - List parks POST /api/v1/parks/ - Create park GET /api/v1/parks/{slug}/ - Get park detail PUT /api/v1/parks/{slug}/ - Update park PATCH /api/v1/parks/{slug}/ - Partial update park DELETE /api/v1/parks/{slug}/ - Delete park """ permission_classes = [IsAuthenticatedOrReadOnly] lookup_field = "slug" filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter] search_fields = ["name", "description"] ordering_fields = [ "name", "opening_date", "average_rating", "coaster_count", "created_at", ] ordering = ["name"] # Serializers for different operations InputSerializer = ParkCreateInputSerializer # Used for create UpdateInputSerializer = ParkUpdateInputSerializer # Used for update OutputSerializer = ParkDetailOutputSerializer # Used for retrieve ListOutputSerializer = ParkListOutputSerializer # Used for list FilterSerializer = ParkFilterInputSerializer def get_queryset(self): """Use selector to get optimized queryset.""" if self.action == "list": # Parse filter parameters for list view filter_serializer = self.FilterSerializer(data=self.request.query_params) filter_serializer.is_valid(raise_exception=True) filters = filter_serializer.validated_data return park_list_with_stats(**filters) # For detail views, this won't be used since we override get_object return [] def get_object(self): """Use selector for optimized detail query.""" slug = self.kwargs.get("slug") return park_detail_optimized(slug=slug) def get_output_serializer(self, *args, **kwargs): """Return appropriate output serializer based on action.""" if self.action == "list": return self.ListOutputSerializer(*args, **kwargs) return self.OutputSerializer(*args, **kwargs) def get_input_serializer(self, *args, **kwargs): """Return appropriate input serializer based on action.""" if self.action in ["update", "partial_update"]: return self.UpdateInputSerializer(*args, **kwargs) return self.InputSerializer(*args, **kwargs) def perform_create(self, **validated_data): """Create park using service layer.""" return ParkService.create_park(**validated_data) def perform_update(self, instance, **validated_data): """Update park using service layer.""" return ParkService.update_park(park_id=instance.id, **validated_data) def perform_destroy(self, instance): """Delete park using service layer.""" ParkService.delete_park(park_id=instance.id) @action(detail=False, methods=["get"]) def stats(self, request: Request) -> Response: """ Get park statistics. GET /api/v1/parks/stats/ """ stats = park_statistics() serializer = ParkStatsOutputSerializer(stats) return self.create_response( data=serializer.data, metadata={"cache_duration": 3600} ) @action(detail=True, methods=["get"]) def reviews(self, request: Request, slug: str = None) -> Response: """ Get reviews for a specific park. GET /api/v1/parks/{slug}/reviews/ """ park = self.get_object() reviews = park_reviews_for_park(park_id=park.id, limit=50) serializer = ParkReviewOutputSerializer(reviews, many=True) return self.create_response( data=serializer.data, metadata={"total_reviews": len(reviews), "park_name": park.name}, )