diff --git a/core/templates/index.html b/core/templates/index.html index a961c26..f8f5fc8 100644 --- a/core/templates/index.html +++ b/core/templates/index.html @@ -10,7 +10,7 @@ {{ grouped_drops|length }} game{{ grouped_drops|length|pluralize }} {% if grouped_drops %} - {% for game, drops_list in grouped_drops.items %} + {% for game, campaigns in grouped_drops.items %}
@@ -40,64 +40,79 @@ {% endif %} - {% if drops_list %} + {% if campaigns %}
- {% for drop in drops_list %} - {% with campaign=drop.campaign %} -

{{ campaign.name|default:"Unknown Campaign" }}

- Details - {% if campaign.details_url != campaign.account_link_url and campaign.account_link_url %} - | Link Account - {% endif %} -

- Ends in: - - {{ campaign.end_at|timeuntil }} - -

- -
- - - - - - - - - - {% if drop.benefits.exists %} - {% for benefit in drop.benefits.all %} - - - - - - {% endfor %} - {% else %} - - - - - + {% for campaign, drops_list in campaigns.items %} +
+
+

{{ campaign.name|default:"Unknown Campaign" }}

+
+ Details + {% if campaign.details_url != campaign.account_link_url and campaign.account_link_url %} + | Link + Account {% endif %} -
-
Benefit ImageBenefit NameRequired Minutes Watched
- {{ benefit.name|default:'Unknown Benefit' }} - - - {{ benefit.name|default:'Unknown Benefit' }} - - {{ drop.required_minutes_watched|minutes_to_hours }}
- {{ drop.name|default:'Unknown Drop' }} - {{ drop.name|default:'Unknown Drop' }}N/A
+
+

+ Ends in: + + {{ campaign.end_at|timeuntil }} + +

+
+
+
+ + + + + + + + + + + {% for drop in drops_list %} + {% if drop.benefits.exists %} + {% for benefit in drop.benefits.all %} + + + + + + + {% endfor %} + {% else %} + + + + + + + {% endif %} + {% endfor %} + +
Benefit ImageDrop NameBenefit NameRequired Minutes Watched
+ {{ benefit.name|default:'Unknown Benefit' }} + {{ drop.name|default:'Unknown Drop' }} + + {{ benefit.name|default:'Unknown Benefit' }} + + {{ drop.required_minutes_watched|minutes_to_hours }}
+ {{ drop.name|default:'Unknown Drop' }} + {{ drop.name|default:'Unknown Drop' }}N/A{{ drop.required_minutes_watched|minutes_to_hours|default:'N/A' }} +
+
+
- {% endwith %} {% endfor %}
{% else %} diff --git a/core/views.py b/core/views.py index cbf1372..fdb3bb2 100644 --- a/core/views.py +++ b/core/views.py @@ -1,7 +1,6 @@ from __future__ import annotations import logging -from collections import defaultdict from typing import TYPE_CHECKING, Any from django.db.models import Prefetch @@ -22,33 +21,69 @@ logger: logging.Logger = logging.getLogger(__name__) @require_http_methods(request_method_list=["GET", "HEAD"]) def get_home(request: HttpRequest) -> HttpResponse: - """Render the index page. + """Render the index page with drops grouped hierarchically by game and campaign. + + This view fetches all currently active drops (where current time is between start_at and end_at), + and organizes them by game and campaign for display. Args: request (HttpRequest): The request object. Returns: - HttpResponse: The response object + HttpResponse: The rendered index template with grouped drops. """ now: timezone.datetime = timezone.now() - grouped_drops = defaultdict(list) - current_drops_qs = ( - TimeBasedDrop.objects.filter(start_at__lte=now, end_at__gte=now) - .select_related("campaign__game") - .prefetch_related("benefits") - .order_by("campaign__game__display_name", "name") - ) + try: + # Dictionary structure: {Game: {Campaign: [TimeBasedDrop, ...]}} + grouped_drops: dict[Game, dict[DropCampaign, list[TimeBasedDrop]]] = {} - for drop in current_drops_qs: - if drop.campaign and drop.campaign.game: - game: Game = drop.campaign.game - grouped_drops[game].append(drop) - else: - logger.warning("Drop %s does not have an associated game or campaign.", drop.name) + # Fetch all currently active drops with optimized queries + current_drops_qs = ( + TimeBasedDrop.objects.filter(start_at__lte=now, end_at__gte=now) + .select_related("campaign", "campaign__game", "campaign__owner") + .prefetch_related("benefits") + .order_by("campaign__game__display_name", "campaign__name", "name") + ) - context = {"grouped_drops": dict(grouped_drops)} - return render(request, "index.html", context) + # Drops without associated games or campaigns + orphaned_drops: list[TimeBasedDrop] = [] + + for drop in current_drops_qs: + # Check if drop has both campaign and game + if drop.campaign and drop.campaign.game: + game: Game = drop.campaign.game + campaign: DropCampaign = drop.campaign + + # Initialize the game entry if it doesn't exist + if game not in grouped_drops: + grouped_drops[game] = {} + + # Initialize the campaign entry if it doesn't exist + if campaign not in grouped_drops[game]: + grouped_drops[game][campaign] = [] + + # Add the drop to the appropriate campaign + grouped_drops[game][campaign].append(drop) + else: + # Store drops without proper association separately + orphaned_drops.append(drop) + logger.warning("Drop %s does not have an associated game or campaign.", drop.name or drop.drop_id) + + context: dict[str, Any] = { + "grouped_drops": grouped_drops, + "orphaned_drops": orphaned_drops, + "current_time": now, + } + + return render(request, "index.html", context) + + except Exception: + logger.exception("Error in get_home view") + return HttpResponse( + status=500, + content="An error occurred while processing your request. Please try again later.", + ) @require_http_methods(request_method_list=["GET", "HEAD"])