mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 08:31:08 -05:00
- Add complete backend/ directory with full Django application - Add frontend/ directory with Vite + TypeScript setup ready for Next.js - Add comprehensive shared/ directory with: - Complete documentation and memory-bank archives - Media files and avatars (letters, park/ride images) - Deployment scripts and automation tools - Shared types and utilities - Add architecture/ directory with migration guides - Configure pnpm workspace for monorepo development - Update .gitignore to exclude .django_tailwind_cli/ build artifacts - Preserve all historical documentation in shared/docs/memory-bank/ - Set up proper structure for full-stack development with shared resources
296 lines
8.9 KiB
Python
296 lines
8.9 KiB
Python
"""
|
|
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},
|
|
)
|