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

@@ -0,0 +1,226 @@
"""
Ride-Specific Statistics Models
This module contains specialized statistics models for different ride categories:
- WaterRideStats: For water rides (WR)
- DarkRideStats: For dark rides (DR)
- FlatRideStats: For flat rides (FR)
These complement the existing RollerCoasterStats model in rides.py.
"""
import pghistory
from django.db import models
from apps.core.history import TrackedModel
# Wetness Level Choices for Water Rides
WETNESS_LEVELS = [
("DRY", "Dry - No water contact"),
("MILD", "Mild - Light misting"),
("MODERATE", "Moderate - Some splashing"),
("SOAKING", "Soaking - Prepare to get drenched"),
]
# Motion Type Choices for Flat Rides
MOTION_TYPES = [
("SPINNING", "Spinning"),
("SWINGING", "Swinging"),
("BOUNCING", "Bouncing"),
("ROTATING", "Rotating"),
("DROPPING", "Dropping"),
("MIXED", "Mixed Motion"),
]
@pghistory.track()
class WaterRideStats(TrackedModel):
"""
Statistics specific to water rides (category=WR).
Tracks water-related attributes like wetness level and splash characteristics.
"""
ride = models.OneToOneField(
"rides.Ride",
on_delete=models.CASCADE,
related_name="water_stats",
help_text="Ride these water statistics belong to",
)
wetness_level = models.CharField(
max_length=10,
choices=WETNESS_LEVELS,
default="MODERATE",
help_text="How wet riders typically get",
)
splash_height_ft = models.DecimalField(
max_digits=5,
decimal_places=2,
null=True,
blank=True,
help_text="Maximum splash height in feet",
)
has_splash_zone = models.BooleanField(
default=False,
help_text="Whether there is a designated splash zone for spectators",
)
boat_capacity = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Number of riders per boat/raft",
)
uses_flume = models.BooleanField(
default=False,
help_text="Whether the ride uses a flume/log system",
)
rapids_sections = models.PositiveIntegerField(
default=0,
help_text="Number of rapids/whitewater sections",
)
class Meta(TrackedModel.Meta):
verbose_name = "Water Ride Statistics"
verbose_name_plural = "Water Ride Statistics"
ordering = ["ride"]
def __str__(self) -> str:
return f"Water Stats for {self.ride.name}"
@pghistory.track()
class DarkRideStats(TrackedModel):
"""
Statistics specific to dark rides (category=DR).
Tracks theming elements like scenes, animatronics, and technology.
"""
ride = models.OneToOneField(
"rides.Ride",
on_delete=models.CASCADE,
related_name="dark_stats",
help_text="Ride these dark ride statistics belong to",
)
scene_count = models.PositiveIntegerField(
default=0,
help_text="Number of themed scenes",
)
animatronic_count = models.PositiveIntegerField(
default=0,
help_text="Number of animatronic figures",
)
has_projection_technology = models.BooleanField(
default=False,
help_text="Whether the ride uses projection mapping or screens",
)
is_interactive = models.BooleanField(
default=False,
help_text="Whether riders can interact with elements (shooting, etc.)",
)
ride_system = models.CharField(
max_length=100,
blank=True,
help_text="Type of ride system (Omnimover, Trackless, Classic Track, etc.)",
)
uses_practical_effects = models.BooleanField(
default=True,
help_text="Whether the ride uses practical/physical effects",
)
uses_motion_base = models.BooleanField(
default=False,
help_text="Whether vehicles have motion simulation capability",
)
class Meta(TrackedModel.Meta):
verbose_name = "Dark Ride Statistics"
verbose_name_plural = "Dark Ride Statistics"
ordering = ["ride"]
def __str__(self) -> str:
return f"Dark Ride Stats for {self.ride.name}"
@pghistory.track()
class FlatRideStats(TrackedModel):
"""
Statistics specific to flat rides (category=FR).
Tracks motion characteristics like rotation, swing angles, and height.
"""
ride = models.OneToOneField(
"rides.Ride",
on_delete=models.CASCADE,
related_name="flat_stats",
help_text="Ride these flat ride statistics belong to",
)
max_height_ft = models.DecimalField(
max_digits=6,
decimal_places=2,
null=True,
blank=True,
help_text="Maximum ride height in feet",
)
rotation_speed_rpm = models.DecimalField(
max_digits=5,
decimal_places=2,
null=True,
blank=True,
help_text="Maximum rotation speed in RPM",
)
swing_angle_degrees = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Maximum swing angle in degrees (for swinging rides)",
)
motion_type = models.CharField(
max_length=20,
choices=MOTION_TYPES,
default="SPINNING",
help_text="Primary type of motion",
)
arm_count = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Number of arms/gondolas",
)
seats_per_gondola = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Number of seats per gondola/arm",
)
max_g_force = models.DecimalField(
max_digits=4,
decimal_places=2,
null=True,
blank=True,
help_text="Maximum G-force experienced",
)
class Meta(TrackedModel.Meta):
verbose_name = "Flat Ride Statistics"
verbose_name_plural = "Flat Ride Statistics"
ordering = ["ride"]
def __str__(self) -> str:
return f"Flat Ride Stats for {self.ride.name}"