from __future__ import annotations import logging from collections import defaultdict from typing import TYPE_CHECKING, Any from django.db.models import Prefetch from django.http import HttpRequest, HttpResponse from django.shortcuts import render from django.template.response import TemplateResponse from django.utils import timezone from django.views.decorators.http import require_http_methods from core.models import DropCampaign, Game, TimeBasedDrop if TYPE_CHECKING: from django.db.models.query import QuerySet from django.http import HttpRequest logger: logging.Logger = logging.getLogger(__name__) @require_http_methods(request_method_list=["GET", "HEAD"]) def get_home(request: HttpRequest) -> HttpResponse: """Render the index page. Args: request (HttpRequest): The request object. Returns: HttpResponse: The response object """ now = timezone.now() grouped_drops = defaultdict(list) # Query for active drops, efficiently fetching related campaign and game # Also prefetch benefits if you need them in the template current_drops_qs = ( TimeBasedDrop.objects.filter(start_at__lte=now, end_at__gte=now) .select_related( "campaign__game", # Follows ForeignKey relationships campaign -> game ) .prefetch_related( "benefits", # Efficiently fetches ManyToMany benefits ) .order_by( "campaign__game__display_name", # Order by game name first "name", # Then by drop name ) ) # Group the drops by game in Python for drop in current_drops_qs: # Check if the drop has an associated campaign and game if drop.campaign and drop.campaign.game: game = drop.campaign.game grouped_drops[game].append(drop) else: # Handle drops without a game (optional, based on your data integrity) # You could group them under a 'None' key or log a warning # grouped_drops[None].append(drop) pass # Or ignore them context = { "grouped_drops": dict(grouped_drops), # Convert defaultdict back to dict for template if preferred } return render(request, "index.html", context) @require_http_methods(request_method_list=["GET", "HEAD"]) def get_game(request: HttpRequest, twitch_id: int) -> HttpResponse: """Render the game view page. Args: request (HttpRequest): The request object. twitch_id (int): The Twitch ID of the game. Returns: HttpResponse: The response object. """ try: time_based_drops_prefetch = Prefetch( lookup="drops", queryset=TimeBasedDrop.objects.prefetch_related("benefits"), ) drop_campaigns_prefetch = Prefetch( lookup="drop_campaigns", queryset=DropCampaign.objects.prefetch_related(time_based_drops_prefetch), ) game: Game = ( Game.objects.select_related("org").prefetch_related(drop_campaigns_prefetch).get(twitch_id=twitch_id) ) except Game.DoesNotExist: return HttpResponse(status=404, content="Game not found.") except Game.MultipleObjectsReturned: return HttpResponse(status=500, content="Multiple games found with the same Twitch ID.") context: dict[str, Any] = {"game": game} return TemplateResponse(request=request, template="game.html", context=context) @require_http_methods(request_method_list=["GET", "HEAD"]) def get_games(request: HttpRequest) -> HttpResponse: """Render the game view page. Args: request (HttpRequest): The request object. Returns: HttpResponse: The response object. """ games: QuerySet[Game] = Game.objects.all() context: dict[str, QuerySet[Game] | str] = {"games": games} return TemplateResponse(request=request, template="games.html", context=context)