This commit is contained in:
pacnpal
2026-01-02 07:58:58 -05:00
parent b243b17af7
commit 1adba1b804
36 changed files with 6345 additions and 6 deletions

View File

@@ -0,0 +1,454 @@
# Generated by Django 5.2.9 on 2026-01-01 21:25
import django.db.models.deletion
import pgtrigger.compiler
import pgtrigger.migrations
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pghistory", "0007_auto_20250421_0444"),
("rides", "0029_darkridestats_darkridestatsevent_flatridestats_and_more"),
]
operations = [
migrations.CreateModel(
name="KiddieRideStats",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
(
"min_age",
models.PositiveIntegerField(blank=True, help_text="Minimum recommended age in years", null=True),
),
(
"max_age",
models.PositiveIntegerField(blank=True, help_text="Maximum recommended age in years", null=True),
),
(
"educational_theme",
models.CharField(
blank=True,
help_text="Educational theme if applicable (e.g., 'Dinosaurs', 'Space')",
max_length=200,
),
),
(
"character_theme",
models.CharField(
blank=True,
help_text="Character theme if applicable (e.g., 'Paw Patrol', 'Peppa Pig')",
max_length=200,
),
),
(
"guardian_required",
models.BooleanField(default=False, help_text="Whether a guardian must be present during the ride"),
),
(
"adult_ride_along",
models.BooleanField(default=True, help_text="Whether adults can ride along with children"),
),
(
"seats_per_vehicle",
models.PositiveIntegerField(blank=True, help_text="Number of seats per ride vehicle", null=True),
),
],
options={
"verbose_name": "Kiddie Ride Statistics",
"verbose_name_plural": "Kiddie Ride Statistics",
"ordering": ["ride"],
"abstract": False,
},
),
migrations.CreateModel(
name="KiddieRideStatsEvent",
fields=[
("pgh_id", models.AutoField(primary_key=True, serialize=False)),
("pgh_created_at", models.DateTimeField(auto_now_add=True)),
("pgh_label", models.TextField(help_text="The event label.")),
("id", models.BigIntegerField()),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
(
"min_age",
models.PositiveIntegerField(blank=True, help_text="Minimum recommended age in years", null=True),
),
(
"max_age",
models.PositiveIntegerField(blank=True, help_text="Maximum recommended age in years", null=True),
),
(
"educational_theme",
models.CharField(
blank=True,
help_text="Educational theme if applicable (e.g., 'Dinosaurs', 'Space')",
max_length=200,
),
),
(
"character_theme",
models.CharField(
blank=True,
help_text="Character theme if applicable (e.g., 'Paw Patrol', 'Peppa Pig')",
max_length=200,
),
),
(
"guardian_required",
models.BooleanField(default=False, help_text="Whether a guardian must be present during the ride"),
),
(
"adult_ride_along",
models.BooleanField(default=True, help_text="Whether adults can ride along with children"),
),
(
"seats_per_vehicle",
models.PositiveIntegerField(blank=True, help_text="Number of seats per ride vehicle", null=True),
),
],
options={
"abstract": False,
},
),
migrations.CreateModel(
name="TransportationStats",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
(
"transport_type",
models.CharField(
choices=[
("TRAIN", "Train"),
("MONORAIL", "Monorail"),
("SKYLIFT", "Skylift / Chairlift"),
("FERRY", "Ferry / Boat"),
("PEOPLEMOVER", "PeopleMover"),
("CABLE_CAR", "Cable Car"),
("TRAM", "Tram"),
],
default="TRAIN",
help_text="Type of transportation",
max_length=20,
),
),
(
"route_length_ft",
models.DecimalField(
blank=True, decimal_places=2, help_text="Total route length in feet", max_digits=8, null=True
),
),
(
"stations_count",
models.PositiveIntegerField(
blank=True, help_text="Number of stations/stops on the route", null=True
),
),
(
"vehicle_capacity",
models.PositiveIntegerField(blank=True, help_text="Passenger capacity per vehicle", null=True),
),
(
"vehicles_count",
models.PositiveIntegerField(
blank=True, help_text="Total number of vehicles in operation", null=True
),
),
(
"round_trip_duration_minutes",
models.PositiveIntegerField(
blank=True, help_text="Duration of a complete round trip in minutes", null=True
),
),
(
"scenic_highlights",
models.TextField(blank=True, help_text="Notable scenic views or attractions along the route"),
),
(
"is_one_way",
models.BooleanField(
default=False, help_text="Whether this is a one-way transportation (vs round-trip)"
),
),
],
options={
"verbose_name": "Transportation Statistics",
"verbose_name_plural": "Transportation Statistics",
"ordering": ["ride"],
"abstract": False,
},
),
migrations.CreateModel(
name="TransportationStatsEvent",
fields=[
("pgh_id", models.AutoField(primary_key=True, serialize=False)),
("pgh_created_at", models.DateTimeField(auto_now_add=True)),
("pgh_label", models.TextField(help_text="The event label.")),
("id", models.BigIntegerField()),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
(
"transport_type",
models.CharField(
choices=[
("TRAIN", "Train"),
("MONORAIL", "Monorail"),
("SKYLIFT", "Skylift / Chairlift"),
("FERRY", "Ferry / Boat"),
("PEOPLEMOVER", "PeopleMover"),
("CABLE_CAR", "Cable Car"),
("TRAM", "Tram"),
],
default="TRAIN",
help_text="Type of transportation",
max_length=20,
),
),
(
"route_length_ft",
models.DecimalField(
blank=True, decimal_places=2, help_text="Total route length in feet", max_digits=8, null=True
),
),
(
"stations_count",
models.PositiveIntegerField(
blank=True, help_text="Number of stations/stops on the route", null=True
),
),
(
"vehicle_capacity",
models.PositiveIntegerField(blank=True, help_text="Passenger capacity per vehicle", null=True),
),
(
"vehicles_count",
models.PositiveIntegerField(
blank=True, help_text="Total number of vehicles in operation", null=True
),
),
(
"round_trip_duration_minutes",
models.PositiveIntegerField(
blank=True, help_text="Duration of a complete round trip in minutes", null=True
),
),
(
"scenic_highlights",
models.TextField(blank=True, help_text="Notable scenic views or attractions along the route"),
),
(
"is_one_way",
models.BooleanField(
default=False, help_text="Whether this is a one-way transportation (vs round-trip)"
),
),
],
options={
"abstract": False,
},
),
migrations.AlterModelOptions(
name="ridecredit",
options={
"ordering": ["display_order", "-last_ridden_at", "-first_ridden_at", "-created_at"],
"verbose_name": "Ride Credit",
"verbose_name_plural": "Ride Credits",
},
),
pgtrigger.migrations.RemoveTrigger(
model_name="ridecredit",
name="insert_insert",
),
pgtrigger.migrations.RemoveTrigger(
model_name="ridecredit",
name="update_update",
),
migrations.AddField(
model_name="ridecredit",
name="display_order",
field=models.PositiveIntegerField(default=0, help_text="User-defined display order for drag-drop sorting"),
),
migrations.AddField(
model_name="ridecreditevent",
name="display_order",
field=models.PositiveIntegerField(default=0, help_text="User-defined display order for drag-drop sorting"),
),
pgtrigger.migrations.AddTrigger(
model_name="ridecredit",
trigger=pgtrigger.compiler.Trigger(
name="insert_insert",
sql=pgtrigger.compiler.UpsertTriggerSql(
func='INSERT INTO "rides_ridecreditevent" ("count", "created_at", "display_order", "first_ridden_at", "id", "last_ridden_at", "notes", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "rating", "ride_id", "updated_at", "user_id") VALUES (NEW."count", NEW."created_at", NEW."display_order", NEW."first_ridden_at", NEW."id", NEW."last_ridden_at", NEW."notes", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."rating", NEW."ride_id", NEW."updated_at", NEW."user_id"); RETURN NULL;',
hash="680f93dab99a404aea8f73f8328eff04cd561254",
operation="INSERT",
pgid="pgtrigger_insert_insert_00439",
table="rides_ridecredit",
when="AFTER",
),
),
),
pgtrigger.migrations.AddTrigger(
model_name="ridecredit",
trigger=pgtrigger.compiler.Trigger(
name="update_update",
sql=pgtrigger.compiler.UpsertTriggerSql(
condition="WHEN (OLD.* IS DISTINCT FROM NEW.*)",
func='INSERT INTO "rides_ridecreditevent" ("count", "created_at", "display_order", "first_ridden_at", "id", "last_ridden_at", "notes", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "rating", "ride_id", "updated_at", "user_id") VALUES (NEW."count", NEW."created_at", NEW."display_order", NEW."first_ridden_at", NEW."id", NEW."last_ridden_at", NEW."notes", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."rating", NEW."ride_id", NEW."updated_at", NEW."user_id"); RETURN NULL;',
hash="e9889a572acd9261c1355ab47458f3eaf2b07c13",
operation="UPDATE",
pgid="pgtrigger_update_update_32a65",
table="rides_ridecredit",
when="AFTER",
),
),
),
migrations.AddField(
model_name="kiddieridestats",
name="ride",
field=models.OneToOneField(
help_text="Ride these kiddie ride statistics belong to",
on_delete=django.db.models.deletion.CASCADE,
related_name="kiddie_stats",
to="rides.ride",
),
),
migrations.AddField(
model_name="kiddieridestatsevent",
name="pgh_context",
field=models.ForeignKey(
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to="pghistory.context",
),
),
migrations.AddField(
model_name="kiddieridestatsevent",
name="pgh_obj",
field=models.ForeignKey(
db_constraint=False,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="events",
to="rides.kiddieridestats",
),
),
migrations.AddField(
model_name="kiddieridestatsevent",
name="ride",
field=models.ForeignKey(
db_constraint=False,
help_text="Ride these kiddie ride statistics belong to",
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
related_query_name="+",
to="rides.ride",
),
),
migrations.AddField(
model_name="transportationstats",
name="ride",
field=models.OneToOneField(
help_text="Ride these transportation statistics belong to",
on_delete=django.db.models.deletion.CASCADE,
related_name="transport_stats",
to="rides.ride",
),
),
migrations.AddField(
model_name="transportationstatsevent",
name="pgh_context",
field=models.ForeignKey(
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to="pghistory.context",
),
),
migrations.AddField(
model_name="transportationstatsevent",
name="pgh_obj",
field=models.ForeignKey(
db_constraint=False,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="events",
to="rides.transportationstats",
),
),
migrations.AddField(
model_name="transportationstatsevent",
name="ride",
field=models.ForeignKey(
db_constraint=False,
help_text="Ride these transportation statistics belong to",
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
related_query_name="+",
to="rides.ride",
),
),
pgtrigger.migrations.AddTrigger(
model_name="kiddieridestats",
trigger=pgtrigger.compiler.Trigger(
name="insert_insert",
sql=pgtrigger.compiler.UpsertTriggerSql(
func='INSERT INTO "rides_kiddieridestatsevent" ("adult_ride_along", "character_theme", "created_at", "educational_theme", "guardian_required", "id", "max_age", "min_age", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "ride_id", "seats_per_vehicle", "updated_at") VALUES (NEW."adult_ride_along", NEW."character_theme", NEW."created_at", NEW."educational_theme", NEW."guardian_required", NEW."id", NEW."max_age", NEW."min_age", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."ride_id", NEW."seats_per_vehicle", NEW."updated_at"); RETURN NULL;',
hash="b5d181566e5d0710c5b4093a5b61dc54591e5639",
operation="INSERT",
pgid="pgtrigger_insert_insert_9949f",
table="rides_kiddieridestats",
when="AFTER",
),
),
),
pgtrigger.migrations.AddTrigger(
model_name="kiddieridestats",
trigger=pgtrigger.compiler.Trigger(
name="update_update",
sql=pgtrigger.compiler.UpsertTriggerSql(
condition="WHEN (OLD.* IS DISTINCT FROM NEW.*)",
func='INSERT INTO "rides_kiddieridestatsevent" ("adult_ride_along", "character_theme", "created_at", "educational_theme", "guardian_required", "id", "max_age", "min_age", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "ride_id", "seats_per_vehicle", "updated_at") VALUES (NEW."adult_ride_along", NEW."character_theme", NEW."created_at", NEW."educational_theme", NEW."guardian_required", NEW."id", NEW."max_age", NEW."min_age", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."ride_id", NEW."seats_per_vehicle", NEW."updated_at"); RETURN NULL;',
hash="672bb3e42cda03094d9300b6e0d9d89b2797bd05",
operation="UPDATE",
pgid="pgtrigger_update_update_fb19d",
table="rides_kiddieridestats",
when="AFTER",
),
),
),
pgtrigger.migrations.AddTrigger(
model_name="transportationstats",
trigger=pgtrigger.compiler.Trigger(
name="insert_insert",
sql=pgtrigger.compiler.UpsertTriggerSql(
func='INSERT INTO "rides_transportationstatsevent" ("created_at", "id", "is_one_way", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "ride_id", "round_trip_duration_minutes", "route_length_ft", "scenic_highlights", "stations_count", "transport_type", "updated_at", "vehicle_capacity", "vehicles_count") VALUES (NEW."created_at", NEW."id", NEW."is_one_way", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."ride_id", NEW."round_trip_duration_minutes", NEW."route_length_ft", NEW."scenic_highlights", NEW."stations_count", NEW."transport_type", NEW."updated_at", NEW."vehicle_capacity", NEW."vehicles_count"); RETURN NULL;',
hash="95a70a6e71eb5ea32726a19e5eb6286c20b19952",
operation="INSERT",
pgid="pgtrigger_insert_insert_c811e",
table="rides_transportationstats",
when="AFTER",
),
),
),
pgtrigger.migrations.AddTrigger(
model_name="transportationstats",
trigger=pgtrigger.compiler.Trigger(
name="update_update",
sql=pgtrigger.compiler.UpsertTriggerSql(
condition="WHEN (OLD.* IS DISTINCT FROM NEW.*)",
func='INSERT INTO "rides_transportationstatsevent" ("created_at", "id", "is_one_way", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "ride_id", "round_trip_duration_minutes", "route_length_ft", "scenic_highlights", "stations_count", "transport_type", "updated_at", "vehicle_capacity", "vehicles_count") VALUES (NEW."created_at", NEW."id", NEW."is_one_way", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."ride_id", NEW."round_trip_duration_minutes", NEW."route_length_ft", NEW."scenic_highlights", NEW."stations_count", NEW."transport_type", NEW."updated_at", NEW."vehicle_capacity", NEW."vehicles_count"); RETURN NULL;',
hash="901b16a5411e78635442895d7107b298634d25c3",
operation="UPDATE",
pgid="pgtrigger_update_update_ccccf",
table="rides_transportationstats",
when="AFTER",
),
),
),
]

