from django.db import models from django.contrib.contenttypes.fields import GenericRelation from django.utils.text import slugify from simple_history.models import HistoricalRecords class Ride(models.Model): CATEGORY_CHOICES = [ ('RC', 'Roller Coaster'), ('DR', 'Dark Ride'), ('FR', 'Flat Ride'), ('WR', 'Water Ride'), ('TR', 'Transport'), ('OT', 'Other'), ] STATUS_CHOICES = [ ('OPERATING', 'Operating'), ('CLOSED_TEMP', 'Temporarily Closed'), ('CLOSED_PERM', 'Permanently Closed'), ('UNDER_CONSTRUCTION', 'Under Construction'), ('DEMOLISHED', 'Demolished'), ('RELOCATED', 'Relocated'), ] 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='OT' ) manufacturer = models.ForeignKey( 'companies.manufacturer', on_delete=models.CASCADE, null=False, blank=False ) designer = models.ForeignKey( 'designers.Designer', on_delete=models.SET_NULL, related_name='rides', null=True, blank=True, help_text='The designer/engineering firm responsible for the ride' ) model_name = models.CharField(max_length=255, blank=True) status = models.CharField( max_length=20, choices=STATUS_CHOICES, default='OPERATING' ) 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) accessibility_options = models.TextField(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') history = HistoricalRecords() class Meta: ordering = ['name'] unique_together = ['park', 'slug'] def __str__(self): return f"{self.name} at {self.park.name}" def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.name) super().save(*args, **kwargs) @classmethod def get_by_slug(cls, slug): """Get ride by current or historical slug""" try: return cls.objects.get(slug=slug), False except cls.DoesNotExist: # Check historical slugs history = cls.history.filter(slug=slug).order_by('-history_date').first() if history: return cls.objects.get(id=history.id), True raise cls.DoesNotExist("No ride found with this slug") class RollerCoasterStats(models.Model): LAUNCH_CHOICES = [ ('CHAIN', 'Chain Lift'), ('CABLE', 'Cable Launch'), ('HYDRAULIC', 'Hydraulic Launch'), ('LSM', 'Linear Synchronous Motor'), ('LIM', 'Linear Induction Motor'), ('GRAVITY', 'Gravity'), ('OTHER', 'Other'), ] TRACK_MATERIAL_CHOICES = [ ('STEEL', 'Steel'), ('WOOD', 'Wood'), ('HYBRID', 'Hybrid'), ('OTHER', 'Other'), ] COASTER_TYPE_CHOICES = [ ('SITDOWN', 'Sit-Down'), ('INVERTED', 'Inverted'), ('FLYING', 'Flying'), ('STANDUP', 'Stand-Up'), ('WING', 'Wing'), ('SUSPENDED', 'Suspended'), ('BOBSLED', 'Bobsled'), ('PIPELINE', 'Pipeline'), ('MOTORBIKE', 'Motorbike'), ('FLOORLESS', 'Floorless'), ('DIVE', 'Dive'), ('FAMILY', 'Family'), ('WILD_MOUSE', 'Wild Mouse'), ('SPINNING', 'Spinning'), ('FOURTH_DIMENSION', '4th Dimension'), ('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, help_text='The type/style of roller coaster (e.g. Sit-Down, Inverted, Flying)' ) max_drop_height_ft = models.DecimalField( max_digits=6, decimal_places=2, null=True, blank=True, help_text='Maximum vertical drop height in feet' ) 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) history = HistoricalRecords() class Meta: verbose_name = 'Roller Coaster Statistics' verbose_name_plural = 'Roller Coaster Statistics' def __str__(self): return f"Stats for {self.ride.name}"