first commit

This commit is contained in:
pacnpal
2024-10-28 17:09:57 -04:00
commit 1339baec59
9993 changed files with 1182741 additions and 0 deletions

0
rides/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

169
rides/admin.py Normal file
View File

@@ -0,0 +1,169 @@
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):
model = RollerCoasterStats
can_delete = False
extra = 0
fieldsets = (
('Basic Stats', {
'fields': (
('height_ft', 'length_ft'),
('speed_mph', 'inversions'),
'ride_time_seconds'
)
}),
('Track Details', {
'fields': (
'track_type',
'launch_type'
)
}),
('Train Configuration', {
'fields': (
'train_style',
('trains_count', 'cars_per_train', 'seats_per_car')
)
}),
)
@admin.register(Ride)
class RideAdmin(SimpleHistoryAdmin):
list_display = ('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 = ('created_at', 'updated_at')
history_list_display = ['status', 'manufacturer']
actions = ['mark_as_operating', 'mark_as_closed', 'mark_as_under_maintenance', 'mark_as_removed']
fieldsets = (
('Basic Information', {
'fields': (
'name',
'slug',
'description',
'park',
'park_area'
)
}),
('Ride Details', {
'fields': (
'category',
'manufacturer',
'model_name',
'status'
)
}),
('Dates', {
'fields': (
'opening_date',
'closing_date',
'status_since'
)
}),
('Requirements', {
'fields': (
'min_height_in',
'max_height_in',
'accessibility_options'
)
}),
('Capacity', {
'fields': (
'capacity_per_hour',
'ride_duration_seconds'
)
}),
('Metadata', {
'fields': ('created_at', 'updated_at'),
'classes': ('collapse',)
}),
)
def get_status(self, obj):
status_colors = {
'operating': 'green',
'closed': 'red',
'under_maintenance': 'orange',
'under_construction': 'blue',
'removed': 'grey'
}
return format_html(
'<span style="color: {};">{}</span>',
status_colors.get(obj.status, 'black'),
obj.get_status_display()
)
get_status.short_description = 'Status'
def get_avg_rating(self, obj):
avg = obj.reviews.filter(status='approved').aggregate(avg_rating=Avg('rating'))['avg_rating']
if avg:
return format_html(
'<span style="color: {};">★ {:.1f}</span>',
'gold',
avg
)
return '-'
get_avg_rating.short_description = 'Rating'
def mark_as_operating(self, request, queryset):
queryset.update(status='operating')
mark_as_operating.short_description = "Mark selected rides as operating"
def mark_as_closed(self, request, queryset):
queryset.update(status='closed')
mark_as_closed.short_description = "Mark selected rides as closed"
def mark_as_under_maintenance(self, request, queryset):
queryset.update(status='under_maintenance')
mark_as_under_maintenance.short_description = "Mark selected rides as under maintenance"
def mark_as_removed(self, request, queryset):
queryset.update(status='removed')
mark_as_removed.short_description = "Mark selected rides as removed"
@admin.register(RollerCoasterStats)
class RollerCoasterStatsAdmin(SimpleHistoryAdmin):
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 = ('ride',)
history_list_display = ['height_ft', 'length_ft', 'speed_mph', 'inversions']
fieldsets = (
('Basic Stats', {
'fields': (
'ride',
('height_ft', 'length_ft'),
('speed_mph', 'inversions'),
'ride_time_seconds'
)
}),
('Track Details', {
'fields': (
'track_type',
'launch_type'
)
}),
('Train Configuration', {
'fields': (
'train_style',
('trains_count', 'cars_per_train', 'seats_per_car')
)
}),
)
def get_capacity(self, obj):
if obj.trains_count and obj.cars_per_train and obj.seats_per_car:
capacity = obj.trains_count * obj.cars_per_train * obj.seats_per_car
return format_html(
'{} seats total',
capacity
)
return '-'
get_capacity.short_description = 'Total Capacity'

8
rides/apps.py Normal file
View File

@@ -0,0 +1,8 @@
from django.apps import AppConfig
class RidesConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'rides'
def ready(self):
import rides.signals # noqa

View File

@@ -0,0 +1,140 @@
# Generated by Django 5.1.2 on 2024-10-28 20:17
import django.db.models.deletion
import simple_history.models
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('companies', '0001_initial'),
('parks', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='HistoricalRide',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('slug', models.SlugField(max_length=255)),
('description', models.TextField(blank=True)),
('category', models.CharField(choices=[('RC', 'Roller Coaster'), ('DR', 'Dark Ride'), ('FR', 'Flat Ride'), ('WR', 'Water Ride'), ('TR', 'Transport'), ('OT', 'Other')], default='OT', max_length=2)),
('model_name', models.CharField(blank=True, max_length=255)),
('status', models.CharField(choices=[('OPERATING', 'Operating'), ('CLOSED_TEMP', 'Temporarily Closed'), ('CLOSED_PERM', 'Permanently Closed'), ('UNDER_CONSTRUCTION', 'Under Construction'), ('DEMOLISHED', 'Demolished'), ('RELOCATED', 'Relocated')], default='OPERATING', max_length=20)),
('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)),
('accessibility_options', models.TextField(blank=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(blank=True, editable=False)),
('updated_at', models.DateTimeField(blank=True, editable=False)),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('manufacturer', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='companies.manufacturer')),
('park', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_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='+', to='parks.parkarea')),
],
options={
'verbose_name': 'historical ride',
'verbose_name_plural': 'historical rides',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='Ride',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('slug', models.SlugField(max_length=255)),
('description', models.TextField(blank=True)),
('category', models.CharField(choices=[('RC', 'Roller Coaster'), ('DR', 'Dark Ride'), ('FR', 'Flat Ride'), ('WR', 'Water Ride'), ('TR', 'Transport'), ('OT', 'Other')], default='OT', max_length=2)),
('model_name', models.CharField(blank=True, max_length=255)),
('status', models.CharField(choices=[('OPERATING', 'Operating'), ('CLOSED_TEMP', 'Temporarily Closed'), ('CLOSED_PERM', 'Permanently Closed'), ('UNDER_CONSTRUCTION', 'Under Construction'), ('DEMOLISHED', 'Demolished'), ('RELOCATED', 'Relocated')], default='OPERATING', max_length=20)),
('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)),
('accessibility_options', models.TextField(blank=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)),
('manufacturer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='rides', 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'],
'unique_together': {('park', 'slug')},
},
),
migrations.CreateModel(
name='HistoricalRollerCoasterStats',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, 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)),
('launch_type', models.CharField(choices=[('CHAIN', 'Chain Lift'), ('CABLE', 'Cable Launch'), ('HYDRAULIC', 'Hydraulic Launch'), ('LSM', 'Linear Synchronous Motor'), ('LIM', 'Linear Induction Motor'), ('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)),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('ride', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='rides.ride')),
],
options={
'verbose_name': 'historical Roller Coaster Statistics',
'verbose_name_plural': 'historical Roller Coaster Statistics',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
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)),
('launch_type', models.CharField(choices=[('CHAIN', 'Chain Lift'), ('CABLE', 'Cable Launch'), ('HYDRAULIC', 'Hydraulic Launch'), ('LSM', 'Linear Synchronous Motor'), ('LIM', 'Linear Induction Motor'), ('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',
},
),
]

View File

Binary file not shown.

155
rides/models.py Normal file
View File

@@ -0,0 +1,155 @@
from django.db import models
from django.contrib.contenttypes.fields import GenericRelation
from django.utils.text import slugify
from simple_history.models import HistoricalRecords
class Ride(models.Model):
CATEGORY_CHOICES = [
('RC', 'Roller Coaster'),
('DR', 'Dark Ride'),
('FR', 'Flat Ride'),
('WR', 'Water Ride'),
('TR', 'Transport'),
('OT', 'Other'),
]
STATUS_CHOICES = [
('OPERATING', 'Operating'),
('CLOSED_TEMP', 'Temporarily Closed'),
('CLOSED_PERM', 'Permanently Closed'),
('UNDER_CONSTRUCTION', 'Under Construction'),
('DEMOLISHED', 'Demolished'),
('RELOCATED', 'Relocated'),
]
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255)
description = models.TextField(blank=True)
park = models.ForeignKey(
'parks.Park',
on_delete=models.CASCADE,
related_name='rides'
)
park_area = models.ForeignKey(
'parks.ParkArea',
on_delete=models.SET_NULL,
related_name='rides',
null=True,
blank=True
)
category = models.CharField(
max_length=2,
choices=CATEGORY_CHOICES,
default='OT'
)
manufacturer = models.ForeignKey(
'companies.Manufacturer',
on_delete=models.SET_NULL,
related_name='rides',
null=True,
blank=True
)
model_name = models.CharField(max_length=255, blank=True)
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES,
default='OPERATING'
)
opening_date = models.DateField(null=True, blank=True)
closing_date = models.DateField(null=True, blank=True)
status_since = models.DateField(null=True, blank=True)
min_height_in = models.PositiveIntegerField(null=True, blank=True)
max_height_in = models.PositiveIntegerField(null=True, blank=True)
accessibility_options = models.TextField(blank=True)
capacity_per_hour = models.PositiveIntegerField(null=True, blank=True)
ride_duration_seconds = models.PositiveIntegerField(null=True, blank=True)
average_rating = models.DecimalField(
max_digits=3,
decimal_places=2,
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')
history = HistoricalRecords()
class Meta:
ordering = ['name']
unique_together = ['park', 'slug']
def __str__(self):
return f"{self.name} at {self.park.name}"
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.name)
super().save(*args, **kwargs)
@classmethod
def get_by_slug(cls, slug):
"""Get ride by current or historical slug"""
try:
return cls.objects.get(slug=slug), False
except cls.DoesNotExist:
# Check historical slugs
history = cls.history.filter(slug=slug).order_by('-history_date').first()
if history:
return cls.objects.get(id=history.id), True
raise cls.DoesNotExist("No ride found with this slug")
class RollerCoasterStats(models.Model):
LAUNCH_CHOICES = [
('CHAIN', 'Chain Lift'),
('CABLE', 'Cable Launch'),
('HYDRAULIC', 'Hydraulic Launch'),
('LSM', 'Linear Synchronous Motor'),
('LIM', 'Linear Induction Motor'),
('GRAVITY', 'Gravity'),
('OTHER', 'Other'),
]
ride = models.OneToOneField(
Ride,
on_delete=models.CASCADE,
related_name='coaster_stats'
)
height_ft = models.DecimalField(
max_digits=6,
decimal_places=2,
null=True,
blank=True
)
length_ft = models.DecimalField(
max_digits=7,
decimal_places=2,
null=True,
blank=True
)
speed_mph = models.DecimalField(
max_digits=5,
decimal_places=2,
null=True,
blank=True
)
inversions = models.PositiveIntegerField(default=0)
ride_time_seconds = models.PositiveIntegerField(null=True, blank=True)
track_type = models.CharField(max_length=255, blank=True)
launch_type = models.CharField(
max_length=20,
choices=LAUNCH_CHOICES,
default='CHAIN'
)
train_style = models.CharField(max_length=255, blank=True)
trains_count = models.PositiveIntegerField(null=True, blank=True)
cars_per_train = models.PositiveIntegerField(null=True, blank=True)
seats_per_car = models.PositiveIntegerField(null=True, blank=True)
history = HistoricalRecords()
class Meta:
verbose_name = 'Roller Coaster Statistics'
verbose_name_plural = 'Roller Coaster Statistics'
def __str__(self):
return f"Stats for {self.ride.name}"

0
rides/signals.py Normal file
View File

3
rides/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

9
rides/urls.py Normal file
View File

@@ -0,0 +1,9 @@
from django.urls import path
from . import views
app_name = 'rides'
urlpatterns = [
path('', views.RideListView.as_view(), name='ride_list'),
path('<slug:park_slug>/<slug:ride_slug>/', views.RideDetailView.as_view(), name='ride_detail'),
]

70
rides/views.py Normal file
View File

@@ -0,0 +1,70 @@
from django.views.generic import DetailView, ListView
from django.shortcuts import get_object_or_404
from .models import Ride, RollerCoasterStats
from parks.models import Park
from core.views import SlugRedirectMixin
class RideDetailView(SlugRedirectMixin, DetailView):
model = Ride
template_name = 'rides/ride_detail.html'
context_object_name = 'ride'
slug_url_kwarg = 'ride_slug'
def get_object(self, queryset=None):
if queryset is None:
queryset = self.get_queryset()
park_slug = self.kwargs.get('park_slug')
ride_slug = self.kwargs.get('ride_slug')
# Try to get by current or historical slug
obj, is_old_slug = self.model.get_by_slug(ride_slug)
if obj.park.slug != park_slug:
raise self.model.DoesNotExist("Park slug doesn't match")
return obj
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.object.category == 'RC':
context['coaster_stats'] = RollerCoasterStats.objects.filter(ride=self.object).first()
return context
def get_redirect_url_pattern(self):
return 'ride_detail'
def get_redirect_url_kwargs(self):
return {
'park_slug': self.object.park.slug,
'ride_slug': self.object.slug
}
class RideListView(ListView):
model = Ride
template_name = 'rides/ride_list.html'
context_object_name = 'rides'
paginate_by = 12
def get_queryset(self):
queryset = Ride.objects.select_related('park', 'coaster_stats')
# Filter by category if specified
category = self.request.GET.get('category')
if category:
queryset = queryset.filter(category=category)
# Filter by status if specified
status = self.request.GET.get('status')
if status:
queryset = queryset.filter(status=status)
# Filter by manufacturer if specified
manufacturer = self.request.GET.get('manufacturer')
if manufacturer:
queryset = queryset.filter(manufacturer=manufacturer)
return queryset.order_by('name')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['manufacturers'] = Ride.objects.values_list(
'manufacturer', flat=True
).distinct().order_by('manufacturer')
return context