View File

@@ -0,0 +1,155 @@
# Generated by Django 5.2.9 on 2026-01-02 00:33
import django.db.models.deletion
import pgtrigger.compiler
import pgtrigger.migrations
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pghistory", "0007_auto_20250421_0444"),
("rides", "0030_add_kiddie_and_transportation_stats"),
]
operations = [
migrations.CreateModel(
name="RideNameHistory",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
("former_name", models.CharField(help_text="The previous name of the ride", max_length=200)),
(
"from_year",
models.PositiveSmallIntegerField(
blank=True, help_text="Year the ride started using this name", null=True
),
),
(
"to_year",
models.PositiveSmallIntegerField(
blank=True, help_text="Year the ride stopped using this name", null=True
),
),
(
"reason",
models.CharField(
blank=True, help_text="Reason for the name change (e.g., 'Retheme to Peanuts')", max_length=500
),
),
(
"ride",
models.ForeignKey(
help_text="The ride this name history entry belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="former_names",
to="rides.ride",
),
),
],
options={
"verbose_name": "Ride Name History",
"verbose_name_plural": "Ride Name Histories",
"ordering": ["-to_year", "-from_year"],
"abstract": False,
},
),
migrations.CreateModel(
name="RideNameHistoryEvent",
fields=[
("pgh_id", models.AutoField(primary_key=True, serialize=False)),
("pgh_created_at", models.DateTimeField(auto_now_add=True)),
("pgh_label", models.TextField(help_text="The event label.")),
("id", models.BigIntegerField()),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
("former_name", models.CharField(help_text="The previous name of the ride", max_length=200)),
(
"from_year",
models.PositiveSmallIntegerField(
blank=True, help_text="Year the ride started using this name", null=True
),
),
(
"to_year",
models.PositiveSmallIntegerField(
blank=True, help_text="Year the ride stopped using this name", null=True
),
),
(
"reason",
models.CharField(
blank=True, help_text="Reason for the name change (e.g., 'Retheme to Peanuts')", max_length=500
),
),
(
"pgh_context",
models.ForeignKey(
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to="pghistory.context",
),
),
(
"pgh_obj",
models.ForeignKey(
db_constraint=False,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="events",
to="rides.ridenamehistory",
),
),
(
"ride",
models.ForeignKey(
db_constraint=False,
help_text="The ride this name history entry belongs to",
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
related_query_name="+",
to="rides.ride",
),
),
],
options={
"abstract": False,
},
),
migrations.AddIndex(
model_name="ridenamehistory",
index=models.Index(fields=["ride", "-to_year"], name="rides_riden_ride_id_b546e5_idx"),
),
pgtrigger.migrations.AddTrigger(
model_name="ridenamehistory",
trigger=pgtrigger.compiler.Trigger(
name="insert_insert",
sql=pgtrigger.compiler.UpsertTriggerSql(
func='INSERT INTO "rides_ridenamehistoryevent" ("created_at", "former_name", "from_year", "id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "reason", "ride_id", "to_year", "updated_at") VALUES (NEW."created_at", NEW."former_name", NEW."from_year", NEW."id", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."reason", NEW."ride_id", NEW."to_year", NEW."updated_at"); RETURN NULL;',
hash="b79231914244e431999014db94fd52759cc41541",
operation="INSERT",
pgid="pgtrigger_insert_insert_ca1f7",
table="rides_ridenamehistory",
when="AFTER",
),
),
),
pgtrigger.migrations.AddTrigger(
model_name="ridenamehistory",
trigger=pgtrigger.compiler.Trigger(
name="update_update",
sql=pgtrigger.compiler.UpsertTriggerSql(
condition="WHEN (OLD.* IS DISTINCT FROM NEW.*)",
func='INSERT INTO "rides_ridenamehistoryevent" ("created_at", "former_name", "from_year", "id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "reason", "ride_id", "to_year", "updated_at") VALUES (NEW."created_at", NEW."former_name", NEW."from_year", NEW."id", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."reason", NEW."ride_id", NEW."to_year", NEW."updated_at"); RETURN NULL;',
hash="c760d29ecb57f1f3c92e76c7f5d45027db136b84",
operation="UPDATE",
pgid="pgtrigger_update_update_99e4e",
table="rides_ridenamehistory",
when="AFTER",
),
),
),
]

