mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 14:11:09 -05:00
266 lines
9.3 KiB
Python
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)
|