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 from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.contenttypes.models import ContentType from django.http import HttpRequest, HttpResponse, Http404 from django.db.models import Count from .models import Ride, RideModel, CATEGORY_CHOICES, Company from .forms import RideForm, RideSearchForm from parks.models import Park from moderation.mixins import EditSubmissionMixin, HistoryMixin from moderation.models import EditSubmission 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 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(Company), submission_type="CREATE", changes={"name": manufacturer_name, "roles": ["MANUFACTURER"]}, ) # 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(Company), submission_type="CREATE", changes={"name": designer_name, "roles": ["DESIGNER"]}, ) # 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(Company), submission_type="CREATE", changes={"name": manufacturer_name, "roles": ["MANUFACTURER"]}, ) # 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(Company), submission_type="CREATE", changes={"name": designer_name, "roles": ["DESIGNER"]}, ) # 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 def search_companies(request: HttpRequest) -> HttpResponse: """Search companies and return results for HTMX""" query = request.GET.get("q", "").strip() role = request.GET.get("role", "").upper() companies = Company.objects.all().order_by("name") if role: companies = companies.filter(roles__contains=[role]) if query: companies = companies.filter(name__icontains=query) companies = companies[:10] return render( request, "rides/partials/company_search_results.html", {"companies": companies, "search_term": query}, ) 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}, ) class RideSearchView(ListView): """View for ride search functionality with HTMX support.""" model = Ride template_name = "search/partials/ride_search_results.html" context_object_name = "rides" paginate_by = 20 def get_queryset(self): """Get filtered rides based on search form.""" queryset = Ride.objects.select_related("park").order_by("name") # Process search form form = RideSearchForm(self.request.GET) if form.is_valid(): ride = form.cleaned_data.get("ride") if ride: # If specific ride selected, return just that ride queryset = queryset.filter(id=ride.id) else: # If no specific ride, filter by search term search_term = self.request.GET.get("ride", "").strip() if search_term: queryset = queryset.filter(name__icontains=search_term) return queryset def get_template_names(self): """Return appropriate template based on request type.""" if self.request.htmx: return ["search/partials/ride_search_results.html"] return ["search/ride_search.html"] def get_context_data(self, **kwargs): """Add search form to context.""" context = super().get_context_data(**kwargs) context["search_form"] = RideSearchForm(self.request.GET) return context