Update migration files for Django 5.1.4; remove obsolete merge migrations and adjust history tracking context in templates

This commit is contained in:
pacnpal
2025-02-10 00:11:29 -05:00
parent 228eeeb3c8
commit 4b32580b13
44 changed files with 2353 additions and 2543 deletions

View File

@@ -1,7 +1,6 @@
from django.contrib import admin
from django.utils.html import format_html
from django.db.models import Avg
from simple_history.admin import SimpleHistoryAdmin
from .models import Ride, RollerCoasterStats
class RollerCoasterStatsInline(admin.StackedInline):
@@ -31,14 +30,13 @@ class RollerCoasterStatsInline(admin.StackedInline):
)
@admin.register(Ride)
class RideAdmin(SimpleHistoryAdmin):
class RideAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'park', 'category', 'get_status', 'manufacturer', 'opening_date', 'get_avg_rating')
list_filter = ('status', 'category', 'manufacturer', 'park')
search_fields = ('name', 'park__name', 'manufacturer__name', 'description')
prepopulated_fields = {'slug': ('name',)}
inlines = [RollerCoasterStatsInline]
readonly_fields = ('id', 'created_at', 'updated_at')
history_list_display = ['status', 'manufacturer']
actions = ['mark_as_operating', 'mark_as_closed', 'mark_as_under_maintenance', 'mark_as_removed']
fieldsets = (
@@ -129,12 +127,11 @@ class RideAdmin(SimpleHistoryAdmin):
mark_as_removed.short_description = "Mark selected rides as demolished"
@admin.register(RollerCoasterStats)
class RollerCoasterStatsAdmin(SimpleHistoryAdmin):
class RollerCoasterStatsAdmin(admin.ModelAdmin):
list_display = ('ride', 'height_ft', 'length_ft', 'speed_mph', 'inversions', 'get_capacity')
list_filter = ('launch_type', 'track_type', 'train_style')
search_fields = ('ride__name', 'track_type')
readonly_fields = ('id', 'ride')
history_list_display = ['height_ft', 'length_ft', 'speed_mph', 'inversions']
fieldsets = (
('Basic Stats', {

70
rides/events.py Normal file
View File

@@ -0,0 +1,70 @@
from typing import Dict
def get_ride_display_changes(changes: Dict) -> Dict:
"""Returns a human-readable version of the ride changes"""
field_names = {
'name': 'Name',
'description': 'Description',
'status': 'Status',
'post_closing_status': 'Post-Closing Status',
'opening_date': 'Opening Date',
'closing_date': 'Closing Date',
'status_since': 'Status Since',
'capacity_per_hour': 'Hourly Capacity',
'min_height_in': 'Minimum Height',
'max_height_in': 'Maximum Height',
'ride_duration_seconds': 'Ride Duration'
}
display_changes = {}
for field, change in changes.items():
if field in field_names:
old_value = change.get('old', '')
new_value = change.get('new', '')
# Format specific fields
if field == 'status':
from .models import Ride
choices = dict(Ride.STATUS_CHOICES)
old_value = choices.get(old_value, old_value)
new_value = choices.get(new_value, new_value)
elif field == 'post_closing_status':
from .models import Ride
choices = dict(Ride.POST_CLOSING_STATUS_CHOICES)
old_value = choices.get(old_value, old_value)
new_value = choices.get(new_value, new_value)
display_changes[field_names[field]] = {
'old': old_value,
'new': new_value
}
return display_changes
def get_ride_model_display_changes(changes: Dict) -> Dict:
"""Returns a human-readable version of the ride model changes"""
field_names = {
'name': 'Name',
'description': 'Description',
'category': 'Category'
}
display_changes = {}
for field, change in changes.items():
if field in field_names:
old_value = change.get('old', '')
new_value = change.get('new', '')
# Format category field
if field == 'category':
from .models import CATEGORY_CHOICES
choices = dict(CATEGORY_CHOICES)
old_value = choices.get(old_value, old_value)
new_value = choices.get(new_value, new_value)
display_changes[field_names[field]] = {
'old': old_value,
'new': new_value
}
return display_changes

View File

@@ -0,0 +1,584 @@
# Generated by Django 5.1.4 on 2025-02-10 01:26
import django.db.models.deletion
import pgtrigger.compiler
import pgtrigger.migrations
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
("companies", "0001_initial"),
("designers", "0001_initial"),
("parks", "0001_initial"),
("pghistory", "0006_delete_aggregateevent"),
]
operations = [
migrations.CreateModel(
name="Ride",
fields=[
("id", models.BigAutoField(primary_key=True, serialize=False)),
("name", models.CharField(max_length=255)),
("slug", models.SlugField(max_length=255)),
("description", models.TextField(blank=True)),
(
"category",
models.CharField(
blank=True,
choices=[
("", "Select ride type"),
("RC", "Roller Coaster"),
("DR", "Dark Ride"),
("FR", "Flat Ride"),
("WR", "Water Ride"),
("TR", "Transport"),
("OT", "Other"),
],
default="",
max_length=2,
),
),
(
"status",
models.CharField(
choices=[
("OPERATING", "Operating"),
("SBNO", "Standing But Not Operating"),
("CLOSING", "Closing"),
("CLOSED_PERM", "Permanently Closed"),
("UNDER_CONSTRUCTION", "Under Construction"),
("DEMOLISHED", "Demolished"),
("RELOCATED", "Relocated"),
],
default="OPERATING",
max_length=20,
),
),
(
"post_closing_status",
models.CharField(
blank=True,
choices=[
("SBNO", "Standing But Not Operating"),
("CLOSED_PERM", "Permanently Closed"),
],
help_text="Status to change to after closing date",
max_length=20,
null=True,
),
),
("opening_date", models.DateField(blank=True, null=True)),
("closing_date", models.DateField(blank=True, null=True)),
("status_since", models.DateField(blank=True, null=True)),
("min_height_in", models.PositiveIntegerField(blank=True, null=True)),
("max_height_in", models.PositiveIntegerField(blank=True, null=True)),
(
"capacity_per_hour",
models.PositiveIntegerField(blank=True, null=True),
),
(
"ride_duration_seconds",
models.PositiveIntegerField(blank=True, null=True),
),
(
"average_rating",
models.DecimalField(
blank=True, decimal_places=2, max_digits=3, null=True
),
),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
(
"designer",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="rides",
to="designers.designer",
),
),
(
"manufacturer",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="companies.manufacturer",
),
),
(
"park",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="rides",
to="parks.park",
),
),
(
"park_area",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="rides",
to="parks.parkarea",
),
),
],
options={
"ordering": ["name"],
},
),
migrations.CreateModel(
name="RideModel",
fields=[
("id", models.BigAutoField(primary_key=True, serialize=False)),
("name", models.CharField(max_length=255)),
("description", models.TextField(blank=True)),
(
"category",
models.CharField(
blank=True,
choices=[
("", "Select ride type"),
("RC", "Roller Coaster"),
("DR", "Dark Ride"),
("FR", "Flat Ride"),
("WR", "Water Ride"),
("TR", "Transport"),
("OT", "Other"),
],
default="",
max_length=2,
),
),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
(
"manufacturer",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="ride_models",
to="companies.manufacturer",
),
),
],
options={
"ordering": ["manufacturer", "name"],
"unique_together": {("manufacturer", "name")},
},
),
migrations.CreateModel(
name="RideEvent",
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()),
("name", models.CharField(max_length=255)),
("slug", models.SlugField(db_index=False, max_length=255)),
("description", models.TextField(blank=True)),
(
"category",
models.CharField(
blank=True,
choices=[
("", "Select ride type"),
("RC", "Roller Coaster"),
("DR", "Dark Ride"),
("FR", "Flat Ride"),
("WR", "Water Ride"),
("TR", "Transport"),
("OT", "Other"),
],
default="",
max_length=2,
),
),
(
"status",
models.CharField(
choices=[
("OPERATING", "Operating"),
("SBNO", "Standing But Not Operating"),
("CLOSING", "Closing"),
("CLOSED_PERM", "Permanently Closed"),
("UNDER_CONSTRUCTION", "Under Construction"),
("DEMOLISHED", "Demolished"),
("RELOCATED", "Relocated"),
],
default="OPERATING",
max_length=20,
),
),
(
"post_closing_status",
models.CharField(
blank=True,
choices=[
("SBNO", "Standing But Not Operating"),
("CLOSED_PERM", "Permanently Closed"),
],
help_text="Status to change to after closing date",
max_length=20,
null=True,
),
),
("opening_date", models.DateField(blank=True, null=True)),
("closing_date", models.DateField(blank=True, null=True)),
("status_since", models.DateField(blank=True, null=True)),
("min_height_in", models.PositiveIntegerField(blank=True, null=True)),
("max_height_in", models.PositiveIntegerField(blank=True, null=True)),
(
"capacity_per_hour",
models.PositiveIntegerField(blank=True, null=True),
),
(
"ride_duration_seconds",
models.PositiveIntegerField(blank=True, null=True),
),
(
"average_rating",
models.DecimalField(
blank=True, decimal_places=2, max_digits=3, null=True
),
),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
(
"designer",
models.ForeignKey(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
related_query_name="+",
to="designers.designer",
),
),
(
"manufacturer",
models.ForeignKey(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
related_query_name="+",
to="companies.manufacturer",
),
),
(
"park",
models.ForeignKey(
db_constraint=False,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
related_query_name="+",
to="parks.park",
),
),
(
"park_area",
models.ForeignKey(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
related_query_name="+",
to="parks.parkarea",
),
),
(
"pgh_context",
models.ForeignKey(
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to="pghistory.context",
),
),
(
"pgh_obj",
models.ForeignKey(
db_constraint=False,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="events",
to="rides.ride",
),
),
(
"ride_model",
models.ForeignKey(
blank=True,
db_constraint=False,
help_text="The specific model/type of this ride",
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
related_query_name="+",
to="rides.ridemodel",
),
),
],
options={
"abstract": False,
},
),
migrations.AddField(
model_name="ride",
name="ride_model",
field=models.ForeignKey(
blank=True,
help_text="The specific model/type of this ride",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="rides",
to="rides.ridemodel",
),
),
migrations.CreateModel(
name="RideModelEvent",
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()),
("name", models.CharField(max_length=255)),
("description", models.TextField(blank=True)),
(
"category",
models.CharField(
blank=True,
choices=[
("", "Select ride type"),
("RC", "Roller Coaster"),
("DR", "Dark Ride"),
("FR", "Flat Ride"),
("WR", "Water Ride"),
("TR", "Transport"),
("OT", "Other"),
],
default="",
max_length=2,
),
),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
(
"manufacturer",
models.ForeignKey(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
related_query_name="+",
to="companies.manufacturer",
),
),
(
"pgh_context",
models.ForeignKey(
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to="pghistory.context",
),
),
(
"pgh_obj",
models.ForeignKey(
db_constraint=False,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="events",
to="rides.ridemodel",
),
),
],
options={
"abstract": False,
},
),
migrations.CreateModel(
name="RollerCoasterStats",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"height_ft",
models.DecimalField(
blank=True, decimal_places=2, max_digits=6, null=True
),
),
(
"length_ft",
models.DecimalField(
blank=True, decimal_places=2, max_digits=7, null=True
),
),
(
"speed_mph",
models.DecimalField(
blank=True, decimal_places=2, max_digits=5, null=True
),
),
("inversions", models.PositiveIntegerField(default=0)),
(
"ride_time_seconds",
models.PositiveIntegerField(blank=True, null=True),
),
("track_type", models.CharField(blank=True, max_length=255)),
(
"track_material",
models.CharField(
blank=True,
choices=[
("STEEL", "Steel"),
("WOOD", "Wood"),
("HYBRID", "Hybrid"),
],
default="STEEL",
max_length=20,
),
),
(
"roller_coaster_type",
models.CharField(
blank=True,
choices=[
("SITDOWN", "Sit Down"),
("INVERTED", "Inverted"),
("FLYING", "Flying"),
("STANDUP", "Stand Up"),
("WING", "Wing"),
("DIVE", "Dive"),
("FAMILY", "Family"),
("WILD_MOUSE", "Wild Mouse"),
("SPINNING", "Spinning"),
("FOURTH_DIMENSION", "4th Dimension"),
("OTHER", "Other"),
],
default="SITDOWN",
max_length=20,
),
),
(
"max_drop_height_ft",
models.DecimalField(
blank=True, decimal_places=2, max_digits=6, null=True
),
),
(
"launch_type",
models.CharField(
choices=[
("CHAIN", "Chain Lift"),
("LSM", "LSM Launch"),
("HYDRAULIC", "Hydraulic Launch"),
("GRAVITY", "Gravity"),
("OTHER", "Other"),
],
default="CHAIN",
max_length=20,
),
),
("train_style", models.CharField(blank=True, max_length=255)),
("trains_count", models.PositiveIntegerField(blank=True, null=True)),
("cars_per_train", models.PositiveIntegerField(blank=True, null=True)),
("seats_per_car", models.PositiveIntegerField(blank=True, null=True)),
(
"ride",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="coaster_stats",
to="rides.ride",
),
),
],
options={
"verbose_name": "Roller Coaster Statistics",
"verbose_name_plural": "Roller Coaster Statistics",
},
),
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", "id", "manufacturer_id", "name", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "updated_at") VALUES (NEW."category", NEW."created_at", NEW."description", NEW."id", NEW."manufacturer_id", NEW."name", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."updated_at"); RETURN NULL;',
hash="[AWS-SECRET-REMOVED]",
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", "id", "manufacturer_id", "name", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "updated_at") VALUES (NEW."category", NEW."created_at", NEW."description", NEW."id", NEW."manufacturer_id", NEW."name", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."updated_at"); RETURN NULL;',
hash="[AWS-SECRET-REMOVED]",
operation="UPDATE",
pgid="pgtrigger_update_update_0ca1a",
table="rides_ridemodel",
when="AFTER",
),
),
),
migrations.AlterUniqueTogether(
name="ride",
unique_together={("park", "slug")},
),
pgtrigger.migrations.AddTrigger(
model_name="ride",
trigger=pgtrigger.compiler.Trigger(
name="insert_insert",
sql=pgtrigger.compiler.UpsertTriggerSql(
func='INSERT INTO "rides_rideevent" ("average_rating", "capacity_per_hour", "category", "closing_date", "created_at", "description", "designer_id", "id", "manufacturer_id", "max_height_in", "min_height_in", "name", "opening_date", "park_area_id", "park_id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "post_closing_status", "ride_duration_seconds", "ride_model_id", "slug", "status", "status_since", "updated_at") VALUES (NEW."average_rating", NEW."capacity_per_hour", NEW."category", NEW."closing_date", NEW."created_at", NEW."description", NEW."designer_id", NEW."id", NEW."manufacturer_id", NEW."max_height_in", NEW."min_height_in", NEW."name", NEW."opening_date", NEW."park_area_id", NEW."park_id", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."post_closing_status", NEW."ride_duration_seconds", NEW."ride_model_id", NEW."slug", NEW."status", NEW."status_since", NEW."updated_at"); RETURN NULL;',
hash="[AWS-SECRET-REMOVED]",
operation="INSERT",
pgid="pgtrigger_insert_insert_52074",
table="rides_ride",
when="AFTER",
),
),
),
pgtrigger.migrations.AddTrigger(
model_name="ride",
trigger=pgtrigger.compiler.Trigger(
name="update_update",
sql=pgtrigger.compiler.UpsertTriggerSql(
condition="WHEN (OLD.* IS DISTINCT FROM NEW.*)",
func='INSERT INTO "rides_rideevent" ("average_rating", "capacity_per_hour", "category", "closing_date", "created_at", "description", "designer_id", "id", "manufacturer_id", "max_height_in", "min_height_in", "name", "opening_date", "park_area_id", "park_id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "post_closing_status", "ride_duration_seconds", "ride_model_id", "slug", "status", "status_since", "updated_at") VALUES (NEW."average_rating", NEW."capacity_per_hour", NEW."category", NEW."closing_date", NEW."created_at", NEW."description", NEW."designer_id", NEW."id", NEW."manufacturer_id", NEW."max_height_in", NEW."min_height_in", NEW."name", NEW."opening_date", NEW."park_area_id", NEW."park_id", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."post_closing_status", NEW."ride_duration_seconds", NEW."ride_model_id", NEW."slug", NEW."status", NEW."status_since", NEW."updated_at"); RETURN NULL;',
hash="[AWS-SECRET-REMOVED]",
operation="UPDATE",
pgid="pgtrigger_update_update_4917a",
table="rides_ride",
when="AFTER",
),
),
),
]

