Add game list and detail views with templates for Twitch Drops Tracker

This commit is contained in:
Joakim Hellsén 2025-07-10 03:09:27 +02:00
commit c995c82dcb
8 changed files with 343 additions and 12 deletions

View file

@ -8,7 +8,7 @@ from pathlib import Path
from typing import Any
from django.core.management.base import BaseCommand, CommandError, CommandParser
from django.db import transaction, OperationalError
from django.db import OperationalError, transaction
from twitch.models import DropBenefit, DropBenefitEdge, DropCampaign, Game, Organization, TimeBasedDrop
@ -60,7 +60,7 @@ class Command(BaseCommand):
help="Delay in seconds between retries for database operations (default: 0.5)",
)
def handle(self, **options) -> None:
def handle(self, **options) -> None: # noqa: ANN003
"""Execute the command.
Args:
@ -235,14 +235,14 @@ class Command(BaseCommand):
Args:
campaign_data: The drop campaign data to import.
Raises:
OperationalError: If the database is still locked after max retries.
"""
# Retry logic for database operations
max_retries = getattr(self, "max_retries", 5) # Default to 5 if not set
retry_delay = getattr(self, "retry_delay", 0.5) # Default to 0.5 seconds if not set
for attempt in range(max_retries):
try:
with transaction.atomic():
@ -326,11 +326,9 @@ class Command(BaseCommand):
# Check if this is a database lock error
if "database is locked" in str(e).lower():
if attempt < max_retries - 1: # Don't sleep on the last attempt
sleep_time = retry_delay * (2 ** attempt) # Exponential backoff
sleep_time = retry_delay * (2**attempt) # Exponential backoff
self.stdout.write(
self.style.WARNING(
f"Database locked, retrying in {sleep_time:.2f}s (attempt {attempt + 1}/{max_retries})"
)
self.style.WARNING(f"Database locked, retrying in {sleep_time:.2f}s (attempt {attempt + 1}/{max_retries})")
)
time.sleep(sleep_time)
else:

View file

@ -10,4 +10,6 @@ urlpatterns = [
path("", views.dashboard, name="dashboard"),
path("campaigns/", views.DropCampaignListView.as_view(), name="campaign_list"),
path("campaigns/<str:pk>/", views.DropCampaignDetailView.as_view(), name="campaign_detail"),
path("games/", views.GameListView.as_view(), name="game_list"),
path("games/<str:pk>/", views.GameDetailView.as_view(), name="game_detail"),
]

View file

@ -40,7 +40,7 @@ class DropCampaignListView(ListView):
return queryset.select_related("game", "owner").order_by("-start_at")
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
def get_context_data(self, **kwargs) -> dict[str, Any]:
"""Add additional context data.
Args:
@ -74,7 +74,7 @@ class DropCampaignDetailView(DetailView):
template_name = "twitch/campaign_detail.html"
context_object_name = "campaign"
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
def get_context_data(self, **kwargs) -> dict[str, Any]:
"""Add additional context data.
Args:
@ -99,6 +99,106 @@ class DropCampaignDetailView(DetailView):
return context
class GameListView(ListView):
"""List view for games."""
model = Game
template_name = "twitch/game_list.html"
context_object_name = "games"
def get_queryset(self) -> QuerySet[Game]:
"""Get queryset of games.
Returns:
QuerySet: Sorted games.
"""
return super().get_queryset().order_by("display_name")
def get_context_data(self, **kwargs) -> dict[str, Any]:
"""Add additional context data.
Args:
**kwargs: Additional arguments.
Returns:
dict: Context data.
"""
context = super().get_context_data(**kwargs)
# Get campaign count for each game
games_with_counts = []
for game in context["games"]:
campaign_count = DropCampaign.objects.filter(game=game).count()
active_count = DropCampaign.objects.filter(
game=game,
start_at__lte=timezone.now(),
end_at__gte=timezone.now(),
status="ACTIVE",
).count()
games_with_counts.append({
"game": game,
"campaign_count": campaign_count,
"active_count": active_count,
})
context["games_with_counts"] = games_with_counts
return context
class GameDetailView(DetailView):
"""Detail view for a game."""
model = Game
template_name = "twitch/game_detail.html"
context_object_name = "game"
def get_context_data(self, **kwargs) -> dict[str, Any]:
"""Add additional context data.
Args:
**kwargs: Additional arguments.
Returns:
dict: Context data.
"""
context = super().get_context_data(**kwargs)
game = self.get_object()
# Get all campaigns for this game
now = timezone.now()
# Active campaigns
active_campaigns = (
DropCampaign.objects.filter(
game=game,
start_at__lte=now,
end_at__gte=now,
status="ACTIVE",
)
.select_related("owner")
.order_by("end_at")
)
# Upcoming campaigns
upcoming_campaigns = (
DropCampaign.objects.filter(game=game, start_at__gt=now, status="UPCOMING").select_related("owner").order_by("start_at")
)
# Expired campaigns
expired_campaigns = (
DropCampaign.objects.filter(game=game, end_at__lt=now).select_related("owner").order_by("-end_at")[:10]
) # Limit to 10 most recent
context.update({
"active_campaigns": active_campaigns,
"upcoming_campaigns": upcoming_campaigns,
"expired_campaigns": expired_campaigns,
"now": now,
})
return context
def dashboard(request: HttpRequest) -> HttpResponse:
"""Dashboard view showing active campaigns and progress.