View File

@@ -0,0 +1,96 @@
# Generated by Django 5.2.9 on 2026-01-02 02:30
import pgtrigger.compiler
import pgtrigger.migrations
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("rides", "0031_add_ride_name_history"),
]
operations = [
pgtrigger.migrations.RemoveTrigger(
model_name="ride",
name="insert_insert",
),
pgtrigger.migrations.RemoveTrigger(
model_name="ride",
name="update_update",
),
migrations.AddField(
model_name="ride",
name="closing_date_precision",
field=models.CharField(
blank=True,
choices=[("YEAR", "Year"), ("MONTH", "Month"), ("DAY", "Day")],
default="DAY",
help_text="Precision of the closing date",
max_length=10,
),
),
migrations.AddField(
model_name="ride",
name="opening_date_precision",
field=models.CharField(
blank=True,
choices=[("YEAR", "Year"), ("MONTH", "Month"), ("DAY", "Day")],
default="DAY",
help_text="Precision of the opening date",
max_length=10,
),
),
migrations.AddField(
model_name="rideevent",
name="closing_date_precision",
field=models.CharField(
blank=True,
choices=[("YEAR", "Year"), ("MONTH", "Month"), ("DAY", "Day")],
default="DAY",
help_text="Precision of the closing date",
max_length=10,
),
),
migrations.AddField(
model_name="rideevent",
name="opening_date_precision",
field=models.CharField(
blank=True,
choices=[("YEAR", "Year"), ("MONTH", "Month"), ("DAY", "Day")],
default="DAY",
help_text="Precision of the opening date",
max_length=10,
),
),
pgtrigger.migrations.AddTrigger(
model_name="ride",
trigger=pgtrigger.compiler.Trigger(
name="insert_insert",
sql=pgtrigger.compiler.UpsertTriggerSql(
func='INSERT INTO "rides_rideevent" ("average_rating", "banner_image_id", "capacity_per_hour", "card_image_id", "category", "closing_date", "closing_date_precision", "created_at", "description", "designer_id", "id", "manufacturer_id", "max_height_in", "min_height_in", "name", "opening_date", "opening_date_precision", "opening_year", "park_area_id", "park_id", "park_url", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "post_closing_status", "ride_duration_seconds", "ride_model_id", "search_text", "slug", "status", "status_since", "updated_at", "url") VALUES (NEW."average_rating", NEW."banner_image_id", NEW."capacity_per_hour", NEW."card_image_id", NEW."category", NEW."closing_date", NEW."closing_date_precision", NEW."created_at", NEW."description", NEW."designer_id", NEW."id", NEW."manufacturer_id", NEW."max_height_in", NEW."min_height_in", NEW."name", NEW."opening_date", NEW."opening_date_precision", NEW."opening_year", NEW."park_area_id", NEW."park_id", NEW."park_url", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."post_closing_status", NEW."ride_duration_seconds", NEW."ride_model_id", NEW."search_text", NEW."slug", NEW."status", NEW."status_since", NEW."updated_at", NEW."url"); RETURN NULL;',
hash="e0a64190a51762d71e695238a7ee9feedb95fd41",
operation="INSERT",
pgid="pgtrigger_insert_insert_52074",
table="rides_ride",
when="AFTER",
),
),
),
pgtrigger.migrations.AddTrigger(
model_name="ride",
trigger=pgtrigger.compiler.Trigger(
name="update_update",
sql=pgtrigger.compiler.UpsertTriggerSql(
condition="WHEN (OLD.* IS DISTINCT FROM NEW.*)",
func='INSERT INTO "rides_rideevent" ("average_rating", "banner_image_id", "capacity_per_hour", "card_image_id", "category", "closing_date", "closing_date_precision", "created_at", "description", "designer_id", "id", "manufacturer_id", "max_height_in", "min_height_in", "name", "opening_date", "opening_date_precision", "opening_year", "park_area_id", "park_id", "park_url", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "post_closing_status", "ride_duration_seconds", "ride_model_id", "search_text", "slug", "status", "status_since", "updated_at", "url") VALUES (NEW."average_rating", NEW."banner_image_id", NEW."capacity_per_hour", NEW."card_image_id", NEW."category", NEW."closing_date", NEW."closing_date_precision", NEW."created_at", NEW."description", NEW."designer_id", NEW."id", NEW."manufacturer_id", NEW."max_height_in", NEW."min_height_in", NEW."name", NEW."opening_date", NEW."opening_date_precision", NEW."opening_year", NEW."park_area_id", NEW."park_id", NEW."park_url", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."post_closing_status", NEW."ride_duration_seconds", NEW."ride_model_id", NEW."search_text", NEW."slug", NEW."status", NEW."status_since", NEW."updated_at", NEW."url"); RETURN NULL;',
hash="e3991e794f1239191cfe9095bab207527123b94f",
operation="UPDATE",
pgid="pgtrigger_update_update_4917a",
table="rides_ride",
when="AFTER",
),
),
),
]

