Files
thrillwiki_django_no_react/backend/apps/parks/selectors.py
pacnpal 540f40e689 Revert "update"
This reverts commit 75cc618c2b.
2025-09-21 20:11:00 -04:00

249 lines
6.8 KiB
Python

"""
Selectors for park-related data retrieval.
Following Django styleguide pattern for separating data access from business logic.
"""
from typing import Optional, Dict, Any
from django.db.models import QuerySet, Q, Count, Avg, Prefetch
from django.contrib.gis.geos import Point
from django.contrib.gis.measure import Distance
from .models import Park, ParkArea, ParkReview
from apps.rides.models import Ride
def park_list_with_stats(*, filters: Optional[Dict[str, Any]] = None) -> QuerySet[Park]:
"""
Get parks optimized for list display with basic stats.
Args:
filters: Optional dictionary of filter parameters
Returns:
QuerySet of parks with optimized queries
"""
queryset = (
Park.objects.select_related("operator", "property_owner")
.prefetch_related("location")
.annotate(
ride_count_calculated=Count("rides", distinct=True),
coaster_count_calculated=Count(
"rides",
filter=Q(rides__category__in=["RC", "WC"]),
distinct=True,
),
average_rating_calculated=Avg("reviews__rating"),
)
)
if filters:
if "status" in filters:
queryset = queryset.filter(status=filters["status"])
if "operator" in filters:
queryset = queryset.filter(operator=filters["operator"])
if "country" in filters:
queryset = queryset.filter(location__country=filters["country"])
if "search" in filters:
search_term = filters["search"]
queryset = queryset.filter(
Q(name__icontains=search_term) | Q(description__icontains=search_term)
)
return queryset.order_by("name")
def park_detail_optimized(*, slug: str) -> Park:
"""
Get a single park with all related data optimized for detail view.
Args:
slug: Park slug identifier
Returns:
Park instance with optimized prefetches
Raises:
Park.DoesNotExist: If park with slug doesn't exist
"""
return (
Park.objects.select_related("operator", "property_owner")
.prefetch_related(
"location",
"areas",
Prefetch(
"rides",
queryset=Ride.objects.select_related(
"manufacturer", "designer", "ride_model"
),
),
Prefetch(
"reviews",
queryset=ParkReview.objects.select_related("user").filter(
is_published=True
),
),
"photos",
)
.get(slug=slug)
)
def parks_near_location(
*, point: Point, distance_km: float = 50, limit: int = 10
) -> QuerySet[Park]:
"""
Get parks near a specific geographic location.
Args:
point: Geographic point (longitude, latitude)
distance_km: Maximum distance in kilometers
limit: Maximum number of results
Returns:
QuerySet of nearby parks ordered by distance
"""
return (
Park.objects.filter(
location__coordinates__distance_lte=(
point,
Distance(km=distance_km),
)
)
.select_related("operator")
.prefetch_related("location")
.distance(point)
.order_by("distance")[:limit]
)
def park_statistics() -> Dict[str, Any]:
"""
Get overall park statistics for dashboard/analytics.
Returns:
Dictionary containing park statistics
"""
total_parks = Park.objects.count()
operating_parks = Park.objects.filter(status="OPERATING").count()
total_rides = Ride.objects.count()
total_coasters = Ride.objects.filter(category__in=["RC", "WC"]).count()
return {
"total_parks": total_parks,
"operating_parks": operating_parks,
"closed_parks": total_parks - operating_parks,
"total_rides": total_rides,
"total_coasters": total_coasters,
"average_rides_per_park": (total_rides / total_parks if total_parks > 0 else 0),
}
def parks_by_operator(*, operator_id: int) -> QuerySet[Park]:
"""
Get all parks operated by a specific company.
Args:
operator_id: Company ID of the operator
Returns:
QuerySet of parks operated by the company
"""
return (
Park.objects.filter(operator_id=operator_id)
.select_related("operator")
.prefetch_related("location")
.annotate(ride_count_calculated=Count("rides"))
.order_by("name")
)
def parks_with_recent_reviews(*, days: int = 30) -> QuerySet[Park]:
"""
Get parks that have received reviews in the last N days.
Args:
days: Number of days to look back for reviews
Returns:
QuerySet of parks with recent reviews
"""
from django.utils import timezone
from datetime import timedelta
cutoff_date = timezone.now() - timedelta(days=days)
return (
Park.objects.filter(
reviews__created_at__gte=cutoff_date, reviews__is_published=True
)
.select_related("operator")
.prefetch_related("location")
.annotate(
recent_review_count=Count(
"reviews", filter=Q(reviews__created_at__gte=cutoff_date)
)
)
.order_by("-recent_review_count")
.distinct()
)
def park_search_autocomplete(*, query: str, limit: int = 10) -> QuerySet[Park]:
"""
Get parks matching a search query for autocomplete functionality.
Args:
query: Search string
limit: Maximum number of results
Returns:
QuerySet of matching parks for autocomplete
"""
return (
Park.objects.filter(
Q(name__icontains=query)
| Q(location__city__icontains=query)
| Q(location__region__icontains=query)
)
.select_related("operator")
.prefetch_related("location")
.order_by("name")[:limit]
)
def park_areas_for_park(*, park_slug: str) -> QuerySet[ParkArea]:
"""
Get all areas for a specific park.
Args:
park_slug: Slug of the park
Returns:
QuerySet of park areas with related data
"""
return (
ParkArea.objects.filter(park__slug=park_slug)
.select_related("park")
.prefetch_related("rides")
.annotate(ride_count=Count("rides"))
.order_by("name")
)
def park_reviews_for_park(*, park_id: int, limit: int = 20) -> QuerySet[ParkReview]:
"""
Get reviews for a specific park.
Args:
park_id: Park ID
limit: Maximum number of reviews to return
Returns:
QuerySet of park reviews
"""
return (
ParkReview.objects.filter(park_id=park_id, is_published=True)
.select_related("user", "park")
.order_by("-created_at")[:limit]
)