Files

435 lines
15 KiB
Python

from typing import Any, Dict, Optional, Tuple, Union, cast, Type
from django.views.generic import DetailView, ListView, CreateView, UpdateView
from django.shortcuts import get_object_or_404, render
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 HttpRequest, HttpResponse, Http404
from django.db.models import Count
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 companies.models import Manufacturer
from designers.models import Designer
class ParkContextRequired:
"""Mixin to require park context for views"""
def dispatch(self, request, *args, **kwargs):
if 'park_slug' not in self.kwargs:
raise Http404("Park context is required")
return super().dispatch(request, *args, **kwargs)
def show_coaster_fields(request: HttpRequest) -> HttpResponse:
"""Show roller coaster specific fields based on category selection"""
category = request.GET.get('category')
if category != 'RC': # Only show for roller coasters
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, ParkContextRequired, CreateView):
"""View for creating a new ride"""
model = Ride
form_class = RideForm
template_name = 'rides/ride_form.html'
def get_success_url(self):
"""Get URL to redirect to after successful creation"""
return reverse('parks:rides:ride_detail', kwargs={
'park_slug': self.park.slug,
'ride_slug': self.object.slug
})
def get_form_kwargs(self):
"""Pass park to the form"""
kwargs = super().get_form_kwargs()
self.park = get_object_or_404(Park, slug=self.kwargs['park_slug'])
kwargs['park'] = self.park
return kwargs
def get_context_data(self, **kwargs):
"""Add park and park_slug to context"""
context = super().get_context_data(**kwargs)
context['park'] = self.park
context['park_slug'] = self.park.slug
context['is_edit'] = False
return context
def form_valid(self, form):
"""Handle form submission including new items"""
# Check for new manufacturer
manufacturer_name = form.cleaned_data.get('manufacturer_search')
if manufacturer_name and not form.cleaned_data.get('manufacturer'):
EditSubmission.objects.create(
user=self.request.user,
content_type=ContentType.objects.get_for_model(Manufacturer),
submission_type="CREATE",
changes={"name": manufacturer_name},
)
# Check for new designer
designer_name = form.cleaned_data.get('designer_search')
if designer_name and not form.cleaned_data.get('designer'):
EditSubmission.objects.create(
user=self.request.user,
content_type=ContentType.objects.get_for_model(Designer),
submission_type="CREATE",
changes={"name": designer_name},
)
# Check for new ride model
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:
EditSubmission.objects.create(
user=self.request.user,
content_type=ContentType.objects.get_for_model(RideModel),
submission_type="CREATE",
changes={
"name": ride_model_name,
"manufacturer": manufacturer.id
},
)
return super().form_valid(form)
class RideUpdateView(LoginRequiredMixin, ParkContextRequired, EditSubmissionMixin, UpdateView):
"""View for updating an existing ride"""
model = Ride
form_class = RideForm
template_name = 'rides/ride_form.html'
slug_url_kwarg = 'ride_slug'
def get_success_url(self):
"""Get URL to redirect to after successful update"""
return reverse('parks:rides:ride_detail', kwargs={
'park_slug': self.park.slug,
'ride_slug': self.object.slug
})
def get_queryset(self):
"""Get ride for the specific park"""
return Ride.objects.filter(park__slug=self.kwargs['park_slug'])
def get_form_kwargs(self):
"""Pass park to the form"""
kwargs = super().get_form_kwargs()
self.park = get_object_or_404(Park, slug=self.kwargs['park_slug'])
kwargs['park'] = self.park
return kwargs
def get_context_data(self, **kwargs):
"""Add park and park_slug to context"""
context = super().get_context_data(**kwargs)
context['park'] = self.park
context['park_slug'] = self.park.slug
context['is_edit'] = True
return context
def form_valid(self, form):
"""Handle form submission including new items"""
# Check for new manufacturer
manufacturer_name = form.cleaned_data.get('manufacturer_search')
if manufacturer_name and not form.cleaned_data.get('manufacturer'):
EditSubmission.objects.create(
user=self.request.user,
content_type=ContentType.objects.get_for_model(Manufacturer),
submission_type="CREATE",
changes={"name": manufacturer_name}
)
# Check for new designer
designer_name = form.cleaned_data.get('designer_search')
if designer_name and not form.cleaned_data.get('designer'):
EditSubmission.objects.create(
user=self.request.user,
content_type=ContentType.objects.get_for_model(Designer),
submission_type="CREATE",
changes={"name": designer_name}
)
# Check for new ride model
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:
EditSubmission.objects.create(
user=self.request.user,
content_type=ContentType.objects.get_for_model(RideModel),
submission_type="CREATE",
changes={
"name": ride_model_name,
"manufacturer": manufacturer.id
}
)
return super().form_valid(form)
class RideListView(ListView):
"""View for displaying a list of rides"""
model = Ride
template_name = 'rides/ride_list.html'
context_object_name = 'rides'
def get_queryset(self):
"""Get filtered rides based on search and filters"""
queryset = Ride.objects.all().select_related(
'park',
'ride_model',
'ride_model__manufacturer'
).prefetch_related('photos')
# Park filter
if 'park_slug' in self.kwargs:
self.park = get_object_or_404(Park, slug=self.kwargs['park_slug'])
queryset = queryset.filter(park=self.park)
# Search term handling
search = self.request.GET.get('q', '').strip()
if search:
# Split search terms for more flexible matching
search_terms = search.split()
search_query = Q()
for term in search_terms:
term_query = Q(
name__icontains=term
) | Q(
park__name__icontains=term
) | Q(
description__icontains=term
)
search_query &= term_query
queryset = queryset.filter(search_query)
# Category filter
category = self.request.GET.get('category')
if category and category != 'all':
queryset = queryset.filter(category=category)
# Operating status filter
if self.request.GET.get('operating') == 'true':
queryset = queryset.filter(status='operating')
return queryset
def get_template_names(self):
"""Return appropriate template based on request type"""
if self.request.htmx:
return ["rides/partials/ride_list_results.html"]
return [self.template_name]
def get_context_data(self, **kwargs):
"""Add park and category choices to context"""
context = super().get_context_data(**kwargs)
if hasattr(self, 'park'):
context['park'] = self.park
context['park_slug'] = self.kwargs['park_slug']
context['category_choices'] = CATEGORY_CHOICES
return context
class SingleCategoryListView(ListView):
"""View for displaying rides of a specific category"""
model = Ride
template_name = 'rides/park_category_list.html'
context_object_name = 'rides'
def get_queryset(self):
"""Get rides filtered by category and optionally by park"""
category = self.kwargs.get('category')
queryset = Ride.objects.filter(
category=category
).select_related(
'park',
'ride_model',
'ride_model__manufacturer'
)
if 'park_slug' in self.kwargs:
self.park = get_object_or_404(Park, slug=self.kwargs['park_slug'])
queryset = queryset.filter(park=self.park)
return queryset
def get_context_data(self, **kwargs):
"""Add park and category information to context"""
context = super().get_context_data(**kwargs)
if hasattr(self, 'park'):
context['park'] = self.park
context['park_slug'] = self.kwargs['park_slug']
context['category'] = dict(CATEGORY_CHOICES).get(
self.kwargs['category'])
return context
# Alias for parks app to maintain backward compatibility
ParkSingleCategoryListView = SingleCategoryListView
@login_required
def search_manufacturers(request: HttpRequest) -> HttpResponse:
"""Search manufacturers and return results for HTMX"""
query = request.GET.get("q", "").strip()
# Show all manufacturers on click, filter on input
manufacturers = Manufacturer.objects.all().order_by("name")
if query:
manufacturers = manufacturers.filter(name__icontains=query)
manufacturers = manufacturers[:10]
return render(
request,
"rides/partials/manufacturer_search_results.html",
{"manufacturers": manufacturers, "search_term": query},
)
@login_required
def search_designers(request: HttpRequest) -> HttpResponse:
"""Search designers and return results for HTMX"""
query = request.GET.get("q", "").strip()
# Show all designers on click, filter on input
designers = Designer.objects.all().order_by("name")
if query:
designers = designers.filter(name__icontains=query)
designers = designers[:10]
return render(
request,
"rides/partials/designer_search_results.html",
{"designers": designers, "search_term": query},
)
@login_required
def search_ride_models(request: HttpRequest) -> HttpResponse:
"""Search ride models and return results for HTMX"""
query = request.GET.get("q", "").strip()
manufacturer_id = request.GET.get("manufacturer")
# Show all ride models on click, filter on input
ride_models = RideModel.objects.select_related(
"manufacturer").order_by("name")
if query:
ride_models = ride_models.filter(name__icontains=query)
if manufacturer_id:
ride_models = ride_models.filter(manufacturer_id=manufacturer_id)
ride_models = ride_models[:10]
return render(
request,
"rides/partials/ride_model_search_results.html",
{"ride_models": ride_models, "search_term": query,
"manufacturer_id": manufacturer_id},
)
def get_search_suggestions(request: HttpRequest) -> HttpResponse:
"""Get smart search suggestions for rides
Returns suggestions including:
- Common matching ride names
- Matching parks
- Matching categories
"""
query = request.GET.get('q', '').strip().lower()
suggestions = []
if query:
# Get common ride names
matching_names = Ride.objects.filter(
name__icontains=query
).values('name').annotate(
count=Count('id')
).order_by('-count')[:3]
for match in matching_names:
suggestions.append({
'type': 'ride',
'text': match['name'],
'count': match['count']
})
# Get matching parks
matching_parks = Park.objects.filter(
Q(name__icontains=query) |
Q(location__city__icontains=query)
)[:3]
for park in matching_parks:
suggestions.append({
'type': 'park',
'text': park.name,
'location': park.location.city if park.location else None
})
# Add category matches
for code, name in CATEGORY_CHOICES:
if query in name.lower():
ride_count = Ride.objects.filter(category=code).count()
suggestions.append({
'type': 'category',
'code': code,
'text': name,
'count': ride_count
})
return render(
request,
'rides/partials/search_suggestions.html',
{
'suggestions': suggestions,
'query': query
}
)