feat: Implement MFA authentication, add ride statistics model, and update various services, APIs, and tests across the application.

This commit is contained in:
pacnpal
2025-12-28 17:32:53 -05:00
parent aa56c46c27
commit c95f99ca10
452 changed files with 7948 additions and 6073 deletions

View File

@@ -8,14 +8,15 @@ These serializers prevent contract violations by providing a single source of tr
for common data structures used throughout the API.
"""
from typing import Any
from rest_framework import serializers
from typing import Dict, Any, List
class FilterOptionSerializer(serializers.Serializer):
"""
Standard filter option format - matches frontend TypeScript exactly.
Frontend TypeScript interface:
interface FilterOption {
value: string;
@@ -31,7 +32,7 @@ class FilterOptionSerializer(serializers.Serializer):
help_text="Human-readable display label"
)
count = serializers.IntegerField(
required=False,
required=False,
allow_null=True,
help_text="Number of items matching this filter option"
)
@@ -44,7 +45,7 @@ class FilterOptionSerializer(serializers.Serializer):
class FilterRangeSerializer(serializers.Serializer):
"""
Standard range filter format.
Frontend TypeScript interface:
interface FilterRange {
min: number;
@@ -66,7 +67,7 @@ class FilterRangeSerializer(serializers.Serializer):
help_text="Step size for range inputs"
)
unit = serializers.CharField(
required=False,
required=False,
allow_null=True,
help_text="Unit of measurement (e.g., 'feet', 'mph', 'stars')"
)
@@ -75,7 +76,7 @@ class FilterRangeSerializer(serializers.Serializer):
class BooleanFilterSerializer(serializers.Serializer):
"""
Standard boolean filter format.
Frontend TypeScript interface:
interface BooleanFilter {
key: string;
@@ -97,7 +98,7 @@ class BooleanFilterSerializer(serializers.Serializer):
class OrderingOptionSerializer(serializers.Serializer):
"""
Standard ordering option format.
Frontend TypeScript interface:
interface OrderingOption {
value: string;
@@ -115,7 +116,7 @@ class OrderingOptionSerializer(serializers.Serializer):
class StandardizedFilterMetadataSerializer(serializers.Serializer):
"""
Matches frontend TypeScript interface exactly.
This serializer ensures all filter metadata responses follow the same structure
that the frontend expects, preventing runtime type errors.
"""
@@ -131,7 +132,7 @@ class StandardizedFilterMetadataSerializer(serializers.Serializer):
help_text="Total number of items in the filtered dataset"
)
ordering_options = FilterOptionSerializer(
many=True,
many=True,
required=False,
help_text="Available ordering options"
)
@@ -145,7 +146,7 @@ class StandardizedFilterMetadataSerializer(serializers.Serializer):
class PaginationMetadataSerializer(serializers.Serializer):
"""
Standard pagination metadata format.
Frontend TypeScript interface:
interface PaginationMetadata {
count: number;
@@ -183,7 +184,7 @@ class PaginationMetadataSerializer(serializers.Serializer):
class ApiResponseSerializer(serializers.Serializer):
"""
Standard API response wrapper.
Frontend TypeScript interface:
interface ApiResponse<T> {
success: boolean;
@@ -214,7 +215,7 @@ class ApiResponseSerializer(serializers.Serializer):
class ErrorResponseSerializer(serializers.Serializer):
"""
Standard error response format.
Frontend TypeScript interface:
interface ApiError {
status: "error";
@@ -245,7 +246,7 @@ class ErrorResponseSerializer(serializers.Serializer):
class LocationSerializer(serializers.Serializer):
"""
Standard location format.
Frontend TypeScript interface:
interface Location {
city: string;
@@ -291,7 +292,7 @@ LocationOutputSerializer = LocationSerializer
class CompanyOutputSerializer(serializers.Serializer):
"""
Standard company output format.
Frontend TypeScript interface:
interface Company {
id: number;
@@ -322,24 +323,24 @@ class ModelChoices:
"""
Utility class to provide model choices for serializers using Rich Choice Objects.
This prevents circular imports while providing access to model choices from the registry.
NO FALLBACKS - All choices must be properly defined in Rich Choice Objects.
"""
@staticmethod
def get_park_status_choices():
"""Get park status choices from Rich Choice registry."""
from apps.core.choices.registry import get_choices
choices = get_choices("statuses", "parks")
return [(choice.value, choice.label) for choice in choices]
@staticmethod
def get_ride_status_choices():
"""Get ride status choices from Rich Choice registry."""
from apps.core.choices.registry import get_choices
choices = get_choices("statuses", "rides")
return [(choice.value, choice.label) for choice in choices]
@staticmethod
def get_company_role_choices():
"""Get company role choices from Rich Choice registry."""
@@ -350,91 +351,91 @@ class ModelChoices:
parks_choices = get_choices("company_roles", "parks")
all_choices = list(rides_choices) + list(parks_choices)
return [(choice.value, choice.label) for choice in all_choices]
@staticmethod
def get_ride_category_choices():
"""Get ride category choices from Rich Choice registry."""
from apps.core.choices.registry import get_choices
choices = get_choices("categories", "rides")
return [(choice.value, choice.label) for choice in choices]
@staticmethod
def get_ride_post_closing_choices():
"""Get ride post-closing status choices from Rich Choice registry."""
from apps.core.choices.registry import get_choices
choices = get_choices("post_closing_statuses", "rides")
return [(choice.value, choice.label) for choice in choices]
@staticmethod
def get_coaster_track_choices():
"""Get coaster track material choices from Rich Choice registry."""
from apps.core.choices.registry import get_choices
choices = get_choices("track_materials", "rides")
return [(choice.value, choice.label) for choice in choices]
@staticmethod
def get_coaster_type_choices():
"""Get coaster type choices from Rich Choice registry."""
from apps.core.choices.registry import get_choices
choices = get_choices("coaster_types", "rides")
return [(choice.value, choice.label) for choice in choices]
@staticmethod
def get_launch_choices():
"""Get launch system choices from Rich Choice registry (legacy method)."""
from apps.core.choices.registry import get_choices
choices = get_choices("propulsion_systems", "rides")
return [(choice.value, choice.label) for choice in choices]
@staticmethod
def get_propulsion_system_choices():
"""Get propulsion system choices from Rich Choice registry."""
from apps.core.choices.registry import get_choices
choices = get_choices("propulsion_systems", "rides")
return [(choice.value, choice.label) for choice in choices]
@staticmethod
def get_photo_type_choices():
"""Get photo type choices from Rich Choice registry."""
from apps.core.choices.registry import get_choices
choices = get_choices("photo_types", "rides")
return [(choice.value, choice.label) for choice in choices]
@staticmethod
def get_spec_category_choices():
"""Get technical specification category choices from Rich Choice registry."""
from apps.core.choices.registry import get_choices
choices = get_choices("spec_categories", "rides")
return [(choice.value, choice.label) for choice in choices]
@staticmethod
def get_technical_spec_category_choices():
"""Get technical specification category choices from Rich Choice registry."""
from apps.core.choices.registry import get_choices
choices = get_choices("spec_categories", "rides")
return [(choice.value, choice.label) for choice in choices]
@staticmethod
def get_target_market_choices():
"""Get target market choices from Rich Choice registry."""
from apps.core.choices.registry import get_choices
choices = get_choices("target_markets", "rides")
return [(choice.value, choice.label) for choice in choices]
@staticmethod
def get_entity_type_choices():
"""Get entity type choices for search functionality."""
from apps.core.choices.registry import get_choices
choices = get_choices("entity_types", "core")
return [(choice.value, choice.label) for choice in choices]
@staticmethod
def get_health_status_choices():
"""Get health check status choices from Rich Choice registry."""
from apps.core.choices.registry import get_choices
choices = get_choices("health_statuses", "core")
return [(choice.value, choice.label) for choice in choices]
@staticmethod
def get_simple_health_status_choices():
"""Get simple health check status choices from Rich Choice registry."""
@@ -446,7 +447,7 @@ class ModelChoices:
class EntityReferenceSerializer(serializers.Serializer):
"""
Standard entity reference format.
Frontend TypeScript interface:
interface Entity {
id: number;
@@ -468,7 +469,7 @@ class EntityReferenceSerializer(serializers.Serializer):
class ImageVariantsSerializer(serializers.Serializer):
"""
Standard image variants format.
Frontend TypeScript interface:
interface ImageVariants {
thumbnail: string;
@@ -495,7 +496,7 @@ class ImageVariantsSerializer(serializers.Serializer):
class PhotoSerializer(serializers.Serializer):
"""
Standard photo format.
Frontend TypeScript interface:
interface Photo {
id: number;
@@ -546,7 +547,7 @@ class PhotoSerializer(serializers.Serializer):
class UserInfoSerializer(serializers.Serializer):
"""
Standard user info format.
Frontend TypeScript interface:
interface UserInfo {
id: number;
@@ -571,19 +572,19 @@ class UserInfoSerializer(serializers.Serializer):
)
def validate_filter_metadata_contract(data: Dict[str, Any]) -> Dict[str, Any]:
def validate_filter_metadata_contract(data: dict[str, Any]) -> dict[str, Any]:
"""
Validate that filter metadata follows the expected contract.
This function can be used in views to ensure filter metadata
matches the frontend TypeScript interface before returning it.
Args:
data: Filter metadata dictionary
Returns:
Validated and potentially transformed data
Raises:
serializers.ValidationError: If data doesn't match contract
"""
@@ -593,21 +594,21 @@ def validate_filter_metadata_contract(data: Dict[str, Any]) -> Dict[str, Any]:
return serializer.validated_data
def ensure_filter_option_format(options: List[Any]) -> List[Dict[str, Any]]:
def ensure_filter_option_format(options: list[Any]) -> list[dict[str, Any]]:
"""
Ensure a list of filter options follows the expected format.
This utility function converts various input formats to the standard
FilterOption format expected by the frontend.
Args:
options: List of options in various formats
Returns:
List of options in standard format
"""
standardized = []
for option in options:
if isinstance(option, dict):
# Already in correct format or close to it
@@ -633,19 +634,19 @@ def ensure_filter_option_format(options: List[Any]) -> List[Dict[str, Any]]:
'count': None,
'selected': False
}
standardized.append(standardized_option)
return standardized
def ensure_range_format(range_data: Dict[str, Any]) -> Dict[str, Any]:
def ensure_range_format(range_data: dict[str, Any]) -> dict[str, Any]:
"""
Ensure range data follows the expected format.
Args:
range_data: Range data dictionary
Returns:
Range data in standard format
"""