Refactor models

This commit is contained in:
2025-05-01 02:41:25 +02:00
parent d137ad61f0
commit d7b31e1d42
17 changed files with 704 additions and 1392 deletions

View File

@ -1,17 +1,17 @@
from __future__ import annotations
import json
import logging
from collections import defaultdict
from typing import TYPE_CHECKING, Any
from django.db.models import F, Prefetch
from django.http import HttpRequest, HttpResponse, JsonResponse
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.import_json import import_data
from core.models import Benefit, DropCampaign, Game, TimeBasedDrop
from core.models import DropCampaign, Game, TimeBasedDrop
if TYPE_CHECKING:
from django.db.models.query import QuerySet
@ -20,36 +20,6 @@ if TYPE_CHECKING:
logger: logging.Logger = logging.getLogger(__name__)
def get_games_with_drops() -> QuerySet[Game]:
"""Get the games with drops, sorted by when the drop campaigns end.
Returns:
QuerySet[Game]: The games with drops.
"""
# Prefetch the benefits for the time-based drops.
benefits_prefetch = Prefetch(lookup="benefits", queryset=Benefit.objects.all())
active_time_based_drops: QuerySet[TimeBasedDrop] = TimeBasedDrop.objects.filter(
ends_at__gte=timezone.now(),
starts_at__lte=timezone.now(),
).prefetch_related(benefits_prefetch)
# Prefetch the active time-based drops for the drop campaigns.
drops_prefetch = Prefetch(lookup="drops", queryset=active_time_based_drops)
active_campaigns: QuerySet[DropCampaign] = DropCampaign.objects.filter(
ends_at__gte=timezone.now(),
starts_at__lte=timezone.now(),
).prefetch_related(drops_prefetch)
return (
Game.objects.filter(drop_campaigns__in=active_campaigns)
.annotate(drop_campaign_end=F("drop_campaigns__ends_at"))
.distinct()
.prefetch_related(Prefetch("drop_campaigns", queryset=active_campaigns))
.select_related("org")
.order_by("drop_campaign_end")
)
@require_http_methods(request_method_list=["GET", "HEAD"])
def get_home(request: HttpRequest) -> HttpResponse:
"""Render the index page.
@ -60,14 +30,41 @@ def get_home(request: HttpRequest) -> HttpResponse:
Returns:
HttpResponse: The response object
"""
try:
games: QuerySet[Game] = get_games_with_drops()
except Exception:
logger.exception("Error fetching reward campaigns or games.")
return HttpResponse(status=500)
now = timezone.now()
grouped_drops = defaultdict(list)
context: dict[str, Any] = {"games": games}
return TemplateResponse(request, "index.html", context)
# 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"])
@ -117,25 +114,3 @@ def get_games(request: HttpRequest) -> HttpResponse:
context: dict[str, QuerySet[Game] | str] = {"games": games}
return TemplateResponse(request=request, template="games.html", context=context)
@require_http_methods(request_method_list=["POST"])
def get_import(request: HttpRequest) -> HttpResponse:
"""Import data that are sent from Twitch Drop Miner.
Args:
request (HttpRequest): The request object.
Returns:
HttpResponse: The response object.
"""
try:
data = json.loads(request.body)
logger.info(data)
# Import the data.
import_data(data)
return JsonResponse({"status": "success"}, status=200)
except json.JSONDecodeError as e:
return JsonResponse({"status": "error", "message": str(e)}, status=400)