mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 05:11:09 -05:00
feat: Implement Entity Suggestion Manager and Modal components
- Added EntitySuggestionManager.vue to manage entity suggestions and authentication. - Created EntitySuggestionModal.vue for displaying suggestions and adding new entities. - Integrated AuthManager for user authentication within the suggestion modal. - Enhanced signal handling in start-servers.sh for graceful shutdown of servers. - Improved server startup script to ensure proper cleanup and responsiveness to termination signals. - Added documentation for signal handling fixes and usage instructions.
This commit is contained in:
@@ -12,6 +12,8 @@ from .forms import RideForm, RideSearchForm
|
||||
from apps.parks.models import Park
|
||||
from apps.moderation.mixins import EditSubmissionMixin, HistoryMixin
|
||||
from apps.moderation.models import EditSubmission
|
||||
from .models.rankings import RideRanking, RankingSnapshot
|
||||
from .services.ranking_service import RideRankingService
|
||||
|
||||
|
||||
class ParkContextRequired:
|
||||
@@ -452,3 +454,166 @@ class RideSearchView(ListView):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["search_form"] = RideSearchForm(self.request.GET)
|
||||
return context
|
||||
|
||||
|
||||
class RideRankingsView(ListView):
|
||||
"""View for displaying ride rankings using the Internet Roller Coaster Poll algorithm."""
|
||||
|
||||
model = RideRanking
|
||||
template_name = "rides/rankings.html"
|
||||
context_object_name = "rankings"
|
||||
paginate_by = 50
|
||||
|
||||
def get_queryset(self):
|
||||
"""Get rankings with optimized queries."""
|
||||
queryset = RideRanking.objects.select_related(
|
||||
"ride", "ride__park", "ride__manufacturer", "ride__ride_model"
|
||||
).order_by("rank")
|
||||
|
||||
# Filter by category if specified
|
||||
category = self.request.GET.get("category")
|
||||
if category and category != "all":
|
||||
queryset = queryset.filter(ride__category=category)
|
||||
|
||||
# Filter by minimum mutual riders
|
||||
min_riders = self.request.GET.get("min_riders")
|
||||
if min_riders:
|
||||
try:
|
||||
min_riders = int(min_riders)
|
||||
queryset = queryset.filter(mutual_riders_count__gte=min_riders)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return queryset
|
||||
|
||||
def get_template_names(self):
|
||||
"""Return appropriate template based on request type."""
|
||||
if self.request.htmx:
|
||||
return ["rides/partials/rankings_table.html"]
|
||||
return [self.template_name]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Add context for rankings view."""
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["category_choices"] = Categories
|
||||
context["selected_category"] = self.request.GET.get("category", "all")
|
||||
context["min_riders"] = self.request.GET.get("min_riders", "")
|
||||
|
||||
# Add statistics
|
||||
if self.object_list:
|
||||
context["total_ranked"] = RideRanking.objects.count()
|
||||
context["last_updated"] = (
|
||||
self.object_list[0].last_calculated if self.object_list else None
|
||||
)
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class RideRankingDetailView(DetailView):
|
||||
"""View for displaying detailed ranking information for a specific ride."""
|
||||
|
||||
model = Ride
|
||||
template_name = "rides/ranking_detail.html"
|
||||
slug_url_kwarg = "ride_slug"
|
||||
|
||||
def get_queryset(self):
|
||||
"""Get ride with ranking data."""
|
||||
return Ride.objects.select_related(
|
||||
"park", "manufacturer", "ranking"
|
||||
).prefetch_related("comparisons_as_a", "comparisons_as_b", "ranking_history")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Add ranking details to context."""
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
# Get ranking details from service
|
||||
service = RideRankingService()
|
||||
ranking_details = service.get_ride_ranking_details(self.object)
|
||||
|
||||
if ranking_details:
|
||||
context.update(ranking_details)
|
||||
|
||||
# Get recent movement
|
||||
recent_snapshots = RankingSnapshot.objects.filter(
|
||||
ride=self.object
|
||||
).order_by("-snapshot_date")[:7]
|
||||
|
||||
if len(recent_snapshots) >= 2:
|
||||
context["rank_change"] = (
|
||||
recent_snapshots[0].rank - recent_snapshots[1].rank
|
||||
)
|
||||
context["previous_rank"] = recent_snapshots[1].rank
|
||||
else:
|
||||
context["not_ranked"] = True
|
||||
|
||||
return context
|
||||
|
||||
|
||||
def ranking_history_chart(request: HttpRequest, ride_slug: str) -> HttpResponse:
|
||||
"""HTMX endpoint for ranking history chart data."""
|
||||
ride = get_object_or_404(Ride, slug=ride_slug)
|
||||
|
||||
# Get last 30 days of ranking history
|
||||
history = RankingSnapshot.objects.filter(ride=ride).order_by("-snapshot_date")[:30]
|
||||
|
||||
# Prepare data for chart
|
||||
chart_data = [
|
||||
{
|
||||
"date": snapshot.snapshot_date.isoformat(),
|
||||
"rank": snapshot.rank,
|
||||
"win_pct": float(snapshot.winning_percentage) * 100,
|
||||
}
|
||||
for snapshot in reversed(history)
|
||||
]
|
||||
|
||||
return render(
|
||||
request,
|
||||
"rides/partials/ranking_chart.html",
|
||||
{"chart_data": chart_data, "ride": ride},
|
||||
)
|
||||
|
||||
|
||||
def ranking_comparisons(request: HttpRequest, ride_slug: str) -> HttpResponse:
|
||||
"""HTMX endpoint for ride head-to-head comparisons."""
|
||||
ride = get_object_or_404(Ride, slug=ride_slug)
|
||||
|
||||
# Get head-to-head comparisons
|
||||
from django.db.models import Q
|
||||
from .models.rankings import RidePairComparison
|
||||
|
||||
comparisons = (
|
||||
RidePairComparison.objects.filter(Q(ride_a=ride) | Q(ride_b=ride))
|
||||
.select_related("ride_a", "ride_b", "ride_a__park", "ride_b__park")
|
||||
.order_by("-mutual_riders_count")[:20]
|
||||
)
|
||||
|
||||
# Format comparisons for display
|
||||
comparison_data = []
|
||||
for comp in comparisons:
|
||||
if comp.ride_a == ride:
|
||||
opponent = comp.ride_b
|
||||
wins = comp.ride_a_wins
|
||||
losses = comp.ride_b_wins
|
||||
else:
|
||||
opponent = comp.ride_a
|
||||
wins = comp.ride_b_wins
|
||||
losses = comp.ride_a_wins
|
||||
|
||||
result = "win" if wins > losses else "loss" if losses > wins else "tie"
|
||||
|
||||
comparison_data.append(
|
||||
{
|
||||
"opponent": opponent,
|
||||
"wins": wins,
|
||||
"losses": losses,
|
||||
"ties": comp.ties,
|
||||
"result": result,
|
||||
"mutual_riders": comp.mutual_riders_count,
|
||||
}
|
||||
)
|
||||
|
||||
return render(
|
||||
request,
|
||||
"rides/partials/ranking_comparisons.html",
|
||||
{"comparisons": comparison_data, "ride": ride},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user