This commit is contained in:
pacnpal
2026-01-08 13:44:37 -05:00
parent 40cba5bdb2
commit fe960e8b62
21 changed files with 2008 additions and 59 deletions

View File

@@ -3,9 +3,15 @@ Core API URL configuration.
Centralized from apps.core.urls
"""
from django.urls import path
from django.urls import include, path
from rest_framework.routers import DefaultRouter
from . import views
from apps.core.api.milestone_views import MilestoneViewSet
# Create router for viewsets
router = DefaultRouter()
router.register(r"milestones", MilestoneViewSet, basename="milestone")
# Entity search endpoints - migrated from apps.core.urls
urlpatterns = [
@@ -30,4 +36,7 @@ urlpatterns = [
views.TelemetryView.as_view(),
name="telemetry",
),
# Include router URLs (milestones, etc.)
path("", include(router.urls)),
]

View File

@@ -0,0 +1,93 @@
"""
Milestone serializers for timeline events.
"""
from rest_framework import serializers
from apps.core.models import Milestone
class MilestoneSerializer(serializers.ModelSerializer):
"""Serializer for Milestone model matching frontend milestoneValidationSchema."""
class Meta:
model = Milestone
fields = [
"id",
"title",
"description",
"event_type",
"event_date",
"event_date_precision",
"entity_type",
"entity_id",
"is_public",
"display_order",
"from_value",
"to_value",
"from_entity_id",
"to_entity_id",
"from_location_id",
"to_location_id",
"created_at",
"updated_at",
]
read_only_fields = ["id", "created_at", "updated_at"]
class MilestoneCreateSerializer(serializers.ModelSerializer):
"""Serializer for creating milestones."""
class Meta:
model = Milestone
fields = [
"title",
"description",
"event_type",
"event_date",
"event_date_precision",
"entity_type",
"entity_id",
"is_public",
"display_order",
"from_value",
"to_value",
"from_entity_id",
"to_entity_id",
"from_location_id",
"to_location_id",
]
def validate(self, attrs):
"""Validate change events have from/to values."""
change_events = ["name_change", "operator_change", "owner_change", "location_change", "status_change"]
if attrs.get("event_type") in change_events:
has_change_data = (
attrs.get("from_value")
or attrs.get("to_value")
or attrs.get("from_entity_id")
or attrs.get("to_entity_id")
or attrs.get("from_location_id")
or attrs.get("to_location_id")
)
if not has_change_data:
raise serializers.ValidationError(
"Change events must specify what changed (from/to values or entity IDs)"
)
return attrs
class MilestoneListSerializer(serializers.ModelSerializer):
"""Lightweight serializer for listing milestones."""
class Meta:
model = Milestone
fields = [
"id",
"title",
"event_type",
"event_date",
"entity_type",
"entity_id",
"is_public",
]

View File

@@ -0,0 +1,79 @@
"""
Milestone views for timeline events.
"""
from django_filters import rest_framework as filters
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly
from rest_framework.response import Response
from apps.core.models import Milestone
from .milestone_serializers import (
MilestoneCreateSerializer,
MilestoneListSerializer,
MilestoneSerializer,
)
class MilestoneFilter(filters.FilterSet):
"""Filters for milestone listing."""
entity_type = filters.CharFilter(field_name="entity_type")
entity_id = filters.UUIDFilter(field_name="entity_id")
event_type = filters.CharFilter(field_name="event_type")
is_public = filters.BooleanFilter(field_name="is_public")
event_date_after = filters.DateFilter(field_name="event_date", lookup_expr="gte")
event_date_before = filters.DateFilter(field_name="event_date", lookup_expr="lte")
class Meta:
model = Milestone
fields = ["entity_type", "entity_id", "event_type", "is_public"]
class MilestoneViewSet(viewsets.ModelViewSet):
"""
ViewSet for managing milestones/timeline events.
Supports filtering by entity_type, entity_id, event_type, and date range.
"""
queryset = Milestone.objects.all()
filterset_class = MilestoneFilter
permission_classes = [IsAuthenticatedOrReadOnly]
def get_serializer_class(self):
if self.action == "list":
return MilestoneListSerializer
if self.action == "create":
return MilestoneCreateSerializer
return MilestoneSerializer
def get_queryset(self):
"""Filter queryset based on visibility."""
queryset = super().get_queryset()
# Non-authenticated users only see public milestones
if not self.request.user.is_authenticated:
queryset = queryset.filter(is_public=True)
return queryset.order_by("-event_date", "display_order")
@action(detail=False, methods=["get"], url_path="entity/(?P<entity_type>[^/]+)/(?P<entity_id>[^/]+)")
def by_entity(self, request, entity_type=None, entity_id=None):
"""Get all milestones for a specific entity."""
queryset = self.get_queryset().filter(
entity_type=entity_type,
entity_id=entity_id,
)
serializer = MilestoneListSerializer(queryset, many=True)
return Response(serializer.data)
@action(detail=False, methods=["get"], url_path="timeline")
def timeline(self, request):
"""Get a unified timeline view of recent milestones across all entities."""
limit = int(request.query_params.get("limit", 50))
queryset = self.get_queryset()[:limit]
serializer = MilestoneListSerializer(queryset, many=True)
return Response(serializer.data)

View File

@@ -0,0 +1,94 @@
# Generated by Django 5.2.9 on 2026-01-08 17:59
import django.db.models.deletion
import pgtrigger.compiler
import pgtrigger.migrations
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0009_pageview_pageviewevent_and_more'),
('pghistory', '0007_auto_20250421_0444'),
]
operations = [
migrations.CreateModel(
name='MilestoneEvent',
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)),
('title', models.CharField(help_text='Title or name of the event', max_length=200)),
('description', models.TextField(blank=True, help_text='Detailed description of the event')),
('event_type', models.CharField(help_text="Type of event (e.g., 'opening', 'closing', 'name_change', 'status_change')", max_length=50)),
('event_date', models.DateField(help_text='Date when the event occurred or will occur')),
('event_date_precision', models.CharField(choices=[('exact', 'Exact Date'), ('month', 'Month and Year'), ('year', 'Year Only'), ('decade', 'Decade'), ('century', 'Century'), ('approximate', 'Approximate')], default='exact', help_text='Precision of the event date', max_length=20)),
('entity_type', models.CharField(help_text="Type of entity (e.g., 'park', 'ride', 'company')", max_length=50)),
('entity_id', models.UUIDField(help_text='UUID of the associated entity')),
('is_public', models.BooleanField(default=True, help_text='Whether this milestone is publicly visible')),
('display_order', models.IntegerField(default=0, help_text='Order for displaying multiple milestones on the same date')),
('from_value', models.CharField(blank=True, help_text='Previous value (for change events)', max_length=200)),
('to_value', models.CharField(blank=True, help_text='New value (for change events)', max_length=200)),
('from_entity_id', models.UUIDField(blank=True, help_text='Previous entity reference (e.g., old operator)', null=True)),
('to_entity_id', models.UUIDField(blank=True, help_text='New entity reference (e.g., new operator)', null=True)),
('from_location_id', models.UUIDField(blank=True, help_text='Previous location reference (for relocations)', null=True)),
('to_location_id', models.UUIDField(blank=True, help_text='New location reference (for relocations)', null=True)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Milestone',
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)),
('title', models.CharField(help_text='Title or name of the event', max_length=200)),
('description', models.TextField(blank=True, help_text='Detailed description of the event')),
('event_type', models.CharField(db_index=True, help_text="Type of event (e.g., 'opening', 'closing', 'name_change', 'status_change')", max_length=50)),
('event_date', models.DateField(db_index=True, help_text='Date when the event occurred or will occur')),
('event_date_precision', models.CharField(choices=[('exact', 'Exact Date'), ('month', 'Month and Year'), ('year', 'Year Only'), ('decade', 'Decade'), ('century', 'Century'), ('approximate', 'Approximate')], default='exact', help_text='Precision of the event date', max_length=20)),
('entity_type', models.CharField(db_index=True, help_text="Type of entity (e.g., 'park', 'ride', 'company')", max_length=50)),
('entity_id', models.UUIDField(db_index=True, help_text='UUID of the associated entity')),
('is_public', models.BooleanField(default=True, help_text='Whether this milestone is publicly visible')),
('display_order', models.IntegerField(default=0, help_text='Order for displaying multiple milestones on the same date')),
('from_value', models.CharField(blank=True, help_text='Previous value (for change events)', max_length=200)),
('to_value', models.CharField(blank=True, help_text='New value (for change events)', max_length=200)),
('from_entity_id', models.UUIDField(blank=True, help_text='Previous entity reference (e.g., old operator)', null=True)),
('to_entity_id', models.UUIDField(blank=True, help_text='New entity reference (e.g., new operator)', null=True)),
('from_location_id', models.UUIDField(blank=True, help_text='Previous location reference (for relocations)', null=True)),
('to_location_id', models.UUIDField(blank=True, help_text='New location reference (for relocations)', null=True)),
],
options={
'verbose_name': 'Milestone',
'verbose_name_plural': 'Milestones',
'ordering': ['-event_date', 'display_order'],
'abstract': False,
'indexes': [models.Index(fields=['entity_type', 'entity_id'], name='core_milest_entity__effdde_idx'), models.Index(fields=['event_type', 'event_date'], name='core_milest_event_t_0070b8_idx'), models.Index(fields=['is_public', 'event_date'], name='core_milest_is_publ_2ce98c_idx')],
},
),
pgtrigger.migrations.AddTrigger(
model_name='milestone',
trigger=pgtrigger.compiler.Trigger(name='insert_insert', sql=pgtrigger.compiler.UpsertTriggerSql(func='INSERT INTO "core_milestoneevent" ("created_at", "description", "display_order", "entity_id", "entity_type", "event_date", "event_date_precision", "event_type", "from_entity_id", "from_location_id", "from_value", "id", "is_public", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "title", "to_entity_id", "to_location_id", "to_value", "updated_at") VALUES (NEW."created_at", NEW."description", NEW."display_order", NEW."entity_id", NEW."entity_type", NEW."event_date", NEW."event_date_precision", NEW."event_type", NEW."from_entity_id", NEW."from_location_id", NEW."from_value", NEW."id", NEW."is_public", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."title", NEW."to_entity_id", NEW."to_location_id", NEW."to_value", NEW."updated_at"); RETURN NULL;', hash='6c4386ed0356cf9a3db65c829163401409e79622', operation='INSERT', pgid='pgtrigger_insert_insert_52c81', table='core_milestone', when='AFTER')),
),
pgtrigger.migrations.AddTrigger(
model_name='milestone',
trigger=pgtrigger.compiler.Trigger(name='update_update', sql=pgtrigger.compiler.UpsertTriggerSql(condition='WHEN (OLD.* IS DISTINCT FROM NEW.*)', func='INSERT INTO "core_milestoneevent" ("created_at", "description", "display_order", "entity_id", "entity_type", "event_date", "event_date_precision", "event_type", "from_entity_id", "from_location_id", "from_value", "id", "is_public", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "title", "to_entity_id", "to_location_id", "to_value", "updated_at") VALUES (NEW."created_at", NEW."description", NEW."display_order", NEW."entity_id", NEW."entity_type", NEW."event_date", NEW."event_date_precision", NEW."event_type", NEW."from_entity_id", NEW."from_location_id", NEW."from_value", NEW."id", NEW."is_public", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."title", NEW."to_entity_id", NEW."to_location_id", NEW."to_value", NEW."updated_at"); RETURN NULL;', hash='fafe30b7266d1d1a0a2b3486f5b7e713a8252f97', operation='UPDATE', pgid='pgtrigger_update_update_0209b', table='core_milestone', when='AFTER')),
),
migrations.AddField(
model_name='milestoneevent',
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='milestoneevent',
name='pgh_obj',
field=models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, related_name='events', to='core.milestone'),
),
]

View File

@@ -1049,3 +1049,115 @@ class ApprovalTransactionMetric(models.Model):
status = "" if self.success else ""
return f"{status} Submission {self.submission_id[:8]} by {self.moderator_id[:8]}"
@pghistory.track()
class Milestone(TrackedModel):
"""
Timeline event / milestone for any entity.
Supports various event types like openings, closures, name changes,
operator changes, and other significant events. Uses a generic
entity reference pattern to work with Parks, Rides, Companies, etc.
Maps to frontend milestoneValidationSchema in entityValidationSchemas.ts
"""
class DatePrecision(models.TextChoices):
EXACT = "exact", "Exact Date"
MONTH = "month", "Month and Year"
YEAR = "year", "Year Only"
DECADE = "decade", "Decade"
CENTURY = "century", "Century"
APPROXIMATE = "approximate", "Approximate"
# Core event information
title = models.CharField(
max_length=200,
help_text="Title or name of the event",
)
description = models.TextField(
blank=True,
help_text="Detailed description of the event",
)
event_type = models.CharField(
max_length=50,
db_index=True,
help_text="Type of event (e.g., 'opening', 'closing', 'name_change', 'status_change')",
)
event_date = models.DateField(
db_index=True,
help_text="Date when the event occurred or will occur",
)
event_date_precision = models.CharField(
max_length=20,
choices=DatePrecision.choices,
default=DatePrecision.EXACT,
help_text="Precision of the event date",
)
# Generic entity reference
entity_type = models.CharField(
max_length=50,
db_index=True,
help_text="Type of entity (e.g., 'park', 'ride', 'company')",
)
entity_id = models.UUIDField(
db_index=True,
help_text="UUID of the associated entity",
)
# Display settings
is_public = models.BooleanField(
default=True,
help_text="Whether this milestone is publicly visible",
)
display_order = models.IntegerField(
default=0,
help_text="Order for displaying multiple milestones on the same date",
)
# Change tracking fields (for name_change, operator_change, etc.)
from_value = models.CharField(
max_length=200,
blank=True,
help_text="Previous value (for change events)",
)
to_value = models.CharField(
max_length=200,
blank=True,
help_text="New value (for change events)",
)
from_entity_id = models.UUIDField(
null=True,
blank=True,
help_text="Previous entity reference (e.g., old operator)",
)
to_entity_id = models.UUIDField(
null=True,
blank=True,
help_text="New entity reference (e.g., new operator)",
)
from_location_id = models.UUIDField(
null=True,
blank=True,
help_text="Previous location reference (for relocations)",
)
to_location_id = models.UUIDField(
null=True,
blank=True,
help_text="New location reference (for relocations)",
)
class Meta(TrackedModel.Meta):
ordering = ["-event_date", "display_order"]
verbose_name = "Milestone"
verbose_name_plural = "Milestones"
indexes = [
models.Index(fields=["entity_type", "entity_id"]),
models.Index(fields=["event_type", "event_date"]),
models.Index(fields=["is_public", "event_date"]),
]
def __str__(self) -> str:
return f"{self.title} ({self.event_date})"

View File

@@ -864,12 +864,13 @@ class PhotoSubmission(StateMachineMixin, TrackedModel):
self.save()
def auto_approve(self) -> None:
"""Auto - approve submissions from moderators"""
"""Auto-approve submissions from moderators."""
# Get user role safely
user_role = getattr(self.user, "role", None)
# If user is moderator or above, auto-approve
# If user is moderator or above, claim then approve
if user_role in ["MODERATOR", "ADMIN", "SUPERUSER"]:
self.claim(user=self.user)
self.approve(self.user)
def escalate(self, moderator: UserType = None, notes: str = "", user=None) -> None:

View File

@@ -1718,6 +1718,148 @@ class EditSubmissionViewSet(viewsets.ModelViewSet):
except Exception as e:
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
@action(detail=False, methods=["post"], permission_classes=[IsModeratorOrAdmin], url_path="release-expired")
def release_expired_locks(self, request):
"""
Release all expired claim locks.
This is typically handled by a Celery task, but can be triggered manually.
Claims are expired after 30 minutes by default.
"""
from datetime import timedelta
expiry_threshold = timezone.now() - timedelta(minutes=30)
expired_claims = EditSubmission.objects.filter(
status="CLAIMED",
claimed_at__lt=expiry_threshold
)
released_count = 0
for submission in expired_claims:
submission.status = "PENDING"
submission.claimed_by = None
submission.claimed_at = None
submission.save(update_fields=["status", "claimed_by", "claimed_at"])
released_count += 1
return Response({
"released_count": released_count,
"message": f"Released {released_count} expired lock(s)"
})
@action(detail=True, methods=["post"], permission_classes=[IsAdminOrSuperuser], url_path="admin-release")
def admin_release(self, request, pk=None):
"""
Admin/superuser force release of a specific claim.
"""
submission = self.get_object()
if submission.status != "CLAIMED":
return Response(
{"error": "Submission is not claimed"},
status=status.HTTP_400_BAD_REQUEST
)
submission.status = "PENDING"
submission.claimed_by = None
submission.claimed_at = None
submission.save(update_fields=["status", "claimed_by", "claimed_at"])
return Response({
"success": True,
"message": f"Lock released on submission {submission.id}"
})
@action(detail=False, methods=["post"], permission_classes=[IsAdminOrSuperuser], url_path="admin-release-all")
def admin_release_all(self, request):
"""
Admin/superuser force release of all active claims.
"""
claimed_submissions = EditSubmission.objects.filter(status="CLAIMED")
released_count = 0
for submission in claimed_submissions:
submission.status = "PENDING"
submission.claimed_by = None
submission.claimed_at = None
submission.save(update_fields=["status", "claimed_by", "claimed_at"])
released_count += 1
return Response({
"released_count": released_count,
"message": f"Released all {released_count} active lock(s)"
})
@action(detail=True, methods=["post"], permission_classes=[IsModeratorOrAdmin], url_path="reassign")
def reassign(self, request, pk=None):
"""
Reassign a submission to a different moderator.
Only admins can reassign submissions claimed by other moderators.
The submission must be in CLAIMED status.
"""
submission = self.get_object()
new_moderator_id = request.data.get("new_moderator_id")
if not new_moderator_id:
return Response(
{"error": "new_moderator_id is required"},
status=status.HTTP_400_BAD_REQUEST
)
try:
new_moderator = User.objects.get(pk=new_moderator_id)
except User.DoesNotExist:
return Response(
{"error": "Moderator not found"},
status=status.HTTP_404_NOT_FOUND
)
# Check moderator permissions
if new_moderator.role not in ["MODERATOR", "ADMIN", "SUPERUSER"]:
return Response(
{"error": "User is not a moderator"},
status=status.HTTP_400_BAD_REQUEST
)
# Update the claim
submission.claimed_by = new_moderator
submission.claimed_at = timezone.now()
submission.save(update_fields=["claimed_by", "claimed_at"])
return Response({
"success": True,
"message": f"Submission reassigned to {new_moderator.username}"
})
@action(detail=False, methods=["post"], permission_classes=[IsModeratorOrAdmin], url_path="audit-log")
def log_admin_action(self, request):
"""
Log an admin action for audit trail.
This creates an audit log entry for moderator actions.
"""
action_type = request.data.get("action_type", "")
action_details = request.data.get("action_details", {})
target_entity = request.data.get("target_entity", {})
# Create audit log entry
logger.info(
f"[AdminAction] User {request.user.username} - {action_type}",
extra={
"user_id": request.user.id,
"action_type": action_type,
"action_details": action_details,
"target_entity": target_entity,
}
)
return Response({
"success": True,
"message": "Action logged successfully"
})
@action(detail=False, methods=["get"], permission_classes=[IsModeratorOrAdmin], url_path="my-active-claim")
def my_active_claim(self, request):
"""