View File

@@ -0,0 +1,84 @@
# Generated by Django 5.2.9 on 2026-01-02 02:36
import pgtrigger.compiler
import pgtrigger.migrations
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("rides", "0032_add_date_precision_fields"),
]
operations = [
pgtrigger.migrations.RemoveTrigger(
model_name="ride",
name="insert_insert",
),
pgtrigger.migrations.RemoveTrigger(
model_name="ride",
name="update_update",
),
migrations.AddField(
model_name="ride",
name="age_requirement",
field=models.PositiveIntegerField(
blank=True, help_text="Minimum age requirement in years (if any)", null=True
),
),
migrations.AddField(
model_name="ride",
name="ride_sub_type",
field=models.CharField(
blank=True,
help_text="Sub-category of ride (e.g., 'Flying Coaster', 'Inverted Coaster', 'Log Flume')",
max_length=100,
),
),
migrations.AddField(
model_name="rideevent",
name="age_requirement",
field=models.PositiveIntegerField(
blank=True, help_text="Minimum age requirement in years (if any)", null=True
),
),
migrations.AddField(
model_name="rideevent",
name="ride_sub_type",
field=models.CharField(
blank=True,
help_text="Sub-category of ride (e.g., 'Flying Coaster', 'Inverted Coaster', 'Log Flume')",
max_length=100,
),
),
pgtrigger.migrations.AddTrigger(
model_name="ride",
trigger=pgtrigger.compiler.Trigger(
name="insert_insert",
sql=pgtrigger.compiler.UpsertTriggerSql(
func='INSERT INTO "rides_rideevent" ("age_requirement", "average_rating", "banner_image_id", "capacity_per_hour", "card_image_id", "category", "closing_date", "closing_date_precision", "created_at", "description", "designer_id", "id", "manufacturer_id", "max_height_in", "min_height_in", "name", "opening_date", "opening_date_precision", "opening_year", "park_area_id", "park_id", "park_url", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "post_closing_status", "ride_duration_seconds", "ride_model_id", "ride_sub_type", "search_text", "slug", "status", "status_since", "updated_at", "url") VALUES (NEW."age_requirement", NEW."average_rating", NEW."banner_image_id", NEW."capacity_per_hour", NEW."card_image_id", NEW."category", NEW."closing_date", NEW."closing_date_precision", NEW."created_at", NEW."description", NEW."designer_id", NEW."id", NEW."manufacturer_id", NEW."max_height_in", NEW."min_height_in", NEW."name", NEW."opening_date", NEW."opening_date_precision", NEW."opening_year", NEW."park_area_id", NEW."park_id", NEW."park_url", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."post_closing_status", NEW."ride_duration_seconds", NEW."ride_model_id", NEW."ride_sub_type", NEW."search_text", NEW."slug", NEW."status", NEW."status_since", NEW."updated_at", NEW."url"); RETURN NULL;',
hash="cd829a8030511234c62ed355a58c753d15d09df9",
operation="INSERT",
pgid="pgtrigger_insert_insert_52074",
table="rides_ride",
when="AFTER",
),
),
),
pgtrigger.migrations.AddTrigger(
model_name="ride",
trigger=pgtrigger.compiler.Trigger(
name="update_update",
sql=pgtrigger.compiler.UpsertTriggerSql(
condition="WHEN (OLD.* IS DISTINCT FROM NEW.*)",
func='INSERT INTO "rides_rideevent" ("age_requirement", "average_rating", "banner_image_id", "capacity_per_hour", "card_image_id", "category", "closing_date", "closing_date_precision", "created_at", "description", "designer_id", "id", "manufacturer_id", "max_height_in", "min_height_in", "name", "opening_date", "opening_date_precision", "opening_year", "park_area_id", "park_id", "park_url", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "post_closing_status", "ride_duration_seconds", "ride_model_id", "ride_sub_type", "search_text", "slug", "status", "status_since", "updated_at", "url") VALUES (NEW."age_requirement", NEW."average_rating", NEW."banner_image_id", NEW."capacity_per_hour", NEW."card_image_id", NEW."category", NEW."closing_date", NEW."closing_date_precision", NEW."created_at", NEW."description", NEW."designer_id", NEW."id", NEW."manufacturer_id", NEW."max_height_in", NEW."min_height_in", NEW."name", NEW."opening_date", NEW."opening_date_precision", NEW."opening_year", NEW."park_area_id", NEW."park_id", NEW."park_url", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."post_closing_status", NEW."ride_duration_seconds", NEW."ride_model_id", NEW."ride_sub_type", NEW."search_text", NEW."slug", NEW."status", NEW."status_since", NEW."updated_at", NEW."url"); RETURN NULL;',
hash="32d2f4a4547c7fd91e136ea0ac378f1b6bb8ef30",
operation="UPDATE",
pgid="pgtrigger_update_update_4917a",
table="rides_ride",
when="AFTER",
),
),
),
]