View File

@@ -1,8 +1,8 @@
from django.db import models
from django.utils.text import slugify
from django.contrib.contenttypes.fields import GenericRelation
from history_tracking.models import TrackedModel
import pghistory
from history_tracking.models import TrackedModel, DiffMixin
from .events import get_ride_display_changes, get_ride_model_display_changes
# Shared choices that will be used by multiple models
CATEGORY_CHOICES = [
@@ -15,7 +15,81 @@ CATEGORY_CHOICES = [
('OT', 'Other'),
]
@pghistory.track()
class RideEvent(models.Model, DiffMixin):
"""Event model for tracking Ride changes - uses existing pghistory table"""
pgh_id = models.AutoField(primary_key=True)
pgh_created_at = models.DateTimeField(auto_now_add=True)
pgh_label = models.TextField()
# Original model fields
id = models.BigIntegerField()
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255)
description = models.TextField(blank=True)
category = models.CharField(max_length=2)
status = models.CharField(max_length=20)
post_closing_status = models.CharField(max_length=20, null=True)
opening_date = models.DateField(null=True)
closing_date = models.DateField(null=True)
status_since = models.DateField(null=True)
min_height_in = models.PositiveIntegerField(null=True)
max_height_in = models.PositiveIntegerField(null=True)
capacity_per_hour = models.PositiveIntegerField(null=True)
ride_duration_seconds = models.PositiveIntegerField(null=True)
average_rating = models.DecimalField(max_digits=3, decimal_places=2, null=True)
created_at = models.DateTimeField()
updated_at = models.DateTimeField()
# Foreign keys as IDs
park_id = models.BigIntegerField()
park_area_id = models.BigIntegerField(null=True)
manufacturer_id = models.BigIntegerField(null=True)
designer_id = models.BigIntegerField(null=True)
ride_model_id = models.BigIntegerField(null=True)
# Context fields
pgh_obj = models.ForeignKey('Ride', on_delete=models.CASCADE)
pgh_context = models.JSONField(null=True)
class Meta:
db_table = 'rides_rideevent'
managed = False
def get_display_changes(self) -> dict:
"""Returns human-readable changes"""
return get_ride_display_changes(self.diff_against_previous())
class RideModelEvent(models.Model, DiffMixin):
"""Event model for tracking RideModel changes - uses existing pghistory table"""
pgh_id = models.AutoField(primary_key=True)
pgh_created_at = models.DateTimeField(auto_now_add=True)
pgh_label = models.TextField()
# Original model fields
id = models.BigIntegerField()
name = models.CharField(max_length=255)
description = models.TextField(blank=True)
category = models.CharField(max_length=2)
created_at = models.DateTimeField()
updated_at = models.DateTimeField()
# Foreign keys as IDs
manufacturer_id = models.BigIntegerField(null=True)
# Context fields
pgh_obj = models.ForeignKey('RideModel', on_delete=models.CASCADE)
pgh_context = models.JSONField(null=True)
class Meta:
db_table = 'rides_ridemodelevent'
managed = False
def get_display_changes(self) -> dict:
"""Returns human-readable changes"""
return get_ride_model_display_changes(self.diff_against_previous())
class RideModel(TrackedModel):
"""
Represents a specific model/type of ride that can be manufactured by different companies.
@@ -24,10 +98,10 @@ class RideModel(TrackedModel):
name = models.CharField(max_length=255)
manufacturer = models.ForeignKey(
'companies.Manufacturer',
on_delete=models.SET_NULL, # Changed to SET_NULL since it's optional
on_delete=models.SET_NULL,
related_name='ride_models',
null=True, # Made optional
blank=True # Made optional
null=True,
blank=True
)
description = models.TextField(blank=True)
category = models.CharField(
@@ -36,8 +110,6 @@ class RideModel(TrackedModel):
default='',
blank=True
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['manufacturer', 'name']
@@ -46,10 +118,12 @@ class RideModel(TrackedModel):
def __str__(self) -> str:
return self.name if not self.manufacturer else f"{self.manufacturer.name} {self.name}"
@pghistory.track()
class Ride(TrackedModel):
"""Model for individual ride installations at parks"""
STATUS_CHOICES = [
('', 'Select status'),
('OPERATING', 'Operating'),
('CLOSED_TEMP', 'Temporarily Closed'),
('SBNO', 'Standing But Not Operating'),
('CLOSING', 'Closing'),
('CLOSED_PERM', 'Permanently Closed'),
@@ -91,7 +165,7 @@ class Ride(TrackedModel):
blank=True
)
designer = models.ForeignKey(
'designers.Designer', # Updated to point to the new Designer model
'designers.Designer',
on_delete=models.SET_NULL,
related_name='rides',
null=True,
@@ -130,8 +204,6 @@ class Ride(TrackedModel):
null=True,
blank=True
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
photos = GenericRelation('media.Photo')
reviews = GenericRelation('reviews.Review')
@@ -148,6 +220,7 @@ class Ride(TrackedModel):
super().save(*args, **kwargs)
class RollerCoasterStats(models.Model):
"""Model for tracking roller coaster specific statistics"""
TRACK_MATERIAL_CHOICES = [
('STEEL', 'Steel'),
('WOOD', 'Wood'),

View File

@@ -1,39 +1,26 @@
from typing import Any, Dict, Optional, Tuple, Union, cast, Type
from django.views.generic import DetailView, ListView, CreateView, UpdateView, RedirectView
from django.views.generic import DetailView, ListView, CreateView, UpdateView
from django.shortcuts import get_object_or_404, render
from django.core.serializers.json import DjangoJSONEncoder
from django.urls import reverse
from django.db.models import Q, Model
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.decorators import login_required
from django.contrib.contenttypes.models import ContentType
from django.contrib import messages
from django.http import (
JsonResponse,
HttpResponseRedirect,
Http404,
HttpRequest,
HttpResponse,
)
from django.http import HttpRequest, HttpResponse
from django.db.models import Count
from django.core.files.uploadedfile import UploadedFile
from django.forms import ModelForm
from django.db.models.query import QuerySet
from simple_history.models import HistoricalRecords
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser
from .models import Ride, RollerCoasterStats, RideModel, CATEGORY_CHOICES
from .models import (
Ride, RollerCoasterStats, RideModel, RideEvent,
CATEGORY_CHOICES
)
from .forms import RideForm
from parks.models import Park
from core.views import SlugRedirectMixin
from moderation.mixins import EditSubmissionMixin, PhotoSubmissionMixin, HistoryMixin
from moderation.models import EditSubmission
from media.models import Photo
from accounts.models import User
from companies.models import Manufacturer
from designers.models import Designer
def show_coaster_fields(request: HttpRequest) -> HttpResponse:
"""Show roller coaster specific fields based on category selection"""
category = request.GET.get('category')
@@ -41,6 +28,38 @@ def show_coaster_fields(request: HttpRequest) -> HttpResponse:
return HttpResponse('')
return render(request, "rides/partials/coaster_fields.html")
class RideDetailView(HistoryMixin, DetailView):
"""View for displaying ride details"""
model = Ride
template_name = 'rides/ride_detail.html'
slug_url_kwarg = 'ride_slug'
def get_queryset(self):
"""Get ride for the specific park if park_slug is provided"""
queryset = Ride.objects.all().select_related(
'park',
'ride_model',
'ride_model__manufacturer'
).prefetch_related('photos')
if 'park_slug' in self.kwargs:
queryset = queryset.filter(park__slug=self.kwargs['park_slug'])
return queryset
def get_context_data(self, **kwargs):
"""Add context data"""
context = super().get_context_data(**kwargs)
if 'park_slug' in self.kwargs:
context['park_slug'] = self.kwargs['park_slug']
context['park'] = self.object.park
# Add history records
context['history'] = RideEvent.objects.filter(
pgh_obj_id=self.object.id
).order_by('-pgh_created_at')
return context
class RideCreateView(LoginRequiredMixin, CreateView):
"""View for creating a new ride"""
@@ -79,7 +98,6 @@ class RideCreateView(LoginRequiredMixin, CreateView):
# Check for new manufacturer
manufacturer_name = form.cleaned_data.get('manufacturer_search')
if manufacturer_name and not form.cleaned_data.get('manufacturer'):
# Create submission for new manufacturer
EditSubmission.objects.create(
user=self.request.user,
content_type=ContentType.objects.get_for_model(Manufacturer),
@@ -90,7 +108,6 @@ class RideCreateView(LoginRequiredMixin, CreateView):
# Check for new designer
designer_name = form.cleaned_data.get('designer_search')
if designer_name and not form.cleaned_data.get('designer'):
# Create submission for new designer
EditSubmission.objects.create(
user=self.request.user,
content_type=ContentType.objects.get_for_model(Designer),
@@ -102,7 +119,6 @@ class RideCreateView(LoginRequiredMixin, CreateView):
ride_model_name = form.cleaned_data.get('ride_model_search')
manufacturer = form.cleaned_data.get('manufacturer')
if ride_model_name and not form.cleaned_data.get('ride_model') and manufacturer:
# Create submission for new ride model
EditSubmission.objects.create(
user=self.request.user,
content_type=ContentType.objects.get_for_model(RideModel),
@@ -115,34 +131,6 @@ class RideCreateView(LoginRequiredMixin, CreateView):
return super().form_valid(form)
class RideDetailView(DetailView):
"""View for displaying ride details"""
model = Ride
template_name = 'rides/ride_detail.html'
slug_url_kwarg = 'ride_slug'
def get_queryset(self):
"""Get ride for the specific park if park_slug is provided"""
queryset = Ride.objects.all().select_related(
'park',
'ride_model',
'ride_model__manufacturer'
).prefetch_related('photos')
if 'park_slug' in self.kwargs:
queryset = queryset.filter(park__slug=self.kwargs['park_slug'])
return queryset
def get_context_data(self, **kwargs):
"""Add park_slug to context if it exists"""
context = super().get_context_data(**kwargs)
if 'park_slug' in self.kwargs:
context['park_slug'] = self.kwargs['park_slug']
return context
class RideUpdateView(LoginRequiredMixin, EditSubmissionMixin, UpdateView):
"""View for updating an existing ride"""
model = Ride
@@ -193,7 +181,6 @@ class RideUpdateView(LoginRequiredMixin, EditSubmissionMixin, UpdateView):
# Check for new manufacturer
manufacturer_name = form.cleaned_data.get('manufacturer_search')
if manufacturer_name and not form.cleaned_data.get('manufacturer'):
# Create submission for new manufacturer
EditSubmission.objects.create(
user=self.request.user,
content_type=ContentType.objects.get_for_model(Manufacturer),
@@ -204,7 +191,6 @@ class RideUpdateView(LoginRequiredMixin, EditSubmissionMixin, UpdateView):
# Check for new designer
designer_name = form.cleaned_data.get('designer_search')
if designer_name and not form.cleaned_data.get('designer'):
# Create submission for new designer
EditSubmission.objects.create(
user=self.request.user,
content_type=ContentType.objects.get_for_model(Designer),
@@ -216,7 +202,6 @@ class RideUpdateView(LoginRequiredMixin, EditSubmissionMixin, UpdateView):
ride_model_name = form.cleaned_data.get('ride_model_search')
manufacturer = form.cleaned_data.get('manufacturer')
if ride_model_name and not form.cleaned_data.get('ride_model') and manufacturer:
# Create submission for new ride model
EditSubmission.objects.create(
user=self.request.user,
content_type=ContentType.objects.get_for_model(RideModel),
@@ -229,7 +214,6 @@ class RideUpdateView(LoginRequiredMixin, EditSubmissionMixin, UpdateView):
return super().form_valid(form)
class RideListView(ListView):
"""View for displaying a list of rides"""
model = Ride
@@ -258,7 +242,6 @@ class RideListView(ListView):
context['park_slug'] = self.kwargs['park_slug']
return context
class SingleCategoryListView(ListView):
"""View for displaying rides of a specific category"""
model = Ride
@@ -291,16 +274,9 @@ class SingleCategoryListView(ListView):
context['category'] = dict(CATEGORY_CHOICES).get(self.kwargs['category'])
return context
# Alias for parks app to maintain backward compatibility
ParkSingleCategoryListView = SingleCategoryListView
def is_privileged_user(user: Any) -> bool:
"""Check if user has privileged access"""
return bool(user and hasattr(user, 'is_staff') and (user.is_staff or user.is_superuser))
@login_required
def search_manufacturers(request: HttpRequest) -> HttpResponse:
"""Search manufacturers and return results for HTMX"""
@@ -318,7 +294,6 @@ def search_manufacturers(request: HttpRequest) -> HttpResponse:
{"manufacturers": manufacturers, "search_term": query},
)
@login_required
def search_designers(request: HttpRequest) -> HttpResponse:
"""Search designers and return results for HTMX"""
@@ -336,7 +311,6 @@ def search_designers(request: HttpRequest) -> HttpResponse:
{"designers": designers, "search_term": query},
)
@login_required
def search_ride_models(request: HttpRequest) -> HttpResponse:
"""Search ride models and return results for HTMX"""