View File

@@ -0,0 +1,117 @@
# Generated by Django 5.2.9 on 2026-01-08 18:05
import pgtrigger.compiler
import pgtrigger.migrations
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('parks', '0028_add_date_precision_fields'),
]
operations = [
pgtrigger.migrations.RemoveTrigger(
model_name='company',
name='insert_insert',
),
pgtrigger.migrations.RemoveTrigger(
model_name='company',
name='update_update',
),
pgtrigger.migrations.RemoveTrigger(
model_name='park',
name='insert_insert',
),
pgtrigger.migrations.RemoveTrigger(
model_name='park',
name='update_update',
),
migrations.AddField(
model_name='company',
name='is_test_data',
field=models.BooleanField(default=False, help_text='Whether this is test/development data'),
),
migrations.AddField(
model_name='company',
name='source_url',
field=models.URLField(blank=True, help_text='Source URL for the data (e.g., official website, Wikipedia)'),
),
migrations.AddField(
model_name='companyevent',
name='is_test_data',
field=models.BooleanField(default=False, help_text='Whether this is test/development data'),
),
migrations.AddField(
model_name='companyevent',
name='source_url',
field=models.URLField(blank=True, help_text='Source URL for the data (e.g., official website, Wikipedia)'),
),
migrations.AddField(
model_name='park',
name='is_test_data',
field=models.BooleanField(default=False, help_text='Whether this is test/development data'),
),
migrations.AddField(
model_name='park',
name='source_url',
field=models.URLField(blank=True, help_text='Source URL for the data (e.g., official website, Wikipedia)'),
),
migrations.AddField(
model_name='parkevent',
name='is_test_data',
field=models.BooleanField(default=False, help_text='Whether this is test/development data'),
),
migrations.AddField(
model_name='parkevent',
name='source_url',
field=models.URLField(blank=True, help_text='Source URL for the data (e.g., official website, Wikipedia)'),
),
migrations.AlterField(
model_name='company',
name='founded_date_precision',
field=models.CharField(blank=True, choices=[('exact', 'Exact Date'), ('month', 'Month and Year'), ('year', 'Year Only'), ('decade', 'Decade'), ('century', 'Century'), ('approximate', 'Approximate')], help_text='Precision of the founding date', max_length=20),
),
migrations.AlterField(
model_name='companyevent',
name='founded_date_precision',
field=models.CharField(blank=True, choices=[('exact', 'Exact Date'), ('month', 'Month and Year'), ('year', 'Year Only'), ('decade', 'Decade'), ('century', 'Century'), ('approximate', 'Approximate')], help_text='Precision of the founding date', max_length=20),
),
migrations.AlterField(
model_name='park',
name='closing_date_precision',
field=models.CharField(blank=True, choices=[('exact', 'Exact Date'), ('month', 'Month and Year'), ('year', 'Year Only'), ('decade', 'Decade'), ('century', 'Century'), ('approximate', 'Approximate')], default='exact', help_text='Precision of the closing date', max_length=20),
),
migrations.AlterField(
model_name='park',
name='opening_date_precision',
field=models.CharField(blank=True, choices=[('exact', 'Exact Date'), ('month', 'Month and Year'), ('year', 'Year Only'), ('decade', 'Decade'), ('century', 'Century'), ('approximate', 'Approximate')], default='exact', help_text='Precision of the opening date', max_length=20),
),
migrations.AlterField(
model_name='parkevent',
name='closing_date_precision',
field=models.CharField(blank=True, choices=[('exact', 'Exact Date'), ('month', 'Month and Year'), ('year', 'Year Only'), ('decade', 'Decade'), ('century', 'Century'), ('approximate', 'Approximate')], default='exact', help_text='Precision of the closing date', max_length=20),
),
migrations.AlterField(
model_name='parkevent',
name='opening_date_precision',
field=models.CharField(blank=True, choices=[('exact', 'Exact Date'), ('month', 'Month and Year'), ('year', 'Year Only'), ('decade', 'Decade'), ('century', 'Century'), ('approximate', 'Approximate')], default='exact', help_text='Precision of the opening date', max_length=20),
),
pgtrigger.migrations.AddTrigger(
model_name='company',
trigger=pgtrigger.compiler.Trigger(name='insert_insert', sql=pgtrigger.compiler.UpsertTriggerSql(func='INSERT INTO "parks_companyevent" ("average_rating", "banner_image_url", "card_image_url", "created_at", "description", "founded_date", "founded_date_precision", "founded_year", "id", "is_test_data", "logo_url", "name", "parks_count", "person_type", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "review_count", "rides_count", "roles", "slug", "source_url", "status", "updated_at", "website") VALUES (NEW."average_rating", NEW."banner_image_url", NEW."card_image_url", NEW."created_at", NEW."description", NEW."founded_date", NEW."founded_date_precision", NEW."founded_year", NEW."id", NEW."is_test_data", NEW."logo_url", NEW."name", NEW."parks_count", NEW."person_type", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."review_count", NEW."rides_count", NEW."roles", NEW."slug", NEW."source_url", NEW."status", NEW."updated_at", NEW."website"); RETURN NULL;', hash='8352ecabfefc26dab2c91be68a9e137a1e48cbd2', operation='INSERT', pgid='pgtrigger_insert_insert_35b57', table='parks_company', when='AFTER')),
),
pgtrigger.migrations.AddTrigger(
model_name='company',
trigger=pgtrigger.compiler.Trigger(name='update_update', sql=pgtrigger.compiler.UpsertTriggerSql(condition='WHEN (OLD.* IS DISTINCT FROM NEW.*)', func='INSERT INTO "parks_companyevent" ("average_rating", "banner_image_url", "card_image_url", "created_at", "description", "founded_date", "founded_date_precision", "founded_year", "id", "is_test_data", "logo_url", "name", "parks_count", "person_type", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "review_count", "rides_count", "roles", "slug", "source_url", "status", "updated_at", "website") VALUES (NEW."average_rating", NEW."banner_image_url", NEW."card_image_url", NEW."created_at", NEW."description", NEW."founded_date", NEW."founded_date_precision", NEW."founded_year", NEW."id", NEW."is_test_data", NEW."logo_url", NEW."name", NEW."parks_count", NEW."person_type", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."review_count", NEW."rides_count", NEW."roles", NEW."slug", NEW."source_url", NEW."status", NEW."updated_at", NEW."website"); RETURN NULL;', hash='5d8b399ed7573fa0d5411042902c0a494785e071', operation='UPDATE', pgid='pgtrigger_update_update_d3286', table='parks_company', when='AFTER')),
),
pgtrigger.migrations.AddTrigger(
model_name='park',
trigger=pgtrigger.compiler.Trigger(name='insert_insert', sql=pgtrigger.compiler.UpsertTriggerSql(func='INSERT INTO "parks_parkevent" ("average_rating", "banner_image_id", "card_image_id", "closing_date", "closing_date_precision", "coaster_count", "created_at", "description", "email", "id", "is_test_data", "name", "opening_date", "opening_date_precision", "opening_year", "operating_season", "operator_id", "park_type", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "phone", "property_owner_id", "ride_count", "search_text", "size_acres", "slug", "source_url", "status", "timezone", "updated_at", "url", "website") VALUES (NEW."average_rating", NEW."banner_image_id", NEW."card_image_id", NEW."closing_date", NEW."closing_date_precision", NEW."coaster_count", NEW."created_at", NEW."description", NEW."email", NEW."id", NEW."is_test_data", NEW."name", NEW."opening_date", NEW."opening_date_precision", NEW."opening_year", NEW."operating_season", NEW."operator_id", NEW."park_type", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."phone", NEW."property_owner_id", NEW."ride_count", NEW."search_text", NEW."size_acres", NEW."slug", NEW."source_url", NEW."status", NEW."timezone", NEW."updated_at", NEW."url", NEW."website"); RETURN NULL;', hash='cb0e4e056880e2e6febc5a0905a437e56dab89de', operation='INSERT', pgid='pgtrigger_insert_insert_66883', table='parks_park', when='AFTER')),
),
pgtrigger.migrations.AddTrigger(
model_name='park',
trigger=pgtrigger.compiler.Trigger(name='update_update', sql=pgtrigger.compiler.UpsertTriggerSql(condition='WHEN (OLD.* IS DISTINCT FROM NEW.*)', func='INSERT INTO "parks_parkevent" ("average_rating", "banner_image_id", "card_image_id", "closing_date", "closing_date_precision", "coaster_count", "created_at", "description", "email", "id", "is_test_data", "name", "opening_date", "opening_date_precision", "opening_year", "operating_season", "operator_id", "park_type", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "phone", "property_owner_id", "ride_count", "search_text", "size_acres", "slug", "source_url", "status", "timezone", "updated_at", "url", "website") VALUES (NEW."average_rating", NEW."banner_image_id", NEW."card_image_id", NEW."closing_date", NEW."closing_date_precision", NEW."coaster_count", NEW."created_at", NEW."description", NEW."email", NEW."id", NEW."is_test_data", NEW."name", NEW."opening_date", NEW."opening_date_precision", NEW."opening_year", NEW."operating_season", NEW."operator_id", NEW."park_type", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."phone", NEW."property_owner_id", NEW."ride_count", NEW."search_text", NEW."size_acres", NEW."slug", NEW."source_url", NEW."status", NEW."timezone", NEW."updated_at", NEW."url", NEW."website"); RETURN NULL;', hash='dd10d0b79ed3bf1caca8d4ffb520cd0be298bc0d', operation='UPDATE', pgid='pgtrigger_update_update_19f56', table='parks_park', when='AFTER')),
),
]

