mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2026-02-05 00:55:19 -05:00
w
This commit is contained in:
@@ -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)),
|
||||
]
|
||||
|
||||
|
||||
93
backend/apps/core/api/milestone_serializers.py
Normal file
93
backend/apps/core/api/milestone_serializers.py
Normal 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",
|
||||
]
|
||||
79
backend/apps/core/api/milestone_views.py
Normal file
79
backend/apps/core/api/milestone_views.py
Normal 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)
|
||||
94
backend/apps/core/migrations/0010_add_milestone_model.py
Normal file
94
backend/apps/core/migrations/0010_add_milestone_model.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
@@ -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})"
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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')),
|
||||
),
|
||||
]
|
||||
72
backend/apps/parks/migrations/0030_company_schema_parity.py
Normal file
72
backend/apps/parks/migrations/0030_company_schema_parity.py
Normal 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')),
|
||||
),
|
||||
]
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
432
backend/apps/rides/migrations/0034_add_ride_category_fields.py
Normal file
432
backend/apps/rides/migrations/0034_add_ride_category_fields.py
Normal 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')),
|
||||
),
|
||||
]
|
||||
@@ -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')),
|
||||
),
|
||||
]
|
||||
@@ -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')),
|
||||
),
|
||||
]
|
||||
@@ -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')),
|
||||
),
|
||||
]
|
||||
51
backend/apps/rides/migrations/0038_company_schema_parity.py
Normal file
51
backend/apps/rides/migrations/0038_company_schema_parity.py
Normal 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')),
|
||||
),
|
||||
]
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
Reference in New Issue
Block a user