Refactor test utilities and enhance ASGI settings

- Cleaned up and standardized assertions in ApiTestMixin for API response validation.
- Updated ASGI settings to use os.environ for setting the DJANGO_SETTINGS_MODULE.
- Removed unused imports and improved formatting in settings.py.
- Refactored URL patterns in urls.py for better readability and organization.
- Enhanced view functions in views.py for consistency and clarity.
- Added .flake8 configuration for linting and style enforcement.
- Introduced type stubs for django-environ to improve type checking with Pylance.
This commit is contained in:
pacnpal
2025-08-20 19:51:59 -04:00
parent 69c07d1381
commit 66ed4347a9
230 changed files with 15094 additions and 11578 deletions

View File

@@ -3,30 +3,29 @@ Parks API views following Django styleguide patterns.
Uses ClassNameApi naming convention and proper Input/Output serializers.
"""
from typing import Any, Dict
from rest_framework import status
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 rest_framework.permissions import (
IsAuthenticated,
IsAuthenticatedOrReadOnly,
)
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter, OrderingFilter
from core.api.mixins import (
ApiMixin,
CreateApiMixin,
UpdateApiMixin,
ListApiMixin,
CreateApiMixin,
UpdateApiMixin,
ListApiMixin,
RetrieveApiMixin,
DestroyApiMixin
DestroyApiMixin,
)
from ..selectors import (
park_list_with_stats,
park_detail_optimized,
park_reviews_for_park,
park_statistics
park_statistics,
)
from ..services import ParkService
from .serializers import (
@@ -36,165 +35,148 @@ from .serializers import (
ParkUpdateInputSerializer,
ParkFilterInputSerializer,
ParkReviewOutputSerializer,
ParkStatsOutputSerializer
ParkStatsOutputSerializer,
)
class ParkListApi(
ListApiMixin,
GenericViewSet
):
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']
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'])
@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
metadata={"cache_duration": 3600}, # 1 hour cache hint
)
class ParkDetailApi(
RetrieveApiMixin,
GenericViewSet
):
class ParkDetailApi(RetrieveApiMixin, GenericViewSet):
"""
API endpoint for retrieving individual park details.
GET /api/v1/parks/{id}/
"""
permission_classes = [IsAuthenticatedOrReadOnly]
lookup_field = 'slug'
lookup_field = "slug"
OutputSerializer = ParkDetailOutputSerializer
def get_object(self):
"""Use selector for optimized detail query."""
slug = self.kwargs.get('slug')
slug = self.kwargs.get("slug")
return park_detail_optimized(slug=slug)
@action(detail=True, methods=['get'])
@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
}
metadata={"total_reviews": len(reviews), "park_name": park.name},
)
class ParkCreateApi(
CreateApiMixin,
GenericViewSet
):
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
):
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'
lookup_field = "slug"
InputSerializer = ParkUpdateInputSerializer
OutputSerializer = ParkDetailOutputSerializer
def get_object(self):
"""Use selector for optimized detail query."""
slug = self.kwargs.get('slug')
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
)
return ParkService.update_park(park_id=instance.id, **validated_data)
class ParkDeleteApi(
DestroyApiMixin,
RetrieveApiMixin,
GenericViewSet
):
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'
lookup_field = "slug"
def get_object(self):
"""Use selector for optimized detail query."""
slug = self.kwargs.get('slug')
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)
@@ -207,11 +189,11 @@ class ParkApi(
ListApiMixin,
RetrieveApiMixin,
DestroyApiMixin,
GenericViewSet
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
@@ -219,96 +201,95 @@ class ParkApi(
PATCH /api/v1/parks/{slug}/ - Partial update park
DELETE /api/v1/parks/{slug}/ - Delete park
"""
permission_classes = [IsAuthenticatedOrReadOnly]
lookup_field = 'slug'
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']
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':
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')
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':
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']:
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
)
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'])
@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}
data=serializer.data, metadata={"cache_duration": 3600}
)
@action(detail=True, methods=['get'])
@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
}
metadata={"total_reviews": len(reviews), "park_name": park.name},
)