from django.db import models from django.utils.text import slugify from django.contrib.contenttypes.fields import GenericRelation from history_tracking.models import HistoricalModel # Shared choices that will be used by multiple models CATEGORY_CHOICES = [ ('', 'Select ride type'), ('RC', 'Roller Coaster'), ('DR', 'Dark Ride'), ('FR', 'Flat Ride'), ('WR', 'Water Ride'), ('TR', 'Transport'), ('OT', 'Other'), ] class RideModel(HistoricalModel): """ Represents a specific model/type of ride that can be manufactured by different companies. For example: B&M Dive Coaster, Vekoma Boomerang, etc. """ name = models.CharField(max_length=255) manufacturer = models.ForeignKey( 'companies.Manufacturer', on_delete=models.SET_NULL, # Changed to SET_NULL since it's optional related_name='ride_models', null=True, # Made optional blank=True # Made optional ) description = models.TextField(blank=True) category = models.CharField( max_length=2, choices=CATEGORY_CHOICES, default='', blank=True ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ['manufacturer', 'name'] unique_together = ['manufacturer', 'name'] def __str__(self) -> str: return self.name if not self.manufacturer else f"{self.manufacturer.name} {self.name}" class Ride(HistoricalModel): STATUS_CHOICES = [ ('OPERATING', 'Operating'), ('SBNO', 'Standing But Not Operating'), ('CLOSING', 'Closing'), ('CLOSED_PERM', 'Permanently Closed'), ('UNDER_CONSTRUCTION', 'Under Construction'), ('DEMOLISHED', 'Demolished'), ('RELOCATED', 'Relocated'), ] POST_CLOSING_STATUS_CHOICES = [ ('SBNO', 'Standing But Not Operating'), ('CLOSED_PERM', 'Permanently Closed'), ] name = models.CharField(max_length=255) slug = models.SlugField(max_length=255) description = models.TextField(blank=True) park = models.ForeignKey( 'parks.Park', on_delete=models.CASCADE, related_name='rides' ) park_area = models.ForeignKey( 'parks.ParkArea', on_delete=models.SET_NULL, related_name='rides', null=True, blank=True ) category = models.CharField( max_length=2, choices=CATEGORY_CHOICES, default='', blank=True ) manufacturer = models.ForeignKey( 'companies.Manufacturer', on_delete=models.CASCADE, null=True, blank=True ) designer = models.ForeignKey( 'companies.Designer', on_delete=models.SET_NULL, related_name='rides', null=True, blank=True ) ride_model = models.ForeignKey( 'RideModel', on_delete=models.SET_NULL, related_name='rides', null=True, blank=True, help_text="The specific model/type of this ride" ) status = models.CharField( max_length=20, choices=STATUS_CHOICES, default='OPERATING' ) post_closing_status = models.CharField( max_length=20, choices=POST_CLOSING_STATUS_CHOICES, null=True, blank=True, help_text="Status to change to after closing date" ) opening_date = models.DateField(null=True, blank=True) closing_date = models.DateField(null=True, blank=True) status_since = models.DateField(null=True, blank=True) min_height_in = models.PositiveIntegerField(null=True, blank=True) max_height_in = models.PositiveIntegerField(null=True, blank=True) capacity_per_hour = models.PositiveIntegerField(null=True, blank=True) ride_duration_seconds = models.PositiveIntegerField(null=True, blank=True) average_rating = models.DecimalField( max_digits=3, decimal_places=2, null=True, blank=True ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) photos = GenericRelation('media.Photo') reviews = GenericRelation('reviews.Review') class Meta: ordering = ['name'] unique_together = ['park', 'slug'] def __str__(self) -> str: return f"{self.name} at {self.park.name}" def save(self, *args, **kwargs) -> None: if not self.slug: self.slug = slugify(self.name) super().save(*args, **kwargs) class RollerCoasterStats(models.Model): TRACK_MATERIAL_CHOICES = [ ('STEEL', 'Steel'), ('WOOD', 'Wood'), ('HYBRID', 'Hybrid'), ] COASTER_TYPE_CHOICES = [ ('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'), ] LAUNCH_CHOICES = [ ('CHAIN', 'Chain Lift'), ('LSM', 'LSM Launch'), ('HYDRAULIC', 'Hydraulic Launch'), ('GRAVITY', 'Gravity'), ('OTHER', 'Other'), ] ride = models.OneToOneField( Ride, on_delete=models.CASCADE, related_name='coaster_stats' ) height_ft = models.DecimalField( max_digits=6, decimal_places=2, null=True, blank=True ) length_ft = models.DecimalField( max_digits=7, decimal_places=2, null=True, blank=True ) speed_mph = models.DecimalField( max_digits=5, decimal_places=2, null=True, blank=True ) inversions = models.PositiveIntegerField(default=0) ride_time_seconds = models.PositiveIntegerField(null=True, blank=True) track_type = models.CharField(max_length=255, blank=True) track_material = models.CharField( max_length=20, choices=TRACK_MATERIAL_CHOICES, default='STEEL', blank=True ) roller_coaster_type = models.CharField( max_length=20, choices=COASTER_TYPE_CHOICES, default='SITDOWN', blank=True ) max_drop_height_ft = models.DecimalField( max_digits=6, decimal_places=2, null=True, blank=True ) launch_type = models.CharField( max_length=20, choices=LAUNCH_CHOICES, default='CHAIN' ) train_style = models.CharField(max_length=255, blank=True) trains_count = models.PositiveIntegerField(null=True, blank=True) cars_per_train = models.PositiveIntegerField(null=True, blank=True) seats_per_car = models.PositiveIntegerField(null=True, blank=True) class Meta: verbose_name = 'Roller Coaster Statistics' verbose_name_plural = 'Roller Coaster Statistics' def __str__(self) -> str: return f"Stats for {self.ride.name}"