View File

@@ -0,0 +1,72 @@
# Generated by Django 5.2.9 on 2026-01-08 18:20
import django.db.models.deletion
import pgtrigger.compiler
import pgtrigger.migrations
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('parks', '0029_add_source_url_is_test_data_and_date_precision'),
]
operations = [
pgtrigger.migrations.RemoveTrigger(
model_name='company',
name='insert_insert',
),
pgtrigger.migrations.RemoveTrigger(
model_name='company',
name='update_update',
),
migrations.AddField(
model_name='company',
name='banner_image_id',
field=models.CharField(blank=True, help_text='Cloudflare image ID for banner image', max_length=255),
),
migrations.AddField(
model_name='company',
name='card_image_id',
field=models.CharField(blank=True, help_text='Cloudflare image ID for card image', max_length=255),
),
migrations.AddField(
model_name='company',
name='headquarters_location',
field=models.CharField(blank=True, help_text="Headquarters location description (e.g., 'Los Angeles, CA, USA')", max_length=200),
),
migrations.AddField(
model_name='company',
name='location',
field=models.ForeignKey(blank=True, help_text='Linked location record for headquarters', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='companies_hq', to='parks.parklocation'),
),
migrations.AddField(
model_name='companyevent',
name='banner_image_id',
field=models.CharField(blank=True, help_text='Cloudflare image ID for banner image', max_length=255),
),
migrations.AddField(
model_name='companyevent',
name='card_image_id',
field=models.CharField(blank=True, help_text='Cloudflare image ID for card image', max_length=255),
),
migrations.AddField(
model_name='companyevent',
name='headquarters_location',
field=models.CharField(blank=True, help_text="Headquarters location description (e.g., 'Los Angeles, CA, USA')", max_length=200),
),
migrations.AddField(
model_name='companyevent',
name='location',
field=models.ForeignKey(blank=True, db_constraint=False, help_text='Linked location record for headquarters', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', related_query_name='+', to='parks.parklocation'),
),
pgtrigger.migrations.AddTrigger(
model_name='company',
trigger=pgtrigger.compiler.Trigger(name='insert_insert', sql=pgtrigger.compiler.UpsertTriggerSql(func='INSERT INTO "parks_companyevent" ("average_rating", "banner_image_id", "banner_image_url", "card_image_id", "card_image_url", "created_at", "description", "founded_date", "founded_date_precision", "founded_year", "headquarters_location", "id", "is_test_data", "location_id", "logo_url", "name", "parks_count", "person_type", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "review_count", "rides_count", "roles", "slug", "source_url", "status", "updated_at", "website") VALUES (NEW."average_rating", NEW."banner_image_id", NEW."banner_image_url", NEW."card_image_id", NEW."card_image_url", NEW."created_at", NEW."description", NEW."founded_date", NEW."founded_date_precision", NEW."founded_year", NEW."headquarters_location", NEW."id", NEW."is_test_data", NEW."location_id", NEW."logo_url", NEW."name", NEW."parks_count", NEW."person_type", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."review_count", NEW."rides_count", NEW."roles", NEW."slug", NEW."source_url", NEW."status", NEW."updated_at", NEW."website"); RETURN NULL;', hash='9e3f8a98696e2655ada53342a59b11a71bfa384c', operation='INSERT', pgid='pgtrigger_insert_insert_35b57', table='parks_company', when='AFTER')),
),
pgtrigger.migrations.AddTrigger(
model_name='company',
trigger=pgtrigger.compiler.Trigger(name='update_update', sql=pgtrigger.compiler.UpsertTriggerSql(condition='WHEN (OLD.* IS DISTINCT FROM NEW.*)', func='INSERT INTO "parks_companyevent" ("average_rating", "banner_image_id", "banner_image_url", "card_image_id", "card_image_url", "created_at", "description", "founded_date", "founded_date_precision", "founded_year", "headquarters_location", "id", "is_test_data", "location_id", "logo_url", "name", "parks_count", "person_type", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "review_count", "rides_count", "roles", "slug", "source_url", "status", "updated_at", "website") VALUES (NEW."average_rating", NEW."banner_image_id", NEW."banner_image_url", NEW."card_image_id", NEW."card_image_url", NEW."created_at", NEW."description", NEW."founded_date", NEW."founded_date_precision", NEW."founded_year", NEW."headquarters_location", NEW."id", NEW."is_test_data", NEW."location_id", NEW."logo_url", NEW."name", NEW."parks_count", NEW."person_type", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."review_count", NEW."rides_count", NEW."roles", NEW."slug", NEW."source_url", NEW."status", NEW."updated_at", NEW."website"); RETURN NULL;', hash='953a919e1969082370e189b0b47a2ce3fc9dafcf', operation='UPDATE', pgid='pgtrigger_update_update_d3286', table='parks_company', when='AFTER')),
),
]

View File

@@ -62,12 +62,15 @@ class Company(TrackedModel):
founded_year = models.PositiveIntegerField(blank=True, null=True, help_text="Year the company was founded")
founded_date = models.DateField(blank=True, null=True, help_text="Full founding date if known")
DATE_PRECISION_CHOICES = [
("YEAR", "Year only"),
("MONTH", "Month and year"),
("DAY", "Full date"),
("exact", "Exact Date"),
("month", "Month and Year"),
("year", "Year Only"),
("decade", "Decade"),
("century", "Century"),
("approximate", "Approximate"),
]
founded_date_precision = models.CharField(
max_length=10,
max_length=20,
choices=DATE_PRECISION_CHOICES,
blank=True,
help_text="Precision of the founding date",
@@ -78,6 +81,35 @@ class Company(TrackedModel):
banner_image_url = models.URLField(blank=True, help_text="Banner image for company page header")
card_image_url = models.URLField(blank=True, help_text="Card/thumbnail image for listings")
# Image ID fields (for frontend submissions - Cloudflare image IDs)
banner_image_id = models.CharField(
max_length=255,
blank=True,
help_text="Cloudflare image ID for banner image",
)
card_image_id = models.CharField(
max_length=255,
blank=True,
help_text="Cloudflare image ID for card image",
)
# Location relationship (for headquarters coordinates)
location = models.ForeignKey(
"ParkLocation",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="companies_hq",
help_text="Linked location record for headquarters",
)
# Text-based headquarters location (matches frontend schema)
headquarters_location = models.CharField(
max_length=200,
blank=True,
help_text="Headquarters location description (e.g., 'Los Angeles, CA, USA')",
)
# Rating & Review Aggregates (computed fields, updated by triggers/signals)
average_rating = models.DecimalField(
max_digits=3,
@@ -95,6 +127,16 @@ class Company(TrackedModel):
parks_count = models.IntegerField(default=0, help_text="Number of parks operated (auto-calculated)")
rides_count = models.IntegerField(default=0, help_text="Number of rides manufactured (auto-calculated)")
# Submission metadata fields (from frontend schema)
source_url = models.URLField(
blank=True,
help_text="Source URL for the data (e.g., official website, Wikipedia)",
)
is_test_data = models.BooleanField(
default=False,
help_text="Whether this is test/development data",
)
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.name)

View File

@@ -55,17 +55,31 @@ class Park(StateMachineMixin, TrackedModel):
# Details
opening_date = models.DateField(null=True, blank=True, help_text="Opening date")
opening_date_precision = models.CharField(
max_length=10,
choices=[("YEAR", "Year"), ("MONTH", "Month"), ("DAY", "Day")],
default="DAY",
max_length=20,
choices=[
("exact", "Exact Date"),
("month", "Month and Year"),
("year", "Year Only"),
("decade", "Decade"),
("century", "Century"),
("approximate", "Approximate"),
],
default="exact",
blank=True,
help_text="Precision of the opening date (YEAR for circa dates)",
help_text="Precision of the opening date",
)
closing_date = models.DateField(null=True, blank=True, help_text="Closing date")
closing_date_precision = models.CharField(
max_length=10,
choices=[("YEAR", "Year"), ("MONTH", "Month"), ("DAY", "Day")],
default="DAY",
max_length=20,
choices=[
("exact", "Exact Date"),
("month", "Month and Year"),
("year", "Year Only"),
("decade", "Decade"),
("century", "Century"),
("approximate", "Approximate"),
],
default="exact",
blank=True,
help_text="Precision of the closing date",
)
@@ -146,6 +160,16 @@ class Park(StateMachineMixin, TrackedModel):
help_text="Timezone identifier for park operations (e.g., 'America/New_York')",
)
# Submission metadata fields (from frontend schema)
source_url = models.URLField(
blank=True,
help_text="Source URL for the data (e.g., official website, Wikipedia)",
)
is_test_data = models.BooleanField(
default=False,
help_text="Whether this is test/development data",
)
class Meta:
verbose_name = "Park"
verbose_name_plural = "Parks"

View File

@@ -0,0 +1,432 @@
# Generated by Django 5.2.9 on 2026-01-07 20:30
import django.contrib.postgres.fields
import pgtrigger.compiler
import pgtrigger.migrations
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rides', '0033_add_ride_subtype_and_age'),
]
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='animatronics_count',
field=models.PositiveIntegerField(blank=True, help_text='Number of animatronic figures', null=True),
),
migrations.AddField(
model_name='ride',
name='arm_length_meters',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Length of ride arm in meters', max_digits=5, null=True),
),
migrations.AddField(
model_name='ride',
name='boat_capacity',
field=models.PositiveIntegerField(blank=True, help_text='Number of passengers per boat/vehicle', null=True),
),
migrations.AddField(
model_name='ride',
name='character_theme',
field=models.CharField(blank=True, help_text='Character or IP theme (e.g., Paw Patrol, Sesame Street)', max_length=200),
),
migrations.AddField(
model_name='ride',
name='coaster_type',
field=models.CharField(blank=True, help_text='Coaster structure type: steel, wood, or hybrid', max_length=20),
),
migrations.AddField(
model_name='ride',
name='drop_meters',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Maximum drop height in meters', max_digits=6, null=True),
),
migrations.AddField(
model_name='ride',
name='educational_theme',
field=models.CharField(blank=True, help_text='Educational or learning theme if applicable', max_length=200),
),
migrations.AddField(
model_name='ride',
name='flume_type',
field=models.CharField(blank=True, help_text='Type of flume or water channel', max_length=100),
),
migrations.AddField(
model_name='ride',
name='gforce_max',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Maximum G-force experienced', max_digits=4, null=True),
),
migrations.AddField(
model_name='ride',
name='height_meters',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Height of the ride structure in meters', max_digits=6, null=True),
),
migrations.AddField(
model_name='ride',
name='intensity_level',
field=models.CharField(blank=True, help_text='Intensity classification: family, thrill, or extreme', max_length=20),
),
migrations.AddField(
model_name='ride',
name='length_meters',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Total track/ride length in meters', max_digits=8, null=True),
),
migrations.AddField(
model_name='ride',
name='max_age',
field=models.PositiveIntegerField(blank=True, help_text='Maximum recommended age in years', null=True),
),
migrations.AddField(
model_name='ride',
name='max_height_reached_meters',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Maximum height reached during ride cycle in meters', max_digits=6, null=True),
),
migrations.AddField(
model_name='ride',
name='max_speed_kmh',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Maximum speed in kilometers per hour', max_digits=6, null=True),
),
migrations.AddField(
model_name='ride',
name='min_age',
field=models.PositiveIntegerField(blank=True, help_text='Minimum recommended age in years', null=True),
),
migrations.AddField(
model_name='ride',
name='motion_pattern',
field=models.CharField(blank=True, help_text="Description of the ride's motion pattern", max_length=200),
),
migrations.AddField(
model_name='ride',
name='platform_count',
field=models.PositiveIntegerField(blank=True, help_text='Number of ride platforms or gondolas', null=True),
),
migrations.AddField(
model_name='ride',
name='projection_type',
field=models.CharField(blank=True, help_text='Type of projection technology used', max_length=100),
),
migrations.AddField(
model_name='ride',
name='propulsion_method',
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=50), blank=True, default=list, help_text="Propulsion methods (e.g., ['chain_lift', 'lsm'])", size=None),
),
migrations.AddField(
model_name='ride',
name='ride_system',
field=models.CharField(blank=True, help_text='Ride system type (e.g., trackless, omnimover)', max_length=100),
),
migrations.AddField(
model_name='ride',
name='rotation_speed_rpm',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Rotation speed in revolutions per minute', max_digits=6, null=True),
),
migrations.AddField(
model_name='ride',
name='rotation_type',
field=models.CharField(blank=True, help_text='Rotation axis: horizontal, vertical, multi_axis, pendulum, or none', max_length=20),
),
migrations.AddField(
model_name='ride',
name='round_trip_duration_seconds',
field=models.PositiveIntegerField(blank=True, help_text='Duration of a complete round trip in seconds', null=True),
),
migrations.AddField(
model_name='ride',
name='route_length_meters',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Total route length in meters', max_digits=8, null=True),
),
migrations.AddField(
model_name='ride',
name='scenes_count',
field=models.PositiveIntegerField(blank=True, help_text='Number of distinct scenes or show sections', null=True),
),
migrations.AddField(
model_name='ride',
name='seating_type',
field=models.CharField(blank=True, help_text='Seating configuration: sit_down, inverted, flying, stand_up, etc.', max_length=20),
),
migrations.AddField(
model_name='ride',
name='show_duration_seconds',
field=models.PositiveIntegerField(blank=True, help_text='Duration of show elements in seconds', null=True),
),
migrations.AddField(
model_name='ride',
name='splash_height_meters',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Maximum splash height in meters', max_digits=5, null=True),
),
migrations.AddField(
model_name='ride',
name='stations_count',
field=models.PositiveIntegerField(blank=True, help_text='Number of stations or stops', null=True),
),
migrations.AddField(
model_name='ride',
name='story_description',
field=models.TextField(blank=True, help_text='Narrative or story description for the ride'),
),
migrations.AddField(
model_name='ride',
name='support_material',
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=50), blank=True, default=list, help_text='Support structure material types', size=None),
),
migrations.AddField(
model_name='ride',
name='swing_angle_degrees',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Maximum swing angle in degrees', max_digits=5, null=True),
),
migrations.AddField(
model_name='ride',
name='theme_name',
field=models.CharField(blank=True, help_text='Primary theme or IP name', max_length=200),
),
migrations.AddField(
model_name='ride',
name='track_material',
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=50), blank=True, default=list, help_text="Track material types (e.g., ['steel', 'wood'])", size=None),
),
migrations.AddField(
model_name='ride',
name='transport_type',
field=models.CharField(blank=True, help_text='Transport mode: train, monorail, skylift, ferry, peoplemover, or cable_car', max_length=20),
),
migrations.AddField(
model_name='ride',
name='vehicle_capacity',
field=models.PositiveIntegerField(blank=True, help_text='Passenger capacity per vehicle', null=True),
),
migrations.AddField(
model_name='ride',
name='vehicles_count',
field=models.PositiveIntegerField(blank=True, help_text='Number of vehicles in operation', null=True),
),
migrations.AddField(
model_name='ride',
name='water_depth_cm',
field=models.PositiveIntegerField(blank=True, help_text='Water depth in centimeters', null=True),
),
migrations.AddField(
model_name='ride',
name='wetness_level',
field=models.CharField(blank=True, help_text='Expected wetness: dry, light, moderate, or soaked', max_length=20),
),
migrations.AddField(
model_name='rideevent',
name='animatronics_count',
field=models.PositiveIntegerField(blank=True, help_text='Number of animatronic figures', null=True),
),
migrations.AddField(
model_name='rideevent',
name='arm_length_meters',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Length of ride arm in meters', max_digits=5, null=True),
),
migrations.AddField(
model_name='rideevent',
name='boat_capacity',
field=models.PositiveIntegerField(blank=True, help_text='Number of passengers per boat/vehicle', null=True),
),
migrations.AddField(
model_name='rideevent',
name='character_theme',
field=models.CharField(blank=True, help_text='Character or IP theme (e.g., Paw Patrol, Sesame Street)', max_length=200),
),
migrations.AddField(
model_name='rideevent',
name='coaster_type',
field=models.CharField(blank=True, help_text='Coaster structure type: steel, wood, or hybrid', max_length=20),
),
migrations.AddField(
model_name='rideevent',
name='drop_meters',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Maximum drop height in meters', max_digits=6, null=True),
),
migrations.AddField(
model_name='rideevent',
name='educational_theme',
field=models.CharField(blank=True, help_text='Educational or learning theme if applicable', max_length=200),
),
migrations.AddField(
model_name='rideevent',
name='flume_type',
field=models.CharField(blank=True, help_text='Type of flume or water channel', max_length=100),
),
migrations.AddField(
model_name='rideevent',
name='gforce_max',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Maximum G-force experienced', max_digits=4, null=True),
),
migrations.AddField(
model_name='rideevent',
name='height_meters',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Height of the ride structure in meters', max_digits=6, null=True),
),
migrations.AddField(
model_name='rideevent',
name='intensity_level',
field=models.CharField(blank=True, help_text='Intensity classification: family, thrill, or extreme', max_length=20),
),
migrations.AddField(
model_name='rideevent',
name='length_meters',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Total track/ride length in meters', max_digits=8, null=True),
),
migrations.AddField(
model_name='rideevent',
name='max_age',
field=models.PositiveIntegerField(blank=True, help_text='Maximum recommended age in years', null=True),
),
migrations.AddField(
model_name='rideevent',
name='max_height_reached_meters',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Maximum height reached during ride cycle in meters', max_digits=6, null=True),
),
migrations.AddField(
model_name='rideevent',
name='max_speed_kmh',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Maximum speed in kilometers per hour', max_digits=6, null=True),
),
migrations.AddField(
model_name='rideevent',
name='min_age',
field=models.PositiveIntegerField(blank=True, help_text='Minimum recommended age in years', null=True),
),
migrations.AddField(
model_name='rideevent',
name='motion_pattern',
field=models.CharField(blank=True, help_text="Description of the ride's motion pattern", max_length=200),
),
migrations.AddField(
model_name='rideevent',
name='platform_count',
field=models.PositiveIntegerField(blank=True, help_text='Number of ride platforms or gondolas', null=True),
),
migrations.AddField(
model_name='rideevent',
name='projection_type',
field=models.CharField(blank=True, help_text='Type of projection technology used', max_length=100),
),
migrations.AddField(
model_name='rideevent',
name='propulsion_method',
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=50), blank=True, default=list, help_text="Propulsion methods (e.g., ['chain_lift', 'lsm'])", size=None),
),
migrations.AddField(
model_name='rideevent',
name='ride_system',
field=models.CharField(blank=True, help_text='Ride system type (e.g., trackless, omnimover)', max_length=100),
),
migrations.AddField(
model_name='rideevent',
name='rotation_speed_rpm',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Rotation speed in revolutions per minute', max_digits=6, null=True),
),
migrations.AddField(
model_name='rideevent',
name='rotation_type',
field=models.CharField(blank=True, help_text='Rotation axis: horizontal, vertical, multi_axis, pendulum, or none', max_length=20),
),
migrations.AddField(
model_name='rideevent',
name='round_trip_duration_seconds',
field=models.PositiveIntegerField(blank=True, help_text='Duration of a complete round trip in seconds', null=True),
),
migrations.AddField(
model_name='rideevent',
name='route_length_meters',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Total route length in meters', max_digits=8, null=True),
),
migrations.AddField(
model_name='rideevent',
name='scenes_count',
field=models.PositiveIntegerField(blank=True, help_text='Number of distinct scenes or show sections', null=True),
),
migrations.AddField(
model_name='rideevent',
name='seating_type',
field=models.CharField(blank=True, help_text='Seating configuration: sit_down, inverted, flying, stand_up, etc.', max_length=20),
),
migrations.AddField(
model_name='rideevent',
name='show_duration_seconds',
field=models.PositiveIntegerField(blank=True, help_text='Duration of show elements in seconds', null=True),
),
migrations.AddField(
model_name='rideevent',
name='splash_height_meters',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Maximum splash height in meters', max_digits=5, null=True),
),
migrations.AddField(
model_name='rideevent',
name='stations_count',
field=models.PositiveIntegerField(blank=True, help_text='Number of stations or stops', null=True),
),
migrations.AddField(
model_name='rideevent',
name='story_description',
field=models.TextField(blank=True, help_text='Narrative or story description for the ride'),
),
migrations.AddField(
model_name='rideevent',
name='support_material',
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=50), blank=True, default=list, help_text='Support structure material types', size=None),
),
migrations.AddField(
model_name='rideevent',
name='swing_angle_degrees',
field=models.DecimalField(blank=True, decimal_places=2, help_text='Maximum swing angle in degrees', max_digits=5, null=True),
),
migrations.AddField(
model_name='rideevent',
name='theme_name',
field=models.CharField(blank=True, help_text='Primary theme or IP name', max_length=200),
),
migrations.AddField(
model_name='rideevent',
name='track_material',
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=50), blank=True, default=list, help_text="Track material types (e.g., ['steel', 'wood'])", size=None),
),
migrations.AddField(
model_name='rideevent',
name='transport_type',
field=models.CharField(blank=True, help_text='Transport mode: train, monorail, skylift, ferry, peoplemover, or cable_car', max_length=20),
),
migrations.AddField(
model_name='rideevent',
name='vehicle_capacity',
field=models.PositiveIntegerField(blank=True, help_text='Passenger capacity per vehicle', null=True),
),
migrations.AddField(
model_name='rideevent',
name='vehicles_count',
field=models.PositiveIntegerField(blank=True, help_text='Number of vehicles in operation', null=True),
),
migrations.AddField(
model_name='rideevent',
name='water_depth_cm',
field=models.PositiveIntegerField(blank=True, help_text='Water depth in centimeters', null=True),
),
migrations.AddField(
model_name='rideevent',
name='wetness_level',
field=models.CharField(blank=True, help_text='Expected wetness: dry, light, moderate, or soaked', max_length=20),
),
pgtrigger.migrations.AddTrigger(
model_name='ride',
trigger=pgtrigger.compiler.Trigger(name='insert_insert', sql=pgtrigger.compiler.UpsertTriggerSql(func='INSERT INTO "rides_rideevent" ("age_requirement", "animatronics_count", "arm_length_meters", "average_rating", "banner_image_id", "boat_capacity", "capacity_per_hour", "card_image_id", "category", "character_theme", "closing_date", "closing_date_precision", "coaster_type", "created_at", "description", "designer_id", "drop_meters", "educational_theme", "flume_type", "gforce_max", "height_meters", "id", "intensity_level", "length_meters", "manufacturer_id", "max_age", "max_height_in", "max_height_reached_meters", "max_speed_kmh", "min_age", "min_height_in", "motion_pattern", "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", "platform_count", "post_closing_status", "projection_type", "propulsion_method", "ride_duration_seconds", "ride_model_id", "ride_sub_type", "ride_system", "rotation_speed_rpm", "rotation_type", "round_trip_duration_seconds", "route_length_meters", "scenes_count", "search_text", "seating_type", "show_duration_seconds", "slug", "splash_height_meters", "stations_count", "status", "status_since", "story_description", "support_material", "swing_angle_degrees", "theme_name", "track_material", "transport_type", "updated_at", "url", "vehicle_capacity", "vehicles_count", "water_depth_cm", "wetness_level") VALUES (NEW."age_requirement", NEW."animatronics_count", NEW."arm_length_meters", NEW."average_rating", NEW."banner_image_id", NEW."boat_capacity", NEW."capacity_per_hour", NEW."card_image_id", NEW."category", NEW."character_theme", NEW."closing_date", NEW."closing_date_precision", NEW."coaster_type", NEW."created_at", NEW."description", NEW."designer_id", NEW."drop_meters", NEW."educational_theme", NEW."flume_type", NEW."gforce_max", NEW."height_meters", NEW."id", NEW."intensity_level", NEW."length_meters", NEW."manufacturer_id", NEW."max_age", NEW."max_height_in", NEW."max_height_reached_meters", NEW."max_speed_kmh", NEW."min_age", NEW."min_height_in", NEW."motion_pattern", 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."platform_count", NEW."post_closing_status", NEW."projection_type", NEW."propulsion_method", NEW."ride_duration_seconds", NEW."ride_model_id", NEW."ride_sub_type", NEW."ride_system", NEW."rotation_speed_rpm", NEW."rotation_type", NEW."round_trip_duration_seconds", NEW."route_length_meters", NEW."scenes_count", NEW."search_text", NEW."seating_type", NEW."show_duration_seconds", NEW."slug", NEW."splash_height_meters", NEW."stations_count", NEW."status", NEW."status_since", NEW."story_description", NEW."support_material", NEW."swing_angle_degrees", NEW."theme_name", NEW."track_material", NEW."transport_type", NEW."updated_at", NEW."url", NEW."vehicle_capacity", NEW."vehicles_count", NEW."water_depth_cm", NEW."wetness_level"); RETURN NULL;', hash='0515185b26eb9635e7b7f7d52cfa87b90636c409', 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", "animatronics_count", "arm_length_meters", "average_rating", "banner_image_id", "boat_capacity", "capacity_per_hour", "card_image_id", "category", "character_theme", "closing_date", "closing_date_precision", "coaster_type", "created_at", "description", "designer_id", "drop_meters", "educational_theme", "flume_type", "gforce_max", "height_meters", "id", "intensity_level", "length_meters", "manufacturer_id", "max_age", "max_height_in", "max_height_reached_meters", "max_speed_kmh", "min_age", "min_height_in", "motion_pattern", "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", "platform_count", "post_closing_status", "projection_type", "propulsion_method", "ride_duration_seconds", "ride_model_id", "ride_sub_type", "ride_system", "rotation_speed_rpm", "rotation_type", "round_trip_duration_seconds", "route_length_meters", "scenes_count", "search_text", "seating_type", "show_duration_seconds", "slug", "splash_height_meters", "stations_count", "status", "status_since", "story_description", "support_material", "swing_angle_degrees", "theme_name", "track_material", "transport_type", "updated_at", "url", "vehicle_capacity", "vehicles_count", "water_depth_cm", "wetness_level") VALUES (NEW."age_requirement", NEW."animatronics_count", NEW."arm_length_meters", NEW."average_rating", NEW."banner_image_id", NEW."boat_capacity", NEW."capacity_per_hour", NEW."card_image_id", NEW."category", NEW."character_theme", NEW."closing_date", NEW."closing_date_precision", NEW."coaster_type", NEW."created_at", NEW."description", NEW."designer_id", NEW."drop_meters", NEW."educational_theme", NEW."flume_type", NEW."gforce_max", NEW."height_meters", NEW."id", NEW."intensity_level", NEW."length_meters", NEW."manufacturer_id", NEW."max_age", NEW."max_height_in", NEW."max_height_reached_meters", NEW."max_speed_kmh", NEW."min_age", NEW."min_height_in", NEW."motion_pattern", 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."platform_count", NEW."post_closing_status", NEW."projection_type", NEW."propulsion_method", NEW."ride_duration_seconds", NEW."ride_model_id", NEW."ride_sub_type", NEW."ride_system", NEW."rotation_speed_rpm", NEW."rotation_type", NEW."round_trip_duration_seconds", NEW."route_length_meters", NEW."scenes_count", NEW."search_text", NEW."seating_type", NEW."show_duration_seconds", NEW."slug", NEW."splash_height_meters", NEW."stations_count", NEW."status", NEW."status_since", NEW."story_description", NEW."support_material", NEW."swing_angle_degrees", NEW."theme_name", NEW."track_material", NEW."transport_type", NEW."updated_at", NEW."url", NEW."vehicle_capacity", NEW."vehicles_count", NEW."water_depth_cm", NEW."wetness_level"); RETURN NULL;', hash='e0bb5999b75a6d10f73651cba99c40e06bb2b49c', operation='UPDATE', pgid='pgtrigger_update_update_4917a', table='rides_ride', when='AFTER')),
),
]

View File

@@ -0,0 +1,119 @@
# Generated by Django 5.2.9 on 2026-01-07 21:01
import django.db.models.deletion
import pgtrigger.compiler
import pgtrigger.migrations
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('parks', '0028_add_date_precision_fields'),
('rides', '0034_add_ride_category_fields'),
]
operations = [
pgtrigger.migrations.RemoveTrigger(
model_name='company',
name='insert_insert',
),
pgtrigger.migrations.RemoveTrigger(
model_name='company',
name='update_update',
),
pgtrigger.migrations.RemoveTrigger(
model_name='ridemodel',
name='insert_insert',
),
pgtrigger.migrations.RemoveTrigger(
model_name='ridemodel',
name='update_update',
),
migrations.AddField(
model_name='company',
name='banner_image_id',
field=models.CharField(blank=True, help_text='Cloudflare image ID for banner image', max_length=255),
),
migrations.AddField(
model_name='company',
name='card_image_id',
field=models.CharField(blank=True, help_text='Cloudflare image ID for card image', max_length=255),
),
migrations.AddField(
model_name='company',
name='founded_date_precision',
field=models.CharField(blank=True, choices=[('exact', 'Exact'), ('month', 'Month'), ('year', 'Year'), ('decade', 'Decade'), ('century', 'Century'), ('approximate', 'Approximate')], default='', help_text='Precision of the founded date', max_length=20),
),
migrations.AddField(
model_name='company',
name='founded_year',
field=models.PositiveIntegerField(blank=True, help_text='Year the company was founded (alternative to founded_date)', null=True),
),
migrations.AddField(
model_name='company',
name='headquarters_location',
field=models.CharField(blank=True, help_text="Headquarters location description (e.g., 'Los Angeles, CA, USA')", max_length=200),
),
migrations.AddField(
model_name='company',
name='location',
field=models.ForeignKey(blank=True, help_text='Linked location record for headquarters', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='companies', to='parks.parklocation'),
),
migrations.AddField(
model_name='companyevent',
name='banner_image_id',
field=models.CharField(blank=True, help_text='Cloudflare image ID for banner image', max_length=255),
),
migrations.AddField(
model_name='companyevent',
name='card_image_id',
field=models.CharField(blank=True, help_text='Cloudflare image ID for card image', max_length=255),
),
migrations.AddField(
model_name='companyevent',
name='founded_date_precision',
field=models.CharField(blank=True, choices=[('exact', 'Exact'), ('month', 'Month'), ('year', 'Year'), ('decade', 'Decade'), ('century', 'Century'), ('approximate', 'Approximate')], default='', help_text='Precision of the founded date', max_length=20),
),
migrations.AddField(
model_name='companyevent',
name='founded_year',
field=models.PositiveIntegerField(blank=True, help_text='Year the company was founded (alternative to founded_date)', null=True),
),
migrations.AddField(
model_name='companyevent',
name='headquarters_location',
field=models.CharField(blank=True, help_text="Headquarters location description (e.g., 'Los Angeles, CA, USA')", max_length=200),
),
migrations.AddField(
model_name='companyevent',
name='location',
field=models.ForeignKey(blank=True, db_constraint=False, help_text='Linked location record for headquarters', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', related_query_name='+', to='parks.parklocation'),
),
migrations.AddField(
model_name='ridemodel',
name='ride_type',
field=models.CharField(blank=True, help_text="Specific ride type within the category (e.g., 'Flying Coaster', 'Inverted Coaster')", max_length=100),
),
migrations.AddField(
model_name='ridemodelevent',
name='ride_type',
field=models.CharField(blank=True, help_text="Specific ride type within the category (e.g., 'Flying Coaster', 'Inverted Coaster')", max_length=100),
),
pgtrigger.migrations.AddTrigger(
model_name='company',
trigger=pgtrigger.compiler.Trigger(name='insert_insert', sql=pgtrigger.compiler.UpsertTriggerSql(func='INSERT INTO "rides_companyevent" ("banner_image_id", "card_image_id", "coasters_count", "created_at", "description", "founded_date", "founded_date_precision", "founded_year", "headquarters_location", "id", "location_id", "name", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "rides_count", "roles", "slug", "updated_at", "url", "website") VALUES (NEW."banner_image_id", NEW."card_image_id", NEW."coasters_count", NEW."created_at", NEW."description", NEW."founded_date", NEW."founded_date_precision", NEW."founded_year", NEW."headquarters_location", NEW."id", NEW."location_id", NEW."name", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."rides_count", NEW."roles", NEW."slug", NEW."updated_at", NEW."url", NEW."website"); RETURN NULL;', hash='d1efc807d08a85e448a3294e70abb85e1c9c40ff', operation='INSERT', pgid='pgtrigger_insert_insert_e7194', table='rides_company', when='AFTER')),
),
pgtrigger.migrations.AddTrigger(
model_name='company',
trigger=pgtrigger.compiler.Trigger(name='update_update', sql=pgtrigger.compiler.UpsertTriggerSql(condition='WHEN (OLD.* IS DISTINCT FROM NEW.*)', func='INSERT INTO "rides_companyevent" ("banner_image_id", "card_image_id", "coasters_count", "created_at", "description", "founded_date", "founded_date_precision", "founded_year", "headquarters_location", "id", "location_id", "name", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "rides_count", "roles", "slug", "updated_at", "url", "website") VALUES (NEW."banner_image_id", NEW."card_image_id", NEW."coasters_count", NEW."created_at", NEW."description", NEW."founded_date", NEW."founded_date_precision", NEW."founded_year", NEW."headquarters_location", NEW."id", NEW."location_id", NEW."name", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."rides_count", NEW."roles", NEW."slug", NEW."updated_at", NEW."url", NEW."website"); RETURN NULL;', hash='dd4644183deefdfa27ec6d282c6da0c09d4df927', operation='UPDATE', pgid='pgtrigger_update_update_456a8', table='rides_company', when='AFTER')),
),
pgtrigger.migrations.AddTrigger(
model_name='ridemodel',
trigger=pgtrigger.compiler.Trigger(name='insert_insert', sql=pgtrigger.compiler.UpsertTriggerSql(func='INSERT INTO "rides_ridemodelevent" ("category", "created_at", "description", "first_installation_year", "id", "is_discontinued", "last_installation_year", "manufacturer_id", "meta_description", "meta_title", "name", "notable_features", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "primary_image_id", "restraint_system", "ride_type", "slug", "support_structure", "target_market", "total_installations", "track_type", "train_configuration", "typical_capacity_range_max", "typical_capacity_range_min", "typical_height_range_max_ft", "typical_height_range_min_ft", "typical_speed_range_max_mph", "typical_speed_range_min_mph", "updated_at", "url") VALUES (NEW."category", NEW."created_at", NEW."description", NEW."first_installation_year", NEW."id", NEW."is_discontinued", NEW."last_installation_year", NEW."manufacturer_id", NEW."meta_description", NEW."meta_title", NEW."name", NEW."notable_features", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."primary_image_id", NEW."restraint_system", NEW."ride_type", NEW."slug", NEW."support_structure", NEW."target_market", NEW."total_installations", NEW."track_type", NEW."train_configuration", NEW."typical_capacity_range_max", NEW."typical_capacity_range_min", NEW."typical_height_range_max_ft", NEW."typical_height_range_min_ft", NEW."typical_speed_range_max_mph", NEW."typical_speed_range_min_mph", NEW."updated_at", NEW."url"); RETURN NULL;', hash='715219f75d39aa2d59ffe836084dab943a322c5f', operation='INSERT', pgid='pgtrigger_insert_insert_0aaee', table='rides_ridemodel', when='AFTER')),
),
pgtrigger.migrations.AddTrigger(
model_name='ridemodel',
trigger=pgtrigger.compiler.Trigger(name='update_update', sql=pgtrigger.compiler.UpsertTriggerSql(condition='WHEN (OLD.* IS DISTINCT FROM NEW.*)', func='INSERT INTO "rides_ridemodelevent" ("category", "created_at", "description", "first_installation_year", "id", "is_discontinued", "last_installation_year", "manufacturer_id", "meta_description", "meta_title", "name", "notable_features", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "primary_image_id", "restraint_system", "ride_type", "slug", "support_structure", "target_market", "total_installations", "track_type", "train_configuration", "typical_capacity_range_max", "typical_capacity_range_min", "typical_height_range_max_ft", "typical_height_range_min_ft", "typical_speed_range_max_mph", "typical_speed_range_min_mph", "updated_at", "url") VALUES (NEW."category", NEW."created_at", NEW."description", NEW."first_installation_year", NEW."id", NEW."is_discontinued", NEW."last_installation_year", NEW."manufacturer_id", NEW."meta_description", NEW."meta_title", NEW."name", NEW."notable_features", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."primary_image_id", NEW."restraint_system", NEW."ride_type", NEW."slug", NEW."support_structure", NEW."target_market", NEW."total_installations", NEW."track_type", NEW."train_configuration", NEW."typical_capacity_range_max", NEW."typical_capacity_range_min", NEW."typical_height_range_max_ft", NEW."typical_height_range_min_ft", NEW."typical_speed_range_max_mph", NEW."typical_speed_range_min_mph", NEW."updated_at", NEW."url"); RETURN NULL;', hash='4f1d59b4ef9ddd207f7e4a56843d830ab67cff38', operation='UPDATE', pgid='pgtrigger_update_update_0ca1a', table='rides_ridemodel', when='AFTER')),
),
]

View File

@@ -0,0 +1,87 @@
# Generated by Django 5.2.9 on 2026-01-08 01:40
import pgtrigger.compiler
import pgtrigger.migrations
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rides', '0035_add_company_and_ridemodel_fields'),
]
operations = [
pgtrigger.migrations.RemoveTrigger(
model_name='company',
name='insert_insert',
),
pgtrigger.migrations.RemoveTrigger(
model_name='company',
name='update_update',
),
pgtrigger.migrations.RemoveTrigger(
model_name='ride',
name='insert_insert',
),
pgtrigger.migrations.RemoveTrigger(
model_name='ride',
name='update_update',
),
migrations.AddField(
model_name='company',
name='person_type',
field=models.CharField(blank=True, choices=[('company', 'Company'), ('individual', 'Individual'), ('firm', 'Firm'), ('organization', 'Organization')], default='company', help_text='Type of entity (company, individual, firm, organization)', max_length=20),
),
migrations.AddField(
model_name='companyevent',
name='person_type',
field=models.CharField(blank=True, choices=[('company', 'Company'), ('individual', 'Individual'), ('firm', 'Firm'), ('organization', 'Organization')], default='company', help_text='Type of entity (company, individual, firm, organization)', max_length=20),
),
migrations.AddField(
model_name='ride',
name='duration_seconds',
field=models.PositiveIntegerField(blank=True, help_text='Ride duration in seconds', null=True),
),
migrations.AddField(
model_name='ride',
name='height_requirement_cm',
field=models.PositiveIntegerField(blank=True, help_text='Minimum height requirement in centimeters', null=True),
),
migrations.AddField(
model_name='ride',
name='inversions_count',
field=models.PositiveIntegerField(blank=True, help_text='Number of inversions (for coasters)', null=True),
),
migrations.AddField(
model_name='rideevent',
name='duration_seconds',
field=models.PositiveIntegerField(blank=True, help_text='Ride duration in seconds', null=True),
),
migrations.AddField(
model_name='rideevent',
name='height_requirement_cm',
field=models.PositiveIntegerField(blank=True, help_text='Minimum height requirement in centimeters', null=True),
),
migrations.AddField(
model_name='rideevent',
name='inversions_count',
field=models.PositiveIntegerField(blank=True, help_text='Number of inversions (for coasters)', null=True),
),
pgtrigger.migrations.AddTrigger(
model_name='company',
trigger=pgtrigger.compiler.Trigger(name='insert_insert', sql=pgtrigger.compiler.UpsertTriggerSql(func='INSERT INTO "rides_companyevent" ("banner_image_id", "card_image_id", "coasters_count", "created_at", "description", "founded_date", "founded_date_precision", "founded_year", "headquarters_location", "id", "location_id", "name", "person_type", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "rides_count", "roles", "slug", "updated_at", "url", "website") VALUES (NEW."banner_image_id", NEW."card_image_id", NEW."coasters_count", NEW."created_at", NEW."description", NEW."founded_date", NEW."founded_date_precision", NEW."founded_year", NEW."headquarters_location", NEW."id", NEW."location_id", NEW."name", NEW."person_type", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."rides_count", NEW."roles", NEW."slug", NEW."updated_at", NEW."url", NEW."website"); RETURN NULL;', hash='636ad62fbef5026486e8eb22d7b3ad3a08b08972', operation='INSERT', pgid='pgtrigger_insert_insert_e7194', table='rides_company', when='AFTER')),
),
pgtrigger.migrations.AddTrigger(
model_name='company',
trigger=pgtrigger.compiler.Trigger(name='update_update', sql=pgtrigger.compiler.UpsertTriggerSql(condition='WHEN (OLD.* IS DISTINCT FROM NEW.*)', func='INSERT INTO "rides_companyevent" ("banner_image_id", "card_image_id", "coasters_count", "created_at", "description", "founded_date", "founded_date_precision", "founded_year", "headquarters_location", "id", "location_id", "name", "person_type", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "rides_count", "roles", "slug", "updated_at", "url", "website") VALUES (NEW."banner_image_id", NEW."card_image_id", NEW."coasters_count", NEW."created_at", NEW."description", NEW."founded_date", NEW."founded_date_precision", NEW."founded_year", NEW."headquarters_location", NEW."id", NEW."location_id", NEW."name", NEW."person_type", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."rides_count", NEW."roles", NEW."slug", NEW."updated_at", NEW."url", NEW."website"); RETURN NULL;', hash='d0c405cab0f8f61aa24dd2074fd615a56fcc812a', operation='UPDATE', pgid='pgtrigger_update_update_456a8', table='rides_company', when='AFTER')),
),
pgtrigger.migrations.AddTrigger(
model_name='ride',
trigger=pgtrigger.compiler.Trigger(name='insert_insert', sql=pgtrigger.compiler.UpsertTriggerSql(func='INSERT INTO "rides_rideevent" ("age_requirement", "animatronics_count", "arm_length_meters", "average_rating", "banner_image_id", "boat_capacity", "capacity_per_hour", "card_image_id", "category", "character_theme", "closing_date", "closing_date_precision", "coaster_type", "created_at", "description", "designer_id", "drop_meters", "duration_seconds", "educational_theme", "flume_type", "gforce_max", "height_meters", "height_requirement_cm", "id", "intensity_level", "inversions_count", "length_meters", "manufacturer_id", "max_age", "max_height_in", "max_height_reached_meters", "max_speed_kmh", "min_age", "min_height_in", "motion_pattern", "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", "platform_count", "post_closing_status", "projection_type", "propulsion_method", "ride_duration_seconds", "ride_model_id", "ride_sub_type", "ride_system", "rotation_speed_rpm", "rotation_type", "round_trip_duration_seconds", "route_length_meters", "scenes_count", "search_text", "seating_type", "show_duration_seconds", "slug", "splash_height_meters", "stations_count", "status", "status_since", "story_description", "support_material", "swing_angle_degrees", "theme_name", "track_material", "transport_type", "updated_at", "url", "vehicle_capacity", "vehicles_count", "water_depth_cm", "wetness_level") VALUES (NEW."age_requirement", NEW."animatronics_count", NEW."arm_length_meters", NEW."average_rating", NEW."banner_image_id", NEW."boat_capacity", NEW."capacity_per_hour", NEW."card_image_id", NEW."category", NEW."character_theme", NEW."closing_date", NEW."closing_date_precision", NEW."coaster_type", NEW."created_at", NEW."description", NEW."designer_id", NEW."drop_meters", NEW."duration_seconds", NEW."educational_theme", NEW."flume_type", NEW."gforce_max", NEW."height_meters", NEW."height_requirement_cm", NEW."id", NEW."intensity_level", NEW."inversions_count", NEW."length_meters", NEW."manufacturer_id", NEW."max_age", NEW."max_height_in", NEW."max_height_reached_meters", NEW."max_speed_kmh", NEW."min_age", NEW."min_height_in", NEW."motion_pattern", 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."platform_count", NEW."post_closing_status", NEW."projection_type", NEW."propulsion_method", NEW."ride_duration_seconds", NEW."ride_model_id", NEW."ride_sub_type", NEW."ride_system", NEW."rotation_speed_rpm", NEW."rotation_type", NEW."round_trip_duration_seconds", NEW."route_length_meters", NEW."scenes_count", NEW."search_text", NEW."seating_type", NEW."show_duration_seconds", NEW."slug", NEW."splash_height_meters", NEW."stations_count", NEW."status", NEW."status_since", NEW."story_description", NEW."support_material", NEW."swing_angle_degrees", NEW."theme_name", NEW."track_material", NEW."transport_type", NEW."updated_at", NEW."url", NEW."vehicle_capacity", NEW."vehicles_count", NEW."water_depth_cm", NEW."wetness_level"); RETURN NULL;', hash='db6754d5334c498976180acdf6f2dd7c043cb9c1', 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", "animatronics_count", "arm_length_meters", "average_rating", "banner_image_id", "boat_capacity", "capacity_per_hour", "card_image_id", "category", "character_theme", "closing_date", "closing_date_precision", "coaster_type", "created_at", "description", "designer_id", "drop_meters", "duration_seconds", "educational_theme", "flume_type", "gforce_max", "height_meters", "height_requirement_cm", "id", "intensity_level", "inversions_count", "length_meters", "manufacturer_id", "max_age", "max_height_in", "max_height_reached_meters", "max_speed_kmh", "min_age", "min_height_in", "motion_pattern", "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", "platform_count", "post_closing_status", "projection_type", "propulsion_method", "ride_duration_seconds", "ride_model_id", "ride_sub_type", "ride_system", "rotation_speed_rpm", "rotation_type", "round_trip_duration_seconds", "route_length_meters", "scenes_count", "search_text", "seating_type", "show_duration_seconds", "slug", "splash_height_meters", "stations_count", "status", "status_since", "story_description", "support_material", "swing_angle_degrees", "theme_name", "track_material", "transport_type", "updated_at", "url", "vehicle_capacity", "vehicles_count", "water_depth_cm", "wetness_level") VALUES (NEW."age_requirement", NEW."animatronics_count", NEW."arm_length_meters", NEW."average_rating", NEW."banner_image_id", NEW."boat_capacity", NEW."capacity_per_hour", NEW."card_image_id", NEW."category", NEW."character_theme", NEW."closing_date", NEW."closing_date_precision", NEW."coaster_type", NEW."created_at", NEW."description", NEW."designer_id", NEW."drop_meters", NEW."duration_seconds", NEW."educational_theme", NEW."flume_type", NEW."gforce_max", NEW."height_meters", NEW."height_requirement_cm", NEW."id", NEW."intensity_level", NEW."inversions_count", NEW."length_meters", NEW."manufacturer_id", NEW."max_age", NEW."max_height_in", NEW."max_height_reached_meters", NEW."max_speed_kmh", NEW."min_age", NEW."min_height_in", NEW."motion_pattern", 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."platform_count", NEW."post_closing_status", NEW."projection_type", NEW."propulsion_method", NEW."ride_duration_seconds", NEW."ride_model_id", NEW."ride_sub_type", NEW."ride_system", NEW."rotation_speed_rpm", NEW."rotation_type", NEW."round_trip_duration_seconds", NEW."route_length_meters", NEW."scenes_count", NEW."search_text", NEW."seating_type", NEW."show_duration_seconds", NEW."slug", NEW."splash_height_meters", NEW."stations_count", NEW."status", NEW."status_since", NEW."story_description", NEW."support_material", NEW."swing_angle_degrees", NEW."theme_name", NEW."track_material", NEW."transport_type", NEW."updated_at", NEW."url", NEW."vehicle_capacity", NEW."vehicles_count", NEW."water_depth_cm", NEW."wetness_level"); RETURN NULL;', hash='3bff6632dbf5e5fab62671b5c2da263fb4682611', operation='UPDATE', pgid='pgtrigger_update_update_4917a', table='rides_ride', when='AFTER')),
),
]

View File

@@ -0,0 +1,107 @@
# Generated by Django 5.2.9 on 2026-01-08 18:05
import pgtrigger.compiler
import pgtrigger.migrations
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rides', '0036_add_remaining_parity_fields'),
]
operations = [
pgtrigger.migrations.RemoveTrigger(
model_name='ride',
name='insert_insert',
),
pgtrigger.migrations.RemoveTrigger(
model_name='ride',
name='update_update',
),
pgtrigger.migrations.RemoveTrigger(
model_name='ridemodel',
name='insert_insert',
),
pgtrigger.migrations.RemoveTrigger(
model_name='ridemodel',
name='update_update',
),
migrations.AddField(
model_name='ride',
name='is_test_data',
field=models.BooleanField(default=False, help_text='Whether this is test/development data'),
),
migrations.AddField(
model_name='ride',
name='source_url',
field=models.URLField(blank=True, help_text='Source URL for the data (e.g., official website, RCDB)'),
),
migrations.AddField(
model_name='rideevent',
name='is_test_data',
field=models.BooleanField(default=False, help_text='Whether this is test/development data'),
),
migrations.AddField(
model_name='rideevent',
name='source_url',
field=models.URLField(blank=True, help_text='Source URL for the data (e.g., official website, RCDB)'),
),
migrations.AddField(
model_name='ridemodel',
name='is_test_data',
field=models.BooleanField(default=False, help_text='Whether this is test/development data'),
),
migrations.AddField(
model_name='ridemodel',
name='source_url',
field=models.URLField(blank=True, help_text='Source URL for the data (e.g., manufacturer website)'),
),
migrations.AddField(
model_name='ridemodelevent',
name='is_test_data',
field=models.BooleanField(default=False, help_text='Whether this is test/development data'),
),
migrations.AddField(
model_name='ridemodelevent',
name='source_url',
field=models.URLField(blank=True, help_text='Source URL for the data (e.g., manufacturer website)'),
),
migrations.AlterField(
model_name='ride',
name='closing_date_precision',
field=models.CharField(blank=True, choices=[('exact', 'Exact Date'), ('month', 'Month and Year'), ('year', 'Year Only'), ('decade', 'Decade'), ('century', 'Century'), ('approximate', 'Approximate')], default='exact', help_text='Precision of the closing date', max_length=20),
),
migrations.AlterField(
model_name='ride',
name='opening_date_precision',
field=models.CharField(blank=True, choices=[('exact', 'Exact Date'), ('month', 'Month and Year'), ('year', 'Year Only'), ('decade', 'Decade'), ('century', 'Century'), ('approximate', 'Approximate')], default='exact', help_text='Precision of the opening date', max_length=20),
),
migrations.AlterField(
model_name='rideevent',
name='closing_date_precision',
field=models.CharField(blank=True, choices=[('exact', 'Exact Date'), ('month', 'Month and Year'), ('year', 'Year Only'), ('decade', 'Decade'), ('century', 'Century'), ('approximate', 'Approximate')], default='exact', help_text='Precision of the closing date', max_length=20),
),
migrations.AlterField(
model_name='rideevent',
name='opening_date_precision',
field=models.CharField(blank=True, choices=[('exact', 'Exact Date'), ('month', 'Month and Year'), ('year', 'Year Only'), ('decade', 'Decade'), ('century', 'Century'), ('approximate', 'Approximate')], default='exact', help_text='Precision of the opening date', max_length=20),
),
pgtrigger.migrations.AddTrigger(
model_name='ride',
trigger=pgtrigger.compiler.Trigger(name='insert_insert', sql=pgtrigger.compiler.UpsertTriggerSql(func='INSERT INTO "rides_rideevent" ("age_requirement", "animatronics_count", "arm_length_meters", "average_rating", "banner_image_id", "boat_capacity", "capacity_per_hour", "card_image_id", "category", "character_theme", "closing_date", "closing_date_precision", "coaster_type", "created_at", "description", "designer_id", "drop_meters", "duration_seconds", "educational_theme", "flume_type", "gforce_max", "height_meters", "height_requirement_cm", "id", "intensity_level", "inversions_count", "is_test_data", "length_meters", "manufacturer_id", "max_age", "max_height_in", "max_height_reached_meters", "max_speed_kmh", "min_age", "min_height_in", "motion_pattern", "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", "platform_count", "post_closing_status", "projection_type", "propulsion_method", "ride_duration_seconds", "ride_model_id", "ride_sub_type", "ride_system", "rotation_speed_rpm", "rotation_type", "round_trip_duration_seconds", "route_length_meters", "scenes_count", "search_text", "seating_type", "show_duration_seconds", "slug", "source_url", "splash_height_meters", "stations_count", "status", "status_since", "story_description", "support_material", "swing_angle_degrees", "theme_name", "track_material", "transport_type", "updated_at", "url", "vehicle_capacity", "vehicles_count", "water_depth_cm", "wetness_level") VALUES (NEW."age_requirement", NEW."animatronics_count", NEW."arm_length_meters", NEW."average_rating", NEW."banner_image_id", NEW."boat_capacity", NEW."capacity_per_hour", NEW."card_image_id", NEW."category", NEW."character_theme", NEW."closing_date", NEW."closing_date_precision", NEW."coaster_type", NEW."created_at", NEW."description", NEW."designer_id", NEW."drop_meters", NEW."duration_seconds", NEW."educational_theme", NEW."flume_type", NEW."gforce_max", NEW."height_meters", NEW."height_requirement_cm", NEW."id", NEW."intensity_level", NEW."inversions_count", NEW."is_test_data", NEW."length_meters", NEW."manufacturer_id", NEW."max_age", NEW."max_height_in", NEW."max_height_reached_meters", NEW."max_speed_kmh", NEW."min_age", NEW."min_height_in", NEW."motion_pattern", 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."platform_count", NEW."post_closing_status", NEW."projection_type", NEW."propulsion_method", NEW."ride_duration_seconds", NEW."ride_model_id", NEW."ride_sub_type", NEW."ride_system", NEW."rotation_speed_rpm", NEW."rotation_type", NEW."round_trip_duration_seconds", NEW."route_length_meters", NEW."scenes_count", NEW."search_text", NEW."seating_type", NEW."show_duration_seconds", NEW."slug", NEW."source_url", NEW."splash_height_meters", NEW."stations_count", NEW."status", NEW."status_since", NEW."story_description", NEW."support_material", NEW."swing_angle_degrees", NEW."theme_name", NEW."track_material", NEW."transport_type", NEW."updated_at", NEW."url", NEW."vehicle_capacity", NEW."vehicles_count", NEW."water_depth_cm", NEW."wetness_level"); RETURN NULL;', hash='07c5cf95d16c49e08014c23a4e5e35f55292c869', 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", "animatronics_count", "arm_length_meters", "average_rating", "banner_image_id", "boat_capacity", "capacity_per_hour", "card_image_id", "category", "character_theme", "closing_date", "closing_date_precision", "coaster_type", "created_at", "description", "designer_id", "drop_meters", "duration_seconds", "educational_theme", "flume_type", "gforce_max", "height_meters", "height_requirement_cm", "id", "intensity_level", "inversions_count", "is_test_data", "length_meters", "manufacturer_id", "max_age", "max_height_in", "max_height_reached_meters", "max_speed_kmh", "min_age", "min_height_in", "motion_pattern", "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", "platform_count", "post_closing_status", "projection_type", "propulsion_method", "ride_duration_seconds", "ride_model_id", "ride_sub_type", "ride_system", "rotation_speed_rpm", "rotation_type", "round_trip_duration_seconds", "route_length_meters", "scenes_count", "search_text", "seating_type", "show_duration_seconds", "slug", "source_url", "splash_height_meters", "stations_count", "status", "status_since", "story_description", "support_material", "swing_angle_degrees", "theme_name", "track_material", "transport_type", "updated_at", "url", "vehicle_capacity", "vehicles_count", "water_depth_cm", "wetness_level") VALUES (NEW."age_requirement", NEW."animatronics_count", NEW."arm_length_meters", NEW."average_rating", NEW."banner_image_id", NEW."boat_capacity", NEW."capacity_per_hour", NEW."card_image_id", NEW."category", NEW."character_theme", NEW."closing_date", NEW."closing_date_precision", NEW."coaster_type", NEW."created_at", NEW."description", NEW."designer_id", NEW."drop_meters", NEW."duration_seconds", NEW."educational_theme", NEW."flume_type", NEW."gforce_max", NEW."height_meters", NEW."height_requirement_cm", NEW."id", NEW."intensity_level", NEW."inversions_count", NEW."is_test_data", NEW."length_meters", NEW."manufacturer_id", NEW."max_age", NEW."max_height_in", NEW."max_height_reached_meters", NEW."max_speed_kmh", NEW."min_age", NEW."min_height_in", NEW."motion_pattern", 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."platform_count", NEW."post_closing_status", NEW."projection_type", NEW."propulsion_method", NEW."ride_duration_seconds", NEW."ride_model_id", NEW."ride_sub_type", NEW."ride_system", NEW."rotation_speed_rpm", NEW."rotation_type", NEW."round_trip_duration_seconds", NEW."route_length_meters", NEW."scenes_count", NEW."search_text", NEW."seating_type", NEW."show_duration_seconds", NEW."slug", NEW."source_url", NEW."splash_height_meters", NEW."stations_count", NEW."status", NEW."status_since", NEW."story_description", NEW."support_material", NEW."swing_angle_degrees", NEW."theme_name", NEW."track_material", NEW."transport_type", NEW."updated_at", NEW."url", NEW."vehicle_capacity", NEW."vehicles_count", NEW."water_depth_cm", NEW."wetness_level"); RETURN NULL;', hash='dabf771ba40b71c4d91ad1b1ed97a9712578096c', operation='UPDATE', pgid='pgtrigger_update_update_4917a', table='rides_ride', when='AFTER')),
),
pgtrigger.migrations.AddTrigger(
model_name='ridemodel',
trigger=pgtrigger.compiler.Trigger(name='insert_insert', sql=pgtrigger.compiler.UpsertTriggerSql(func='INSERT INTO "rides_ridemodelevent" ("category", "created_at", "description", "first_installation_year", "id", "is_discontinued", "is_test_data", "last_installation_year", "manufacturer_id", "meta_description", "meta_title", "name", "notable_features", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "primary_image_id", "restraint_system", "ride_type", "slug", "source_url", "support_structure", "target_market", "total_installations", "track_type", "train_configuration", "typical_capacity_range_max", "typical_capacity_range_min", "typical_height_range_max_ft", "typical_height_range_min_ft", "typical_speed_range_max_mph", "typical_speed_range_min_mph", "updated_at", "url") VALUES (NEW."category", NEW."created_at", NEW."description", NEW."first_installation_year", NEW."id", NEW."is_discontinued", NEW."is_test_data", NEW."last_installation_year", NEW."manufacturer_id", NEW."meta_description", NEW."meta_title", NEW."name", NEW."notable_features", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."primary_image_id", NEW."restraint_system", NEW."ride_type", NEW."slug", NEW."source_url", NEW."support_structure", NEW."target_market", NEW."total_installations", NEW."track_type", NEW."train_configuration", NEW."typical_capacity_range_max", NEW."typical_capacity_range_min", NEW."typical_height_range_max_ft", NEW."typical_height_range_min_ft", NEW."typical_speed_range_max_mph", NEW."typical_speed_range_min_mph", NEW."updated_at", NEW."url"); RETURN NULL;', hash='9cc07f0217f79924bae066b5b8f9e7d5f55e211c', operation='INSERT', pgid='pgtrigger_insert_insert_0aaee', table='rides_ridemodel', when='AFTER')),
),
pgtrigger.migrations.AddTrigger(
model_name='ridemodel',
trigger=pgtrigger.compiler.Trigger(name='update_update', sql=pgtrigger.compiler.UpsertTriggerSql(condition='WHEN (OLD.* IS DISTINCT FROM NEW.*)', func='INSERT INTO "rides_ridemodelevent" ("category", "created_at", "description", "first_installation_year", "id", "is_discontinued", "is_test_data", "last_installation_year", "manufacturer_id", "meta_description", "meta_title", "name", "notable_features", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "primary_image_id", "restraint_system", "ride_type", "slug", "source_url", "support_structure", "target_market", "total_installations", "track_type", "train_configuration", "typical_capacity_range_max", "typical_capacity_range_min", "typical_height_range_max_ft", "typical_height_range_min_ft", "typical_speed_range_max_mph", "typical_speed_range_min_mph", "updated_at", "url") VALUES (NEW."category", NEW."created_at", NEW."description", NEW."first_installation_year", NEW."id", NEW."is_discontinued", NEW."is_test_data", NEW."last_installation_year", NEW."manufacturer_id", NEW."meta_description", NEW."meta_title", NEW."name", NEW."notable_features", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."primary_image_id", NEW."restraint_system", NEW."ride_type", NEW."slug", NEW."source_url", NEW."support_structure", NEW."target_market", NEW."total_installations", NEW."track_type", NEW."train_configuration", NEW."typical_capacity_range_max", NEW."typical_capacity_range_min", NEW."typical_height_range_max_ft", NEW."typical_height_range_min_ft", NEW."typical_speed_range_max_mph", NEW."typical_speed_range_min_mph", NEW."updated_at", NEW."url"); RETURN NULL;', hash='f9f826a678fc0ed93ab788206fdb724c5445e469', operation='UPDATE', pgid='pgtrigger_update_update_0ca1a', table='rides_ridemodel', when='AFTER')),
),
]

View File

@@ -0,0 +1,51 @@
# Generated by Django 5.2.9 on 2026-01-08 18:20
import pgtrigger.compiler
import pgtrigger.migrations
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rides', '0037_add_source_url_is_test_data_and_date_precision'),
]
operations = [
pgtrigger.migrations.RemoveTrigger(
model_name='company',
name='insert_insert',
),
pgtrigger.migrations.RemoveTrigger(
model_name='company',
name='update_update',
),
migrations.AddField(
model_name='company',
name='is_test_data',
field=models.BooleanField(default=False, help_text='Whether this is test/development data'),
),
migrations.AddField(
model_name='company',
name='source_url',
field=models.URLField(blank=True, help_text='Source URL for the data (e.g., official website, Wikipedia)'),
),
migrations.AddField(
model_name='companyevent',
name='is_test_data',
field=models.BooleanField(default=False, help_text='Whether this is test/development data'),
),
migrations.AddField(
model_name='companyevent',
name='source_url',
field=models.URLField(blank=True, help_text='Source URL for the data (e.g., official website, Wikipedia)'),
),
pgtrigger.migrations.AddTrigger(
model_name='company',
trigger=pgtrigger.compiler.Trigger(name='insert_insert', sql=pgtrigger.compiler.UpsertTriggerSql(func='INSERT INTO "rides_companyevent" ("banner_image_id", "card_image_id", "coasters_count", "created_at", "description", "founded_date", "founded_date_precision", "founded_year", "headquarters_location", "id", "is_test_data", "location_id", "name", "person_type", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "rides_count", "roles", "slug", "source_url", "updated_at", "url", "website") VALUES (NEW."banner_image_id", NEW."card_image_id", NEW."coasters_count", NEW."created_at", NEW."description", NEW."founded_date", NEW."founded_date_precision", NEW."founded_year", NEW."headquarters_location", NEW."id", NEW."is_test_data", NEW."location_id", NEW."name", NEW."person_type", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."rides_count", NEW."roles", NEW."slug", NEW."source_url", NEW."updated_at", NEW."url", NEW."website"); RETURN NULL;', hash='26c30b4bcabc0661de7627f32a6b12d2ea9895ac', operation='INSERT', pgid='pgtrigger_insert_insert_e7194', table='rides_company', when='AFTER')),
),
pgtrigger.migrations.AddTrigger(
model_name='company',
trigger=pgtrigger.compiler.Trigger(name='update_update', sql=pgtrigger.compiler.UpsertTriggerSql(condition='WHEN (OLD.* IS DISTINCT FROM NEW.*)', func='INSERT INTO "rides_companyevent" ("banner_image_id", "card_image_id", "coasters_count", "created_at", "description", "founded_date", "founded_date_precision", "founded_year", "headquarters_location", "id", "is_test_data", "location_id", "name", "person_type", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "rides_count", "roles", "slug", "source_url", "updated_at", "url", "website") VALUES (NEW."banner_image_id", NEW."card_image_id", NEW."coasters_count", NEW."created_at", NEW."description", NEW."founded_date", NEW."founded_date_precision", NEW."founded_year", NEW."headquarters_location", NEW."id", NEW."is_test_data", NEW."location_id", NEW."name", NEW."person_type", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."rides_count", NEW."roles", NEW."slug", NEW."source_url", NEW."updated_at", NEW."url", NEW."website"); RETURN NULL;', hash='211e480aa3391c67288564ec1fdfa2552956bbba', operation='UPDATE', pgid='pgtrigger_update_update_456a8', table='rides_company', when='AFTER')),
),
]

View File

@@ -23,8 +23,69 @@ class Company(TrackedModel):
description = models.TextField(blank=True, help_text="Detailed company description")
website = models.URLField(blank=True, help_text="Company website URL")
# Person/Entity type
PERSON_TYPE_CHOICES = [
("company", "Company"),
("individual", "Individual"),
("firm", "Firm"),
("organization", "Organization"),
]
person_type = models.CharField(
max_length=20,
choices=PERSON_TYPE_CHOICES,
blank=True,
default="company",
help_text="Type of entity (company, individual, firm, organization)",
)
# General company info
founded_date = models.DateField(null=True, blank=True, help_text="Date the company was founded")
founded_date_precision = models.CharField(
max_length=20,
choices=[
("exact", "Exact"),
("month", "Month"),
("year", "Year"),
("decade", "Decade"),
("century", "Century"),
("approximate", "Approximate"),
],
blank=True,
default="",
help_text="Precision of the founded date",
)
founded_year = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Year the company was founded (alternative to founded_date)",
)
headquarters_location = models.CharField(
max_length=200,
blank=True,
help_text="Headquarters location description (e.g., 'Los Angeles, CA, USA')",
)
# Location relationship (optional)
location = models.ForeignKey(
"parks.ParkLocation",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="companies",
help_text="Linked location record for headquarters",
)
# Image settings - stored as Cloudflare image IDs/URLs
banner_image_id = models.CharField(
max_length=255,
blank=True,
help_text="Cloudflare image ID for banner image",
)
card_image_id = models.CharField(
max_length=255,
blank=True,
help_text="Cloudflare image ID for card image",
)
# Manufacturer-specific fields
rides_count = models.IntegerField(default=0, help_text="Number of rides manufactured (auto-calculated)")
@@ -33,6 +94,16 @@ class Company(TrackedModel):
# Frontend URL
url = models.URLField(blank=True, help_text="Frontend URL for this company")
# Submission metadata fields (from frontend schema)
source_url = models.URLField(
blank=True,
help_text="Source URL for the data (e.g., official website, Wikipedia)",
)
is_test_data = models.BooleanField(
default=False,
help_text="Whether this is test/development data",
)
def __str__(self):
return self.name

View File

@@ -2,6 +2,7 @@ import contextlib
import pghistory
from django.contrib.auth.models import AbstractBaseUser
from django.contrib.postgres.fields import ArrayField
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.text import slugify
@@ -44,6 +45,11 @@ class RideModel(TrackedModel):
blank=True,
help_text="Primary category classification",
)
ride_type = models.CharField(
max_length=100,
blank=True,
help_text="Specific ride type within the category (e.g., 'Flying Coaster', 'Inverted Coaster')",
)
# Technical specifications
typical_height_range_min_ft = models.DecimalField(
@@ -155,6 +161,16 @@ class RideModel(TrackedModel):
# Frontend URL
url = models.URLField(blank=True, help_text="Frontend URL for this ride model")
# Submission metadata fields (from frontend schema)
source_url = models.URLField(
blank=True,
help_text="Source URL for the data (e.g., manufacturer website)",
)
is_test_data = models.BooleanField(
default=False,
help_text="Whether this is test/development data",
)
class Meta(TrackedModel.Meta):
verbose_name = "Ride Model"
verbose_name_plural = "Ride Models"
@@ -509,17 +525,31 @@ class Ride(StateMachineMixin, TrackedModel):
)
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",
max_length=20,
choices=[
("exact", "Exact Date"),
("month", "Month and Year"),
("year", "Year Only"),
("decade", "Decade"),
("century", "Century"),
("approximate", "Approximate"),
],
default="exact",
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",
max_length=20,
choices=[
("exact", "Exact Date"),
("month", "Month and Year"),
("year", "Year Only"),
("decade", "Decade"),
("century", "Century"),
("approximate", "Approximate"),
],
default="exact",
blank=True,
help_text="Precision of the closing date",
)
@@ -541,11 +571,268 @@ class Ride(StateMachineMixin, TrackedModel):
blank=True,
help_text="Minimum age requirement in years (if any)",
)
height_requirement_cm = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Minimum height requirement in centimeters",
)
duration_seconds = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Ride duration in seconds",
)
inversions_count = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Number of inversions (for coasters)",
)
# 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)
# ===== CATEGORY-SPECIFIC FIELDS =====
# These fields support the frontend validation schemas in entityValidationSchemas.ts
# Fields are nullable since they only apply to specific ride categories
# --- Core Stats (6 fields) ---
max_speed_kmh = models.DecimalField(
max_digits=6,
decimal_places=2,
null=True,
blank=True,
help_text="Maximum speed in kilometers per hour",
)
height_meters = models.DecimalField(
max_digits=6,
decimal_places=2,
null=True,
blank=True,
help_text="Height of the ride structure in meters",
)
length_meters = models.DecimalField(
max_digits=8,
decimal_places=2,
null=True,
blank=True,
help_text="Total track/ride length in meters",
)
drop_meters = models.DecimalField(
max_digits=6,
decimal_places=2,
null=True,
blank=True,
help_text="Maximum drop height in meters",
)
gforce_max = models.DecimalField(
max_digits=4,
decimal_places=2,
null=True,
blank=True,
help_text="Maximum G-force experienced",
)
intensity_level = models.CharField(
max_length=20,
blank=True,
help_text="Intensity classification: family, thrill, or extreme",
)
# --- Coaster-Specific (5 fields) ---
coaster_type = models.CharField(
max_length=20,
blank=True,
help_text="Coaster structure type: steel, wood, or hybrid",
)
seating_type = models.CharField(
max_length=20,
blank=True,
help_text="Seating configuration: sit_down, inverted, flying, stand_up, etc.",
)
track_material = ArrayField(
models.CharField(max_length=50),
blank=True,
default=list,
help_text="Track material types (e.g., ['steel', 'wood'])",
)
support_material = ArrayField(
models.CharField(max_length=50),
blank=True,
default=list,
help_text="Support structure material types",
)
propulsion_method = ArrayField(
models.CharField(max_length=50),
blank=True,
default=list,
help_text="Propulsion methods (e.g., ['chain_lift', 'lsm'])",
)
# --- Water Ride (5 fields) ---
water_depth_cm = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Water depth in centimeters",
)
splash_height_meters = models.DecimalField(
max_digits=5,
decimal_places=2,
null=True,
blank=True,
help_text="Maximum splash height in meters",
)
wetness_level = models.CharField(
max_length=20,
blank=True,
help_text="Expected wetness: dry, light, moderate, or soaked",
)
flume_type = models.CharField(
max_length=100,
blank=True,
help_text="Type of flume or water channel",
)
boat_capacity = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Number of passengers per boat/vehicle",
)
# --- Dark Ride (7 fields) ---
theme_name = models.CharField(
max_length=200,
blank=True,
help_text="Primary theme or IP name",
)
story_description = models.TextField(
blank=True,
help_text="Narrative or story description for the ride",
)
show_duration_seconds = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Duration of show elements in seconds",
)
animatronics_count = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Number of animatronic figures",
)
projection_type = models.CharField(
max_length=100,
blank=True,
help_text="Type of projection technology used",
)
ride_system = models.CharField(
max_length=100,
blank=True,
help_text="Ride system type (e.g., trackless, omnimover)",
)
scenes_count = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Number of distinct scenes or show sections",
)
# --- Flat Ride (7 fields) ---
rotation_type = models.CharField(
max_length=20,
blank=True,
help_text="Rotation axis: horizontal, vertical, multi_axis, pendulum, or none",
)
motion_pattern = models.CharField(
max_length=200,
blank=True,
help_text="Description of the ride's motion pattern",
)
platform_count = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Number of ride platforms or gondolas",
)
swing_angle_degrees = models.DecimalField(
max_digits=5,
decimal_places=2,
null=True,
blank=True,
help_text="Maximum swing angle in degrees",
)
rotation_speed_rpm = models.DecimalField(
max_digits=6,
decimal_places=2,
null=True,
blank=True,
help_text="Rotation speed in revolutions per minute",
)
arm_length_meters = models.DecimalField(
max_digits=5,
decimal_places=2,
null=True,
blank=True,
help_text="Length of ride arm in meters",
)
max_height_reached_meters = models.DecimalField(
max_digits=6,
decimal_places=2,
null=True,
blank=True,
help_text="Maximum height reached during ride cycle in meters",
)
# --- Kiddie Ride (4 fields) ---
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 or learning theme if applicable",
)
character_theme = models.CharField(
max_length=200,
blank=True,
help_text="Character or IP theme (e.g., Paw Patrol, Sesame Street)",
)
# --- Transportation (6 fields) ---
transport_type = models.CharField(
max_length=20,
blank=True,
help_text="Transport mode: train, monorail, skylift, ferry, peoplemover, or cable_car",
)
route_length_meters = models.DecimalField(
max_digits=8,
decimal_places=2,
null=True,
blank=True,
help_text="Total route length in meters",
)
stations_count = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Number of stations or stops",
)
vehicle_capacity = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Passenger capacity per vehicle",
)
vehicles_count = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Number of vehicles in operation",
)
round_trip_duration_seconds = models.PositiveIntegerField(
null=True,
blank=True,
help_text="Duration of a complete round trip in seconds",
)
# Image settings - references to existing photos
banner_image = models.ForeignKey(
"RidePhoto",
@@ -568,6 +855,16 @@ class Ride(StateMachineMixin, TrackedModel):
url = models.URLField(blank=True, help_text="Frontend URL for this ride")
park_url = models.URLField(blank=True, help_text="Frontend URL for this ride's park")
# Submission metadata fields (from frontend schema)
source_url = models.URLField(
blank=True,
help_text="Source URL for the data (e.g., official website, RCDB)",
)
is_test_data = models.BooleanField(
default=False,
help_text="Whether this is test/development data",
)
class Meta(TrackedModel.Meta):
verbose_name = "Ride"
verbose_name_plural = "Rides"

View File

@@ -209,7 +209,7 @@ def update_ride_search_text_on_park_change(sender, instance, **kwargs):
logger.exception(f"Failed to update ride search_text on park change: {e}")
@receiver(post_save, sender="parks.Company")
@receiver(post_save, sender="rides.Company")
def update_ride_search_text_on_company_change(sender, instance, **kwargs):
"""
Update ride search_text when manufacturer/designer name changes.

View File

@@ -30,14 +30,14 @@ class RideOpeningWorkflowTests(TestCase):
def _create_ride(self, status="OPERATING", **kwargs):
"""Helper to create a ride with park."""
from apps.parks.models import Company, Park
from apps.rides.models import Ride
from apps.parks.models import Company as ParkCompany, Park
from apps.rides.models import Company as RideCompany, Ride
# Create manufacturer
manufacturer = Company.objects.create(name=f"Manufacturer {timezone.now().timestamp()}", roles=["MANUFACTURER"])
# Create manufacturer (from rides.Company)
manufacturer = RideCompany.objects.create(name=f"Manufacturer {timezone.now().timestamp()}", roles=["MANUFACTURER"])
# Create park with operator
operator = Company.objects.create(name=f"Operator {timezone.now().timestamp()}", roles=["OPERATOR"])
# Create park with operator (from parks.Company)
operator = ParkCompany.objects.create(name=f"Operator {timezone.now().timestamp()}", roles=["OPERATOR"])
park = Park.objects.create(
name=f"Test Park {timezone.now().timestamp()}",
slug=f"test-park-{timezone.now().timestamp()}",
@@ -84,11 +84,11 @@ class RideMaintenanceWorkflowTests(TestCase):
)
def _create_ride(self, status="OPERATING", **kwargs):
from apps.parks.models import Company, Park
from apps.rides.models import Ride
from apps.parks.models import Company as ParkCompany, Park
from apps.rides.models import Company as RideCompany, Ride
manufacturer = Company.objects.create(name=f"Mfr Maint {timezone.now().timestamp()}", roles=["MANUFACTURER"])
operator = Company.objects.create(name=f"Op Maint {timezone.now().timestamp()}", roles=["OPERATOR"])
manufacturer = RideCompany.objects.create(name=f"Mfr Maint {timezone.now().timestamp()}", roles=["MANUFACTURER"])
operator = ParkCompany.objects.create(name=f"Op Maint {timezone.now().timestamp()}", roles=["OPERATOR"])
park = Park.objects.create(
name=f"Park Maint {timezone.now().timestamp()}",
slug=f"park-maint-{timezone.now().timestamp()}",
@@ -140,11 +140,11 @@ class RideSBNOWorkflowTests(TestCase):
)
def _create_ride(self, status="OPERATING", **kwargs):
from apps.parks.models import Company, Park
from apps.rides.models import Ride
from apps.parks.models import Company as ParkCompany, Park
from apps.rides.models import Company as RideCompany, Ride
manufacturer = Company.objects.create(name=f"Mfr SBNO {timezone.now().timestamp()}", roles=["MANUFACTURER"])
operator = Company.objects.create(name=f"Op SBNO {timezone.now().timestamp()}", roles=["OPERATOR"])
manufacturer = RideCompany.objects.create(name=f"Mfr SBNO {timezone.now().timestamp()}", roles=["MANUFACTURER"])
operator = ParkCompany.objects.create(name=f"Op SBNO {timezone.now().timestamp()}", roles=["OPERATOR"])
park = Park.objects.create(
name=f"Park SBNO {timezone.now().timestamp()}",
slug=f"park-sbno-{timezone.now().timestamp()}",
@@ -234,11 +234,11 @@ class RideScheduledClosureWorkflowTests(TestCase):
)
def _create_ride(self, status="OPERATING", **kwargs):
from apps.parks.models import Company, Park
from apps.rides.models import Ride
from apps.parks.models import Company as ParkCompany, Park
from apps.rides.models import Company as RideCompany, Ride
manufacturer = Company.objects.create(name=f"Mfr Closing {timezone.now().timestamp()}", roles=["MANUFACTURER"])
operator = Company.objects.create(name=f"Op Closing {timezone.now().timestamp()}", roles=["OPERATOR"])
manufacturer = RideCompany.objects.create(name=f"Mfr Closing {timezone.now().timestamp()}", roles=["MANUFACTURER"])
operator = ParkCompany.objects.create(name=f"Op Closing {timezone.now().timestamp()}", roles=["OPERATOR"])
park = Park.objects.create(
name=f"Park Closing {timezone.now().timestamp()}",
slug=f"park-closing-{timezone.now().timestamp()}",
@@ -324,11 +324,11 @@ class RideDemolitionWorkflowTests(TestCase):
)
def _create_ride(self, status="CLOSED_PERM", **kwargs):
from apps.parks.models import Company, Park
from apps.rides.models import Ride
from apps.parks.models import Company as ParkCompany, Park
from apps.rides.models import Company as RideCompany, Ride
manufacturer = Company.objects.create(name=f"Mfr Demo {timezone.now().timestamp()}", roles=["MANUFACTURER"])
operator = Company.objects.create(name=f"Op Demo {timezone.now().timestamp()}", roles=["OPERATOR"])
manufacturer = RideCompany.objects.create(name=f"Mfr Demo {timezone.now().timestamp()}", roles=["MANUFACTURER"])
operator = ParkCompany.objects.create(name=f"Op Demo {timezone.now().timestamp()}", roles=["OPERATOR"])
park = Park.objects.create(
name=f"Park Demo {timezone.now().timestamp()}",
slug=f"park-demo-{timezone.now().timestamp()}",
@@ -383,11 +383,11 @@ class RideRelocationWorkflowTests(TestCase):
)
def _create_ride(self, status="CLOSED_PERM", **kwargs):
from apps.parks.models import Company, Park
from apps.rides.models import Ride
from apps.parks.models import Company as ParkCompany, Park
from apps.rides.models import Company as RideCompany, Ride
manufacturer = Company.objects.create(name=f"Mfr Reloc {timezone.now().timestamp()}", roles=["MANUFACTURER"])
operator = Company.objects.create(name=f"Op Reloc {timezone.now().timestamp()}", roles=["OPERATOR"])
manufacturer = RideCompany.objects.create(name=f"Mfr Reloc {timezone.now().timestamp()}", roles=["MANUFACTURER"])
operator = ParkCompany.objects.create(name=f"Op Reloc {timezone.now().timestamp()}", roles=["OPERATOR"])
park = Park.objects.create(
name=f"Park Reloc {timezone.now().timestamp()}",
slug=f"park-reloc-{timezone.now().timestamp()}",
@@ -445,11 +445,11 @@ class RideWrapperMethodTests(TestCase):
)
def _create_ride(self, status="OPERATING", **kwargs):
from apps.parks.models import Company, Park
from apps.rides.models import Ride
from apps.parks.models import Company as ParkCompany, Park
from apps.rides.models import Company as RideCompany, Ride
manufacturer = Company.objects.create(name=f"Mfr Wrapper {timezone.now().timestamp()}", roles=["MANUFACTURER"])
operator = Company.objects.create(name=f"Op Wrapper {timezone.now().timestamp()}", roles=["OPERATOR"])
manufacturer = RideCompany.objects.create(name=f"Mfr Wrapper {timezone.now().timestamp()}", roles=["MANUFACTURER"])
operator = ParkCompany.objects.create(name=f"Op Wrapper {timezone.now().timestamp()}", roles=["OPERATOR"])
park = Park.objects.create(
name=f"Park Wrapper {timezone.now().timestamp()}",
slug=f"park-wrapper-{timezone.now().timestamp()}",
@@ -573,11 +573,11 @@ class RidePostClosingStatusAutomationTests(TestCase):
)
def _create_ride(self, status="CLOSING", **kwargs):
from apps.parks.models import Company, Park
from apps.rides.models import Ride
from apps.parks.models import Company as ParkCompany, Park
from apps.rides.models import Company as RideCompany, Ride
manufacturer = Company.objects.create(name=f"Mfr Auto {timezone.now().timestamp()}", roles=["MANUFACTURER"])
operator = Company.objects.create(name=f"Op Auto {timezone.now().timestamp()}", roles=["OPERATOR"])
manufacturer = RideCompany.objects.create(name=f"Mfr Auto {timezone.now().timestamp()}", roles=["MANUFACTURER"])
operator = ParkCompany.objects.create(name=f"Op Auto {timezone.now().timestamp()}", roles=["OPERATOR"])
park = Park.objects.create(
name=f"Park Auto {timezone.now().timestamp()}",
slug=f"park-auto-{timezone.now().timestamp()}",
@@ -659,11 +659,11 @@ class RideStateLogTests(TestCase):
)
def _create_ride(self, status="OPERATING", **kwargs):
from apps.parks.models import Company, Park
from apps.rides.models import Ride
from apps.parks.models import Company as ParkCompany, Park
from apps.rides.models import Company as RideCompany, Ride
manufacturer = Company.objects.create(name=f"Mfr Log {timezone.now().timestamp()}", roles=["MANUFACTURER"])
operator = Company.objects.create(name=f"Op Log {timezone.now().timestamp()}", roles=["OPERATOR"])
manufacturer = RideCompany.objects.create(name=f"Mfr Log {timezone.now().timestamp()}", roles=["MANUFACTURER"])
operator = ParkCompany.objects.create(name=f"Op Log {timezone.now().timestamp()}", roles=["OPERATOR"])
park = Park.objects.create(
name=f"Park Log {timezone.now().timestamp()}",
slug=f"park-log-{timezone.now().timestamp()}",

Binary file not shown.