diff --git a/.clinerules/thrillwiki-simple.md b/.clinerules/thrillwiki-simple.md
index f27b9f9c..44df4dbc 100644
--- a/.clinerules/thrillwiki-simple.md
+++ b/.clinerules/thrillwiki-simple.md
@@ -12,8 +12,8 @@ tags: ["django", "architecture", "context7-integration", "thrillwiki"]
Theme park database platform with Django REST Framework serving 120+ API endpoints for parks, rides, companies, and users.
## Core Architecture
-- **Backend**: Django 5.0+, DRF, PostgreSQL+PostGIS, Redis, Celery
-- **Frontend**: HTMX + AlpineJS + Tailwind CSS + Django-Cotton
+- **Backend**: Django 5.1+, DRF, PostgreSQL+PostGIS, Redis, Celery
+- **Frontend**: HTMX (V2+) + AlpineJS + Tailwind CSS (V4+) + Django-Cotton
- 🚨 **ABSOLUTELY NO Custom JS** - use HTMX + AlpineJS ONLY
- Clean, simple UX preferred
- **Media**: Cloudflare Images with Direct Upload
diff --git a/apps/core/urls/search.py b/apps/core/urls/search.py
index 643ff830..7c492d15 100644
--- a/apps/core/urls/search.py
+++ b/apps/core/urls/search.py
@@ -4,6 +4,7 @@ from apps.core.views.search import (
FilterFormView,
LocationSearchView,
LocationSuggestionsView,
+ AdvancedSearchView,
)
from apps.rides.views import RideSearchView
@@ -12,6 +13,7 @@ app_name = "search"
urlpatterns = [
path("parks/", AdaptiveSearchView.as_view(), name="search"),
path("parks/filters/", FilterFormView.as_view(), name="filter_form"),
+ path("advanced/", AdvancedSearchView.as_view(), name="advanced"),
path("rides/", RideSearchView.as_view(), name="ride_search"),
path("rides/results/", RideSearchView.as_view(), name="ride_search_results"),
# Location-aware search
diff --git a/apps/core/views/search.py b/apps/core/views/search.py
index 74affe3b..d9c6dae0 100644
--- a/apps/core/views/search.py
+++ b/apps/core/views/search.py
@@ -176,3 +176,43 @@ class LocationSuggestionsView(TemplateView):
return JsonResponse({"suggestions": suggestions})
except Exception as e:
return JsonResponse({"error": str(e)}, status=500)
+
+
+class AdvancedSearchView(TemplateView):
+ """Advanced search view with comprehensive filtering options for both parks and rides"""
+ template_name = "core/search/advanced.html"
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+
+ # Import here to avoid circular imports
+ from apps.parks.filters import ParkFilter
+ from apps.rides.filters import RideFilter
+ from apps.parks.models import Park
+ from apps.rides.models.rides import Ride
+
+ # Initialize filtersets for both parks and rides
+ park_filterset = ParkFilter(self.request.GET, queryset=Park.objects.all())
+ ride_filterset = RideFilter(self.request.GET, queryset=Ride.objects.all())
+
+ # Determine what type of search to show based on request parameters
+ search_type = self.request.GET.get('search_type', 'parks') # Default to parks
+
+ context.update({
+ 'page_title': 'Advanced Search',
+ 'page_description': 'Find exactly what you\'re looking for with our comprehensive search filters.',
+ 'search_type': search_type,
+ 'park_filters': park_filterset,
+ 'ride_filters': ride_filterset,
+ 'park_results': park_filterset.qs if search_type == 'parks' else None,
+ 'ride_results': ride_filterset.qs if search_type == 'rides' else None,
+ 'has_filters': bool(self.request.GET),
+ })
+
+ return context
+
+ def get_template_names(self):
+ """Return appropriate template for HTMX requests"""
+ if hasattr(self.request, 'htmx') and self.request.htmx:
+ return ["core/search/partials/advanced_results.html"]
+ return [self.template_name]
diff --git a/apps/parks/urls.py b/apps/parks/urls.py
index e4950655..b10c81bd 100644
--- a/apps/parks/urls.py
+++ b/apps/parks/urls.py
@@ -15,6 +15,7 @@ app_name = "parks"
urlpatterns = [
# Park views with autocomplete search
path("", views.ParkListView.as_view(), name="park_list"),
+ path("trending/", views.TrendingParksView.as_view(), name="trending"),
path("operators/", views.OperatorListView.as_view(), name="operator_list"),
path("create/", views.ParkCreateView.as_view(), name="park_create"),
# Add park button endpoint (moved before park detail pattern)
diff --git a/apps/parks/views.py b/apps/parks/views.py
index 2bd79764..ddf2a91e 100644
--- a/apps/parks/views.py
+++ b/apps/parks/views.py
@@ -29,6 +29,9 @@ from django.urls import reverse
from django.shortcuts import get_object_or_404, render
from decimal import InvalidOperation
from django.views.generic import DetailView, ListView, CreateView, UpdateView
+from django.db.models import Count, Avg, Q
+from django.utils import timezone
+from datetime import timedelta
import requests
from decimal import Decimal, ROUND_DOWN
from typing import Any, Optional, cast, Literal, Dict
@@ -224,6 +227,56 @@ def reverse_geocode(request: HttpRequest) -> JsonResponse:
return JsonResponse({"error": "Geocoding failed"}, status=500)
+class TrendingParksView(ListView):
+ """View for displaying trending/popular parks"""
+ model = Park
+ template_name = "parks/trending_parks.html"
+ context_object_name = "parks"
+ paginate_by = 20
+
+ def get_queryset(self) -> QuerySet[Park]:
+ """Get trending parks based on ride count, ratings, and recent activity"""
+ # For now, order by a combination of factors that indicate popularity:
+ # 1. Parks with more rides
+ # 2. Higher average ratings
+ # 3. More recent activity (reviews, photos, etc.)
+ thirty_days_ago = timezone.now() - timedelta(days=30)
+
+ return (
+ get_base_park_queryset()
+ .annotate(
+ recent_reviews=Count(
+ 'reviews',
+ filter=Q(reviews__created_at__gte=thirty_days_ago)
+ ),
+ recent_photos=Count(
+ 'photos',
+ filter=Q(photos__created_at__gte=thirty_days_ago)
+ )
+ )
+ .order_by(
+ '-recent_reviews',
+ '-recent_photos',
+ '-ride_count',
+ '-average_rating'
+ )
+ )
+
+ def get_template_names(self) -> list[str]:
+ """Return appropriate template for HTMX requests"""
+ if self.request.htmx:
+ return ["parks/partials/trending_parks.html"]
+ return [self.template_name]
+
+ def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
+ context = super().get_context_data(**kwargs)
+ context.update({
+ 'page_title': 'Trending Parks',
+ 'page_description': 'Discover the most popular theme parks with recent activity and high ratings.'
+ })
+ return context
+
+
class ParkListView(HTMXFilterableMixin, ListView):
model = Park
template_name = "parks/enhanced_park_list.html"
diff --git a/apps/rides/urls.py b/apps/rides/urls.py
index 27f833bc..b12ebfb3 100644
--- a/apps/rides/urls.py
+++ b/apps/rides/urls.py
@@ -6,6 +6,7 @@ app_name = "rides"
urlpatterns = [
# Global list views
path("", views.RideListView.as_view(), name="global_ride_list"),
+ path("new/", views.NewRidesView.as_view(), name="new"),
# Global category views
path(
"roller-coasters/",
diff --git a/apps/rides/views.py b/apps/rides/views.py
index 09cf0d26..d58982a8 100644
--- a/apps/rides/views.py
+++ b/apps/rides/views.py
@@ -302,6 +302,37 @@ class RideListView(ListView):
return context
+class NewRidesView(ListView):
+ """View for displaying recently added rides"""
+ model = Ride
+ template_name = "rides/new_rides.html"
+ context_object_name = "rides"
+ paginate_by = 20
+
+ def get_queryset(self):
+ """Get recently added rides, ordered by creation date"""
+ return (
+ Ride.objects.all()
+ .select_related("park", "ride_model", "ride_model__manufacturer")
+ .prefetch_related("photos")
+ .order_by("-created_at")
+ )
+
+ def get_template_names(self):
+ """Return appropriate template for HTMX requests"""
+ if hasattr(self.request, "htmx") and self.request.htmx:
+ return ["rides/partials/new_rides.html"]
+ return [self.template_name]
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context.update({
+ 'page_title': 'New Attractions',
+ 'page_description': 'Discover the latest rides and attractions added to theme parks around the world.'
+ })
+ return context
+
+
class SingleCategoryListView(ListView):
"""View for displaying rides of a specific category"""
diff --git a/templates/base/base.html b/templates/base/base.html
index 0ad145ab..0cc513bb 100644
--- a/templates/base/base.html
+++ b/templates/base/base.html
@@ -48,7 +48,6 @@
{% block critical_resources %}
-
{% endblock %}
@@ -62,10 +61,10 @@
/>
-
+
-
+
@@ -173,40 +172,60 @@