mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 02:11:08 -05:00
365 lines
12 KiB
Python
365 lines
12 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 django.utils import timezone
|
|
from rest_framework import status
|
|
from rest_framework.views import APIView
|
|
from rest_framework.request import Request
|
|
from rest_framework.response import Response
|
|
from rest_framework.permissions import AllowAny
|
|
from drf_spectacular.utils import extend_schema, extend_schema_view
|
|
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=[
|
|
{
|
|
"name": "limit",
|
|
"in": "query",
|
|
"description": "Number of trending items to return (default: 20, max: 100)",
|
|
"required": False,
|
|
"schema": {"type": "integer", "default": 20, "maximum": 100},
|
|
},
|
|
{
|
|
"name": "timeframe",
|
|
"in": "query",
|
|
"description": "Timeframe for trending calculation (day, week, month) - default: week",
|
|
"required": False,
|
|
"schema": {
|
|
"type": "string",
|
|
"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."""
|
|
try:
|
|
from apps.core.services.trending_service import TrendingService
|
|
except ImportError:
|
|
# Fallback if trending service is not available
|
|
return self._get_fallback_trending_content(request)
|
|
|
|
# Parse parameters
|
|
limit = min(int(request.query_params.get("limit", 20)), 100)
|
|
|
|
# Get trending content
|
|
trending_service = TrendingService()
|
|
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 []
|
|
|
|
# Create mock latest reviews (since not implemented yet)
|
|
latest_reviews = [
|
|
{
|
|
"id": 1,
|
|
"name": "Steel Vengeance Review",
|
|
"location": "Cedar Point",
|
|
"category": "Roller Coaster",
|
|
"rating": 5.0,
|
|
"rank": 1,
|
|
"views": 1234,
|
|
"views_change": "+45%",
|
|
"slug": "steel-vengeance-review",
|
|
}
|
|
][: limit // 3]
|
|
|
|
# Return in expected frontend format
|
|
response_data = {
|
|
"trending_rides": trending_rides,
|
|
"trending_parks": trending_parks,
|
|
"latest_reviews": latest_reviews,
|
|
}
|
|
|
|
return Response(response_data)
|
|
|
|
def _get_fallback_trending_content(self, request: Request) -> Response:
|
|
"""Fallback method when trending service is not available."""
|
|
limit = min(int(request.query_params.get("limit", 20)), 100)
|
|
|
|
# Mock trending data
|
|
trending_rides = [
|
|
{
|
|
"id": 1,
|
|
"name": "Steel Vengeance",
|
|
"location": "Cedar Point",
|
|
"category": "Roller Coaster",
|
|
"rating": 4.8,
|
|
"rank": 1,
|
|
"views": 15234,
|
|
"views_change": "+25%",
|
|
"slug": "steel-vengeance",
|
|
},
|
|
{
|
|
"id": 2,
|
|
"name": "Lightning Rod",
|
|
"location": "Dollywood",
|
|
"category": "Roller Coaster",
|
|
"rating": 4.7,
|
|
"rank": 2,
|
|
"views": 12456,
|
|
"views_change": "+18%",
|
|
"slug": "lightning-rod",
|
|
},
|
|
][: limit // 3]
|
|
|
|
trending_parks = [
|
|
{
|
|
"id": 1,
|
|
"name": "Cedar Point",
|
|
"location": "Sandusky, OH",
|
|
"category": "Theme Park",
|
|
"rating": 4.6,
|
|
"rank": 1,
|
|
"views": 45678,
|
|
"views_change": "+12%",
|
|
"slug": "cedar-point",
|
|
},
|
|
{
|
|
"id": 2,
|
|
"name": "Magic Kingdom",
|
|
"location": "Orlando, FL",
|
|
"category": "Theme Park",
|
|
"rating": 4.5,
|
|
"rank": 2,
|
|
"views": 67890,
|
|
"views_change": "+8%",
|
|
"slug": "magic-kingdom",
|
|
},
|
|
][: limit // 3]
|
|
|
|
latest_reviews = [
|
|
{
|
|
"id": 1,
|
|
"name": "Steel Vengeance Review",
|
|
"location": "Cedar Point",
|
|
"category": "Roller Coaster",
|
|
"rating": 5.0,
|
|
"rank": 1,
|
|
"views": 1234,
|
|
"views_change": "+45%",
|
|
"slug": "steel-vengeance-review",
|
|
}
|
|
][: limit // 3]
|
|
|
|
response_data = {
|
|
"trending_rides": trending_rides,
|
|
"trending_parks": trending_parks,
|
|
"latest_reviews": latest_reviews,
|
|
}
|
|
|
|
return Response(response_data)
|
|
|
|
|
|
@extend_schema_view(
|
|
get=extend_schema(
|
|
summary="Get new content",
|
|
description="Retrieve recently added parks and rides.",
|
|
parameters=[
|
|
{
|
|
"name": "limit",
|
|
"in": "query",
|
|
"description": "Number of new items to return (default: 20, max: 100)",
|
|
"required": False,
|
|
"schema": {"type": "integer", "default": 20, "maximum": 100},
|
|
},
|
|
{
|
|
"name": "days",
|
|
"in": "query",
|
|
"description": "Number of days to look back for new content (default: 30, max: 365)",
|
|
"required": False,
|
|
"schema": {"type": "integer", "default": 30, "maximum": 365},
|
|
},
|
|
],
|
|
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."""
|
|
try:
|
|
from apps.core.services.trending_service import TrendingService
|
|
except ImportError:
|
|
# Fallback if trending service is not available
|
|
return self._get_fallback_new_content(request)
|
|
|
|
# Parse parameters
|
|
limit = min(int(request.query_params.get("limit", 20)), 100)
|
|
|
|
# Get new content with longer timeframe to get more data
|
|
trending_service = TrendingService()
|
|
all_new_content = trending_service.get_new_content(
|
|
limit=limit * 2, days_back=60
|
|
)
|
|
|
|
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)
|
|
|
|
# Create mock upcoming items
|
|
upcoming = [
|
|
{
|
|
"id": 1,
|
|
"name": "Epic Universe",
|
|
"location": "Universal Orlando",
|
|
"category": "Theme Park",
|
|
"date_added": "Opening 2025",
|
|
"slug": "epic-universe",
|
|
},
|
|
{
|
|
"id": 2,
|
|
"name": "New Fantasyland Expansion",
|
|
"location": "Magic Kingdom",
|
|
"category": "Land Expansion",
|
|
"date_added": "Opening 2026",
|
|
"slug": "fantasyland-expansion",
|
|
},
|
|
]
|
|
|
|
# Limit each category
|
|
recently_added = recently_added[: limit // 3] if recently_added else []
|
|
newly_opened = newly_opened[: limit // 3] if newly_opened else []
|
|
upcoming = upcoming[: limit // 3] if upcoming else []
|
|
|
|
# Return in expected frontend format
|
|
response_data = {
|
|
"recently_added": recently_added,
|
|
"newly_opened": newly_opened,
|
|
"upcoming": upcoming,
|
|
}
|
|
|
|
return Response(response_data)
|
|
|
|
def _get_fallback_new_content(self, request: Request) -> Response:
|
|
"""Fallback method when trending service is not available."""
|
|
limit = min(int(request.query_params.get("limit", 20)), 100)
|
|
|
|
# Mock new content data
|
|
recently_added = [
|
|
{
|
|
"id": 1,
|
|
"name": "Iron Gwazi",
|
|
"location": "Busch Gardens Tampa",
|
|
"category": "Roller Coaster",
|
|
"date_added": "2024-12-01",
|
|
"slug": "iron-gwazi",
|
|
},
|
|
{
|
|
"id": 2,
|
|
"name": "VelociCoaster",
|
|
"location": "Universal's Islands of Adventure",
|
|
"category": "Roller Coaster",
|
|
"date_added": "2024-11-15",
|
|
"slug": "velocicoaster",
|
|
},
|
|
][: limit // 3]
|
|
|
|
newly_opened = [
|
|
{
|
|
"id": 3,
|
|
"name": "Guardians of the Galaxy",
|
|
"location": "EPCOT",
|
|
"category": "Roller Coaster",
|
|
"date_added": "2024-10-01",
|
|
"slug": "guardians-galaxy",
|
|
},
|
|
{
|
|
"id": 4,
|
|
"name": "TRON Lightcycle Run",
|
|
"location": "Magic Kingdom",
|
|
"category": "Roller Coaster",
|
|
"date_added": "2024-09-15",
|
|
"slug": "tron-lightcycle",
|
|
},
|
|
][: limit // 3]
|
|
|
|
upcoming = [
|
|
{
|
|
"id": 5,
|
|
"name": "Epic Universe",
|
|
"location": "Universal Orlando",
|
|
"category": "Theme Park",
|
|
"date_added": "Opening 2025",
|
|
"slug": "epic-universe",
|
|
},
|
|
{
|
|
"id": 6,
|
|
"name": "New Fantasyland Expansion",
|
|
"location": "Magic Kingdom",
|
|
"category": "Land Expansion",
|
|
"date_added": "Opening 2026",
|
|
"slug": "fantasyland-expansion",
|
|
},
|
|
][: limit // 3]
|
|
|
|
response_data = {
|
|
"recently_added": recently_added,
|
|
"newly_opened": newly_opened,
|
|
"upcoming": upcoming,
|
|
}
|
|
|
|
return Response(response_data)
|