View File

@@ -12,19 +12,23 @@ from .company import Company
from .credits import RideCredit
from .location import RideLocation
from .media import RidePhoto
from .name_history import RideNameHistory
from .rankings import RankingSnapshot, RidePairComparison, RideRanking
from .reviews import RideReview
from .rides import Ride, RideModel, RollerCoasterStats
from .stats import DarkRideStats, FlatRideStats, WaterRideStats
from .stats import DarkRideStats, FlatRideStats, KiddieRideStats, TransportationStats, WaterRideStats
__all__ = [
# Primary models
"Ride",
"RideModel",
"RideNameHistory",
"RollerCoasterStats",
"WaterRideStats",
"DarkRideStats",
"FlatRideStats",
"KiddieRideStats",
"TransportationStats",
"Company",
"RideLocation",
"RideReview",
@@ -35,3 +39,4 @@ __all__ = [
"RidePairComparison",
"RankingSnapshot",
]

View File

@@ -0,0 +1,73 @@
"""
Ride Name History model for tracking historical ride names.
This model stores the history of name changes for rides, enabling display of
former names on ride detail pages.
"""
import pghistory
from django.db import models
from apps.core.models import TrackedModel
@pghistory.track()
class RideNameHistory(TrackedModel):
"""
Tracks historical names of rides.
When a ride is renamed, this model stores the previous name along with
the year range it was used and an optional reason for the change.
"""
ride = models.ForeignKey(
"rides.Ride",
on_delete=models.CASCADE,
related_name="former_names",
help_text="The ride this name history entry belongs to",
)
former_name = models.CharField(
max_length=200,
help_text="The previous name of the ride",
)
from_year = models.PositiveSmallIntegerField(
null=True,
blank=True,
help_text="Year the ride started using this name",
)
to_year = models.PositiveSmallIntegerField(
null=True,
blank=True,
help_text="Year the ride stopped using this name",
)
reason = models.CharField(
max_length=500,
blank=True,
help_text="Reason for the name change (e.g., 'Retheme to Peanuts')",
)
class Meta(TrackedModel.Meta):
verbose_name = "Ride Name History"
verbose_name_plural = "Ride Name Histories"
ordering = ["-to_year", "-from_year"]
indexes = [
models.Index(fields=["ride", "-to_year"]),
]
def __str__(self):
year_range = ""
if self.from_year and self.to_year:
year_range = f" ({self.from_year}-{self.to_year})"
elif self.to_year:
year_range = f" (until {self.to_year})"
elif self.from_year:
year_range = f" (from {self.from_year})"
return f"{self.former_name}{year_range}"
def clean(self):
from django.core.exceptions import ValidationError
if self.from_year and self.to_year and self.from_year > self.to_year:
raise ValidationError(
{"from_year": "From year cannot be after to year."}
)

View File

@@ -508,7 +508,21 @@ class Ride(StateMachineMixin, TrackedModel):
help_text="Status to change to after closing date",
)
opening_date = models.DateField(null=True, blank=True)
opening_date_precision = models.CharField(
max_length=10,
choices=[("YEAR", "Year"), ("MONTH", "Month"), ("DAY", "Day")],
default="DAY",
blank=True,
help_text="Precision of the opening date",
)
closing_date = models.DateField(null=True, blank=True)
closing_date_precision = models.CharField(
max_length=10,
choices=[("YEAR", "Year"), ("MONTH", "Month"), ("DAY", "Day")],
default="DAY",
blank=True,
help_text="Precision of the closing date",
)
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)
@@ -516,6 +530,18 @@ class Ride(StateMachineMixin, TrackedModel):
ride_duration_seconds = models.PositiveIntegerField(null=True, blank=True)
average_rating = models.DecimalField(max_digits=3, decimal_places=2, null=True, blank=True)
# Additional ride classification
ride_sub_type = models.CharField(
max_length=100,
blank=True,
help_text="Sub-category of ride (e.g., 'Flying Coaster', 'Inverted Coaster', 'Log Flume')",
)
age_requirement = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Minimum age requirement in years (if any)",
)
# Computed fields for hybrid filtering
opening_year = models.IntegerField(null=True, blank=True, db_index=True)
search_text = models.TextField(blank=True, db_index=True)
@@ -680,6 +706,14 @@ class Ride(StateMachineMixin, TrackedModel):
self.save()
@property
def is_closing(self) -> bool:
"""Returns True if this ride has a closing date in the future (announced closure)."""
from django.utils import timezone
if self.closing_date:
return self.closing_date > timezone.now().date()
return False
def save(self, *args, **kwargs) -> None:
# Handle slug generation and conflicts
if not self.slug:

