Files
thrillwiki_django_no_react/rides/views.py
pacnpal 66ed4347a9 Refactor test utilities and enhance ASGI settings
- Cleaned up and standardized assertions in ApiTestMixin for API response validation.
- Updated ASGI settings to use os.environ for setting the DJANGO_SETTINGS_MODULE.
- Removed unused imports and improved formatting in settings.py.
- Refactored URL patterns in urls.py for better readability and organization.
- Enhanced view functions in views.py for consistency and clarity.
- Added .flake8 configuration for linting and style enforcement.
- Introduced type stubs for django-environ to improve type checking with Pylance.
2025-08-20 19:51:59 -04:00

454 lines
15 KiB
Python

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