mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 07:11:08 -05:00
feat: Enhance parks listing with view mode toggle and search functionality
- Implemented a consolidated search bar for parks with live search capabilities. - Added view mode toggle between grid and list views for better user experience. - Updated park listing template to support dynamic rendering based on selected view mode. - Improved pagination controls with HTMX for seamless navigation. - Fixed import paths in parks and rides API to resolve 501 errors, ensuring proper functionality. - Documented changes and integration requirements for frontend compatibility.
This commit is contained in:
@@ -26,8 +26,7 @@ from drf_spectacular.types import OpenApiTypes
|
||||
|
||||
# Import models
|
||||
try:
|
||||
from apps.parks.models import Park
|
||||
from apps.companies.models import Company
|
||||
from apps.parks.models import Park, Company
|
||||
MODELS_AVAILABLE = True
|
||||
except Exception:
|
||||
Park = None # type: ignore
|
||||
@@ -165,9 +164,10 @@ class ParkListCreateAPIView(APIView):
|
||||
qs = Park.objects.all().select_related(
|
||||
"operator", "property_owner", "location"
|
||||
).prefetch_related("rides").annotate(
|
||||
ride_count=Count('rides'),
|
||||
roller_coaster_count=Count('rides', filter=Q(rides__category='RC')),
|
||||
average_rating=Avg('reviews__rating')
|
||||
ride_count_calculated=Count('rides'),
|
||||
roller_coaster_count_calculated=Count(
|
||||
'rides', filter=Q(rides__category='RC')),
|
||||
average_rating_calculated=Avg('reviews__rating')
|
||||
)
|
||||
|
||||
# Apply comprehensive filtering
|
||||
|
||||
@@ -36,16 +36,15 @@ from apps.api.v1.serializers.rides import (
|
||||
|
||||
# Attempt to import model-level helpers; fall back gracefully if not present.
|
||||
try:
|
||||
from apps.rides.models import Ride, RideModel, Company as RideCompany # type: ignore
|
||||
from apps.parks.models import Park, Company as ParkCompany # type: ignore
|
||||
from apps.rides.models import Ride, RideModel
|
||||
from apps.parks.models import Park, Company
|
||||
|
||||
MODELS_AVAILABLE = True
|
||||
except Exception:
|
||||
Ride = None # type: ignore
|
||||
RideModel = None # type: ignore
|
||||
RideCompany = None # type: ignore
|
||||
Company = None # type: ignore
|
||||
Park = None # type: ignore
|
||||
ParkCompany = None # type: ignore
|
||||
MODELS_AVAILABLE = False
|
||||
|
||||
# Attempt to import ModelChoices to return filter options
|
||||
@@ -630,10 +629,10 @@ class RideDetailAPIView(APIView):
|
||||
},
|
||||
status=status.HTTP_501_NOT_IMPLEMENTED,
|
||||
)
|
||||
|
||||
|
||||
validated_data = serializer_in.validated_data
|
||||
park_change_info = None
|
||||
|
||||
|
||||
# Handle park change specially if park_id is being updated
|
||||
if 'park_id' in validated_data:
|
||||
new_park_id = validated_data.pop('park_id')
|
||||
@@ -644,21 +643,21 @@ class RideDetailAPIView(APIView):
|
||||
park_change_info = ride.move_to_park(new_park)
|
||||
except Park.DoesNotExist: # type: ignore
|
||||
raise NotFound("Target park not found")
|
||||
|
||||
|
||||
# Apply other field updates
|
||||
for key, value in validated_data.items():
|
||||
setattr(ride, key, value)
|
||||
|
||||
|
||||
ride.save()
|
||||
|
||||
|
||||
# Prepare response data
|
||||
serializer = RideDetailOutputSerializer(ride, context={"request": request})
|
||||
response_data = serializer.data
|
||||
|
||||
|
||||
# Add park change information to response if applicable
|
||||
if park_change_info:
|
||||
response_data['park_change_info'] = park_change_info
|
||||
|
||||
|
||||
return Response(response_data)
|
||||
|
||||
def put(self, request: Request, pk: int) -> Response:
|
||||
@@ -894,7 +893,7 @@ class CompanySearchAPIView(APIView):
|
||||
if not q:
|
||||
return Response([], status=status.HTTP_200_OK)
|
||||
|
||||
if RideCompany is None:
|
||||
if Company is None:
|
||||
# Provide helpful placeholder structure
|
||||
return Response(
|
||||
[
|
||||
@@ -903,7 +902,7 @@ class CompanySearchAPIView(APIView):
|
||||
]
|
||||
)
|
||||
|
||||
qs = RideCompany.objects.filter(name__icontains=q)[:20] # type: ignore
|
||||
qs = Company.objects.filter(name__icontains=q)[:20] # type: ignore
|
||||
results = [
|
||||
{"id": c.id, "name": c.name, "slug": getattr(c, "slug", "")} for c in qs
|
||||
]
|
||||
|
||||
@@ -235,9 +235,9 @@ class ParkListView(HTMXFilterableMixin, ListView):
|
||||
self.filter_service = ParkFilterService()
|
||||
|
||||
def get_template_names(self) -> list[str]:
|
||||
"""Return park_list_item.html for HTMX requests"""
|
||||
"""Return park_list.html for HTMX requests"""
|
||||
if self.request.htmx:
|
||||
return ["parks/partials/park_list_item.html"]
|
||||
return ["parks/partials/park_list.html"]
|
||||
return [self.template_name]
|
||||
|
||||
def get_view_mode(self) -> ViewMode:
|
||||
@@ -514,7 +514,7 @@ class ParkCreateView(LoginRequiredMixin, CreateView):
|
||||
if result['status'] == 'auto_approved':
|
||||
# Moderator submission was auto-approved
|
||||
self.object = result['created_object']
|
||||
|
||||
|
||||
if form.cleaned_data.get("latitude") and form.cleaned_data.get("longitude"):
|
||||
# Create or update ParkLocation
|
||||
park_location, created = ParkLocation.objects.get_or_create(
|
||||
@@ -555,7 +555,7 @@ class ParkCreateView(LoginRequiredMixin, CreateView):
|
||||
f"Added {uploaded_count} photo(s).",
|
||||
)
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
|
||||
elif result['status'] == 'queued':
|
||||
# Regular user submission was queued
|
||||
messages.success(
|
||||
@@ -565,7 +565,7 @@ class ParkCreateView(LoginRequiredMixin, CreateView):
|
||||
)
|
||||
# Redirect to parks list since we don't have an object yet
|
||||
return HttpResponseRedirect(reverse("parks:park_list"))
|
||||
|
||||
|
||||
elif result['status'] == 'failed':
|
||||
# Auto-approval failed
|
||||
messages.error(
|
||||
@@ -573,7 +573,7 @@ class ParkCreateView(LoginRequiredMixin, CreateView):
|
||||
f"Error creating park: {result['message']}. Please check your input and try again.",
|
||||
)
|
||||
return self.form_invalid(form)
|
||||
|
||||
|
||||
# Fallback error case
|
||||
messages.error(
|
||||
self.request,
|
||||
@@ -727,7 +727,7 @@ class ParkUpdateView(LoginRequiredMixin, UpdateView):
|
||||
f"Added {uploaded_count} new photo(s).",
|
||||
)
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
|
||||
elif result['status'] == 'queued':
|
||||
# Regular user submission was queued
|
||||
messages.success(
|
||||
@@ -738,7 +738,7 @@ class ParkUpdateView(LoginRequiredMixin, UpdateView):
|
||||
return HttpResponseRedirect(
|
||||
reverse(PARK_DETAIL_URL, kwargs={"slug": self.object.slug})
|
||||
)
|
||||
|
||||
|
||||
elif result['status'] == 'failed':
|
||||
# Auto-approval failed
|
||||
messages.error(
|
||||
@@ -746,7 +746,7 @@ class ParkUpdateView(LoginRequiredMixin, UpdateView):
|
||||
f"Error updating park: {result['message']}. Please check your input and try again.",
|
||||
)
|
||||
return self.form_invalid(form)
|
||||
|
||||
|
||||
# Fallback error case
|
||||
messages.error(
|
||||
self.request,
|
||||
|
||||
Reference in New Issue
Block a user