feat: Add PrimeProgress, PrimeSelect, and PrimeSkeleton components with customizable styles and props

- Implemented PrimeProgress component with support for labels, helper text, and various styles (size, variant, color).
- Created PrimeSelect component with dropdown functionality, custom templates, and validation states.
- Developed PrimeSkeleton component for loading placeholders with different shapes and animations.
- Updated index.ts to export new components for easy import.
- Enhanced PrimeVueTest.vue to include tests for new components and their functionalities.
- Introduced a custom ThrillWiki theme for PrimeVue with tailored color schemes and component styles.
- Added ambient type declarations for various components to improve TypeScript support.
This commit is contained in:
pacnpal
2025-08-27 21:00:02 -04:00
parent 6125c4ee44
commit 08a4a2d034
164 changed files with 73094 additions and 11001 deletions

View File

@@ -11,22 +11,121 @@ from rest_framework.filters import OrderingFilter
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework.viewsets import ReadOnlyModelViewSet
from rest_framework.request import Request
from typing import Optional, cast, Sequence
from django.shortcuts import get_object_or_404
from django.db.models import Count
from django.db.models import Count, QuerySet
import pghistory.models
from datetime import datetime
# Import models
from apps.parks.models import Park
from apps.rides.models import Ride
# Import serializers
from ..serializers import (
ParkHistoryEventSerializer,
RideHistoryEventSerializer,
ParkHistoryOutputSerializer,
RideHistoryOutputSerializer,
UnifiedHistoryTimelineSerializer,
from .. import serializers as history_serializers
from rest_framework import serializers as drf_serializers
# Minimal fallback serializer used when a specific serializer symbol is missing.
class _FallbackSerializer(drf_serializers.Serializer):
def to_representation(self, instance):
# return minimal safe representation so responses serialize without errors
return {}
ParkHistoryEventSerializer = getattr(
history_serializers, "ParkHistoryEventSerializer", _FallbackSerializer
)
RideHistoryEventSerializer = getattr(
history_serializers, "RideHistoryEventSerializer", _FallbackSerializer
)
ParkHistoryOutputSerializer = getattr(
history_serializers, "ParkHistoryOutputSerializer", _FallbackSerializer
)
RideHistoryOutputSerializer = getattr(
history_serializers, "RideHistoryOutputSerializer", _FallbackSerializer
)
UnifiedHistoryTimelineSerializer = getattr(
history_serializers, "UnifiedHistoryTimelineSerializer", _FallbackSerializer
)
# --- Constants for model strings to avoid duplication ---
PARK_MODEL = "parks.park"
RIDE_MODELS: Sequence[str] = [
"rides.ride",
"rides.ridemodel",
"rides.rollercoasterstats",
]
COMPANY_MODELS: Sequence[str] = [
"companies.operator",
"companies.propertyowner",
"companies.manufacturer",
"companies.designer",
]
ACCOUNT_MODEL = "accounts.user"
ALL_TRACKED_MODELS: Sequence[str] = [
PARK_MODEL,
*RIDE_MODELS,
*COMPANY_MODELS,
ACCOUNT_MODEL,
]
# --- Helper utilities to reduce duplicated logic / cognitive complexity ---
def _parse_date(date_str: Optional[str]) -> Optional[datetime]:
if not date_str:
return None
try:
return datetime.strptime(date_str, "%Y-%m-%d")
except ValueError:
return None
def _apply_list_filters(
queryset: QuerySet,
request: Request,
*,
default_limit: int = 50,
max_limit: int = 500,
) -> QuerySet:
"""
Apply common 'list' filters: event_type, start/end date, and limit.
Expects request to be a rest_framework.request.Request (cast by caller).
"""
# event_type
event_type = request.query_params.get("event_type")
if event_type == "created":
queryset = queryset.filter(pgh_label="created")
elif event_type == "updated":
queryset = queryset.filter(pgh_label="updated")
elif event_type == "deleted":
queryset = queryset.filter(pgh_label="deleted")
# date range
start_date = _parse_date(request.query_params.get("start_date"))
if start_date:
queryset = queryset.filter(pgh_created_at__gte=start_date)
end_date = _parse_date(request.query_params.get("end_date"))
if end_date:
queryset = queryset.filter(pgh_created_at__lte=end_date)
# limit (slice the queryset)
limit_raw = request.query_params.get("limit", str(default_limit))
try:
limit_val = min(int(limit_raw), max_limit)
queryset = queryset[:limit_val]
except (ValueError, TypeError):
queryset = queryset[:default_limit]
return queryset
@extend_schema_view(
@@ -89,7 +188,7 @@ class ParkHistoryViewSet(ReadOnlyModelViewSet):
ordering_fields = ["pgh_created_at"]
ordering = ["-pgh_created_at"]
def get_queryset(self):
def get_queryset(self): # type: ignore[override]
"""Get history events for the specified park."""
park_slug = self.kwargs.get("park_slug")
if not park_slug:
@@ -98,59 +197,24 @@ class ParkHistoryViewSet(ReadOnlyModelViewSet):
# Get the park to ensure it exists
park = get_object_or_404(Park, slug=park_slug)
# Get all history events for this park
# Base queryset for park events
queryset = (
pghistory.models.Events.objects.filter(
pgh_model__in=["parks.park"], pgh_obj_id=park.id
pgh_model__in=[PARK_MODEL], pgh_obj_id=getattr(park, "id", None)
)
.select_related()
.order_by("-pgh_created_at")
)
# Apply filters
# Apply list filters via helper to reduce complexity
if self.action == "list":
# Filter by event type
event_type = self.request.query_params.get("event_type")
if event_type:
if event_type == "created":
queryset = queryset.filter(pgh_label="created")
elif event_type == "updated":
queryset = queryset.filter(pgh_label="updated")
elif event_type == "deleted":
queryset = queryset.filter(pgh_label="deleted")
# Filter by date range
start_date = self.request.query_params.get("start_date")
if start_date:
try:
from datetime import datetime
start_datetime = datetime.strptime(start_date, "%Y-%m-%d")
queryset = queryset.filter(pgh_created_at__gte=start_datetime)
except ValueError:
pass
end_date = self.request.query_params.get("end_date")
if end_date:
try:
from datetime import datetime
end_datetime = datetime.strptime(end_date, "%Y-%m-%d")
queryset = queryset.filter(pgh_created_at__lte=end_datetime)
except ValueError:
pass
# Apply limit
limit = self.request.query_params.get("limit", "50")
try:
limit = min(int(limit), 500) # Max 500 events
queryset = queryset[:limit]
except (ValueError, TypeError):
queryset = queryset[:50]
queryset = _apply_list_filters(
queryset, cast(Request, self.request), default_limit=50, max_limit=500
)
return queryset
def get_serializer_class(self):
def get_serializer_class(self): # type: ignore[override]
"""Return appropriate serializer based on action."""
if self.action == "retrieve":
return ParkHistoryOutputSerializer
@@ -163,18 +227,18 @@ class ParkHistoryViewSet(ReadOnlyModelViewSet):
# Get history events
history_events = self.get_queryset()[:100] # Latest 100 events
# safe attribute access using getattr to avoid static-checker complaints
first_recorded = getattr(history_events.last(), "pgh_created_at", None)
last_modified = getattr(history_events.first(), "pgh_created_at", None)
# Prepare data for serializer
history_data = {
"park": park,
"current_state": park,
"summary": {
"total_events": self.get_queryset().count(),
"first_recorded": (
history_events.last().pgh_created_at if history_events else None
),
"last_modified": (
history_events.first().pgh_created_at if history_events else None
),
"first_recorded": first_recorded,
"last_modified": last_modified,
},
"events": history_events,
}
@@ -243,7 +307,7 @@ class RideHistoryViewSet(ReadOnlyModelViewSet):
ordering_fields = ["pgh_created_at"]
ordering = ["-pgh_created_at"]
def get_queryset(self):
def get_queryset(self): # type: ignore[override]
"""Get history events for the specified ride."""
park_slug = self.kwargs.get("park_slug")
ride_slug = self.kwargs.get("ride_slug")
@@ -254,64 +318,24 @@ class RideHistoryViewSet(ReadOnlyModelViewSet):
# Get the ride to ensure it exists
ride = get_object_or_404(Ride, slug=ride_slug, park__slug=park_slug)
# Get all history events for this ride
# Base queryset for ride events
queryset = (
pghistory.models.Events.objects.filter(
pgh_model__in=[
"rides.ride",
"rides.ridemodel",
"rides.rollercoasterstats",
],
pgh_obj_id=ride.id,
pgh_model__in=RIDE_MODELS, pgh_obj_id=getattr(ride, "id", None)
)
.select_related()
.order_by("-pgh_created_at")
)
# Apply the same filtering logic as ParkHistoryViewSet
# Apply list filters via helper
if self.action == "list":
# Filter by event type
event_type = self.request.query_params.get("event_type")
if event_type:
if event_type == "created":
queryset = queryset.filter(pgh_label="created")
elif event_type == "updated":
queryset = queryset.filter(pgh_label="updated")
elif event_type == "deleted":
queryset = queryset.filter(pgh_label="deleted")
# Filter by date range
start_date = self.request.query_params.get("start_date")
if start_date:
try:
from datetime import datetime
start_datetime = datetime.strptime(start_date, "%Y-%m-%d")
queryset = queryset.filter(pgh_created_at__gte=start_datetime)
except ValueError:
pass
end_date = self.request.query_params.get("end_date")
if end_date:
try:
from datetime import datetime
end_datetime = datetime.strptime(end_date, "%Y-%m-%d")
queryset = queryset.filter(pgh_created_at__lte=end_datetime)
except ValueError:
pass
# Apply limit
limit = self.request.query_params.get("limit", "50")
try:
limit = min(int(limit), 500) # Max 500 events
queryset = queryset[:limit]
except (ValueError, TypeError):
queryset = queryset[:50]
queryset = _apply_list_filters(
queryset, cast(Request, self.request), default_limit=50, max_limit=500
)
return queryset
def get_serializer_class(self):
def get_serializer_class(self): # type: ignore[override]
"""Return appropriate serializer based on action."""
if self.action == "retrieve":
return RideHistoryOutputSerializer
@@ -324,18 +348,18 @@ class RideHistoryViewSet(ReadOnlyModelViewSet):
# Get history events
history_events = self.get_queryset()[:100] # Latest 100 events
# safe attribute access
first_recorded = getattr(history_events.last(), "pgh_created_at", None)
last_modified = getattr(history_events.first(), "pgh_created_at", None)
# Prepare data for serializer
history_data = {
"ride": ride,
"current_state": ride,
"summary": {
"total_events": self.get_queryset().count(),
"first_recorded": (
history_events.last().pgh_created_at if history_events else None
),
"last_modified": (
history_events.first().pgh_created_at if history_events else None
),
"first_recorded": first_recorded,
"last_modified": last_modified,
},
"events": history_events,
}
@@ -395,6 +419,12 @@ class RideHistoryViewSet(ReadOnlyModelViewSet):
responses={200: UnifiedHistoryTimelineSerializer},
tags=["History"],
),
retrieve=extend_schema(
summary="Get unified history timeline item",
description="Retrieve a specific item from the unified history timeline.",
responses={200: UnifiedHistoryTimelineSerializer},
tags=["History"],
),
)
class UnifiedHistoryViewSet(ReadOnlyModelViewSet):
"""
@@ -409,149 +439,54 @@ class UnifiedHistoryViewSet(ReadOnlyModelViewSet):
ordering_fields = ["pgh_created_at"]
ordering = ["-pgh_created_at"]
def get_queryset(self):
def get_queryset(self): # type: ignore[override]
"""Get unified history events across all tracked models."""
queryset = (
pghistory.models.Events.objects.filter(
pgh_model__in=[
"parks.park",
"rides.ride",
"rides.ridemodel",
"rides.rollercoasterstats",
"companies.operator",
"companies.propertyowner",
"companies.manufacturer",
"companies.designer",
"accounts.user",
]
)
pghistory.models.Events.objects.filter(pgh_model__in=ALL_TRACKED_MODELS)
.select_related()
.order_by("-pgh_created_at")
)
# Apply filters
model_type = self.request.query_params.get("model_type")
if model_type:
if model_type == "park":
queryset = queryset.filter(pgh_model="parks.park")
elif model_type == "ride":
queryset = queryset.filter(
pgh_model__in=[
"rides.ride",
"rides.ridemodel",
"rides.rollercoasterstats",
]
)
elif model_type == "company":
queryset = queryset.filter(
pgh_model__in=[
"companies.operator",
"companies.propertyowner",
"companies.manufacturer",
"companies.designer",
]
)
elif model_type == "user":
queryset = queryset.filter(pgh_model="accounts.user")
# Filter by requested model_type (if provided)
model_type = cast(Request, self.request).query_params.get("model_type")
if model_type == "park":
queryset = queryset.filter(pgh_model=PARK_MODEL)
elif model_type == "ride":
queryset = queryset.filter(pgh_model__in=RIDE_MODELS)
elif model_type == "company":
queryset = queryset.filter(pgh_model__in=COMPANY_MODELS)
elif model_type == "user":
queryset = queryset.filter(pgh_model=ACCOUNT_MODEL)
# Filter by event type
event_type = self.request.query_params.get("event_type")
if event_type:
if event_type == "created":
queryset = queryset.filter(pgh_label="created")
elif event_type == "updated":
queryset = queryset.filter(pgh_label="updated")
elif event_type == "deleted":
queryset = queryset.filter(pgh_label="deleted")
# Filter by date range
start_date = self.request.query_params.get("start_date")
if start_date:
try:
from datetime import datetime
start_datetime = datetime.strptime(start_date, "%Y-%m-%d")
queryset = queryset.filter(pgh_created_at__gte=start_datetime)
except ValueError:
pass
end_date = self.request.query_params.get("end_date")
if end_date:
try:
from datetime import datetime
end_datetime = datetime.strptime(end_date, "%Y-%m-%d")
queryset = queryset.filter(pgh_created_at__lte=end_datetime)
except ValueError:
pass
# Apply limit
limit = self.request.query_params.get("limit", "100")
try:
limit = min(int(limit), 1000) # Max 1000 events
queryset = queryset[:limit]
except (ValueError, TypeError):
queryset = queryset[:100]
# Apply shared list filters when serving the list action
if self.action == "list":
queryset = _apply_list_filters(
queryset, cast(Request, self.request), default_limit=100, max_limit=1000
)
return queryset
def get_serializer_class(self):
def get_serializer_class(self): # type: ignore[override]
"""Return unified history timeline serializer."""
return UnifiedHistoryTimelineSerializer
def list(self, request):
"""Get unified history timeline with summary statistics."""
events = self.get_queryset()
events = list(self.get_queryset()) # evaluate for counts / earliest/latest use
# Calculate summary statistics
# Summary statistics across all tracked models
total_events = pghistory.models.Events.objects.filter(
pgh_model__in=[
"parks.park",
"rides.ride",
"rides.ridemodel",
"rides.rollercoasterstats",
"companies.operator",
"companies.propertyowner",
"companies.manufacturer",
"companies.designer",
"accounts.user",
]
pgh_model__in=ALL_TRACKED_MODELS
).count()
# Get event type counts
event_type_counts = (
pghistory.models.Events.objects.filter(
pgh_model__in=[
"parks.park",
"rides.ride",
"rides.ridemodel",
"rides.rollercoasterstats",
"companies.operator",
"companies.propertyowner",
"companies.manufacturer",
"companies.designer",
"accounts.user",
]
)
pghistory.models.Events.objects.filter(pgh_model__in=ALL_TRACKED_MODELS)
.values("pgh_label")
.annotate(count=Count("id"))
)
# Get model type counts
model_type_counts = (
pghistory.models.Events.objects.filter(
pgh_model__in=[
"parks.park",
"rides.ride",
"rides.ridemodel",
"rides.rollercoasterstats",
"companies.operator",
"companies.propertyowner",
"companies.manufacturer",
"companies.designer",
"accounts.user",
]
)
pghistory.models.Events.objects.filter(pgh_model__in=ALL_TRACKED_MODELS)
.values("pgh_model")
.annotate(count=Count("id"))
)
@@ -567,8 +502,8 @@ class UnifiedHistoryViewSet(ReadOnlyModelViewSet):
item["pgh_model"]: item["count"] for item in model_type_counts
},
"time_range": {
"earliest": events.last().pgh_created_at if events else None,
"latest": events.first().pgh_created_at if events else None,
"earliest": events[-1].pgh_created_at if events else None,
"latest": events[0].pgh_created_at if events else None,
},
},
"events": events,