mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 07:11:08 -05:00
ok
This commit is contained in:
@@ -11,6 +11,7 @@ from drf_spectacular.utils import (
|
||||
extend_schema_field,
|
||||
OpenApiExample,
|
||||
)
|
||||
from config.django import base as settings
|
||||
|
||||
from .shared import LocationOutputSerializer, CompanyOutputSerializer, ModelChoices
|
||||
|
||||
@@ -65,10 +66,18 @@ class ParkListOutputSerializer(serializers.Serializer):
|
||||
# Operator info
|
||||
operator = CompanyOutputSerializer()
|
||||
|
||||
# URL
|
||||
url = serializers.SerializerMethodField()
|
||||
|
||||
# Metadata
|
||||
created_at = serializers.DateTimeField()
|
||||
updated_at = serializers.DateTimeField()
|
||||
|
||||
@extend_schema_field(serializers.URLField())
|
||||
def get_url(self, obj) -> str:
|
||||
"""Generate the frontend URL for this park."""
|
||||
return f"{settings.FRONTEND_DOMAIN}/parks/{obj.slug}/"
|
||||
|
||||
|
||||
@extend_schema_serializer(
|
||||
examples=[
|
||||
@@ -166,6 +175,14 @@ class ParkDetailOutputSerializer(serializers.Serializer):
|
||||
banner_image = serializers.SerializerMethodField()
|
||||
card_image = serializers.SerializerMethodField()
|
||||
|
||||
# URL
|
||||
url = serializers.SerializerMethodField()
|
||||
|
||||
@extend_schema_field(serializers.URLField())
|
||||
def get_url(self, obj) -> str:
|
||||
"""Generate the frontend URL for this park."""
|
||||
return f"{settings.FRONTEND_DOMAIN}/parks/{obj.slug}/"
|
||||
|
||||
@extend_schema_field(serializers.ListField(child=serializers.DictField()))
|
||||
def get_areas(self, obj):
|
||||
"""Get simplified area information."""
|
||||
|
||||
98
backend/apps/api/v1/serializers/reviews.py
Normal file
98
backend/apps/api/v1/serializers/reviews.py
Normal file
@@ -0,0 +1,98 @@
|
||||
"""
|
||||
Serializers for review-related API endpoints.
|
||||
"""
|
||||
|
||||
from rest_framework import serializers
|
||||
from apps.parks.models.reviews import ParkReview
|
||||
from apps.rides.models.reviews import RideReview
|
||||
from apps.accounts.models import User
|
||||
|
||||
|
||||
class ReviewUserSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for user information in reviews."""
|
||||
avatar_url = serializers.SerializerMethodField()
|
||||
display_name = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['username', 'display_name', 'avatar_url']
|
||||
|
||||
def get_avatar_url(self, obj):
|
||||
"""Get the user's avatar URL."""
|
||||
if hasattr(obj, 'profile') and obj.profile:
|
||||
return obj.profile.get_avatar()
|
||||
return "/static/images/default-avatar.png"
|
||||
|
||||
def get_display_name(self, obj):
|
||||
"""Get the user's display name."""
|
||||
return obj.get_display_name()
|
||||
|
||||
|
||||
class LatestReviewSerializer(serializers.Serializer):
|
||||
"""Serializer for latest reviews combining park and ride reviews."""
|
||||
id = serializers.IntegerField()
|
||||
type = serializers.CharField() # 'park' or 'ride'
|
||||
title = serializers.CharField()
|
||||
content_snippet = serializers.CharField()
|
||||
rating = serializers.IntegerField()
|
||||
created_at = serializers.DateTimeField()
|
||||
user = ReviewUserSerializer()
|
||||
|
||||
# Subject information (park or ride)
|
||||
subject_name = serializers.CharField()
|
||||
subject_slug = serializers.CharField()
|
||||
subject_url = serializers.CharField()
|
||||
|
||||
# Park information (for ride reviews)
|
||||
park_name = serializers.CharField(allow_null=True)
|
||||
park_slug = serializers.CharField(allow_null=True)
|
||||
park_url = serializers.CharField(allow_null=True)
|
||||
|
||||
def to_representation(self, instance):
|
||||
"""Convert review instance to serialized representation."""
|
||||
if isinstance(instance, ParkReview):
|
||||
return {
|
||||
'id': instance.pk,
|
||||
'type': 'park',
|
||||
'title': instance.title,
|
||||
'content_snippet': self._get_content_snippet(instance.content),
|
||||
'rating': instance.rating,
|
||||
'created_at': instance.created_at,
|
||||
'user': ReviewUserSerializer(instance.user).data,
|
||||
'subject_name': instance.park.name,
|
||||
'subject_slug': instance.park.slug,
|
||||
'subject_url': f"/parks/{instance.park.slug}/",
|
||||
'park_name': None,
|
||||
'park_slug': None,
|
||||
'park_url': None,
|
||||
}
|
||||
elif isinstance(instance, RideReview):
|
||||
return {
|
||||
'id': instance.pk,
|
||||
'type': 'ride',
|
||||
'title': instance.title,
|
||||
'content_snippet': self._get_content_snippet(instance.content),
|
||||
'rating': instance.rating,
|
||||
'created_at': instance.created_at,
|
||||
'user': ReviewUserSerializer(instance.user).data,
|
||||
'subject_name': instance.ride.name,
|
||||
'subject_slug': instance.ride.slug,
|
||||
'subject_url': f"/parks/{instance.ride.park.slug}/rides/{instance.ride.slug}/",
|
||||
'park_name': instance.ride.park.name,
|
||||
'park_slug': instance.ride.park.slug,
|
||||
'park_url': f"/parks/{instance.ride.park.slug}/",
|
||||
}
|
||||
return {}
|
||||
|
||||
def _get_content_snippet(self, content, max_length=150):
|
||||
"""Get a snippet of the review content."""
|
||||
if len(content) <= max_length:
|
||||
return content
|
||||
|
||||
# Find the last complete word within the limit
|
||||
snippet = content[:max_length]
|
||||
last_space = snippet.rfind(' ')
|
||||
if last_space > 0:
|
||||
snippet = snippet[:last_space]
|
||||
|
||||
return snippet + "..."
|
||||
@@ -11,6 +11,7 @@ from drf_spectacular.utils import (
|
||||
extend_schema_field,
|
||||
OpenApiExample,
|
||||
)
|
||||
from config.django import base as settings
|
||||
|
||||
from .shared import ModelChoices
|
||||
|
||||
@@ -142,10 +143,18 @@ class RideModelListOutputSerializer(serializers.Serializer):
|
||||
# Primary image
|
||||
primary_image = RideModelPhotoOutputSerializer(allow_null=True)
|
||||
|
||||
# URL
|
||||
url = serializers.SerializerMethodField()
|
||||
|
||||
# Metadata
|
||||
created_at = serializers.DateTimeField()
|
||||
updated_at = serializers.DateTimeField()
|
||||
|
||||
@extend_schema_field(serializers.URLField())
|
||||
def get_url(self, obj) -> str:
|
||||
"""Generate the frontend URL for this ride model."""
|
||||
return f"{settings.FRONTEND_DOMAIN}/rides/manufacturers/{obj.manufacturer.slug}/{obj.slug}/"
|
||||
|
||||
|
||||
@extend_schema_serializer(
|
||||
examples=[
|
||||
@@ -277,10 +286,18 @@ class RideModelDetailOutputSerializer(serializers.Serializer):
|
||||
technical_specs = RideModelTechnicalSpecOutputSerializer(many=True)
|
||||
installations = serializers.SerializerMethodField()
|
||||
|
||||
# URL
|
||||
url = serializers.SerializerMethodField()
|
||||
|
||||
# Metadata
|
||||
created_at = serializers.DateTimeField()
|
||||
updated_at = serializers.DateTimeField()
|
||||
|
||||
@extend_schema_field(serializers.URLField())
|
||||
def get_url(self, obj) -> str:
|
||||
"""Generate the frontend URL for this ride model."""
|
||||
return f"{settings.FRONTEND_DOMAIN}/rides/manufacturers/{obj.manufacturer.slug}/{obj.slug}/"
|
||||
|
||||
@extend_schema_field(serializers.ListField(child=serializers.DictField()))
|
||||
def get_installations(self, obj):
|
||||
"""Get ride installations using this model."""
|
||||
|
||||
@@ -11,7 +11,7 @@ from drf_spectacular.utils import (
|
||||
extend_schema_field,
|
||||
OpenApiExample,
|
||||
)
|
||||
|
||||
from config.django import base as settings
|
||||
from .shared import ModelChoices
|
||||
|
||||
|
||||
@@ -90,10 +90,18 @@ class RideListOutputSerializer(serializers.Serializer):
|
||||
opening_date = serializers.DateField(allow_null=True)
|
||||
closing_date = serializers.DateField(allow_null=True)
|
||||
|
||||
# URL
|
||||
url = serializers.SerializerMethodField()
|
||||
|
||||
# Metadata
|
||||
created_at = serializers.DateTimeField()
|
||||
updated_at = serializers.DateTimeField()
|
||||
|
||||
@extend_schema_field(serializers.URLField())
|
||||
def get_url(self, obj) -> str:
|
||||
"""Generate the frontend URL for this ride."""
|
||||
return f"{settings.FRONTEND_DOMAIN}/parks/{obj.park.slug}/rides/{obj.slug}/"
|
||||
|
||||
|
||||
@extend_schema_serializer(
|
||||
examples=[
|
||||
@@ -194,10 +202,18 @@ class RideDetailOutputSerializer(serializers.Serializer):
|
||||
banner_image = serializers.SerializerMethodField()
|
||||
card_image = serializers.SerializerMethodField()
|
||||
|
||||
# URL
|
||||
url = serializers.SerializerMethodField()
|
||||
|
||||
# Metadata
|
||||
created_at = serializers.DateTimeField()
|
||||
updated_at = serializers.DateTimeField()
|
||||
|
||||
@extend_schema_field(serializers.URLField())
|
||||
def get_url(self, obj) -> str:
|
||||
"""Generate the frontend URL for this ride."""
|
||||
return f"{settings.FRONTEND_DOMAIN}/parks/{obj.park.slug}/rides/{obj.slug}/"
|
||||
|
||||
@extend_schema_field(serializers.DictField(allow_null=True))
|
||||
def get_park_area(self, obj) -> dict | None:
|
||||
if obj.park_area:
|
||||
|
||||
@@ -8,6 +8,7 @@ to avoid code duplication and maintain consistency.
|
||||
from rest_framework import serializers
|
||||
from drf_spectacular.utils import extend_schema_field
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.conf import settings
|
||||
|
||||
# Import models inside class methods to avoid Django initialization issues
|
||||
|
||||
@@ -173,3 +174,31 @@ class CompanyOutputSerializer(serializers.Serializer):
|
||||
name = serializers.CharField()
|
||||
slug = serializers.CharField()
|
||||
roles = serializers.ListField(child=serializers.CharField(), required=False)
|
||||
url = serializers.SerializerMethodField()
|
||||
|
||||
@extend_schema_field(serializers.URLField())
|
||||
def get_url(self, obj) -> str:
|
||||
"""Generate the frontend URL for this company based on their primary role.
|
||||
|
||||
CRITICAL DOMAIN SEPARATION:
|
||||
- OPERATOR and PROPERTY_OWNER are for parks domain
|
||||
- MANUFACTURER and DESIGNER are for rides domain
|
||||
"""
|
||||
# Use the URL field from the model if it exists (auto-generated on save)
|
||||
if hasattr(obj, 'url') and obj.url:
|
||||
return obj.url
|
||||
|
||||
# Fallback URL generation (should not be needed if model save works correctly)
|
||||
if hasattr(obj, 'roles') and obj.roles:
|
||||
frontend_domain = getattr(
|
||||
settings, 'FRONTEND_DOMAIN', 'https://thrillwiki.com')
|
||||
primary_role = obj.roles[0] if obj.roles else None
|
||||
|
||||
# Only generate URLs for rides domain roles here
|
||||
if primary_role == 'MANUFACTURER':
|
||||
return f"{frontend_domain}/rides/manufacturers/{obj.slug}/"
|
||||
elif primary_role == 'DESIGNER':
|
||||
return f"{frontend_domain}/rides/designers/{obj.slug}/"
|
||||
# OPERATOR and PROPERTY_OWNER URLs are handled by parks domain
|
||||
|
||||
return ""
|
||||
|
||||
@@ -21,8 +21,10 @@ from .views import (
|
||||
# Trending system views
|
||||
TrendingAPIView,
|
||||
NewContentAPIView,
|
||||
TriggerTrendingCalculationAPIView,
|
||||
)
|
||||
from .views.stats import StatsAPIView, StatsRecalculateAPIView
|
||||
from .views.reviews import LatestReviewsAPIView
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
@@ -57,11 +59,15 @@ urlpatterns = [
|
||||
name="performance-metrics",
|
||||
),
|
||||
# Trending system endpoints
|
||||
path("trending/content/", TrendingAPIView.as_view(), name="trending"),
|
||||
path("trending/new/", NewContentAPIView.as_view(), name="new-content"),
|
||||
path("trending/", TrendingAPIView.as_view(), name="trending"),
|
||||
path("new-content/", NewContentAPIView.as_view(), name="new-content"),
|
||||
path("trending/calculate/", TriggerTrendingCalculationAPIView.as_view(),
|
||||
name="trigger-trending-calculation"),
|
||||
# Statistics endpoints
|
||||
path("stats/", StatsAPIView.as_view(), name="stats"),
|
||||
path("stats/recalculate/", StatsRecalculateAPIView.as_view(), name="stats-recalculate"),
|
||||
# Reviews endpoints
|
||||
path("reviews/latest/", LatestReviewsAPIView.as_view(), name="latest-reviews"),
|
||||
# Ranking system endpoints
|
||||
path(
|
||||
"rankings/calculate/",
|
||||
|
||||
@@ -28,6 +28,7 @@ from .health import (
|
||||
from .trending import (
|
||||
TrendingAPIView,
|
||||
NewContentAPIView,
|
||||
TriggerTrendingCalculationAPIView,
|
||||
)
|
||||
|
||||
# Export all views for import convenience
|
||||
@@ -48,4 +49,5 @@ __all__ = [
|
||||
# Trending views
|
||||
"TrendingAPIView",
|
||||
"NewContentAPIView",
|
||||
"TriggerTrendingCalculationAPIView",
|
||||
]
|
||||
|
||||
85
backend/apps/api/v1/views/reviews.py
Normal file
85
backend/apps/api/v1/views/reviews.py
Normal file
@@ -0,0 +1,85 @@
|
||||
"""
|
||||
Views for review-related API endpoints.
|
||||
"""
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework import status
|
||||
from django.db.models import Q
|
||||
from drf_spectacular.utils import extend_schema, OpenApiParameter
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from itertools import chain
|
||||
from operator import attrgetter
|
||||
|
||||
from apps.parks.models.reviews import ParkReview
|
||||
from apps.rides.models.reviews import RideReview
|
||||
from ..serializers.reviews import LatestReviewSerializer
|
||||
|
||||
|
||||
class LatestReviewsAPIView(APIView):
|
||||
"""
|
||||
API endpoint to get the latest reviews from both parks and rides.
|
||||
|
||||
Returns a combined list of the most recent reviews across the platform,
|
||||
including username, user avatar, date, score, and review snippet.
|
||||
"""
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
@extend_schema(
|
||||
summary="Get Latest Reviews",
|
||||
description=(
|
||||
"Retrieve the latest reviews from both parks and rides. "
|
||||
"Returns a combined list sorted by creation date, including "
|
||||
"user information, ratings, and content snippets."
|
||||
),
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="limit",
|
||||
type=OpenApiTypes.INT,
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Number of reviews to return (default: 20, max: 100)",
|
||||
default=20,
|
||||
),
|
||||
],
|
||||
responses={
|
||||
200: LatestReviewSerializer(many=True),
|
||||
},
|
||||
tags=["Reviews"],
|
||||
)
|
||||
def get(self, request):
|
||||
"""Get the latest reviews from both parks and rides."""
|
||||
# Get limit parameter with validation
|
||||
try:
|
||||
limit = int(request.query_params.get('limit', 20))
|
||||
limit = min(max(limit, 1), 100) # Clamp between 1 and 100
|
||||
except (ValueError, TypeError):
|
||||
limit = 20
|
||||
|
||||
# Get published reviews from both models
|
||||
park_reviews = ParkReview.objects.filter(
|
||||
is_published=True
|
||||
).select_related(
|
||||
'user', 'user__profile', 'park'
|
||||
).order_by('-created_at')[:limit]
|
||||
|
||||
ride_reviews = RideReview.objects.filter(
|
||||
is_published=True
|
||||
).select_related(
|
||||
'user', 'user__profile', 'ride', 'ride__park'
|
||||
).order_by('-created_at')[:limit]
|
||||
|
||||
# Combine and sort by created_at
|
||||
all_reviews = sorted(
|
||||
chain(park_reviews, ride_reviews),
|
||||
key=attrgetter('created_at'),
|
||||
reverse=True
|
||||
)[:limit]
|
||||
|
||||
# Serialize the combined results
|
||||
serializer = LatestReviewSerializer(all_reviews, many=True)
|
||||
|
||||
return Response({
|
||||
'count': len(all_reviews),
|
||||
'results': serializer.data
|
||||
}, status=status.HTTP_200_OK)
|
||||
@@ -9,7 +9,8 @@ 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
|
||||
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
|
||||
|
||||
@@ -48,17 +49,12 @@ class TrendingAPIView(APIView):
|
||||
|
||||
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)
|
||||
from apps.core.services.trending_service import trending_service
|
||||
|
||||
# Parse parameters
|
||||
limit = min(int(request.query_params.get("limit", 20)), 100)
|
||||
|
||||
# Get trending content
|
||||
trending_service = TrendingService()
|
||||
# Get trending content using direct calculation service
|
||||
all_trending = trending_service.get_trending_content(limit=limit * 2)
|
||||
|
||||
# Separate by content type
|
||||
@@ -75,20 +71,8 @@ class TrendingAPIView(APIView):
|
||||
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]
|
||||
# Latest reviews will be empty until review system is implemented
|
||||
latest_reviews = []
|
||||
|
||||
# Return in expected frontend format
|
||||
response_data = {
|
||||
@@ -99,82 +83,85 @@ class TrendingAPIView(APIView):
|
||||
|
||||
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",
|
||||
@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"},
|
||||
},
|
||||
},
|
||||
{
|
||||
"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]
|
||||
403: {"description": "Admin access required"},
|
||||
},
|
||||
tags=["Trending"],
|
||||
),
|
||||
)
|
||||
class TriggerTrendingCalculationAPIView(APIView):
|
||||
"""API endpoint to manually trigger trending content calculation."""
|
||||
|
||||
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]
|
||||
permission_classes = [IsAdminUser]
|
||||
|
||||
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]
|
||||
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
|
||||
|
||||
response_data = {
|
||||
"trending_rides": trending_rides,
|
||||
"trending_parks": trending_parks,
|
||||
"latest_reviews": latest_reviews,
|
||||
}
|
||||
# Capture command output
|
||||
trending_output = io.StringIO()
|
||||
new_content_output = io.StringIO()
|
||||
|
||||
return Response(response_data)
|
||||
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(
|
||||
@@ -210,19 +197,15 @@ class NewContentAPIView(APIView):
|
||||
|
||||
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)
|
||||
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 with longer timeframe to get more data
|
||||
trending_service = TrendingService()
|
||||
# Get new content using direct calculation service
|
||||
all_new_content = trending_service.get_new_content(
|
||||
limit=limit * 2, days_back=60
|
||||
limit=limit * 2, days_back=days_back
|
||||
)
|
||||
|
||||
recently_added = []
|
||||
@@ -258,30 +241,12 @@ class NewContentAPIView(APIView):
|
||||
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",
|
||||
},
|
||||
]
|
||||
# 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 []
|
||||
upcoming = upcoming[: limit // 3] if upcoming else []
|
||||
|
||||
# Return in expected frontend format
|
||||
response_data = {
|
||||
@@ -291,73 +256,3 @@ class NewContentAPIView(APIView):
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user