View File

@@ -224,3 +224,152 @@ class FlatRideStats(TrackedModel):
def __str__(self) -> str:
return f"Flat Ride Stats for {self.ride.name}"
# Transport Type Choices for Transportation Rides
TRANSPORT_TYPES = [
("TRAIN", "Train"),
("MONORAIL", "Monorail"),
("SKYLIFT", "Skylift / Chairlift"),
("FERRY", "Ferry / Boat"),
("PEOPLEMOVER", "PeopleMover"),
("CABLE_CAR", "Cable Car"),
("TRAM", "Tram"),
]
@pghistory.track()
class KiddieRideStats(TrackedModel):
"""
Statistics specific to kiddie rides (category=KR).
Tracks age-appropriate ride characteristics and theming.
"""
ride = models.OneToOneField(
"rides.Ride",
on_delete=models.CASCADE,
related_name="kiddie_stats",
help_text="Ride these kiddie ride statistics belong to",
)
min_age = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Minimum recommended age in years",
)
max_age = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Maximum recommended age in years",
)
educational_theme = models.CharField(
max_length=200,
blank=True,
help_text="Educational theme if applicable (e.g., 'Dinosaurs', 'Space')",
)
character_theme = models.CharField(
max_length=200,
blank=True,
help_text="Character theme if applicable (e.g., 'Paw Patrol', 'Peppa Pig')",
)
guardian_required = models.BooleanField(
default=False,
help_text="Whether a guardian must be present during the ride",
)
adult_ride_along = models.BooleanField(
default=True,
help_text="Whether adults can ride along with children",
)
seats_per_vehicle = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Number of seats per ride vehicle",
)
class Meta(TrackedModel.Meta):
verbose_name = "Kiddie Ride Statistics"
verbose_name_plural = "Kiddie Ride Statistics"
ordering = ["ride"]
def __str__(self) -> str:
return f"Kiddie Ride Stats for {self.ride.name}"
@pghistory.track()
class TransportationStats(TrackedModel):
"""
Statistics specific to transportation rides (category=TR).
Tracks route, capacity, and vehicle information.
"""
ride = models.OneToOneField(
"rides.Ride",
on_delete=models.CASCADE,
related_name="transport_stats",
help_text="Ride these transportation statistics belong to",
)
transport_type = models.CharField(
max_length=20,
choices=TRANSPORT_TYPES,
default="TRAIN",
help_text="Type of transportation",
)
route_length_ft = models.DecimalField(
max_digits=8,
decimal_places=2,
null=True,
blank=True,
help_text="Total route length in feet",
)
stations_count = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Number of stations/stops on the route",
)
vehicle_capacity = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Passenger capacity per vehicle",
)
vehicles_count = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Total number of vehicles in operation",
)
round_trip_duration_minutes = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Duration of a complete round trip in minutes",
)
scenic_highlights = models.TextField(
blank=True,
help_text="Notable scenic views or attractions along the route",
)
is_one_way = models.BooleanField(
default=False,
help_text="Whether this is a one-way transportation (vs round-trip)",
)
class Meta(TrackedModel.Meta):
verbose_name = "Transportation Statistics"
verbose_name_plural = "Transportation Statistics"
ordering = ["ride"]
def __str__(self) -> str:
return f"Transportation Stats for {self.ride.name}"

View File

@@ -235,3 +235,46 @@ def update_ride_search_text_on_ride_model_change(sender, instance, **kwargs):
update_ride_search_text(ride)
except Exception as e:
logger.exception(f"Failed to update ride search_text on ride model change: {e}")
# =============================================================================
# Automatic Name History Tracking
# =============================================================================
@receiver(pre_save, sender=Ride)
def track_ride_name_changes(sender, instance, **kwargs):
"""
Automatically create RideNameHistory when a ride's name changes.
This ensures versioning is automatic - when a ride is renamed,
the previous name is preserved in the name history.
"""
if not instance.pk:
return # Skip new rides
try:
old_instance = Ride.objects.get(pk=instance.pk)
if old_instance.name != instance.name:
from .models import RideNameHistory
current_year = timezone.now().year
# Create history entry for the old name
RideNameHistory.objects.create(
ride=instance,
former_name=old_instance.name,
to_year=current_year,
reason="Name changed",
)
logger.info(
f"Ride {instance.pk} name changed from '{old_instance.name}' "
f"to '{instance.name}' - history entry created"
)
except Ride.DoesNotExist:
pass # New ride, no history to track
except Exception as e:
logger.exception(f"Failed to track name change for ride {instance.pk}: {e}")