Files
thrillwiki_django_no_react/apps/api/v1/views/trending.py
2025-09-21 20:19:12 -04:00

266 lines
9.3 KiB
Python

"""
Trending content API views for ThrillWiki API v1.
This module contains endpoints for trending and new content discovery
including trending parks, rides, and recently added content.
"""
from datetime import datetime, date
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.permissions import AllowAny, IsAdminUser
from rest_framework import status
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter
from drf_spectacular.types import OpenApiTypes
@extend_schema_view(
get=extend_schema(
summary="Get trending content",
description="Retrieve trending parks and rides based on view counts, ratings, and recency.",
parameters=[
OpenApiParameter(
name="limit",
location=OpenApiParameter.QUERY,
description="Number of trending items to return (default: 20, max: 100)",
required=False,
type=OpenApiTypes.INT,
default=20,
),
OpenApiParameter(
name="timeframe",
location=OpenApiParameter.QUERY,
description="Timeframe for trending calculation (day, week, month) - default: week",
required=False,
type=OpenApiTypes.STR,
enum=["day", "week", "month"],
default="week",
),
],
responses={200: OpenApiTypes.OBJECT},
tags=["Trending"],
),
)
class TrendingAPIView(APIView):
"""API endpoint for trending content."""
permission_classes = [AllowAny]
def get(self, request: Request) -> Response:
"""Get trending parks and rides."""
from apps.core.services.trending_service import trending_service
# Parse parameters
limit = min(int(request.query_params.get("limit", 20)), 100)
# Get trending content using direct calculation service
all_trending = trending_service.get_trending_content(limit=limit * 2)
# Separate by content type
trending_rides = []
trending_parks = []
for item in all_trending:
if item.get("category") == "ride":
trending_rides.append(item)
elif item.get("category") == "park":
trending_parks.append(item)
# Limit each category
trending_rides = trending_rides[: limit // 3] if trending_rides else []
trending_parks = trending_parks[: limit // 3] if trending_parks else []
# Latest reviews will be empty until review system is implemented
latest_reviews = []
# Return in expected frontend format
response_data = {
"trending_rides": trending_rides,
"trending_parks": trending_parks,
"latest_reviews": latest_reviews,
}
return Response(response_data)
@extend_schema_view(
post=extend_schema(
summary="Trigger trending content calculation",
description="Manually trigger the calculation of trending content using Django management commands. Admin access required.",
responses={
202: {
"type": "object",
"properties": {
"message": {"type": "string"},
"trending_completed": {"type": "boolean"},
"new_content_completed": {"type": "boolean"},
"completion_time": {"type": "string"},
},
},
403: {"description": "Admin access required"},
},
tags=["Trending"],
),
)
class TriggerTrendingCalculationAPIView(APIView):
"""API endpoint to manually trigger trending content calculation."""
permission_classes = [IsAdminUser]
def post(self, request: Request) -> Response:
"""Trigger trending content calculation using management commands."""
try:
from django.core.management import call_command
import io
from contextlib import redirect_stdout, redirect_stderr
# Capture command output
trending_output = io.StringIO()
new_content_output = io.StringIO()
trending_completed = False
new_content_completed = False
try:
# Run trending calculation command
with redirect_stdout(trending_output), redirect_stderr(trending_output):
call_command(
"calculate_trending", "--content-type=all", "--limit=50"
)
trending_completed = True
except Exception as e:
trending_output.write(f"Error: {str(e)}")
try:
# Run new content calculation command
with redirect_stdout(new_content_output), redirect_stderr(
new_content_output
):
call_command(
"calculate_new_content",
"--content-type=all",
"--days-back=30",
"--limit=50",
)
new_content_completed = True
except Exception as e:
new_content_output.write(f"Error: {str(e)}")
completion_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
return Response(
{
"message": "Trending content calculation completed",
"trending_completed": trending_completed,
"new_content_completed": new_content_completed,
"completion_time": completion_time,
"trending_output": trending_output.getvalue(),
"new_content_output": new_content_output.getvalue(),
},
status=status.HTTP_202_ACCEPTED,
)
except Exception as e:
return Response(
{
"error": "Failed to trigger trending content calculation",
"details": str(e),
},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
@extend_schema_view(
get=extend_schema(
summary="Get new content",
description="Retrieve recently added parks and rides.",
parameters=[
OpenApiParameter(
name="limit",
location=OpenApiParameter.QUERY,
description="Number of new items to return (default: 20, max: 100)",
required=False,
type=OpenApiTypes.INT,
default=20,
),
OpenApiParameter(
name="days",
location=OpenApiParameter.QUERY,
description="Number of days to look back for new content (default: 30, max: 365)",
required=False,
type=OpenApiTypes.INT,
default=30,
),
],
responses={200: OpenApiTypes.OBJECT},
tags=["Trending"],
),
)
class NewContentAPIView(APIView):
"""API endpoint for new content."""
permission_classes = [AllowAny]
def get(self, request: Request) -> Response:
"""Get new parks and rides."""
from apps.core.services.trending_service import trending_service
# Parse parameters
limit = min(int(request.query_params.get("limit", 20)), 100)
days_back = min(int(request.query_params.get("days", 30)), 365)
# Get new content using direct calculation service
all_new_content = trending_service.get_new_content(
limit=limit * 2, days_back=days_back
)
recently_added = []
newly_opened = []
upcoming = []
# Categorize items based on date
today = date.today()
for item in all_new_content:
date_added = item.get("date_added", "")
if date_added:
try:
# Parse the date string
if isinstance(date_added, str):
item_date = datetime.fromisoformat(date_added).date()
else:
item_date = date_added
# Calculate days difference
days_diff = (today - item_date).days
if days_diff <= 30: # Recently added (last 30 days)
recently_added.append(item)
elif days_diff <= 365: # Newly opened (last year)
newly_opened.append(item)
else: # Older items
newly_opened.append(item)
except (ValueError, TypeError):
# If date parsing fails, add to recently added
recently_added.append(item)
else:
recently_added.append(item)
# Upcoming items will be empty until future content system is implemented
upcoming = []
# Limit each category
recently_added = recently_added[: limit // 3] if recently_added else []
newly_opened = newly_opened[: limit // 3] if newly_opened else []
# Return in expected frontend format
response_data = {
"recently_added": recently_added,
"newly_opened": newly_opened,
"upcoming": upcoming,
}
return Response(response_data)