Refactor code structure and remove redundant sections for improved readability and maintainability

This commit is contained in:
pacnpal
2025-08-28 16:01:24 -04:00
parent 67db0aa46e
commit 02ac587216
12 changed files with 5342 additions and 77 deletions

View File

@@ -15,6 +15,7 @@ Notes:
from typing import Any
from django.db import models
from rest_framework import status, permissions
from rest_framework.views import APIView
from rest_framework.request import Request
@@ -68,27 +69,147 @@ class RideListCreateAPIView(APIView):
permission_classes = [permissions.AllowAny]
@extend_schema(
summary="List rides with filtering and pagination",
description="List rides with basic filtering and pagination.",
summary="List rides with comprehensive filtering and pagination",
description="List rides with comprehensive filtering options including category, status, manufacturer, designer, ride model, and more.",
parameters=[
OpenApiParameter(
name="page", location=OpenApiParameter.QUERY, type=OpenApiTypes.INT
name="page", location=OpenApiParameter.QUERY, type=OpenApiTypes.INT,
description="Page number for pagination"
),
OpenApiParameter(
name="page_size", location=OpenApiParameter.QUERY, type=OpenApiTypes.INT
name="page_size", location=OpenApiParameter.QUERY, type=OpenApiTypes.INT,
description="Number of results per page (max 1000)"
),
OpenApiParameter(
name="search", location=OpenApiParameter.QUERY, type=OpenApiTypes.STR
name="search", location=OpenApiParameter.QUERY, type=OpenApiTypes.STR,
description="Search in ride names and descriptions"
),
OpenApiParameter(
name="park_slug", location=OpenApiParameter.QUERY, type=OpenApiTypes.STR
name="park_slug", location=OpenApiParameter.QUERY, type=OpenApiTypes.STR,
description="Filter by park slug"
),
OpenApiParameter(
name="park_id", location=OpenApiParameter.QUERY, type=OpenApiTypes.INT,
description="Filter by park ID"
),
OpenApiParameter(
name="category", location=OpenApiParameter.QUERY, type=OpenApiTypes.STR,
description="Filter by ride category (RC, DR, FR, WR, TR, OT). Multiple values supported: ?category=RC&category=DR"
),
OpenApiParameter(
name="status", location=OpenApiParameter.QUERY, type=OpenApiTypes.STR,
description="Filter by ride status. Multiple values supported: ?status=OPERATING&status=CLOSED_TEMP"
),
OpenApiParameter(
name="manufacturer_id", location=OpenApiParameter.QUERY, type=OpenApiTypes.INT,
description="Filter by manufacturer company ID"
),
OpenApiParameter(
name="manufacturer_slug", location=OpenApiParameter.QUERY, type=OpenApiTypes.STR,
description="Filter by manufacturer company slug"
),
OpenApiParameter(
name="designer_id", location=OpenApiParameter.QUERY, type=OpenApiTypes.INT,
description="Filter by designer company ID"
),
OpenApiParameter(
name="designer_slug", location=OpenApiParameter.QUERY, type=OpenApiTypes.STR,
description="Filter by designer company slug"
),
OpenApiParameter(
name="ride_model_id", location=OpenApiParameter.QUERY, type=OpenApiTypes.INT,
description="Filter by specific ride model ID"
),
OpenApiParameter(
name="ride_model_slug", location=OpenApiParameter.QUERY, type=OpenApiTypes.STR,
description="Filter by ride model slug (requires manufacturer_slug)"
),
OpenApiParameter(
name="roller_coaster_type", location=OpenApiParameter.QUERY, type=OpenApiTypes.STR,
description="Filter roller coasters by type (SITDOWN, INVERTED, FLYING, etc.)"
),
OpenApiParameter(
name="track_material", location=OpenApiParameter.QUERY, type=OpenApiTypes.STR,
description="Filter roller coasters by track material (STEEL, WOOD, HYBRID)"
),
OpenApiParameter(
name="launch_type", location=OpenApiParameter.QUERY, type=OpenApiTypes.STR,
description="Filter roller coasters by launch type (CHAIN, LSM, HYDRAULIC, etc.)"
),
OpenApiParameter(
name="min_rating", location=OpenApiParameter.QUERY, type=OpenApiTypes.NUMBER,
description="Filter by minimum average rating (1-10)"
),
OpenApiParameter(
name="max_rating", location=OpenApiParameter.QUERY, type=OpenApiTypes.NUMBER,
description="Filter by maximum average rating (1-10)"
),
OpenApiParameter(
name="min_height_requirement", location=OpenApiParameter.QUERY, type=OpenApiTypes.INT,
description="Filter by minimum height requirement in inches"
),
OpenApiParameter(
name="max_height_requirement", location=OpenApiParameter.QUERY, type=OpenApiTypes.INT,
description="Filter by maximum height requirement in inches"
),
OpenApiParameter(
name="min_capacity", location=OpenApiParameter.QUERY, type=OpenApiTypes.INT,
description="Filter by minimum hourly capacity"
),
OpenApiParameter(
name="max_capacity", location=OpenApiParameter.QUERY, type=OpenApiTypes.INT,
description="Filter by maximum hourly capacity"
),
OpenApiParameter(
name="min_height_ft", location=OpenApiParameter.QUERY, type=OpenApiTypes.NUMBER,
description="Filter roller coasters by minimum height in feet"
),
OpenApiParameter(
name="max_height_ft", location=OpenApiParameter.QUERY, type=OpenApiTypes.NUMBER,
description="Filter roller coasters by maximum height in feet"
),
OpenApiParameter(
name="min_speed_mph", location=OpenApiParameter.QUERY, type=OpenApiTypes.NUMBER,
description="Filter roller coasters by minimum speed in mph"
),
OpenApiParameter(
name="max_speed_mph", location=OpenApiParameter.QUERY, type=OpenApiTypes.NUMBER,
description="Filter roller coasters by maximum speed in mph"
),
OpenApiParameter(
name="min_inversions", location=OpenApiParameter.QUERY, type=OpenApiTypes.INT,
description="Filter roller coasters by minimum number of inversions"
),
OpenApiParameter(
name="max_inversions", location=OpenApiParameter.QUERY, type=OpenApiTypes.INT,
description="Filter roller coasters by maximum number of inversions"
),
OpenApiParameter(
name="has_inversions", location=OpenApiParameter.QUERY, type=OpenApiTypes.BOOL,
description="Filter roller coasters that have inversions (true) or don't have inversions (false)"
),
OpenApiParameter(
name="opening_year", location=OpenApiParameter.QUERY, type=OpenApiTypes.INT,
description="Filter by opening year"
),
OpenApiParameter(
name="min_opening_year", location=OpenApiParameter.QUERY, type=OpenApiTypes.INT,
description="Filter by minimum opening year"
),
OpenApiParameter(
name="max_opening_year", location=OpenApiParameter.QUERY, type=OpenApiTypes.INT,
description="Filter by maximum opening year"
),
OpenApiParameter(
name="ordering", location=OpenApiParameter.QUERY, type=OpenApiTypes.STR,
description="Order results by field. Options: name, -name, opening_date, -opening_date, average_rating, -average_rating, capacity_per_hour, -capacity_per_hour, created_at, -created_at, height_ft, -height_ft, speed_mph, -speed_mph"
),
],
responses={200: RideListOutputSerializer(many=True)},
tags=["Rides"],
)
def get(self, request: Request) -> Response:
"""List rides with basic filtering and pagination."""
"""List rides with comprehensive filtering and pagination."""
if not MODELS_AVAILABLE:
return Response(
{
@@ -98,16 +219,230 @@ class RideListCreateAPIView(APIView):
status=status.HTTP_501_NOT_IMPLEMENTED,
)
qs = Ride.objects.all().select_related("park", "manufacturer", "designer") # type: ignore
# Start with base queryset with optimized joins
qs = Ride.objects.all().select_related(
"park", "manufacturer", "designer", "ride_model", "ride_model__manufacturer"
).prefetch_related("coaster_stats") # type: ignore
# Basic filters
q = request.query_params.get("search")
if q:
qs = qs.filter(name__icontains=q) # simplistic search
# Text search
search = request.query_params.get("search")
if search:
qs = qs.filter(
models.Q(name__icontains=search) |
models.Q(description__icontains=search) |
models.Q(park__name__icontains=search)
)
# Park filters
park_slug = request.query_params.get("park_slug")
if park_slug:
qs = qs.filter(park__slug=park_slug) # type: ignore
qs = qs.filter(park__slug=park_slug)
park_id = request.query_params.get("park_id")
if park_id:
try:
qs = qs.filter(park_id=int(park_id))
except (ValueError, TypeError):
pass
# Category filters (multiple values supported)
categories = request.query_params.getlist("category")
if categories:
qs = qs.filter(category__in=categories)
# Status filters (multiple values supported)
statuses = request.query_params.getlist("status")
if statuses:
qs = qs.filter(status__in=statuses)
# Manufacturer filters
manufacturer_id = request.query_params.get("manufacturer_id")
if manufacturer_id:
try:
qs = qs.filter(manufacturer_id=int(manufacturer_id))
except (ValueError, TypeError):
pass
manufacturer_slug = request.query_params.get("manufacturer_slug")
if manufacturer_slug:
qs = qs.filter(manufacturer__slug=manufacturer_slug)
# Designer filters
designer_id = request.query_params.get("designer_id")
if designer_id:
try:
qs = qs.filter(designer_id=int(designer_id))
except (ValueError, TypeError):
pass
designer_slug = request.query_params.get("designer_slug")
if designer_slug:
qs = qs.filter(designer__slug=designer_slug)
# Ride model filters
ride_model_id = request.query_params.get("ride_model_id")
if ride_model_id:
try:
qs = qs.filter(ride_model_id=int(ride_model_id))
except (ValueError, TypeError):
pass
ride_model_slug = request.query_params.get("ride_model_slug")
manufacturer_slug_for_model = request.query_params.get("manufacturer_slug")
if ride_model_slug and manufacturer_slug_for_model:
qs = qs.filter(
ride_model__slug=ride_model_slug,
ride_model__manufacturer__slug=manufacturer_slug_for_model
)
# Rating filters
min_rating = request.query_params.get("min_rating")
if min_rating:
try:
qs = qs.filter(average_rating__gte=float(min_rating))
except (ValueError, TypeError):
pass
max_rating = request.query_params.get("max_rating")
if max_rating:
try:
qs = qs.filter(average_rating__lte=float(max_rating))
except (ValueError, TypeError):
pass
# Height requirement filters
min_height_req = request.query_params.get("min_height_requirement")
if min_height_req:
try:
qs = qs.filter(min_height_in__gte=int(min_height_req))
except (ValueError, TypeError):
pass
max_height_req = request.query_params.get("max_height_requirement")
if max_height_req:
try:
qs = qs.filter(max_height_in__lte=int(max_height_req))
except (ValueError, TypeError):
pass
# Capacity filters
min_capacity = request.query_params.get("min_capacity")
if min_capacity:
try:
qs = qs.filter(capacity_per_hour__gte=int(min_capacity))
except (ValueError, TypeError):
pass
max_capacity = request.query_params.get("max_capacity")
if max_capacity:
try:
qs = qs.filter(capacity_per_hour__lte=int(max_capacity))
except (ValueError, TypeError):
pass
# Opening year filters
opening_year = request.query_params.get("opening_year")
if opening_year:
try:
qs = qs.filter(opening_date__year=int(opening_year))
except (ValueError, TypeError):
pass
min_opening_year = request.query_params.get("min_opening_year")
if min_opening_year:
try:
qs = qs.filter(opening_date__year__gte=int(min_opening_year))
except (ValueError, TypeError):
pass
max_opening_year = request.query_params.get("max_opening_year")
if max_opening_year:
try:
qs = qs.filter(opening_date__year__lte=int(max_opening_year))
except (ValueError, TypeError):
pass
# Roller coaster specific filters
roller_coaster_type = request.query_params.get("roller_coaster_type")
if roller_coaster_type:
qs = qs.filter(coaster_stats__roller_coaster_type=roller_coaster_type)
track_material = request.query_params.get("track_material")
if track_material:
qs = qs.filter(coaster_stats__track_material=track_material)
launch_type = request.query_params.get("launch_type")
if launch_type:
qs = qs.filter(coaster_stats__launch_type=launch_type)
# Roller coaster height filters
min_height_ft = request.query_params.get("min_height_ft")
if min_height_ft:
try:
qs = qs.filter(coaster_stats__height_ft__gte=float(min_height_ft))
except (ValueError, TypeError):
pass
max_height_ft = request.query_params.get("max_height_ft")
if max_height_ft:
try:
qs = qs.filter(coaster_stats__height_ft__lte=float(max_height_ft))
except (ValueError, TypeError):
pass
# Roller coaster speed filters
min_speed_mph = request.query_params.get("min_speed_mph")
if min_speed_mph:
try:
qs = qs.filter(coaster_stats__speed_mph__gte=float(min_speed_mph))
except (ValueError, TypeError):
pass
max_speed_mph = request.query_params.get("max_speed_mph")
if max_speed_mph:
try:
qs = qs.filter(coaster_stats__speed_mph__lte=float(max_speed_mph))
except (ValueError, TypeError):
pass
# Inversion filters
min_inversions = request.query_params.get("min_inversions")
if min_inversions:
try:
qs = qs.filter(coaster_stats__inversions__gte=int(min_inversions))
except (ValueError, TypeError):
pass
max_inversions = request.query_params.get("max_inversions")
if max_inversions:
try:
qs = qs.filter(coaster_stats__inversions__lte=int(max_inversions))
except (ValueError, TypeError):
pass
has_inversions = request.query_params.get("has_inversions")
if has_inversions is not None:
if has_inversions.lower() in ['true', '1', 'yes']:
qs = qs.filter(coaster_stats__inversions__gt=0)
elif has_inversions.lower() in ['false', '0', 'no']:
qs = qs.filter(coaster_stats__inversions=0)
# Ordering
ordering = request.query_params.get("ordering", "name")
valid_orderings = [
"name", "-name", "opening_date", "-opening_date",
"average_rating", "-average_rating", "capacity_per_hour", "-capacity_per_hour",
"created_at", "-created_at", "height_ft", "-height_ft", "speed_mph", "-speed_mph"
]
if ordering in valid_orderings:
if ordering in ["height_ft", "-height_ft", "speed_mph", "-speed_mph"]:
# For coaster stats ordering, we need to join and order by the stats
ordering_field = ordering.replace("height_ft", "coaster_stats__height_ft").replace(
"speed_mph", "coaster_stats__speed_mph")
qs = qs.order_by(ordering_field)
else:
qs = qs.order_by(ordering)
paginator = StandardResultsSetPagination()
page = paginator.paginate_queryset(qs, request)
@@ -234,7 +569,8 @@ class RideDetailAPIView(APIView):
# --- Filter options ---------------------------------------------------------
@extend_schema(
summary="Get filter options for rides",
summary="Get comprehensive filter options for rides",
description="Returns all available filter options for rides including categories, statuses, roller coaster types, track materials, launch types, and ordering options.",
responses={200: OpenApiTypes.OBJECT},
tags=["Rides"],
)
@@ -242,7 +578,7 @@ class FilterOptionsAPIView(APIView):
permission_classes = [permissions.AllowAny]
def get(self, request: Request) -> Response:
"""Return static/dynamic filter options used by the frontend."""
"""Return comprehensive filter options used by the frontend."""
# Try to use ModelChoices if available
if HAVE_MODELCHOICES and ModelChoices is not None:
try:
@@ -250,13 +586,41 @@ class FilterOptionsAPIView(APIView):
"categories": ModelChoices.get_ride_category_choices(),
"statuses": ModelChoices.get_ride_status_choices(),
"post_closing_statuses": ModelChoices.get_ride_post_closing_choices(),
"roller_coaster_types": ModelChoices.get_coaster_type_choices(),
"track_materials": ModelChoices.get_coaster_track_choices(),
"launch_types": ModelChoices.get_launch_choices(),
"ordering_options": [
"name",
"-name",
"opening_date",
"-opening_date",
"average_rating",
"-average_rating",
{"value": "name", "label": "Name (A-Z)"},
{"value": "-name", "label": "Name (Z-A)"},
{"value": "opening_date",
"label": "Opening Date (Oldest First)"},
{"value": "-opening_date",
"label": "Opening Date (Newest First)"},
{"value": "average_rating", "label": "Rating (Lowest First)"},
{"value": "-average_rating", "label": "Rating (Highest First)"},
{"value": "capacity_per_hour",
"label": "Capacity (Lowest First)"},
{"value": "-capacity_per_hour",
"label": "Capacity (Highest First)"},
{"value": "height_ft", "label": "Height (Shortest First)"},
{"value": "-height_ft", "label": "Height (Tallest First)"},
{"value": "speed_mph", "label": "Speed (Slowest First)"},
{"value": "-speed_mph", "label": "Speed (Fastest First)"},
{"value": "created_at", "label": "Date Added (Oldest First)"},
{"value": "-created_at", "label": "Date Added (Newest First)"},
],
"filter_ranges": {
"rating": {"min": 1, "max": 10, "step": 0.1},
"height_requirement": {"min": 30, "max": 90, "step": 1, "unit": "inches"},
"capacity": {"min": 0, "max": 5000, "step": 50, "unit": "riders/hour"},
"height_ft": {"min": 0, "max": 500, "step": 5, "unit": "feet"},
"speed_mph": {"min": 0, "max": 150, "step": 5, "unit": "mph"},
"inversions": {"min": 0, "max": 20, "step": 1, "unit": "inversions"},
"opening_year": {"min": 1800, "max": 2030, "step": 1, "unit": "year"},
},
"boolean_filters": [
{"key": "has_inversions", "label": "Has Inversions",
"description": "Filter roller coasters with or without inversions"},
],
}
return Response(data)
@@ -264,12 +628,82 @@ class FilterOptionsAPIView(APIView):
# fallthrough to fallback
pass
# Fallback minimal options
# Comprehensive fallback options
return Response(
{
"categories": ["ROLLER_COASTER", "WATER_RIDE", "FLAT"],
"statuses": ["OPERATING", "CLOSED", "MAINTENANCE"],
"ordering_options": ["name", "-name", "opening_date", "-opening_date"],
"categories": [
("RC", "Roller Coaster"),
("DR", "Dark Ride"),
("FR", "Flat Ride"),
("WR", "Water Ride"),
("TR", "Transport"),
("OT", "Other"),
],
"statuses": [
("OPERATING", "Operating"),
("CLOSED_TEMP", "Temporarily Closed"),
("SBNO", "Standing But Not Operating"),
("CLOSING", "Closing"),
("CLOSED_PERM", "Permanently Closed"),
("UNDER_CONSTRUCTION", "Under Construction"),
("DEMOLISHED", "Demolished"),
("RELOCATED", "Relocated"),
],
"roller_coaster_types": [
("SITDOWN", "Sit Down"),
("INVERTED", "Inverted"),
("FLYING", "Flying"),
("STANDUP", "Stand Up"),
("WING", "Wing"),
("DIVE", "Dive"),
("FAMILY", "Family"),
("WILD_MOUSE", "Wild Mouse"),
("SPINNING", "Spinning"),
("FOURTH_DIMENSION", "4th Dimension"),
("OTHER", "Other"),
],
"track_materials": [
("STEEL", "Steel"),
("WOOD", "Wood"),
("HYBRID", "Hybrid"),
],
"launch_types": [
("CHAIN", "Chain Lift"),
("LSM", "LSM Launch"),
("HYDRAULIC", "Hydraulic Launch"),
("GRAVITY", "Gravity"),
("OTHER", "Other"),
],
"ordering_options": [
{"value": "name", "label": "Name (A-Z)"},
{"value": "-name", "label": "Name (Z-A)"},
{"value": "opening_date", "label": "Opening Date (Oldest First)"},
{"value": "-opening_date", "label": "Opening Date (Newest First)"},
{"value": "average_rating", "label": "Rating (Lowest First)"},
{"value": "-average_rating", "label": "Rating (Highest First)"},
{"value": "capacity_per_hour", "label": "Capacity (Lowest First)"},
{"value": "-capacity_per_hour",
"label": "Capacity (Highest First)"},
{"value": "height_ft", "label": "Height (Shortest First)"},
{"value": "-height_ft", "label": "Height (Tallest First)"},
{"value": "speed_mph", "label": "Speed (Slowest First)"},
{"value": "-speed_mph", "label": "Speed (Fastest First)"},
{"value": "created_at", "label": "Date Added (Oldest First)"},
{"value": "-created_at", "label": "Date Added (Newest First)"},
],
"filter_ranges": {
"rating": {"min": 1, "max": 10, "step": 0.1},
"height_requirement": {"min": 30, "max": 90, "step": 1, "unit": "inches"},
"capacity": {"min": 0, "max": 5000, "step": 50, "unit": "riders/hour"},
"height_ft": {"min": 0, "max": 500, "step": 5, "unit": "feet"},
"speed_mph": {"min": 0, "max": 150, "step": 5, "unit": "mph"},
"inversions": {"min": 0, "max": 20, "step": 1, "unit": "inversions"},
"opening_year": {"min": 1800, "max": 2030, "step": 1, "unit": "year"},
},
"boolean_filters": [
{"key": "has_inversions", "label": "Has Inversions",
"description": "Filter roller coasters with or without inversions"},
],
}
)