mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-21 16:51:09 -05:00
update
This commit is contained in:
265
apps/api/v1/views/trending.py
Normal file
265
apps/api/v1/views/trending.py
Normal file
@@ -0,0 +1,265 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user