Files
thrillwiki_django_no_react/backend/apps/api/v1/maps/views.py
pacnpal 540f40e689 Revert "update"
This reverts commit 75cc618c2b.
2025-09-21 20:11:00 -04:00

1067 lines
40 KiB
Python

"""
Centralized map API views.
Migrated from apps.core.views.map_views
"""
import logging
from django.http import HttpRequest
from django.db.models import Q
from django.core.cache import cache
from django.contrib.gis.geos import Polygon
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import AllowAny
from drf_spectacular.utils import (
extend_schema,
extend_schema_view,
OpenApiParameter,
OpenApiExample,
)
from drf_spectacular.types import OpenApiTypes
from apps.parks.models import Park
from apps.rides.models import Ride
from ..serializers.maps import (
MapLocationsResponseSerializer,
MapSearchResponseSerializer,
MapLocationDetailSerializer,
)
logger = logging.getLogger(__name__)
@extend_schema_view(
get=extend_schema(
summary="Get map locations",
description="Get map locations with optional clustering and filtering.",
parameters=[
OpenApiParameter(
"north",
type=OpenApiTypes.NUMBER,
location=OpenApiParameter.QUERY,
required=False,
description="Northern latitude bound (-90 to 90). Used with south, east, west to define geographic bounds.",
examples=[OpenApiExample("Example", value=41.5)],
),
OpenApiParameter(
"south",
type=OpenApiTypes.NUMBER,
location=OpenApiParameter.QUERY,
required=False,
description="Southern latitude bound (-90 to 90). Must be less than north bound.",
examples=[OpenApiExample("Example", value=41.4)],
),
OpenApiParameter(
"east",
type=OpenApiTypes.NUMBER,
location=OpenApiParameter.QUERY,
required=False,
description="Eastern longitude bound (-180 to 180). Must be greater than west bound.",
examples=[OpenApiExample("Example", value=-82.6)],
),
OpenApiParameter(
"west",
type=OpenApiTypes.NUMBER,
location=OpenApiParameter.QUERY,
required=False,
description="Western longitude bound (-180 to 180). Used with other bounds for geographic filtering.",
examples=[OpenApiExample("Example", value=-82.8)],
),
OpenApiParameter(
"zoom",
type=OpenApiTypes.INT,
location=OpenApiParameter.QUERY,
required=False,
description="Map zoom level (1-20). Higher values show more detail. Used for clustering decisions.",
examples=[OpenApiExample("Example", value=10)],
),
OpenApiParameter(
"types",
type=OpenApiTypes.STR,
location=OpenApiParameter.QUERY,
required=False,
description="Comma-separated location types to include. Valid values: 'park', 'ride'. Default: 'park,ride'",
examples=[
OpenApiExample("All types", value="park,ride"),
OpenApiExample("Parks only", value="park"),
OpenApiExample("Rides only", value="ride"),
],
),
OpenApiParameter(
"cluster",
type=OpenApiTypes.BOOL,
location=OpenApiParameter.QUERY,
required=False,
description="Enable location clustering for high-density areas. Default: false",
examples=[
OpenApiExample("Enable clustering", value=True),
OpenApiExample("Disable clustering", value=False),
],
),
OpenApiParameter(
"q",
type=OpenApiTypes.STR,
location=OpenApiParameter.QUERY,
required=False,
description="Text search query. Searches park/ride names, cities, and states.",
examples=[
OpenApiExample("Park name", value="Cedar Point"),
OpenApiExample("Ride type", value="roller coaster"),
OpenApiExample("Location", value="Ohio"),
],
),
],
responses={
200: MapLocationsResponseSerializer,
400: OpenApiTypes.OBJECT,
500: OpenApiTypes.OBJECT,
},
tags=["Maps"],
),
)
class MapLocationsAPIView(APIView):
"""API endpoint for getting map locations with optional clustering."""
permission_classes = [AllowAny]
def _parse_request_parameters(self, request: HttpRequest) -> dict:
"""Parse and validate request parameters."""
return {
"north": request.GET.get("north"),
"south": request.GET.get("south"),
"east": request.GET.get("east"),
"west": request.GET.get("west"),
"zoom": request.GET.get("zoom", 10),
"types": request.GET.get("types", "park,ride").split(","),
"cluster": request.GET.get("cluster", "false").lower() == "true",
"query": request.GET.get("q", "").strip(),
}
def _build_cache_key(self, params: dict) -> str:
"""Build cache key from parameters."""
return (
f"map_locations_{params['north']}_{params['south']}_"
f"{params['east']}_{params['west']}_{params['zoom']}_"
f"{','.join(params['types'])}_{params['cluster']}_{params['query']}"
)
def _create_bounds_polygon(self, north: str, south: str, east: str, west: str) -> Polygon | None:
"""Create bounds polygon from coordinate strings."""
if not all([north, south, east, west]):
return None
try:
return Polygon.from_bbox(
(float(west), float(south), float(east), float(north))
)
except (ValueError, TypeError):
return None
def _serialize_park_location(self, park) -> dict:
"""Serialize park location data."""
location = park.location if hasattr(
park, "location") and park.location else None
return {
"city": location.city if location else "",
"state": location.state if location else "",
"country": location.country if location else "",
"formatted_address": location.formatted_address if location else "",
}
def _serialize_park_data(self, park) -> dict:
"""Serialize park data for map response."""
location = park.location if hasattr(
park, "location") and park.location else None
return {
"id": park.id,
"type": "park",
"name": park.name,
"slug": park.slug,
"latitude": location.latitude if location else None,
"longitude": location.longitude if location else None,
"status": park.status,
"location": self._serialize_park_location(park),
"stats": {
"coaster_count": park.coaster_count or 0,
"ride_count": park.ride_count or 0,
"average_rating": (
float(park.average_rating) if park.average_rating else None
),
},
}
def _get_parks_data(self, params: dict) -> list:
"""Get and serialize parks data."""
if "park" not in params["types"]:
return []
parks_query = Park.objects.select_related(
"location", "operator"
).filter(location__point__isnull=False)
# Apply bounds filtering
bounds_polygon = self._create_bounds_polygon(
params["north"], params["south"], params["east"], params["west"]
)
if bounds_polygon:
parks_query = parks_query.filter(location__point__within=bounds_polygon)
# Apply text search
if params["query"]:
parks_query = parks_query.filter(
Q(name__icontains=params["query"])
| Q(location__city__icontains=params["query"])
| Q(location__state__icontains=params["query"])
)
return [self._serialize_park_data(park) for park in parks_query[:100]]
def _serialize_ride_location(self, ride) -> dict:
"""Serialize ride location data."""
location = (
ride.park.location
if hasattr(ride.park, "location") and ride.park.location
else None
)
return {
"city": location.city if location else "",
"state": location.state if location else "",
"country": location.country if location else "",
"formatted_address": location.formatted_address if location else "",
}
def _serialize_ride_data(self, ride) -> dict:
"""Serialize ride data for map response."""
location = (
ride.park.location
if hasattr(ride.park, "location") and ride.park.location
else None
)
return {
"id": ride.id,
"type": "ride",
"name": ride.name,
"slug": ride.slug,
"latitude": location.latitude if location else None,
"longitude": location.longitude if location else None,
"status": ride.status,
"location": self._serialize_ride_location(ride),
"stats": {
"category": ride.get_category_display() if ride.category else None,
"average_rating": (
float(ride.average_rating) if ride.average_rating else None
),
"park_name": ride.park.name,
},
}
def _get_rides_data(self, params: dict) -> list:
"""Get and serialize rides data."""
if "ride" not in params["types"]:
return []
rides_query = Ride.objects.select_related(
"park__location", "manufacturer"
).filter(park__location__point__isnull=False)
# Apply bounds filtering
bounds_polygon = self._create_bounds_polygon(
params["north"], params["south"], params["east"], params["west"]
)
if bounds_polygon:
rides_query = rides_query.filter(
park__location__point__within=bounds_polygon)
# Apply text search
if params["query"]:
rides_query = rides_query.filter(
Q(name__icontains=params["query"])
| Q(park__name__icontains=params["query"])
| Q(park__location__city__icontains=params["query"])
)
return [self._serialize_ride_data(ride) for ride in rides_query[:100]]
def _calculate_bounds(self, locations: list) -> dict:
"""Calculate bounds from location results."""
if not locations:
return {}
lats = [loc["latitude"] for loc in locations if loc["latitude"]]
lngs = [loc["longitude"] for loc in locations if loc["longitude"]]
if not lats or not lngs:
return {}
return {
"north": max(lats),
"south": min(lats),
"east": max(lngs),
"west": min(lngs),
}
def _build_response(self, locations: list, params: dict) -> dict:
"""Build the final response data."""
return {
"status": "success",
"locations": locations,
"clusters": [], # TODO: Implement clustering
"bounds": self._calculate_bounds(locations),
"total_count": len(locations),
"clustered": params["cluster"],
}
def get(self, request: HttpRequest) -> Response:
"""Get map locations with optional clustering and filtering."""
try:
params = self._parse_request_parameters(request)
cache_key = self._build_cache_key(params)
# Check cache first
cached_result = cache.get(cache_key)
if cached_result:
return Response(cached_result)
# Get location data
parks_data = self._get_parks_data(params)
rides_data = self._get_rides_data(params)
locations = parks_data + rides_data
# Build response
result = self._build_response(locations, params)
# Cache result for 5 minutes
cache.set(cache_key, result, 300)
return Response(result)
except Exception as e:
logger.error(f"Error in MapLocationsAPIView: {str(e)}", exc_info=True)
return Response(
{"status": "error", "message": "Failed to retrieve map locations"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
@extend_schema_view(
get=extend_schema(
summary="Get location details",
description="Get detailed information about a specific location.",
parameters=[
OpenApiParameter(
"location_type",
type=OpenApiTypes.STR,
location=OpenApiParameter.PATH,
required=True,
description="Type of location",
),
OpenApiParameter(
"location_id",
type=OpenApiTypes.INT,
location=OpenApiParameter.PATH,
required=True,
description="ID of the location",
),
],
responses={
200: MapLocationDetailSerializer,
400: OpenApiTypes.OBJECT,
404: OpenApiTypes.OBJECT,
500: OpenApiTypes.OBJECT,
},
tags=["Maps"],
),
)
class MapLocationDetailAPIView(APIView):
"""API endpoint for getting detailed information about a specific location."""
permission_classes = [AllowAny]
def get(
self, request: HttpRequest, location_type: str, location_id: int
) -> Response:
"""Get detailed information for a specific location."""
try:
if location_type == "park":
try:
obj = Park.objects.select_related("location", "operator").get(
id=location_id
)
except Park.DoesNotExist:
return Response(
{"status": "error", "message": "Park not found"},
status=status.HTTP_404_NOT_FOUND,
)
elif location_type == "ride":
try:
obj = Ride.objects.select_related(
"park__location", "manufacturer"
).get(id=location_id)
except Ride.DoesNotExist:
return Response(
{"status": "error", "message": "Ride not found"},
status=status.HTTP_404_NOT_FOUND,
)
else:
return Response(
{"status": "error", "message": "Invalid location type"},
status=status.HTTP_400_BAD_REQUEST,
)
# Serialize the object
if location_type == "park":
data = {
"id": obj.id,
"type": "park",
"name": obj.name,
"slug": obj.slug,
"description": obj.description,
"latitude": (
obj.location.latitude
if hasattr(obj, "location") and obj.location
else None
),
"longitude": (
obj.location.longitude
if hasattr(obj, "location") and obj.location
else None
),
"status": obj.status,
"location": {
"street_address": (
obj.location.street_address
if hasattr(obj, "location") and obj.location
else ""
),
"city": (
obj.location.city
if hasattr(obj, "location") and obj.location
else ""
),
"state": (
obj.location.state
if hasattr(obj, "location") and obj.location
else ""
),
"country": (
obj.location.country
if hasattr(obj, "location") and obj.location
else ""
),
"postal_code": (
obj.location.postal_code
if hasattr(obj, "location") and obj.location
else ""
),
"formatted_address": (
obj.location.formatted_address
if hasattr(obj, "location") and obj.location
else ""
),
},
"stats": {
"coaster_count": obj.coaster_count or 0,
"ride_count": obj.ride_count or 0,
"average_rating": (
float(obj.average_rating) if obj.average_rating else None
),
"size_acres": float(obj.size_acres) if obj.size_acres else None,
"opening_date": (
obj.opening_date.isoformat() if obj.opening_date else None
),
},
"nearby_locations": [], # TODO: Implement nearby locations
}
else: # ride
data = {
"id": obj.id,
"type": "ride",
"name": obj.name,
"slug": obj.slug,
"description": obj.description,
"latitude": (
obj.park.location.latitude
if hasattr(obj.park, "location") and obj.park.location
else None
),
"longitude": (
obj.park.location.longitude
if hasattr(obj.park, "location") and obj.park.location
else None
),
"status": obj.status,
"location": {
"street_address": (
obj.park.location.street_address
if hasattr(obj.park, "location") and obj.park.location
else ""
),
"city": (
obj.park.location.city
if hasattr(obj.park, "location") and obj.park.location
else ""
),
"state": (
obj.park.location.state
if hasattr(obj.park, "location") and obj.park.location
else ""
),
"country": (
obj.park.location.country
if hasattr(obj.park, "location") and obj.park.location
else ""
),
"postal_code": (
obj.park.location.postal_code
if hasattr(obj.park, "location") and obj.park.location
else ""
),
"formatted_address": (
obj.park.location.formatted_address
if hasattr(obj.park, "location") and obj.park.location
else ""
),
},
"stats": {
"category": (
obj.get_category_display() if obj.category else None
),
"average_rating": (
float(obj.average_rating) if obj.average_rating else None
),
"park_name": obj.park.name,
"opening_date": (
obj.opening_date.isoformat() if obj.opening_date else None
),
"manufacturer": (
obj.manufacturer.name if obj.manufacturer else None
),
},
"nearby_locations": [], # TODO: Implement nearby locations
}
return Response(
{
"status": "success",
"data": data,
}
)
except Exception as e:
logger.error(f"Error in MapLocationDetailAPIView: {str(e)}", exc_info=True)
return Response(
{"status": "error", "message": "Failed to retrieve location details"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
@extend_schema_view(
get=extend_schema(
summary="Search map locations",
description="Search locations by text query with optional bounds filtering.",
parameters=[
OpenApiParameter(
"q",
type=OpenApiTypes.STR,
location=OpenApiParameter.QUERY,
required=True,
description="Search query",
),
OpenApiParameter(
"types",
type=OpenApiTypes.STR,
location=OpenApiParameter.QUERY,
required=False,
description="Comma-separated location types (park,ride)",
),
OpenApiParameter(
"page",
type=OpenApiTypes.INT,
location=OpenApiParameter.QUERY,
required=False,
description="Page number",
),
OpenApiParameter(
"page_size",
type=OpenApiTypes.INT,
location=OpenApiParameter.QUERY,
required=False,
description="Results per page",
),
],
responses={
200: MapSearchResponseSerializer,
400: OpenApiTypes.OBJECT,
500: OpenApiTypes.OBJECT,
},
tags=["Maps"],
),
)
class MapSearchAPIView(APIView):
"""API endpoint for searching locations by text query."""
permission_classes = [AllowAny]
def get(self, request: HttpRequest) -> Response:
"""Search locations by text query with pagination."""
try:
query = request.GET.get("q", "").strip()
if not query:
return Response(
{
"status": "error",
"message": "Search query 'q' parameter is required",
},
status=status.HTTP_400_BAD_REQUEST,
)
types = request.GET.get("types", "park,ride").split(",")
page = int(request.GET.get("page", 1))
page_size = min(int(request.GET.get("page_size", 20)), 100)
results = []
total_count = 0
# Search parks
if "park" in types:
parks_query = (
Park.objects.select_related("location")
.filter(
Q(name__icontains=query)
| Q(location__city__icontains=query)
| Q(location__state__icontains=query)
)
.filter(location__point__isnull=False)
)
for park in parks_query[:50]: # Limit results
results.append(
{
"id": park.id,
"type": "park",
"name": park.name,
"slug": park.slug,
"latitude": (
park.location.latitude
if hasattr(park, "location") and park.location
else None
),
"longitude": (
park.location.longitude
if hasattr(park, "location") and park.location
else None
),
"location": {
"city": (
park.location.city
if hasattr(park, "location") and park.location
else ""
),
"state": (
park.location.state
if hasattr(park, "location") and park.location
else ""
),
"country": (
park.location.country
if hasattr(park, "location") and park.location
else ""
),
},
"relevance_score": 1.0, # TODO: Implement relevance scoring
}
)
# Search rides
if "ride" in types:
rides_query = (
Ride.objects.select_related("park__location")
.filter(
Q(name__icontains=query)
| Q(park__name__icontains=query)
| Q(park__location__city__icontains=query)
)
.filter(park__location__point__isnull=False)
)
for ride in rides_query[:50]: # Limit results
results.append(
{
"id": ride.id,
"type": "ride",
"name": ride.name,
"slug": ride.slug,
"latitude": (
ride.park.location.latitude
if hasattr(ride.park, "location") and ride.park.location
else None
),
"longitude": (
ride.park.location.longitude
if hasattr(ride.park, "location") and ride.park.location
else None
),
"location": {
"city": (
ride.park.location.city
if hasattr(ride.park, "location")
and ride.park.location
else ""
),
"state": (
ride.park.location.state
if hasattr(ride.park, "location")
and ride.park.location
else ""
),
"country": (
ride.park.location.country
if hasattr(ride.park, "location")
and ride.park.location
else ""
),
},
"relevance_score": 1.0, # TODO: Implement relevance scoring
}
)
total_count = len(results)
# Apply pagination
start_idx = (page - 1) * page_size
end_idx = start_idx + page_size
paginated_results = results[start_idx:end_idx]
return Response(
{
"status": "success",
"results": paginated_results,
"query": query,
"total_count": total_count,
"page": page,
"page_size": page_size,
}
)
except Exception as e:
logger.error(f"Error in MapSearchAPIView: {str(e)}", exc_info=True)
return Response(
{"status": "error", "message": "Search failed due to internal error"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
@extend_schema_view(
get=extend_schema(
summary="Get locations within bounds",
description="Get locations within specific geographic bounds.",
parameters=[
OpenApiParameter(
"north",
type=OpenApiTypes.NUMBER,
location=OpenApiParameter.QUERY,
required=True,
description="Northern latitude bound",
),
OpenApiParameter(
"south",
type=OpenApiTypes.NUMBER,
location=OpenApiParameter.QUERY,
required=True,
description="Southern latitude bound",
),
OpenApiParameter(
"east",
type=OpenApiTypes.NUMBER,
location=OpenApiParameter.QUERY,
required=True,
description="Eastern longitude bound",
),
OpenApiParameter(
"west",
type=OpenApiTypes.NUMBER,
location=OpenApiParameter.QUERY,
required=True,
description="Western longitude bound",
),
OpenApiParameter(
"types",
type=OpenApiTypes.STR,
location=OpenApiParameter.QUERY,
required=False,
description="Comma-separated location types (park,ride)",
),
],
responses={200: OpenApiTypes.OBJECT, 400: OpenApiTypes.OBJECT},
tags=["Maps"],
),
)
class MapBoundsAPIView(APIView):
"""API endpoint for getting locations within specific bounds."""
permission_classes = [AllowAny]
def get(self, request: HttpRequest) -> Response:
"""Get locations within specific geographic bounds."""
try:
# Parse required bounds parameters
north_str = request.GET.get("north")
south_str = request.GET.get("south")
east_str = request.GET.get("east")
west_str = request.GET.get("west")
if not all([north_str, south_str, east_str, west_str]):
return Response(
{"status": "error",
"message": "All bounds parameters (north, south, east, west) are required"},
status=status.HTTP_400_BAD_REQUEST,
)
try:
north = float(north_str) if north_str else 0.0
south = float(south_str) if south_str else 0.0
east = float(east_str) if east_str else 0.0
west = float(west_str) if west_str else 0.0
except (TypeError, ValueError):
return Response(
{"status": "error", "message": "Invalid bounds parameters"},
status=status.HTTP_400_BAD_REQUEST,
)
# Validate bounds
if north <= south:
return Response(
{
"status": "error",
"message": "North bound must be greater than south bound",
},
status=status.HTTP_400_BAD_REQUEST,
)
if west >= east:
return Response(
{
"status": "error",
"message": "West bound must be less than east bound",
},
status=status.HTTP_400_BAD_REQUEST,
)
types = request.GET.get("types", "park,ride").split(",")
locations = []
# Create bounds polygon
bounds_polygon = Polygon.from_bbox((west, south, east, north))
# Get parks within bounds
if "park" in types:
parks_query = Park.objects.select_related("location").filter(
location__point__within=bounds_polygon
)
for park in parks_query[:100]: # Limit results
locations.append(
{
"id": park.id,
"type": "park",
"name": park.name,
"slug": park.slug,
"latitude": (
park.location.latitude
if hasattr(park, "location") and park.location
else None
),
"longitude": (
park.location.longitude
if hasattr(park, "location") and park.location
else None
),
"status": park.status,
}
)
# Get rides within bounds
if "ride" in types:
rides_query = Ride.objects.select_related("park__location").filter(
park__location__point__within=bounds_polygon
)
for ride in rides_query[:100]: # Limit results
locations.append(
{
"id": ride.id,
"type": "ride",
"name": ride.name,
"slug": ride.slug,
"latitude": (
ride.park.location.latitude
if hasattr(ride.park, "location") and ride.park.location
else None
),
"longitude": (
ride.park.location.longitude
if hasattr(ride.park, "location") and ride.park.location
else None
),
"status": ride.status,
}
)
return Response(
{
"status": "success",
"locations": locations,
"bounds": {
"north": north,
"south": south,
"east": east,
"west": west,
},
"total_count": len(locations),
}
)
except Exception as e:
logger.error(f"Error in MapBoundsAPIView: {str(e)}", exc_info=True)
return Response(
{
"status": "error",
"message": "Failed to retrieve locations within bounds",
},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
@extend_schema_view(
get=extend_schema(
summary="Get map service statistics",
description="Get map service statistics and performance metrics.",
responses={200: OpenApiTypes.OBJECT},
tags=["Maps"],
),
)
class MapStatsAPIView(APIView):
"""API endpoint for getting map service statistics and health information."""
permission_classes = [AllowAny]
def get(self, request: HttpRequest) -> Response:
"""Get map service statistics and performance metrics."""
try:
# Count locations with coordinates
parks_with_location = Park.objects.filter(
location__point__isnull=False
).count()
rides_with_location = Ride.objects.filter(
park__location__point__isnull=False
).count()
total_locations = parks_with_location + rides_with_location
return Response(
{
"status": "success",
"data": {
"total_locations": total_locations,
"parks_with_location": parks_with_location,
"rides_with_location": rides_with_location,
"cache_hits": 0, # TODO: Implement cache statistics
"cache_misses": 0, # TODO: Implement cache statistics
},
}
)
except Exception as e:
logger.error(f"Error in MapStatsAPIView: {str(e)}", exc_info=True)
return Response(
{"error": f"Internal server error: {str(e)}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
@extend_schema_view(
delete=extend_schema(
summary="Clear map cache",
description="Clear all map cache (admin only).",
responses={200: OpenApiTypes.OBJECT},
tags=["Maps"],
),
post=extend_schema(
summary="Invalidate specific cache entries",
description="Invalidate specific cache entries.",
responses={200: OpenApiTypes.OBJECT},
tags=["Maps"],
),
)
class MapCacheAPIView(APIView):
"""API endpoint for cache management (admin only)."""
permission_classes = [AllowAny] # TODO: Add admin permission check
def delete(self, request: HttpRequest) -> Response:
"""Clear all map cache (admin only)."""
try:
# Clear all map-related cache keys
# Note: cache.keys() may not be available in all cache backends
try:
cache_keys = cache.keys("map_*")
if cache_keys:
cache.delete_many(cache_keys)
cleared_count = len(cache_keys)
else:
cleared_count = 0
except AttributeError:
# Fallback: clear cache without pattern matching
cache.clear()
cleared_count = 1 # Indicate cache was cleared
return Response(
{
"status": "success",
"message": f"Map cache cleared successfully. Cleared {cleared_count} entries.",
}
)
except Exception as e:
logger.error(f"Error in MapCacheAPIView.delete: {str(e)}", exc_info=True)
return Response(
{"error": f"Internal server error: {str(e)}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
def post(self, request: HttpRequest) -> Response:
"""Invalidate specific cache entries."""
try:
# Get cache keys to invalidate from request data
request_data = getattr(request, 'data', {})
cache_keys = request_data.get("cache_keys", []) if request_data else []
if cache_keys:
cache.delete_many(cache_keys)
invalidated_count = len(cache_keys)
else:
invalidated_count = 0
return Response(
{
"status": "success",
"message": f"Cache invalidated successfully. Invalidated {invalidated_count} entries.",
}
)
except Exception as e:
logger.error(f"Error in MapCacheAPIView.post: {str(e)}", exc_info=True)
return Response(
{"error": f"Internal server error: {str(e)}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
# Legacy compatibility aliases
MapLocationsView = MapLocationsAPIView
MapLocationDetailView = MapLocationDetailAPIView
MapSearchView = MapSearchAPIView
MapBoundsView = MapBoundsAPIView
MapStatsView = MapStatsAPIView
MapCacheView = MapCacheAPIView