diff --git a/backend/apps/api/management/commands/seed_data.py b/backend/apps/api/management/commands/seed_data.py index d34aa1ff..7600dcd5 100644 --- a/backend/apps/api/management/commands/seed_data.py +++ b/backend/apps/api/management/commands/seed_data.py @@ -878,7 +878,7 @@ class Command(BaseCommand): track_material=random.choice(['STEEL', 'WOOD', 'HYBRID']), roller_coaster_type=random.choice(['SITDOWN', 'INVERTED', 'WING', 'DIVE', 'FLYING']), max_drop_height_ft=Decimal(str(random.randint(80, 300))), - launch_type=random.choice(['CHAIN', 'LSM', 'HYDRAULIC']), + propulsion_system=random.choice(['CHAIN', 'LSM', 'HYDRAULIC']), train_style=random.choice(['Traditional', 'Floorless', 'Wing', 'Flying']), trains_count=random.randint(2, 4), cars_per_train=random.randint(6, 8), @@ -939,7 +939,7 @@ class Command(BaseCommand): track_material=random.choice(['STEEL', 'WOOD', 'HYBRID']), roller_coaster_type=random.choice(['SITDOWN', 'INVERTED', 'FAMILY', 'WILD_MOUSE']), max_drop_height_ft=Decimal(str(random.randint(40, 250))), - launch_type=random.choice(['CHAIN', 'LSM', 'GRAVITY']), + propulsion_system=random.choice(['CHAIN', 'LSM', 'GRAVITY']), train_style=random.choice(['Traditional', 'Family', 'Compact']), trains_count=random.randint(1, 3), cars_per_train=random.randint(4, 8), diff --git a/backend/apps/api/v1/parks/views.py b/backend/apps/api/v1/parks/views.py index cbb3d2e7..398377d8 100644 --- a/backend/apps/api/v1/parks/views.py +++ b/backend/apps/api/v1/parks/views.py @@ -22,7 +22,7 @@ from drf_spectacular.types import OpenApiTypes from rest_framework import status from rest_framework.decorators import action from rest_framework.exceptions import ValidationError -from rest_framework.permissions import IsAuthenticated +from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet @@ -109,9 +109,16 @@ class ParkPhotoViewSet(ModelViewSet): Includes advanced features like bulk approval and statistics. """ - permission_classes = [IsAuthenticated] lookup_field = "id" + def get_permissions(self): + """Set permissions based on action.""" + if self.action in ['list', 'retrieve', 'stats']: + permission_classes = [AllowAny] + else: + permission_classes = [IsAuthenticated] + return [permission() for permission in permission_classes] + def get_queryset(self): # type: ignore[override] """Get photos for the current park with optimized queries.""" queryset = ParkPhoto.objects.select_related( diff --git a/backend/apps/api/v1/rides/serializers.py b/backend/apps/api/v1/rides/serializers.py index d7b2896f..4a2cbaab 100644 --- a/backend/apps/api/v1/rides/serializers.py +++ b/backend/apps/api/v1/rides/serializers.py @@ -304,7 +304,7 @@ class HybridRideSerializer(serializers.ModelSerializer): coaster_track_material = serializers.SerializerMethodField() coaster_roller_coaster_type = serializers.SerializerMethodField() coaster_max_drop_height_ft = serializers.SerializerMethodField() - coaster_launch_type = serializers.SerializerMethodField() + coaster_propulsion_system = serializers.SerializerMethodField() coaster_train_style = serializers.SerializerMethodField() coaster_trains_count = serializers.SerializerMethodField() coaster_cars_per_train = serializers.SerializerMethodField() @@ -439,11 +439,11 @@ class HybridRideSerializer(serializers.ModelSerializer): return None @extend_schema_field(serializers.CharField(allow_null=True)) - def get_coaster_launch_type(self, obj): - """Get roller coaster launch type.""" + def get_coaster_propulsion_system(self, obj): + """Get roller coaster propulsion system.""" try: if hasattr(obj, 'coaster_stats') and obj.coaster_stats: - return obj.coaster_stats.launch_type + return obj.coaster_stats.propulsion_system return None except AttributeError: return None @@ -561,7 +561,7 @@ class HybridRideSerializer(serializers.ModelSerializer): "coaster_track_material", "coaster_roller_coaster_type", "coaster_max_drop_height_ft", - "coaster_launch_type", + "coaster_propulsion_system", "coaster_train_style", "coaster_trains_count", "coaster_cars_per_train", diff --git a/backend/apps/api/v1/rides/views.py b/backend/apps/api/v1/rides/views.py index 52648262..a4604f50 100644 --- a/backend/apps/api/v1/rides/views.py +++ b/backend/apps/api/v1/rides/views.py @@ -173,10 +173,10 @@ class RideListCreateAPIView(APIView): description="Filter roller coasters by track material (STEEL, WOOD, HYBRID)", ), OpenApiParameter( - name="launch_type", + name="propulsion_system", location=OpenApiParameter.QUERY, type=OpenApiTypes.STR, - description="Filter roller coasters by launch type (CHAIN, LSM, HYDRAULIC, etc.)", + description="Filter roller coasters by propulsion system (CHAIN, LSM, HYDRAULIC, etc.)", ), OpenApiParameter( name="min_rating", @@ -507,9 +507,9 @@ class RideListCreateAPIView(APIView): if track_material: qs = qs.filter(coaster_stats__track_material=track_material) - launch_type = params.get("launch_type") - if launch_type: - qs = qs.filter(coaster_stats__launch_type=launch_type) + propulsion_system = params.get("propulsion_system") + if propulsion_system: + qs = qs.filter(coaster_stats__propulsion_system=propulsion_system) # Height filters min_height_ft = params.get("min_height_ft") @@ -762,7 +762,7 @@ class FilterOptionsAPIView(APIView): post_closing_statuses = get_choices('post_closing_statuses', 'rides') track_materials = get_choices('track_materials', 'rides') coaster_types = get_choices('coaster_types', 'rides') - launch_systems = get_choices('launch_systems', 'rides') + propulsion_systems = get_choices('propulsion_systems', 'rides') target_markets = get_choices('target_markets', 'rides') # Convert Rich Choice Objects to frontend format with metadata @@ -831,7 +831,7 @@ class FilterOptionsAPIView(APIView): for choice in coaster_types ] - launch_systems_data = [ + propulsion_systems_data = [ { "value": choice.value, "label": choice.label, @@ -841,7 +841,7 @@ class FilterOptionsAPIView(APIView): "css_class": choice.metadata.get('css_class'), "sort_order": choice.metadata.get('sort_order', 0) } - for choice in launch_systems + for choice in propulsion_systems ] target_markets_data = [ @@ -894,7 +894,7 @@ class FilterOptionsAPIView(APIView): {"value": "WING", "label": "Wing", "description": "Seats extend beyond track sides", "color": "indigo", "icon": "wing", "css_class": "bg-indigo-100 text-indigo-800", "sort_order": 5}, {"value": "DIVE", "label": "Dive", "description": "Features steep vertical drops", "color": "red", "icon": "dive", "css_class": "bg-red-100 text-red-800", "sort_order": 6}, ] - launch_systems_data = [ + propulsion_systems_data = [ {"value": "CHAIN", "label": "Chain Lift", "description": "Traditional chain lift hill", "color": "gray", "icon": "chain", "css_class": "bg-gray-100 text-gray-800", "sort_order": 1}, {"value": "LSM", "label": "LSM Launch", "description": "Linear synchronous motor launch", "color": "blue", "icon": "lightning", "css_class": "bg-blue-100 text-blue-800", "sort_order": 2}, {"value": "HYDRAULIC", "label": "Hydraulic Launch", "description": "High-pressure hydraulic launch", "color": "red", "icon": "hydraulic", "css_class": "bg-red-100 text-red-800", "sort_order": 3}, @@ -934,7 +934,7 @@ class FilterOptionsAPIView(APIView): {"value": "WOOD", "label": "Wood"}, {"value": "HYBRID", "label": "Hybrid"}, ], - "launch_types": [ + "propulsion_systems": [ {"value": "CHAIN", "label": "Chain Lift"}, {"value": "LSM", "label": "LSM Launch"}, {"value": "HYDRAULIC", "label": "Hydraulic Launch"}, @@ -1019,7 +1019,7 @@ class FilterOptionsAPIView(APIView): post_closing_statuses = get_choices('post_closing_statuses', 'rides') track_materials = get_choices('track_materials', 'rides') coaster_types = get_choices('coaster_types', 'rides') - launch_systems = get_choices('launch_systems', 'rides') + propulsion_systems = get_choices('propulsion_systems', 'rides') target_markets = get_choices('target_markets', 'rides') # Convert Rich Choice Objects to frontend format with metadata @@ -1088,7 +1088,7 @@ class FilterOptionsAPIView(APIView): for choice in coaster_types ] - launch_systems_data = [ + propulsion_systems_data = [ { "value": choice.value, "label": choice.label, @@ -1098,7 +1098,7 @@ class FilterOptionsAPIView(APIView): "css_class": choice.metadata.get('css_class'), "sort_order": choice.metadata.get('sort_order', 0) } - for choice in launch_systems + for choice in propulsion_systems ] target_markets_data = [ @@ -1274,7 +1274,7 @@ class FilterOptionsAPIView(APIView): "post_closing_statuses": post_closing_statuses_data, "roller_coaster_types": coaster_types_data, "track_materials": track_materials_data, - "launch_types": launch_systems_data, + "propulsion_systems": propulsion_systems_data, "ride_model_target_markets": target_markets_data, "parks": parks, "park_areas": park_areas, @@ -1499,7 +1499,7 @@ class RideImageSettingsAPIView(APIView): OpenApiParameter("capacity_max", OpenApiTypes.INT, description="Maximum hourly capacity"), OpenApiParameter("roller_coaster_type", OpenApiTypes.STR, description="Filter by roller coaster type (comma-separated for multiple)"), OpenApiParameter("track_material", OpenApiTypes.STR, description="Filter by track material (comma-separated for multiple)"), - OpenApiParameter("launch_type", OpenApiTypes.STR, description="Filter by launch type (comma-separated for multiple)"), + OpenApiParameter("propulsion_system", OpenApiTypes.STR, description="Filter by propulsion system (comma-separated for multiple)"), OpenApiParameter("height_ft_min", OpenApiTypes.NUMBER, description="Minimum roller coaster height in feet"), OpenApiParameter("height_ft_max", OpenApiTypes.NUMBER, description="Maximum roller coaster height in feet"), OpenApiParameter("speed_mph_min", OpenApiTypes.NUMBER, description="Minimum roller coaster speed in mph"), @@ -1610,7 +1610,7 @@ class HybridRideAPIView(APIView): filters = {} # Handle comma-separated list parameters - list_params = ['category', 'status', 'manufacturer', 'designer', 'ride_model', 'roller_coaster_type', 'track_material', 'launch_type'] + list_params = ['category', 'status', 'manufacturer', 'designer', 'ride_model', 'roller_coaster_type', 'track_material', 'propulsion_system'] for param in list_params: value = query_params.get(param) if value: @@ -1692,7 +1692,7 @@ class HybridRideAPIView(APIView): "statuses": {"type": "array", "items": {"type": "string"}}, "roller_coaster_types": {"type": "array", "items": {"type": "string"}}, "track_materials": {"type": "array", "items": {"type": "string"}}, - "launch_types": {"type": "array", "items": {"type": "string"}}, + "propulsion_systems": {"type": "array", "items": {"type": "string"}}, "parks": { "type": "array", "items": { diff --git a/backend/apps/api/v1/serializers/rides.py b/backend/apps/api/v1/serializers/rides.py index c0a3c95e..fbd18801 100644 --- a/backend/apps/api/v1/serializers/rides.py +++ b/backend/apps/api/v1/serializers/rides.py @@ -697,7 +697,7 @@ class RideFilterInputSerializer(serializers.Serializer): "ride_time_seconds": 150, "track_material": "HYBRID", "roller_coaster_type": "SITDOWN", - "launch_type": "CHAIN", + "propulsion_system": "CHAIN", }, ) ] @@ -729,8 +729,8 @@ class RollerCoasterStatsOutputSerializer(serializers.Serializer): max_drop_height_ft = serializers.DecimalField( max_digits=6, decimal_places=2, allow_null=True ) - launch_type = RichChoiceFieldSerializer( - choice_group="launch_systems", + propulsion_system = RichChoiceFieldSerializer( + choice_group="propulsion_systems", domain="rides" ) train_style = serializers.CharField() @@ -775,8 +775,8 @@ class RollerCoasterStatsCreateInputSerializer(serializers.Serializer): max_drop_height_ft = serializers.DecimalField( max_digits=6, decimal_places=2, required=False, allow_null=True ) - launch_type = serializers.ChoiceField( - choices=ModelChoices.get_launch_choices(), default="CHAIN" + propulsion_system = serializers.ChoiceField( + choices=ModelChoices.get_propulsion_system_choices(), default="CHAIN" ) train_style = serializers.CharField(max_length=255, allow_blank=True, default="") trains_count = serializers.IntegerField(required=False, allow_null=True) @@ -808,8 +808,8 @@ class RollerCoasterStatsUpdateInputSerializer(serializers.Serializer): max_drop_height_ft = serializers.DecimalField( max_digits=6, decimal_places=2, required=False, allow_null=True ) - launch_type = serializers.ChoiceField( - choices=ModelChoices.get_launch_choices(), required=False + propulsion_system = serializers.ChoiceField( + choices=ModelChoices.get_propulsion_system_choices(), required=False ) train_style = serializers.CharField( max_length=255, allow_blank=True, required=False diff --git a/backend/apps/api/v1/serializers/shared.py b/backend/apps/api/v1/serializers/shared.py index 9ee106b9..1e7e0286 100644 --- a/backend/apps/api/v1/serializers/shared.py +++ b/backend/apps/api/v1/serializers/shared.py @@ -381,9 +381,16 @@ class ModelChoices: @staticmethod def get_launch_choices(): - """Get launch system choices from Rich Choice registry.""" + """Get launch system choices from Rich Choice registry (legacy method).""" from apps.core.choices.registry import get_choices - choices = get_choices("launch_systems", "rides") + 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 diff --git a/backend/apps/moderation/management/commands/seed_submissions.py b/backend/apps/moderation/management/commands/seed_submissions.py index ab6c2627..da5614f8 100644 --- a/backend/apps/moderation/management/commands/seed_submissions.py +++ b/backend/apps/moderation/management/commands/seed_submissions.py @@ -165,7 +165,7 @@ class Command(BaseCommand): "length_ft": 4500, "speed_mph": 80, "inversions": 5, - "launch_type": "LSM", + "propulsion_system": "LSM", "track_material": "STEEL", "roller_coaster_type": "SITDOWN", "trains_count": 3, diff --git a/backend/apps/parks/management/commands/seed_sample_data.py b/backend/apps/parks/management/commands/seed_sample_data.py index 07c1ba72..e8ddba9d 100644 --- a/backend/apps/parks/management/commands/seed_sample_data.py +++ b/backend/apps/parks/management/commands/seed_sample_data.py @@ -658,7 +658,7 @@ class Command(BaseCommand): "track_material": "STEEL", "roller_coaster_type": "SITDOWN", "max_drop_height_ft": 300, - "launch_type": "CHAIN", + "propulsion_system": "CHAIN", "trains_count": 3, "cars_per_train": 9, "seats_per_car": 4, @@ -681,7 +681,7 @@ class Command(BaseCommand): "track_material": "STEEL", "roller_coaster_type": "SITDOWN", "max_drop_height_ft": 400, - "launch_type": "HYDRAULIC", + "propulsion_system": "HYDRAULIC", "trains_count": 1, "cars_per_train": 1, "seats_per_car": 16, @@ -705,7 +705,7 @@ class Command(BaseCommand): "track_material": "STEEL", "roller_coaster_type": "SITDOWN", "max_drop_height_ft": 197, - "launch_type": "CHAIN", + "propulsion_system": "CHAIN", "trains_count": 2, "cars_per_train": 10, "seats_per_car": 2, @@ -729,7 +729,7 @@ class Command(BaseCommand): "track_material": "STEEL", "roller_coaster_type": "SITDOWN", "max_drop_height_ft": 98, - "launch_type": "HYDRAULIC", + "propulsion_system": "HYDRAULIC", "trains_count": 2, "cars_per_train": 5, "seats_per_car": 4, @@ -752,7 +752,7 @@ class Command(BaseCommand): "track_material": "STEEL", "roller_coaster_type": "SITDOWN", "max_drop_height_ft": 150, - "launch_type": "CHAIN", + "propulsion_system": "CHAIN", "trains_count": 2, "cars_per_train": 6, "seats_per_car": 2, @@ -775,7 +775,7 @@ class Command(BaseCommand): "track_material": "STEEL", "roller_coaster_type": "SITDOWN", "max_drop_height_ft": 128, - "launch_type": "CHAIN", + "propulsion_system": "CHAIN", "trains_count": 3, "cars_per_train": 5, "seats_per_car": 4, @@ -798,7 +798,7 @@ class Command(BaseCommand): "track_material": "STEEL", "roller_coaster_type": "WILD_MOUSE", "max_drop_height_ft": 100, - "launch_type": "CHAIN", + "propulsion_system": "CHAIN", "trains_count": 2, "cars_per_train": 4, "seats_per_car": 4, @@ -822,7 +822,7 @@ class Command(BaseCommand): "track_material": "HYBRID", "roller_coaster_type": "SITDOWN", "max_drop_height_ft": 155, - "launch_type": "CHAIN", + "propulsion_system": "CHAIN", "trains_count": 2, "cars_per_train": 6, "seats_per_car": 2, diff --git a/backend/apps/rides/admin.py b/backend/apps/rides/admin.py index c892a754..9c918b5a 100644 --- a/backend/apps/rides/admin.py +++ b/backend/apps/rides/admin.py @@ -94,7 +94,7 @@ class RollerCoasterStatsInline(admin.StackedInline): fields = ( ("height_ft", "length_ft", "speed_mph"), ("track_material", "roller_coaster_type"), - ("launch_type", "inversions"), + ("propulsion_system", "inversions"), ("max_drop_height_ft", "ride_time_seconds"), ("train_style", "trains_count"), ("cars_per_train", "seats_per_car"), @@ -270,7 +270,7 @@ class RollerCoasterStatsAdmin(admin.ModelAdmin): list_filter = ( "track_material", "roller_coaster_type", - "launch_type", + "propulsion_system", "inversions", ) search_fields = ( @@ -301,7 +301,7 @@ class RollerCoasterStatsAdmin(admin.ModelAdmin): "track_material", "track_type", "roller_coaster_type", - "launch_type", + "propulsion_system", "inversions", ) }, diff --git a/backend/apps/rides/choices.py b/backend/apps/rides/choices.py index 801fbdf1..33fbe4db 100644 --- a/backend/apps/rides/choices.py +++ b/backend/apps/rides/choices.py @@ -389,8 +389,8 @@ COASTER_TYPES = [ ), ] -# Launch System Choices -LAUNCH_SYSTEMS = [ +# Propulsion System Choices +PROPULSION_SYSTEMS = [ RichChoice( value="CHAIN", label="Chain Lift", @@ -442,7 +442,7 @@ LAUNCH_SYSTEMS = [ RichChoice( value="OTHER", label="Other", - description="Launch system that doesn't fit standard categories", + description="Propulsion system that doesn't fit standard categories", metadata={ 'color': 'gray', 'icon': 'other', @@ -760,11 +760,11 @@ def register_rides_choices(): ) register_choices( - name="launch_systems", - choices=LAUNCH_SYSTEMS, + name="propulsion_systems", + choices=PROPULSION_SYSTEMS, domain="rides", - description="Roller coaster launch and lift systems", - metadata={'domain': 'rides', 'type': 'launch_system', 'applies_to': 'roller_coasters'} + description="Roller coaster propulsion and lift systems", + metadata={'domain': 'rides', 'type': 'propulsion_system', 'applies_to': 'roller_coasters'} ) register_choices( diff --git a/backend/apps/rides/forms/search.py b/backend/apps/rides/forms/search.py index 3ee1ebf4..d1ace552 100644 --- a/backend/apps/rides/forms/search.py +++ b/backend/apps/rides/forms/search.py @@ -347,12 +347,12 @@ class RollerCoasterForm(BaseFilterForm): # Get choices - let exceptions propagate if registry fails track_material_choices = [(choice.value, choice.label) for choice in get_choices("track_materials", "rides")] coaster_type_choices = [(choice.value, choice.label) for choice in get_choices("coaster_types", "rides")] - launch_type_choices = [(choice.value, choice.label) for choice in get_choices("launch_systems", "rides")] + propulsion_system_choices = [(choice.value, choice.label) for choice in get_choices("propulsion_systems", "rides")] # Update field choices dynamically self.fields['track_material'].choices = track_material_choices self.fields['coaster_type'].choices = coaster_type_choices - self.fields['launch_type'].choices = launch_type_choices + self.fields['propulsion_system'].choices = propulsion_system_choices height_ft_range = NumberRangeField( min_val=0, max_val=500, step=1, required=False, label="Height (feet)" @@ -384,7 +384,7 @@ class RollerCoasterForm(BaseFilterForm): ), ) - launch_type = forms.MultipleChoiceField( + propulsion_system = forms.MultipleChoiceField( choices=[], # Will be populated in __init__ required=False, widget=forms.CheckboxSelectMultiple( @@ -579,7 +579,7 @@ class MasterFilterForm(BaseFilterForm): "inversions_range", "track_material", "coaster_type", - "launch_type", + "propulsion_system", ], "Company": ["manufacturer_roles", "designer_roles", "founded_date_range"], } diff --git a/backend/apps/rides/managers.py b/backend/apps/rides/managers.py index b6ebaed2..419a473c 100644 --- a/backend/apps/rides/managers.py +++ b/backend/apps/rides/managers.py @@ -271,7 +271,7 @@ class RollerCoasterStatsQuerySet(BaseQuerySet): def launched_coasters(self): """Filter for launched coasters.""" - return self.exclude(launch_type="NONE") + return self.exclude(propulsion_system="NONE") def by_track_type(self, *, track_type: str): """Filter by track type.""" diff --git a/backend/apps/rides/migrations/0024_rename_launch_type_to_propulsion_system.py b/backend/apps/rides/migrations/0024_rename_launch_type_to_propulsion_system.py new file mode 100644 index 00000000..5e953e25 --- /dev/null +++ b/backend/apps/rides/migrations/0024_rename_launch_type_to_propulsion_system.py @@ -0,0 +1,99 @@ +# Generated by Django 5.2.5 on 2025-09-17 01:25 + +import apps.core.choices.fields +import pgtrigger.compiler +import pgtrigger.migrations +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("rides", "0023_alter_ridemodelphoto_photo_type_and_more"), + ] + + operations = [ + pgtrigger.migrations.RemoveTrigger( + model_name="rollercoasterstats", + name="insert_insert", + ), + pgtrigger.migrations.RemoveTrigger( + model_name="rollercoasterstats", + name="update_update", + ), + migrations.RemoveField( + model_name="rollercoasterstats", + name="launch_type", + ), + migrations.RemoveField( + model_name="rollercoasterstatsevent", + name="launch_type", + ), + migrations.AddField( + model_name="rollercoasterstats", + name="propulsion_system", + field=apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="propulsion_systems", + choices=[ + ("CHAIN", "Chain Lift"), + ("LSM", "LSM Launch"), + ("HYDRAULIC", "Hydraulic Launch"), + ("GRAVITY", "Gravity"), + ("OTHER", "Other"), + ], + default="CHAIN", + domain="rides", + help_text="Propulsion or lift system type", + max_length=20, + ), + ), + migrations.AddField( + model_name="rollercoasterstatsevent", + name="propulsion_system", + field=apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="propulsion_systems", + choices=[ + ("CHAIN", "Chain Lift"), + ("LSM", "LSM Launch"), + ("HYDRAULIC", "Hydraulic Launch"), + ("GRAVITY", "Gravity"), + ("OTHER", "Other"), + ], + default="CHAIN", + domain="rides", + help_text="Propulsion or lift system type", + max_length=20, + ), + ), + pgtrigger.migrations.AddTrigger( + model_name="rollercoasterstats", + trigger=pgtrigger.compiler.Trigger( + name="insert_insert", + sql=pgtrigger.compiler.UpsertTriggerSql( + func='INSERT INTO "rides_rollercoasterstatsevent" ("cars_per_train", "height_ft", "id", "inversions", "length_ft", "max_drop_height_ft", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "propulsion_system", "ride_id", "ride_time_seconds", "roller_coaster_type", "seats_per_car", "speed_mph", "track_material", "track_type", "train_style", "trains_count") VALUES (NEW."cars_per_train", NEW."height_ft", NEW."id", NEW."inversions", NEW."length_ft", NEW."max_drop_height_ft", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."propulsion_system", NEW."ride_id", NEW."ride_time_seconds", NEW."roller_coaster_type", NEW."seats_per_car", NEW."speed_mph", NEW."track_material", NEW."track_type", NEW."train_style", NEW."trains_count"); RETURN NULL;', + hash="89e2bb56c0befa025a9961f8df34a8a02c09f188", + operation="INSERT", + pgid="pgtrigger_insert_insert_96f8b", + table="rides_rollercoasterstats", + when="AFTER", + ), + ), + ), + pgtrigger.migrations.AddTrigger( + model_name="rollercoasterstats", + trigger=pgtrigger.compiler.Trigger( + name="update_update", + sql=pgtrigger.compiler.UpsertTriggerSql( + condition="WHEN (OLD.* IS DISTINCT FROM NEW.*)", + func='INSERT INTO "rides_rollercoasterstatsevent" ("cars_per_train", "height_ft", "id", "inversions", "length_ft", "max_drop_height_ft", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "propulsion_system", "ride_id", "ride_time_seconds", "roller_coaster_type", "seats_per_car", "speed_mph", "track_material", "track_type", "train_style", "trains_count") VALUES (NEW."cars_per_train", NEW."height_ft", NEW."id", NEW."inversions", NEW."length_ft", NEW."max_drop_height_ft", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."propulsion_system", NEW."ride_id", NEW."ride_time_seconds", NEW."roller_coaster_type", NEW."seats_per_car", NEW."speed_mph", NEW."track_material", NEW."track_type", NEW."train_style", NEW."trains_count"); RETURN NULL;', + hash="047cc99ae3282202b6dc43c8dbe07690076d5068", + operation="UPDATE", + pgid="pgtrigger_update_update_24e8a", + table="rides_rollercoasterstats", + when="AFTER", + ), + ), + ), + ] diff --git a/backend/apps/rides/models/rides.py b/backend/apps/rides/models/rides.py index af7a9a21..275f81f4 100644 --- a/backend/apps/rides/models/rides.py +++ b/backend/apps/rides/models/rides.py @@ -718,10 +718,10 @@ class Ride(TrackedModel): type_choice = stats.get_roller_coaster_type_rich_choice() if type_choice: search_parts.append(type_choice.label) - if stats.launch_type: - launch_choice = stats.get_launch_type_rich_choice() - if launch_choice: - search_parts.append(launch_choice.label) + if stats.propulsion_system: + propulsion_choice = stats.get_propulsion_system_rich_choice() + if propulsion_choice: + search_parts.append(propulsion_choice.label) if stats.train_style: search_parts.append(stats.train_style) except Exception: @@ -878,12 +878,12 @@ class RollerCoasterStats(models.Model): max_drop_height_ft = models.DecimalField( max_digits=6, decimal_places=2, null=True, blank=True ) - launch_type = RichChoiceField( - choice_group="launch_systems", + propulsion_system = RichChoiceField( + choice_group="propulsion_systems", domain="rides", max_length=20, default="CHAIN", - help_text="Launch or lift system type" + help_text="Propulsion or lift system type" ) train_style = models.CharField(max_length=255, blank=True) trains_count = models.PositiveIntegerField(null=True, blank=True) diff --git a/backend/apps/rides/services/hybrid_loader.py b/backend/apps/rides/services/hybrid_loader.py index 31ce5233..2dc9a974 100644 --- a/backend/apps/rides/services/hybrid_loader.py +++ b/backend/apps/rides/services/hybrid_loader.py @@ -290,8 +290,8 @@ class SmartRideLoader: if 'track_material' in filters and filters['track_material']: q_objects &= Q(coaster_stats__track_material__in=filters['track_material']) - if 'launch_type' in filters and filters['launch_type']: - q_objects &= Q(coaster_stats__launch_type__in=filters['launch_type']) + if 'propulsion_system' in filters and filters['propulsion_system']: + q_objects &= Q(coaster_stats__propulsion_system__in=filters['propulsion_system']) # Roller coaster height filters if 'min_height_ft' in filters and filters['min_height_ft']: @@ -429,7 +429,7 @@ class SmartRideLoader: 'track_material': stats.track_material, 'roller_coaster_type': stats.roller_coaster_type, 'max_drop_height_ft': float(stats.max_drop_height_ft) if stats.max_drop_height_ft else None, - 'launch_type': stats.launch_type, + 'propulsion_system': stats.propulsion_system, 'train_style': stats.train_style, 'trains_count': stats.trains_count, 'cars_per_train': stats.cars_per_train, @@ -495,9 +495,9 @@ class SmartRideLoader: count=models.Count('ride') ).exclude(track_material__isnull=True).order_by('track_material')) - launch_types_data = list(RollerCoasterStats.objects.values('launch_type').annotate( + propulsion_systems_data = list(RollerCoasterStats.objects.values('propulsion_system').annotate( count=models.Count('ride') - ).exclude(launch_type__isnull=True).order_by('launch_type')) + ).exclude(propulsion_system__isnull=True).order_by('propulsion_system')) # Convert to frontend-expected format with value/label/count categories = [ @@ -536,13 +536,13 @@ class SmartRideLoader: for item in track_materials_data ] - launch_types = [ + propulsion_systems = [ { - 'value': item['launch_type'], - 'label': self._get_launch_type_label(item['launch_type']), + 'value': item['propulsion_system'], + 'label': self._get_propulsion_system_label(item['propulsion_system']), 'count': item['count'] } - for item in launch_types_data + for item in propulsion_systems_data ] # Convert other data to expected format @@ -633,7 +633,7 @@ class SmartRideLoader: 'statuses': statuses, 'roller_coaster_types': roller_coaster_types, 'track_materials': track_materials, - 'launch_types': launch_types, + 'propulsion_systems': propulsion_systems, 'parks': parks, 'park_areas': park_areas, 'manufacturers': manufacturers, @@ -746,6 +746,7 @@ class SmartRideLoader: 'BOBSLED': 'Bobsled', 'PIPELINE': 'Pipeline', 'FOURTH_DIMENSION': '4th Dimension', + 'FAMILY': 'Family', } if rc_type in rc_type_labels: return rc_type_labels[rc_type] @@ -764,9 +765,9 @@ class SmartRideLoader: else: raise ValueError(f"Unknown track material: {material}") - def _get_launch_type_label(self, launch_type: str) -> str: - """Convert launch type to human-readable label.""" - launch_labels = { + def _get_propulsion_system_label(self, propulsion_system: str) -> str: + """Convert propulsion system to human-readable label.""" + propulsion_labels = { 'CHAIN': 'Chain Lift', 'LSM': 'Linear Synchronous Motor', 'LIM': 'Linear Induction Motor', @@ -774,9 +775,10 @@ class SmartRideLoader: 'PNEUMATIC': 'Pneumatic Launch', 'CABLE': 'Cable Lift', 'FLYWHEEL': 'Flywheel Launch', - 'NONE': 'No Launch System', + 'GRAVITY': 'Gravity', + 'NONE': 'No Propulsion System', } - if launch_type in launch_labels: - return launch_labels[launch_type] + if propulsion_system in propulsion_labels: + return propulsion_labels[propulsion_system] else: - raise ValueError(f"Unknown launch type: {launch_type}") + raise ValueError(f"Unknown propulsion system: {propulsion_system}") diff --git a/backend/apps/rides/services/search.py b/backend/apps/rides/services/search.py index 4cdba074..82ece74b 100644 --- a/backend/apps/rides/services/search.py +++ b/backend/apps/rides/services/search.py @@ -60,7 +60,7 @@ class RideSearchService: "inversions_range", "track_material", "coaster_type", - "launch_type", + "propulsion_system", ], "company": ["manufacturer_roles", "designer_roles", "founded_date_range"], } @@ -434,14 +434,14 @@ class RideSearchService: rollercoasterstats__roller_coaster_type__in=types ) - # Launch type filter (multi-select) - if filters.get("launch_type"): - launch_types = ( - filters["launch_type"] - if isinstance(filters["launch_type"], list) - else [filters["launch_type"]] + # Propulsion system filter (multi-select) + if filters.get("propulsion_system"): + propulsion_systems = ( + filters["propulsion_system"] + if isinstance(filters["propulsion_system"], list) + else [filters["propulsion_system"]] ) - queryset = queryset.filter(rollercoasterstats__launch_type__in=launch_types) + queryset = queryset.filter(rollercoasterstats__propulsion_system__in=propulsion_systems) return queryset @@ -589,7 +589,7 @@ class RideSearchService: "inversions_range": "Inversions", "track_material": "Track Material", "coaster_type": "Coaster Type", - "launch_type": "Launch Type", + "propulsion_system": "Propulsion System", "manufacturer_roles": "Manufacturer Roles", "designer_roles": "Designer Roles", "founded_date_range": "Founded Date", diff --git a/context_portal/conport_vector_data/422ad254-2341-4dce-9133-98aaf02fdb1b/data_level0.bin b/context_portal/conport_vector_data/422ad254-2341-4dce-9133-98aaf02fdb1b/data_level0.bin index 09cffb60..da6391bd 100644 Binary files a/context_portal/conport_vector_data/422ad254-2341-4dce-9133-98aaf02fdb1b/data_level0.bin and b/context_portal/conport_vector_data/422ad254-2341-4dce-9133-98aaf02fdb1b/data_level0.bin differ diff --git a/context_portal/conport_vector_data/422ad254-2341-4dce-9133-98aaf02fdb1b/header.bin b/context_portal/conport_vector_data/422ad254-2341-4dce-9133-98aaf02fdb1b/header.bin index e85f4658..bb547926 100644 Binary files a/context_portal/conport_vector_data/422ad254-2341-4dce-9133-98aaf02fdb1b/header.bin and b/context_portal/conport_vector_data/422ad254-2341-4dce-9133-98aaf02fdb1b/header.bin differ diff --git a/context_portal/conport_vector_data/422ad254-2341-4dce-9133-98aaf02fdb1b/length.bin b/context_portal/conport_vector_data/422ad254-2341-4dce-9133-98aaf02fdb1b/length.bin index d6c666a8..cb3e1628 100644 Binary files a/context_portal/conport_vector_data/422ad254-2341-4dce-9133-98aaf02fdb1b/length.bin and b/context_portal/conport_vector_data/422ad254-2341-4dce-9133-98aaf02fdb1b/length.bin differ diff --git a/context_portal/conport_vector_data/chroma.sqlite3 b/context_portal/conport_vector_data/chroma.sqlite3 index 86867c7b..6bc67a50 100644 Binary files a/context_portal/conport_vector_data/chroma.sqlite3 and b/context_portal/conport_vector_data/chroma.sqlite3 differ diff --git a/context_portal/context.db b/context_portal/context.db index ed031cff..2f6b3330 100644 Binary files a/context_portal/context.db and b/context_portal/context.db differ