Enhance moderation dashboard UI and UX:

- Add HTMX-powered filtering with instant updates
- Add smooth transitions and loading states
- Improve visual hierarchy and styling
- Add review notes functionality
- Add confirmation dialogs for actions
- Make navigation sticky
- Add hover effects and visual feedback
- Improve dark mode support
This commit is contained in:
pacnpal
2024-11-13 14:38:38 +00:00
parent d2c9d02523
commit 9ee380c3ea
98 changed files with 5073 additions and 3040 deletions

View File

@@ -1,29 +1,68 @@
from typing import Tuple, Optional, Any
from django.db import models
from django.contrib.contenttypes.fields import GenericRelation
from django.utils.text import slugify
from simple_history.models import HistoricalRecords
from django.contrib.contenttypes.fields import GenericRelation
from history_tracking.models import HistoricalModel
# Shared choices that will be used by multiple models
CATEGORY_CHOICES = [
('', 'Select ride type'),
('RC', 'Roller Coaster'),
('DR', 'Dark Ride'),
('FR', 'Flat Ride'),
('WR', 'Water Ride'),
('TR', 'Transport'),
('OT', 'Other'),
]
class RideModel(HistoricalModel):
"""
Represents a specific model/type of ride that can be manufactured by different companies.
For example: B&M Dive Coaster, Vekoma Boomerang, etc.
"""
name = models.CharField(max_length=255)
manufacturer = models.ForeignKey(
'companies.Manufacturer',
on_delete=models.SET_NULL, # Changed to SET_NULL since it's optional
related_name='ride_models',
null=True, # Made optional
blank=True # Made optional
)
description = models.TextField(blank=True)
category = models.CharField(
max_length=2,
choices=CATEGORY_CHOICES,
default='',
blank=True
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['manufacturer', 'name']
unique_together = ['manufacturer', 'name']
def __str__(self) -> str:
return self.name if not self.manufacturer else f"{self.manufacturer.name} {self.name}"
class Ride(HistoricalModel):
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'),
('SBNO', 'Standing But Not Operating'),
('CLOSING', 'Closing'),
('CLOSED_PERM', 'Permanently Closed'),
('UNDER_CONSTRUCTION', 'Under Construction'),
('DEMOLISHED', 'Demolished'),
('RELOCATED', 'Relocated'),
]
POST_CLOSING_STATUS_CHOICES = [
('SBNO', 'Standing But Not Operating'),
('CLOSED_PERM', 'Permanently Closed'),
]
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255)
description = models.TextField(blank=True)
@@ -42,34 +81,47 @@ class Ride(HistoricalModel):
category = models.CharField(
max_length=2,
choices=CATEGORY_CHOICES,
default='OT'
default='',
blank=True
)
manufacturer = models.ForeignKey(
'companies.manufacturer',
'companies.Manufacturer',
on_delete=models.CASCADE,
null=False,
blank=False
null=True,
blank=True
)
designer = models.ForeignKey(
'designers.Designer',
'companies.Designer',
on_delete=models.SET_NULL,
related_name='rides',
null=True,
blank=True
)
ride_model = models.ForeignKey(
'RideModel',
on_delete=models.SET_NULL,
related_name='rides',
null=True,
blank=True,
help_text='The designer/engineering firm responsible for the ride'
help_text="The specific model/type of this ride"
)
model_name = models.CharField(max_length=255, blank=True)
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES,
default='OPERATING'
)
post_closing_status = models.CharField(
max_length=20,
choices=POST_CLOSING_STATUS_CHOICES,
null=True,
blank=True,
help_text="Status to change to after closing date"
)
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(
@@ -90,64 +142,25 @@ class Ride(HistoricalModel):
def __str__(self) -> str:
return f"{self.name} at {self.park.name}"
def save(self, *args: Any, **kwargs: Any) -> None:
def save(self, *args, **kwargs) -> None:
if not self.slug:
self.slug = slugify(self.name)
super().save(*args, **kwargs)
@classmethod
def get_by_slug(cls, slug: str) -> Tuple['Ride', bool]:
"""Get ride by current or historical slug.
Args:
slug: The slug to look up
Returns:
A tuple of (Ride object, bool indicating if it's a historical slug)
Raises:
cls.DoesNotExist: If no ride is found with the given slug
"""
try:
return cls.objects.get(slug=slug), False
except cls.DoesNotExist as e:
# Check historical slugs
if history := cls.history.filter(slug=slug).order_by('-history_date').first(): # type: ignore[attr-defined]
try:
return cls.objects.get(pk=history.instance.pk), True
except cls.DoesNotExist as inner_e:
raise cls.DoesNotExist("No ride found with this slug") from inner_e
raise cls.DoesNotExist("No ride found with this slug") from e
class RollerCoasterStats(HistoricalModel):
LAUNCH_CHOICES = [
('CHAIN', 'Chain Lift'),
('CABLE', 'Cable Launch'),
('HYDRAULIC', 'Hydraulic Launch'),
('LSM', 'Linear Synchronous Motor'),
('LIM', 'Linear Induction Motor'),
('GRAVITY', 'Gravity'),
('OTHER', 'Other'),
]
class RollerCoasterStats(models.Model):
TRACK_MATERIAL_CHOICES = [
('STEEL', 'Steel'),
('WOOD', 'Wood'),
('HYBRID', 'Hybrid'),
('OTHER', 'Other'),
]
COASTER_TYPE_CHOICES = [
('SITDOWN', 'Sit-Down'),
('SITDOWN', 'Sit Down'),
('INVERTED', 'Inverted'),
('FLYING', 'Flying'),
('STANDUP', 'Stand-Up'),
('STANDUP', 'Stand Up'),
('WING', 'Wing'),
('SUSPENDED', 'Suspended'),
('BOBSLED', 'Bobsled'),
('PIPELINE', 'Pipeline'),
('MOTORBIKE', 'Motorbike'),
('FLOORLESS', 'Floorless'),
('DIVE', 'Dive'),
('FAMILY', 'Family'),
('WILD_MOUSE', 'Wild Mouse'),
@@ -156,6 +169,14 @@ class RollerCoasterStats(HistoricalModel):
('OTHER', 'Other'),
]
LAUNCH_CHOICES = [
('CHAIN', 'Chain Lift'),
('LSM', 'LSM Launch'),
('HYDRAULIC', 'Hydraulic Launch'),
('GRAVITY', 'Gravity'),
('OTHER', 'Other'),
]
ride = models.OneToOneField(
Ride,
on_delete=models.CASCADE,
@@ -192,15 +213,13 @@ class RollerCoasterStats(HistoricalModel):
max_length=20,
choices=COASTER_TYPE_CHOICES,
default='SITDOWN',
blank=True,
help_text='The type/style of roller coaster (e.g. Sit-Down, Inverted, Flying)'
blank=True
)
max_drop_height_ft = models.DecimalField(
max_digits=6,
decimal_places=2,
null=True,
blank=True,
help_text='Maximum vertical drop height in feet'
blank=True
)
launch_type = models.CharField(
max_length=20,