mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 11:11:10 -05:00
Refactor code structure and remove redundant changes
This commit is contained in:
6
backend/apps/api/v1/history/__init__.py
Normal file
6
backend/apps/api/v1/history/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
"""
|
||||
History API Module
|
||||
|
||||
This module provides API endpoints for accessing historical data and change tracking
|
||||
across all models in the ThrillWiki system.
|
||||
"""
|
||||
45
backend/apps/api/v1/history/urls.py
Normal file
45
backend/apps/api/v1/history/urls.py
Normal file
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
History API URLs
|
||||
|
||||
URL patterns for history-related API endpoints.
|
||||
"""
|
||||
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from .views import (
|
||||
ParkHistoryViewSet,
|
||||
RideHistoryViewSet,
|
||||
UnifiedHistoryViewSet,
|
||||
)
|
||||
|
||||
# Create router for history ViewSets
|
||||
router = DefaultRouter()
|
||||
router.register(r"timeline", UnifiedHistoryViewSet, basename="unified-history")
|
||||
|
||||
urlpatterns = [
|
||||
# Park history endpoints
|
||||
path(
|
||||
"parks/<str:park_slug>/",
|
||||
ParkHistoryViewSet.as_view({"get": "list"}),
|
||||
name="park-history-list",
|
||||
),
|
||||
path(
|
||||
"parks/<str:park_slug>/detail/",
|
||||
ParkHistoryViewSet.as_view({"get": "retrieve"}),
|
||||
name="park-history-detail",
|
||||
),
|
||||
# Ride history endpoints
|
||||
path(
|
||||
"parks/<str:park_slug>/rides/<str:ride_slug>/",
|
||||
RideHistoryViewSet.as_view({"get": "list"}),
|
||||
name="ride-history-list",
|
||||
),
|
||||
path(
|
||||
"parks/<str:park_slug>/rides/<str:ride_slug>/detail/",
|
||||
RideHistoryViewSet.as_view({"get": "retrieve"}),
|
||||
name="ride-history-detail",
|
||||
),
|
||||
# Include router URLs for unified timeline
|
||||
path("", include(router.urls)),
|
||||
]
|
||||
580
backend/apps/api/v1/history/views.py
Normal file
580
backend/apps/api/v1/history/views.py
Normal file
@@ -0,0 +1,580 @@
|
||||
"""
|
||||
History API Views
|
||||
|
||||
This module provides ViewSets for accessing historical data and change tracking
|
||||
across all models in the ThrillWiki system using django-pghistory.
|
||||
"""
|
||||
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from rest_framework.filters import OrderingFilter
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.db.models import Count
|
||||
import pghistory.models
|
||||
|
||||
# Import models
|
||||
from apps.parks.models import Park
|
||||
from apps.rides.models import Ride
|
||||
|
||||
# Import serializers
|
||||
from ..serializers import (
|
||||
ParkHistoryEventSerializer,
|
||||
RideHistoryEventSerializer,
|
||||
ParkHistoryOutputSerializer,
|
||||
RideHistoryOutputSerializer,
|
||||
UnifiedHistoryTimelineSerializer,
|
||||
)
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
list=extend_schema(
|
||||
summary="Get park history",
|
||||
description="Retrieve history timeline for a specific park including all changes over time.",
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="limit",
|
||||
type=OpenApiTypes.INT,
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Number of history events to return (default: 50, max: 500)",
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="offset",
|
||||
type=OpenApiTypes.INT,
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Offset for pagination",
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="event_type",
|
||||
type=OpenApiTypes.STR,
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Filter by event type (created, updated, deleted)",
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="start_date",
|
||||
type=OpenApiTypes.DATE,
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Filter events after this date (YYYY-MM-DD)",
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="end_date",
|
||||
type=OpenApiTypes.DATE,
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Filter events before this date (YYYY-MM-DD)",
|
||||
),
|
||||
],
|
||||
responses={200: ParkHistoryEventSerializer(many=True)},
|
||||
tags=["History", "Parks"],
|
||||
),
|
||||
retrieve=extend_schema(
|
||||
summary="Get complete park history",
|
||||
description="Retrieve complete history for a park including current state and timeline.",
|
||||
responses={200: ParkHistoryOutputSerializer},
|
||||
tags=["History", "Parks"],
|
||||
),
|
||||
)
|
||||
class ParkHistoryViewSet(ReadOnlyModelViewSet):
|
||||
"""
|
||||
ViewSet for accessing park history data.
|
||||
|
||||
Provides read-only access to historical changes for parks,
|
||||
including version history and real-world changes.
|
||||
"""
|
||||
|
||||
permission_classes = [AllowAny]
|
||||
lookup_field = "park_slug"
|
||||
filter_backends = [OrderingFilter]
|
||||
ordering_fields = ["pgh_created_at"]
|
||||
ordering = ["-pgh_created_at"]
|
||||
|
||||
def get_queryset(self):
|
||||
"""Get history events for the specified park."""
|
||||
park_slug = self.kwargs.get("park_slug")
|
||||
if not park_slug:
|
||||
return pghistory.models.Events.objects.none()
|
||||
|
||||
# Get the park to ensure it exists
|
||||
park = get_object_or_404(Park, slug=park_slug)
|
||||
|
||||
# Get all history events for this park
|
||||
queryset = (
|
||||
pghistory.models.Events.objects.filter(
|
||||
pgh_model__in=["parks.park"], pgh_obj_id=park.id
|
||||
)
|
||||
.select_related()
|
||||
.order_by("-pgh_created_at")
|
||||
)
|
||||
|
||||
# Apply filters
|
||||
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]
|
||||
|
||||
return queryset
|
||||
|
||||
def get_serializer_class(self):
|
||||
"""Return appropriate serializer based on action."""
|
||||
if self.action == "retrieve":
|
||||
return ParkHistoryOutputSerializer
|
||||
return ParkHistoryEventSerializer
|
||||
|
||||
def retrieve(self, request, park_slug=None):
|
||||
"""Get complete park history including current state."""
|
||||
park = get_object_or_404(Park, slug=park_slug)
|
||||
|
||||
# Get history events
|
||||
history_events = self.get_queryset()[:100] # Latest 100 events
|
||||
|
||||
# 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
|
||||
),
|
||||
},
|
||||
"events": history_events,
|
||||
}
|
||||
|
||||
serializer = ParkHistoryOutputSerializer(history_data)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
list=extend_schema(
|
||||
summary="Get ride history",
|
||||
description="Retrieve history timeline for a specific ride including all changes over time.",
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="limit",
|
||||
type=OpenApiTypes.INT,
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Number of history events to return (default: 50, max: 500)",
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="offset",
|
||||
type=OpenApiTypes.INT,
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Offset for pagination",
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="event_type",
|
||||
type=OpenApiTypes.STR,
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Filter by event type (created, updated, deleted)",
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="start_date",
|
||||
type=OpenApiTypes.DATE,
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Filter events after this date (YYYY-MM-DD)",
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="end_date",
|
||||
type=OpenApiTypes.DATE,
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Filter events before this date (YYYY-MM-DD)",
|
||||
),
|
||||
],
|
||||
responses={200: RideHistoryEventSerializer(many=True)},
|
||||
tags=["History", "Rides"],
|
||||
),
|
||||
retrieve=extend_schema(
|
||||
summary="Get complete ride history",
|
||||
description="Retrieve complete history for a ride including current state and timeline.",
|
||||
responses={200: RideHistoryOutputSerializer},
|
||||
tags=["History", "Rides"],
|
||||
),
|
||||
)
|
||||
class RideHistoryViewSet(ReadOnlyModelViewSet):
|
||||
"""
|
||||
ViewSet for accessing ride history data.
|
||||
|
||||
Provides read-only access to historical changes for rides,
|
||||
including version history and real-world changes.
|
||||
"""
|
||||
|
||||
permission_classes = [AllowAny]
|
||||
lookup_field = "ride_slug"
|
||||
filter_backends = [OrderingFilter]
|
||||
ordering_fields = ["pgh_created_at"]
|
||||
ordering = ["-pgh_created_at"]
|
||||
|
||||
def get_queryset(self):
|
||||
"""Get history events for the specified ride."""
|
||||
park_slug = self.kwargs.get("park_slug")
|
||||
ride_slug = self.kwargs.get("ride_slug")
|
||||
|
||||
if not park_slug or not ride_slug:
|
||||
return pghistory.models.Events.objects.none()
|
||||
|
||||
# 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
|
||||
queryset = (
|
||||
pghistory.models.Events.objects.filter(
|
||||
pgh_model__in=[
|
||||
"rides.ride",
|
||||
"rides.ridemodel",
|
||||
"rides.rollercoasterstats",
|
||||
],
|
||||
pgh_obj_id=ride.id,
|
||||
)
|
||||
.select_related()
|
||||
.order_by("-pgh_created_at")
|
||||
)
|
||||
|
||||
# Apply the same filtering logic as ParkHistoryViewSet
|
||||
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]
|
||||
|
||||
return queryset
|
||||
|
||||
def get_serializer_class(self):
|
||||
"""Return appropriate serializer based on action."""
|
||||
if self.action == "retrieve":
|
||||
return RideHistoryOutputSerializer
|
||||
return RideHistoryEventSerializer
|
||||
|
||||
def retrieve(self, request, park_slug=None, ride_slug=None):
|
||||
"""Get complete ride history including current state."""
|
||||
ride = get_object_or_404(Ride, slug=ride_slug, park__slug=park_slug)
|
||||
|
||||
# Get history events
|
||||
history_events = self.get_queryset()[:100] # Latest 100 events
|
||||
|
||||
# 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
|
||||
),
|
||||
},
|
||||
"events": history_events,
|
||||
}
|
||||
|
||||
serializer = RideHistoryOutputSerializer(history_data)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
list=extend_schema(
|
||||
summary="Unified history timeline",
|
||||
description="Retrieve a unified timeline of all changes across parks, rides, and companies.",
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="limit",
|
||||
type=OpenApiTypes.INT,
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Number of history events to return (default: 100, max: 1000)",
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="offset",
|
||||
type=OpenApiTypes.INT,
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Offset for pagination",
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="model_type",
|
||||
type=OpenApiTypes.STR,
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Filter by model type (park, ride, company)",
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="event_type",
|
||||
type=OpenApiTypes.STR,
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Filter by event type (created, updated, deleted)",
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="start_date",
|
||||
type=OpenApiTypes.DATE,
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Filter events after this date (YYYY-MM-DD)",
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="end_date",
|
||||
type=OpenApiTypes.DATE,
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Filter events before this date (YYYY-MM-DD)",
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="significance",
|
||||
type=OpenApiTypes.STR,
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Filter by change significance (major, minor, routine)",
|
||||
),
|
||||
],
|
||||
responses={200: UnifiedHistoryTimelineSerializer},
|
||||
tags=["History"],
|
||||
),
|
||||
)
|
||||
class UnifiedHistoryViewSet(ReadOnlyModelViewSet):
|
||||
"""
|
||||
ViewSet for unified history timeline across all models.
|
||||
|
||||
Provides a comprehensive view of all changes across
|
||||
parks, rides, and companies in chronological order.
|
||||
"""
|
||||
|
||||
permission_classes = [AllowAny]
|
||||
filter_backends = [OrderingFilter]
|
||||
ordering_fields = ["pgh_created_at"]
|
||||
ordering = ["-pgh_created_at"]
|
||||
|
||||
def get_queryset(self):
|
||||
"""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",
|
||||
]
|
||||
)
|
||||
.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 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]
|
||||
|
||||
return queryset
|
||||
|
||||
def get_serializer_class(self):
|
||||
"""Return unified history timeline serializer."""
|
||||
return UnifiedHistoryTimelineSerializer
|
||||
|
||||
def list(self, request):
|
||||
"""Get unified history timeline with summary statistics."""
|
||||
events = self.get_queryset()
|
||||
|
||||
# Calculate summary statistics
|
||||
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",
|
||||
]
|
||||
).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",
|
||||
]
|
||||
)
|
||||
.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",
|
||||
]
|
||||
)
|
||||
.values("pgh_model")
|
||||
.annotate(count=Count("id"))
|
||||
)
|
||||
|
||||
timeline_data = {
|
||||
"summary": {
|
||||
"total_events": total_events,
|
||||
"events_returned": len(events),
|
||||
"event_type_breakdown": {
|
||||
item["pgh_label"]: item["count"] for item in event_type_counts
|
||||
},
|
||||
"model_type_breakdown": {
|
||||
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,
|
||||
},
|
||||
},
|
||||
"events": events,
|
||||
}
|
||||
|
||||
serializer = UnifiedHistoryTimelineSerializer(timeline_data)
|
||||
return Response(serializer.data)
|
||||
Reference in New Issue
Block a user