from rest_framework import generics from rest_framework.response import Response from rest_framework.decorators import api_view from rest_framework.pagination import PageNumberPagination from django.shortcuts import get_object_or_404 from django.db.models import Count from django.http import Http404 from .models.rides import Ride, Categories, RideModel from .models.company import Company from .forms.search import MasterFilterForm from .services.search import RideSearchService from .serializers import RideSerializer from apps.parks.models import Park class RidePagination(PageNumberPagination): """Custom pagination for ride API""" page_size = 24 page_size_query_param = "page_size" max_page_size = 100 class RideListAPIView(generics.ListAPIView): """API endpoint for listing and filtering rides""" serializer_class = RideSerializer pagination_class = RidePagination def get_queryset(self): """Get filtered rides using the advanced search service""" # Initialize search service search_service = RideSearchService() # Parse filters from request filter_form = MasterFilterForm(self.request.query_params) # Apply park context if available park = None park_slug = self.request.query_params.get("park_slug") if park_slug: try: park = get_object_or_404(Park, slug=park_slug) except Http404: park = None if filter_form.is_valid(): # Use advanced search service queryset = search_service.search_rides( filters=filter_form.get_filter_dict(), park=park ) else: # Fallback to basic queryset with park filter queryset = ( Ride.objects.all() .select_related("park", "ride_model", "ride_model__manufacturer") .prefetch_related("photos") ) if park: queryset = queryset.filter(park=park) return queryset def list(self, request, *args, **kwargs): """Enhanced list response with filter metadata""" queryset = self.filter_queryset(self.get_queryset()) # Get pagination page = self.paginate_queryset(queryset) if page is not None: serializer = self.get_serializer(page, many=True) paginated_response = self.get_paginated_response(serializer.data) # Add filter metadata filter_form = MasterFilterForm(request.query_params) if filter_form.is_valid(): active_filters = filter_form.get_filter_summary() has_filters = filter_form.has_active_filters() else: active_filters = {} has_filters = False # Add counts park_slug = request.query_params.get("park_slug") if park_slug: try: park = get_object_or_404(Park, slug=park_slug) total_rides = Ride.objects.filter(park=park).count() except Http404: total_rides = Ride.objects.count() else: total_rides = Ride.objects.count() filtered_count = queryset.count() # Enhance response with metadata paginated_response.data.update( { "filter_metadata": { "active_filters": active_filters, "has_filters": has_filters, "total_rides": total_rides, "filtered_count": filtered_count, } } ) return paginated_response serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @api_view(["GET"]) def get_filter_options(request): """API endpoint to get all filter options for the frontend""" # Get park context if provided park_slug = request.query_params.get("park_slug") park = None if park_slug: try: park = get_object_or_404(Park, slug=park_slug) except Http404: park = None # Base queryset rides_queryset = Ride.objects.all() if park: rides_queryset = rides_queryset.filter(park=park) # Categories categories = [{"code": code, "name": name} for code, name in Categories] # Manufacturers manufacturer_ids = rides_queryset.values_list( "ride_model__manufacturer_id", flat=True ).distinct() manufacturers = list( Company.objects.filter( id__in=manufacturer_ids, roles__contains=["MANUFACTURER"] ) .values("id", "name") .order_by("name") ) # Designers designer_ids = rides_queryset.values_list("designer_id", flat=True).distinct() designers = list( Company.objects.filter(id__in=designer_ids, roles__contains=["DESIGNER"]) .values("id", "name") .order_by("name") ) # Parks (for global view) parks = [] if not park: parks = list( Park.objects.filter( id__in=rides_queryset.values_list("park_id", flat=True).distinct() ) .values("id", "name", "slug") .order_by("name") ) # Ride models ride_model_ids = rides_queryset.values_list("ride_model_id", flat=True).distinct() ride_models = list( RideModel.objects.filter(id__in=ride_model_ids) .select_related("manufacturer") .values("id", "name", "manufacturer__name") .order_by("name") ) # Get value ranges for numeric fields from django.db.models import Min, Max ranges = rides_queryset.aggregate( height_min=Min("height_ft"), height_max=Max("height_ft"), speed_min=Min("max_speed_mph"), speed_max=Max("max_speed_mph"), capacity_min=Min("hourly_capacity"), capacity_max=Max("hourly_capacity"), duration_min=Min("duration_seconds"), duration_max=Max("duration_seconds"), ) # Get date ranges date_ranges = rides_queryset.aggregate( opening_min=Min("opening_date"), opening_max=Max("opening_date"), closing_min=Min("closing_date"), closing_max=Max("closing_date"), ) data = { "categories": categories, "manufacturers": manufacturers, "designers": designers, "parks": parks, "ride_models": ride_models, "ranges": { "height": { "min": ranges["height_min"] or 0, "max": ranges["height_max"] or 500, "step": 5, }, "speed": { "min": ranges["speed_min"] or 0, "max": ranges["speed_max"] or 150, "step": 5, }, "capacity": { "min": ranges["capacity_min"] or 0, "max": ranges["capacity_max"] or 3000, "step": 100, }, "duration": { "min": ranges["duration_min"] or 0, "max": ranges["duration_max"] or 600, "step": 10, }, }, "date_ranges": { "opening": { "min": ( date_ranges["opening_min"].isoformat() if date_ranges["opening_min"] else None ), "max": ( date_ranges["opening_max"].isoformat() if date_ranges["opening_max"] else None ), }, "closing": { "min": ( date_ranges["closing_min"].isoformat() if date_ranges["closing_min"] else None ), "max": ( date_ranges["closing_max"].isoformat() if date_ranges["closing_max"] else None ), }, }, } return Response(data) @api_view(["GET"]) def search_companies_api(request): """API endpoint for company search""" query = request.query_params.get("q", "").strip() role = request.query_params.get("role", "").upper() companies = Company.objects.all().order_by("name") if role: companies = companies.filter(roles__contains=[role]) if query: companies = companies.filter(name__icontains=query) companies = companies[:10] data = [ {"id": company.id, "name": company.name, "roles": company.roles} for company in companies ] return Response(data) @api_view(["GET"]) def search_ride_models_api(request): """API endpoint for ride model search""" query = request.query_params.get("q", "").strip() manufacturer_id = request.query_params.get("manufacturer") ride_models = RideModel.objects.select_related("manufacturer").order_by("name") if query: ride_models = ride_models.filter(name__icontains=query) if manufacturer_id: ride_models = ride_models.filter(manufacturer_id=manufacturer_id) ride_models = ride_models[:10] data = [ { "id": model.id, "name": model.name, "manufacturer": ( {"id": model.manufacturer.id, "name": model.manufacturer.name} if model.manufacturer else None ), } for model in ride_models ] return Response(data) @api_view(["GET"]) def get_search_suggestions_api(request): """API endpoint for smart search suggestions""" query = request.query_params.get("q", "").strip().lower() suggestions = [] if query: # Get common ride names matching_names = ( Ride.objects.filter(name__icontains=query) .values("name") .annotate(count=Count("id")) .order_by("-count")[:3] ) for match in matching_names: suggestions.append( { "type": "ride", "text": match["name"], "count": match["count"], } ) # Get matching parks from django.db.models import Q matching_parks = Park.objects.filter( Q(name__icontains=query) | Q(location__city__icontains=query) )[:3] for park in matching_parks: suggestions.append( { "type": "park", "text": park.name, "location": park.location.city if park.location else None, "slug": park.slug, } ) # Add category matches for code, name in Categories: if query in name.lower(): ride_count = Ride.objects.filter(category=code).count() suggestions.append( { "type": "category", "code": code, "text": name, "count": ride_count, } ) return Response({"suggestions": suggestions, "query": query})