mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 07:31:07 -05:00
feat: Implement comprehensive ride filtering system with API integration
- Added `useRideFiltering` composable for managing ride filters and fetching rides from the API. - Created `useParkRideFiltering` for park-specific ride filtering. - Developed `useTheme` composable for theme management with localStorage support. - Established `rideFiltering` Pinia store for centralized state management of ride filters and UI state. - Defined enhanced filter types in `filters.ts` for better type safety and clarity. - Built `RideFilteringPage.vue` to provide a user interface for filtering rides with responsive design. - Integrated filter sidebar and ride list display components for a cohesive user experience. - Added support for filter presets and search suggestions. - Implemented computed properties for active filters, average ratings, and operating counts.
This commit is contained in:
363
backend/apps/rides/api_views.py
Normal file
363
backend/apps/rides/api_views.py
Normal file
@@ -0,0 +1,363 @@
|
||||
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})
|
||||
Reference in New Issue
Block a user