mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 14:51:08 -05:00
Add ParkContextRequired mixin to enforce park context in ride views; update URLs and templates for global ride listing
This commit is contained in:
@@ -7,7 +7,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin
|
|||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse, Http404
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
from .models import (
|
from .models import (
|
||||||
Ride, RollerCoasterStats, RideModel, RideEvent,
|
Ride, RollerCoasterStats, RideModel, RideEvent,
|
||||||
@@ -21,6 +21,13 @@ from moderation.models import EditSubmission
|
|||||||
from companies.models import Manufacturer
|
from companies.models import Manufacturer
|
||||||
from designers.models import Designer
|
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:
|
def show_coaster_fields(request: HttpRequest) -> HttpResponse:
|
||||||
"""Show roller coaster specific fields based on category selection"""
|
"""Show roller coaster specific fields based on category selection"""
|
||||||
category = request.GET.get('category')
|
category = request.GET.get('category')
|
||||||
@@ -61,7 +68,7 @@ class RideDetailView(HistoryMixin, DetailView):
|
|||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
class RideCreateView(LoginRequiredMixin, CreateView):
|
class RideCreateView(LoginRequiredMixin, ParkContextRequired, CreateView):
|
||||||
"""View for creating a new ride"""
|
"""View for creating a new ride"""
|
||||||
model = Ride
|
model = Ride
|
||||||
form_class = RideForm
|
form_class = RideForm
|
||||||
@@ -69,17 +76,14 @@ class RideCreateView(LoginRequiredMixin, CreateView):
|
|||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
"""Get URL to redirect to after successful creation"""
|
"""Get URL to redirect to after successful creation"""
|
||||||
if hasattr(self, 'park'):
|
|
||||||
return reverse('parks:rides:ride_detail', kwargs={
|
return reverse('parks:rides:ride_detail', kwargs={
|
||||||
'park_slug': self.park.slug,
|
'park_slug': self.park.slug,
|
||||||
'ride_slug': self.object.slug
|
'ride_slug': self.object.slug
|
||||||
})
|
})
|
||||||
return reverse('rides:ride_detail', kwargs={'ride_slug': self.object.slug})
|
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
"""Pass park to the form"""
|
"""Pass park to the form"""
|
||||||
kwargs = super().get_form_kwargs()
|
kwargs = super().get_form_kwargs()
|
||||||
if 'park_slug' in self.kwargs:
|
|
||||||
self.park = get_object_or_404(Park, slug=self.kwargs['park_slug'])
|
self.park = get_object_or_404(Park, slug=self.kwargs['park_slug'])
|
||||||
kwargs['park'] = self.park
|
kwargs['park'] = self.park
|
||||||
return kwargs
|
return kwargs
|
||||||
@@ -87,7 +91,6 @@ class RideCreateView(LoginRequiredMixin, CreateView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
"""Add park and park_slug to context"""
|
"""Add park and park_slug to context"""
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
if hasattr(self, 'park'):
|
|
||||||
context['park'] = self.park
|
context['park'] = self.park
|
||||||
context['park_slug'] = self.park.slug
|
context['park_slug'] = self.park.slug
|
||||||
context['is_edit'] = False
|
context['is_edit'] = False
|
||||||
@@ -131,7 +134,7 @@ class RideCreateView(LoginRequiredMixin, CreateView):
|
|||||||
|
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
class RideUpdateView(LoginRequiredMixin, EditSubmissionMixin, UpdateView):
|
class RideUpdateView(LoginRequiredMixin, ParkContextRequired, EditSubmissionMixin, UpdateView):
|
||||||
"""View for updating an existing ride"""
|
"""View for updating an existing ride"""
|
||||||
model = Ride
|
model = Ride
|
||||||
form_class = RideForm
|
form_class = RideForm
|
||||||
@@ -140,37 +143,25 @@ class RideUpdateView(LoginRequiredMixin, EditSubmissionMixin, UpdateView):
|
|||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
"""Get URL to redirect to after successful update"""
|
"""Get URL to redirect to after successful update"""
|
||||||
if hasattr(self, 'park'):
|
|
||||||
return reverse('parks:rides:ride_detail', kwargs={
|
return reverse('parks:rides:ride_detail', kwargs={
|
||||||
'park_slug': self.park.slug,
|
'park_slug': self.park.slug,
|
||||||
'ride_slug': self.object.slug
|
'ride_slug': self.object.slug
|
||||||
})
|
})
|
||||||
return reverse('rides:ride_detail', kwargs={'ride_slug': self.object.slug})
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Get ride for the specific park if park_slug is provided"""
|
"""Get ride for the specific park"""
|
||||||
queryset = Ride.objects.all()
|
return Ride.objects.filter(park__slug=self.kwargs['park_slug'])
|
||||||
if 'park_slug' in self.kwargs:
|
|
||||||
queryset = queryset.filter(park__slug=self.kwargs['park_slug'])
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
"""Pass park to the form"""
|
"""Pass park to the form"""
|
||||||
kwargs = super().get_form_kwargs()
|
kwargs = super().get_form_kwargs()
|
||||||
# For park-specific URLs, use the park from the URL
|
|
||||||
if 'park_slug' in self.kwargs:
|
|
||||||
self.park = get_object_or_404(Park, slug=self.kwargs['park_slug'])
|
self.park = get_object_or_404(Park, slug=self.kwargs['park_slug'])
|
||||||
kwargs['park'] = self.park
|
kwargs['park'] = self.park
|
||||||
# For global URLs, use the ride's park
|
|
||||||
else:
|
|
||||||
self.park = self.get_object().park
|
|
||||||
kwargs['park'] = self.park
|
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
"""Add park and park_slug to context"""
|
"""Add park and park_slug to context"""
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
if hasattr(self, 'park'):
|
|
||||||
context['park'] = self.park
|
context['park'] = self.park
|
||||||
context['park_slug'] = self.park.slug
|
context['park_slug'] = self.park.slug
|
||||||
context['is_edit'] = True
|
context['is_edit'] = True
|
||||||
|
|||||||
@@ -99,7 +99,7 @@
|
|||||||
<i class="fas fa-map-marker-alt"></i>
|
<i class="fas fa-map-marker-alt"></i>
|
||||||
<span>Parks</span>
|
<span>Parks</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'rides:ride_list' %}" class="nav-link">
|
<a href="{% url 'rides:global_ride_list' %}" class="nav-link">
|
||||||
<i class="fas fa-rocket"></i>
|
<i class="fas fa-rocket"></i>
|
||||||
<span>Rides</span>
|
<span>Rides</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
class="px-8 py-3 text-lg btn-primary">
|
class="px-8 py-3 text-lg btn-primary">
|
||||||
Explore Parks
|
Explore Parks
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'rides:ride_list' %}"
|
<a href="{% url 'rides:global_ride_list' %}"
|
||||||
class="px-8 py-3 text-lg btn-secondary">
|
class="px-8 py-3 text-lg btn-secondary">
|
||||||
View Rides
|
View Rides
|
||||||
</a>
|
</a>
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- Total Attractions -->
|
<!-- Total Attractions -->
|
||||||
<a href="{% url 'rides:ride_list' %}"
|
<a href="{% url 'rides:global_ride_list' %}"
|
||||||
class="flex flex-col items-center justify-center p-6 text-center transition-transform transform bg-white rounded-lg shadow-lg dark:bg-gray-800 hover:-translate-y-1 hover:shadow-xl">
|
class="flex flex-col items-center justify-center p-6 text-center transition-transform transform bg-white rounded-lg shadow-lg dark:bg-gray-800 hover:-translate-y-1 hover:shadow-xl">
|
||||||
<div class="mb-2 text-4xl font-bold text-blue-600 dark:text-blue-400">
|
<div class="mb-2 text-4xl font-bold text-blue-600 dark:text-blue-400">
|
||||||
{{ stats.ride_count }}
|
{{ stats.ride_count }}
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- Total Roller Coasters -->
|
<!-- Total Roller Coasters -->
|
||||||
<a href="{% url 'rides:roller_coasters' %}"
|
<a href="{% url 'rides:global_roller_coasters' %}"
|
||||||
class="flex flex-col items-center justify-center p-6 text-center transition-transform transform bg-white rounded-lg shadow-lg dark:bg-gray-800 hover:-translate-y-1 hover:shadow-xl">
|
class="flex flex-col items-center justify-center p-6 text-center transition-transform transform bg-white rounded-lg shadow-lg dark:bg-gray-800 hover:-translate-y-1 hover:shadow-xl">
|
||||||
<div class="mb-2 text-4xl font-bold text-blue-600 dark:text-blue-400">
|
<div class="mb-2 text-4xl font-bold text-blue-600 dark:text-blue-400">
|
||||||
{{ stats.coaster_count }}
|
{{ stats.coaster_count }}
|
||||||
@@ -108,7 +108,7 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
{% for ride in popular_rides %}
|
{% for ride in popular_rides %}
|
||||||
<a href="{% url 'rides:ride_detail' ride.slug %}"
|
<a href="{% url 'parks:rides:ride_detail' ride.park.slug ride.slug %}"
|
||||||
class="relative block h-48 overflow-hidden transition-all rounded-lg group hover:-translate-y-1 hover:shadow-xl"
|
class="relative block h-48 overflow-hidden transition-all rounded-lg group hover:-translate-y-1 hover:shadow-xl"
|
||||||
{% if ride.photos.first %}
|
{% if ride.photos.first %}
|
||||||
style="background: linear-gradient(rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.7)), url('{{ ride.photos.first.image.url }}') center/cover no-repeat;"
|
style="background: linear-gradient(rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.7)), url('{{ ride.photos.first.image.url }}') center/cover no-repeat;"
|
||||||
@@ -147,7 +147,7 @@
|
|||||||
{% for item in highest_rated %}
|
{% for item in highest_rated %}
|
||||||
{% if item.park %}
|
{% if item.park %}
|
||||||
<!-- This is a ride -->
|
<!-- This is a ride -->
|
||||||
<a href="{% url 'rides:ride_detail' item.slug %}"
|
<a href="{% url 'parks:rides:ride_detail' item.park.slug item.slug %}"
|
||||||
class="relative block h-48 overflow-hidden transition-all rounded-lg group hover:-translate-y-1 hover:shadow-xl"
|
class="relative block h-48 overflow-hidden transition-all rounded-lg group hover:-translate-y-1 hover:shadow-xl"
|
||||||
{% if item.photos.first %}
|
{% if item.photos.first %}
|
||||||
style="background: linear-gradient(rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.7)), url('{{ item.photos.first.image.url }}') center/cover no-repeat;"
|
style="background: linear-gradient(rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.7)), url('{{ item.photos.first.image.url }}') center/cover no-repeat;"
|
||||||
|
|||||||
@@ -32,12 +32,7 @@
|
|||||||
Add Ride
|
Add Ride
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{% url 'rides:ride_create' %}" class="inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600">
|
<!-- No add ride button in global view - rides must be added from park pages -->
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
|
||||||
</svg>
|
|
||||||
Add Ride
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -45,7 +40,7 @@
|
|||||||
<!-- Filters -->
|
<!-- Filters -->
|
||||||
<div class="p-4 mb-6 bg-white rounded-lg shadow dark:bg-gray-800">
|
<div class="p-4 mb-6 bg-white rounded-lg shadow dark:bg-gray-800">
|
||||||
<form class="grid grid-cols-1 gap-4 md:grid-cols-3"
|
<form class="grid grid-cols-1 gap-4 md:grid-cols-3"
|
||||||
hx-get="{% if park %}{% url 'parks:rides:ride_list' park.slug %}{% else %}{% url 'rides:ride_list' %}{% endif %}"
|
hx-get="{% if park %}{% url 'parks:rides:ride_list' park.slug %}{% else %}{% url 'rides:global_ride_list' %}{% endif %}"
|
||||||
hx-trigger="change from:select, input from:input[type='text']"
|
hx-trigger="change from:select, input from:input[type='text']"
|
||||||
hx-target="#rides-grid"
|
hx-target="#rides-grid"
|
||||||
hx-push-url="true">
|
hx-push-url="true">
|
||||||
|
|||||||
Reference in New Issue
Block a user