feat: Implement initial schema and add various API, service, and management command enhancements across the application.

This commit is contained in:
pacnpal
2026-01-01 15:13:01 -05:00
parent c95f99ca10
commit b243b17af7
413 changed files with 11164 additions and 17433 deletions

View File

@@ -127,9 +127,7 @@ class RideSearchService:
# Apply text search with ranking
if filters.get("global_search"):
queryset, search_rank = self._apply_full_text_search(
queryset, filters["global_search"]
)
queryset, search_rank = self._apply_full_text_search(queryset, filters["global_search"])
search_metadata["search_applied"] = True
search_metadata["search_term"] = filters["global_search"]
else:
@@ -176,9 +174,7 @@ class RideSearchService:
"applied_filters": self._get_applied_filters_summary(filters),
}
def _apply_full_text_search(
self, queryset, search_term: str
) -> tuple[models.QuerySet, models.Expression]:
def _apply_full_text_search(self, queryset, search_term: str) -> tuple[models.QuerySet, models.Expression]:
"""
Apply PostgreSQL full-text search with ranking and fuzzy matching.
"""
@@ -201,17 +197,14 @@ class RideSearchService:
search_query = SearchQuery(search_term, config="english")
# Calculate search rank
search_rank = SearchRank(
search_vector, search_query, weights=self.SEARCH_RANK_WEIGHTS
)
search_rank = SearchRank(search_vector, search_query, weights=self.SEARCH_RANK_WEIGHTS)
# Apply trigram similarity for fuzzy matching on name
trigram_similarity = TrigramSimilarity("name", search_term)
# Combine full-text search with trigram similarity
queryset = queryset.annotate(trigram_similarity=trigram_similarity).filter(
Q(search_vector=search_query)
| Q(trigram_similarity__gte=self.TRIGRAM_SIMILARITY_THRESHOLD)
Q(search_vector=search_query) | Q(trigram_similarity__gte=self.TRIGRAM_SIMILARITY_THRESHOLD)
)
# Use the greatest of search rank and trigram similarity for final ranking
@@ -219,36 +212,22 @@ class RideSearchService:
return queryset, final_rank
def _apply_basic_info_filters(
self, queryset, filters: dict[str, Any]
) -> models.QuerySet:
def _apply_basic_info_filters(self, queryset, filters: dict[str, Any]) -> models.QuerySet:
"""Apply basic information filters."""
# Category filter (multi-select)
if filters.get("category"):
categories = (
filters["category"]
if isinstance(filters["category"], list)
else [filters["category"]]
)
categories = filters["category"] if isinstance(filters["category"], list) else [filters["category"]]
queryset = queryset.filter(category__in=categories)
# Status filter (multi-select)
if filters.get("status"):
statuses = (
filters["status"]
if isinstance(filters["status"], list)
else [filters["status"]]
)
statuses = filters["status"] if isinstance(filters["status"], list) else [filters["status"]]
queryset = queryset.filter(status__in=statuses)
# Park filter (multi-select)
if filters.get("park"):
parks = (
filters["park"]
if isinstance(filters["park"], list)
else [filters["park"]]
)
parks = filters["park"] if isinstance(filters["park"], list) else [filters["park"]]
if isinstance(parks[0], str): # If slugs provided
queryset = queryset.filter(park__slug__in=parks)
else: # If IDs provided
@@ -256,11 +235,7 @@ class RideSearchService:
# Park area filter (multi-select)
if filters.get("park_area"):
areas = (
filters["park_area"]
if isinstance(filters["park_area"], list)
else [filters["park_area"]]
)
areas = filters["park_area"] if isinstance(filters["park_area"], list) else [filters["park_area"]]
if isinstance(areas[0], str): # If slugs provided
queryset = queryset.filter(park_area__slug__in=areas)
else: # If IDs provided
@@ -297,9 +272,7 @@ class RideSearchService:
return queryset
def _apply_height_safety_filters(
self, queryset, filters: dict[str, Any]
) -> models.QuerySet:
def _apply_height_safety_filters(self, queryset, filters: dict[str, Any]) -> models.QuerySet:
"""Apply height and safety requirement filters."""
# Minimum height range
@@ -320,9 +293,7 @@ class RideSearchService:
return queryset
def _apply_performance_filters(
self, queryset, filters: dict[str, Any]
) -> models.QuerySet:
def _apply_performance_filters(self, queryset, filters: dict[str, Any]) -> models.QuerySet:
"""Apply performance metric filters."""
# Capacity range
@@ -337,13 +308,9 @@ class RideSearchService:
if filters.get("duration_range"):
duration_range = filters["duration_range"]
if duration_range.get("min") is not None:
queryset = queryset.filter(
ride_duration_seconds__gte=duration_range["min"]
)
queryset = queryset.filter(ride_duration_seconds__gte=duration_range["min"])
if duration_range.get("max") is not None:
queryset = queryset.filter(
ride_duration_seconds__lte=duration_range["max"]
)
queryset = queryset.filter(ride_duration_seconds__lte=duration_range["max"])
# Rating range
if filters.get("rating_range"):
@@ -355,17 +322,13 @@ class RideSearchService:
return queryset
def _apply_relationship_filters(
self, queryset, filters: dict[str, Any]
) -> models.QuerySet:
def _apply_relationship_filters(self, queryset, filters: dict[str, Any]) -> models.QuerySet:
"""Apply relationship filters (manufacturer, designer, ride model)."""
# Manufacturer filter (multi-select)
if filters.get("manufacturer"):
manufacturers = (
filters["manufacturer"]
if isinstance(filters["manufacturer"], list)
else [filters["manufacturer"]]
filters["manufacturer"] if isinstance(filters["manufacturer"], list) else [filters["manufacturer"]]
)
if isinstance(manufacturers[0], str): # If slugs provided
queryset = queryset.filter(manufacturer__slug__in=manufacturers)
@@ -374,11 +337,7 @@ class RideSearchService:
# Designer filter (multi-select)
if filters.get("designer"):
designers = (
filters["designer"]
if isinstance(filters["designer"], list)
else [filters["designer"]]
)
designers = filters["designer"] if isinstance(filters["designer"], list) else [filters["designer"]]
if isinstance(designers[0], str): # If slugs provided
queryset = queryset.filter(designer__slug__in=designers)
else: # If IDs provided
@@ -386,11 +345,7 @@ class RideSearchService:
# Ride model filter (multi-select)
if filters.get("ride_model"):
models_list = (
filters["ride_model"]
if isinstance(filters["ride_model"], list)
else [filters["ride_model"]]
)
models_list = filters["ride_model"] if isinstance(filters["ride_model"], list) else [filters["ride_model"]]
if isinstance(models_list[0], str): # If slugs provided
queryset = queryset.filter(ride_model__slug__in=models_list)
else: # If IDs provided
@@ -398,9 +353,7 @@ class RideSearchService:
return queryset
def _apply_roller_coaster_filters(
self, queryset, filters: dict[str, Any]
) -> models.QuerySet:
def _apply_roller_coaster_filters(self, queryset, filters: dict[str, Any]) -> models.QuerySet:
"""Apply roller coaster specific filters."""
queryset = self._apply_numeric_range_filter(
queryset, filters, "height_ft_range", "rollercoasterstats__height_ft"
@@ -426,14 +379,8 @@ class RideSearchService:
# Coaster type filter (multi-select)
if filters.get("coaster_type"):
types = (
filters["coaster_type"]
if isinstance(filters["coaster_type"], list)
else [filters["coaster_type"]]
)
queryset = queryset.filter(
rollercoasterstats__roller_coaster_type__in=types
)
types = filters["coaster_type"] if isinstance(filters["coaster_type"], list) else [filters["coaster_type"]]
queryset = queryset.filter(rollercoasterstats__roller_coaster_type__in=types)
# Propulsion system filter (multi-select)
if filters.get("propulsion_system"):
@@ -457,18 +404,12 @@ class RideSearchService:
if filters.get(filter_key):
range_filter = filters[filter_key]
if range_filter.get("min") is not None:
queryset = queryset.filter(
**{f"{field_name}__gte": range_filter["min"]}
)
queryset = queryset.filter(**{f"{field_name}__gte": range_filter["min"]})
if range_filter.get("max") is not None:
queryset = queryset.filter(
**{f"{field_name}__lte": range_filter["max"]}
)
queryset = queryset.filter(**{f"{field_name}__lte": range_filter["max"]})
return queryset
def _apply_company_filters(
self, queryset, filters: dict[str, Any]
) -> models.QuerySet:
def _apply_company_filters(self, queryset, filters: dict[str, Any]) -> models.QuerySet:
"""Apply company-related filters."""
# Manufacturer roles filter
@@ -518,13 +459,9 @@ class RideSearchService:
return queryset.order_by("-search_rank", "name")
# Apply the sorting
return queryset.order_by(
sort_field, "name"
) # Always add name as secondary sort
return queryset.order_by(sort_field, "name") # Always add name as secondary sort
def _add_search_highlights(
self, results: list[Ride], search_term: str
) -> list[Ride]:
def _add_search_highlights(self, results: list[Ride], search_term: str) -> list[Ride]:
"""Add search highlights to results using SearchHeadline."""
if not search_term or not results:
@@ -601,9 +538,7 @@ class RideSearchService:
else:
raise ValueError(f"Unknown filter key: {filter_key}")
def get_search_suggestions(
self, query: str, limit: int = 10
) -> list[dict[str, Any]]:
def get_search_suggestions(self, query: str, limit: int = 10) -> list[dict[str, Any]]:
"""
Get search suggestions for autocomplete functionality.
"""
@@ -686,17 +621,11 @@ class RideSearchService:
# Apply context filters to narrow down options
if context_filters:
temp_filters = context_filters.copy()
temp_filters.pop(
filter_type, None
) # Remove the filter we're getting options for
temp_filters.pop(filter_type, None) # Remove the filter we're getting options for
base_queryset = self._apply_all_filters(base_queryset, temp_filters)
if filter_type == "park":
return list(
base_queryset.values("park__name", "park__slug")
.distinct()
.order_by("park__name")
)
return list(base_queryset.values("park__name", "park__slug").distinct().order_by("park__name"))
elif filter_type == "manufacturer":
return list(